浅析Git Subtree的原理与实际应用:git subtree是什么、子仓库与仓库共用、共用代码需求常规处理方式及为什么使用git subtree以及如何使用Git Subtree在多个Git项目间双向同步子项目
一、为什么需要使用Git Subtree
关于子仓库或者说是仓库共用,git官方推荐的工具是git subtree。
在实际的项目开发过程中,公共的代码或者模块是必定会出现的,为了不重复写相同的代码;普遍的做法就是将其抽取成一个公共模块,这个模块由不同的使用者引用。
作为Java工程师,可能会选择将这一部分打包封装成一个jar,并且将其推送到Maven的私有仓库,各个使用者将其添加到pom文件即可;作为前端工程师,可能会选择将这一部分打包封装成一个自定义plugin,将其推送至npm的私有仓库,各个使用者去安装即可。
但是有没有更好的方式呢?因为使用这种方式,对使用者来说,并不是透明的,当前可能已经更新版本了,但是我作为使用者并不知道当前已经更新了,知道更新了可能也不知道最新版本是多少,我还得通过咨询模块开发人员或者说到私有仓库去查看才能知道,那有没有更好的方式呢?
接下来,就一起探讨一种更好的管理方式:git subtree。
二、共用代码的需求及处理方式
1、需求背景
项目A与项目B存在公用模块,在项目A中修改Bug或增加新功能需要同步到项目B中,由于存在区别所以还不能完全copy
2、需求分析
(1)公用代码迁移出去独立的 git 仓库,供其他项目共享代码
(2)公用代码原本是什么样,抽取后也是什么样
(3)公用代码库是可以在不同项目间双向同步的而不是单向同步
(4)保留公用代码库的历史提交记录与双向同步记录
双向同步的例子:A项目中依赖了子项目B,最方便的方式自然是直接在A项目里改B子项目对应的目录里的代码,然后测试通过后,直接提交代码,这个更改也提交到B子项目的 Git仓库里。同时子项目B也可以单独提交到 Git 仓库,再在A项目里把子项目B的代码update。
3、现有方案
(1)Git Submodule:这是Git官方以前的推荐方案
(2)Git Subtree:从 Git 1.5.2 开始,Git 新增并推荐使用这个功能来管理子项目
(3)npm:node package manager,实际上不仅仅是 node 的包管理工具
(4)composer:暂且认为他是php版npm
虽然 npm,composer,maven 等更侧重于包的依赖管理,以上几个方案都是能够做到在不同项目中同步同一块代码的,但没法双向同步,更适用于子项目代码比较稳定的情形。
Git Submodule 和 Git Subtree 都是官方支持的功能,不具有依赖管理的功能,但能满足我们的要求。Git Subtree相对来说会更好一些 。git v1.5.2以后建议使用git subtree
4、Git Subtree 好在哪里
用一句话来描述 Git Subtree 的优势就是:
经由 Git Subtree 来维护的子项目代码,对于父项目来说是透明的,所有的开发人员看到的就是一个普通的目录,原来怎么做现在依旧那么做,只需要维护这个 Subtree 的人在合适的时候去做同步代码的操作。
它是怎么做到的呢?后面会通过实操记录简单说下原理。
三、什么是git subtree?有啥优势?
1、git subtree可以将一个仓库作为仓库的子仓库
2、各个仓库之前的版本管理是相对独立的
3、对于使用者来说,是透明的,可能使用者根本都不知道有子仓库的存在
4、主子仓库的分支同步,即你切换主项目分支的时候,关联的子仓库也会同步切换
四、什么时候需要Subtree
1、当多个项目共用同一模块代码,而且这块代码跟着项目在快速更新的时候
2、把一部分代码迁移出去独立为一个新的 git 仓库,但又希望能够保留这部分代码的历史提交记录
五、Git Subtree的原理与实际应用
首先,你需要2个主项目,我利用之前的demo项目、demo2项目,还要有一个被多个项目共用的subtree项目。我们通过简要讲解使用Subtree来同步代码的过程来解释Subtree的原理
1、初始化子项目subtree
通过我之前的demo项目做下示例
cd demo项目的路径
git subtree add --prefix=用来放subtree项目的相对路径 subtree项目git地址 xxx分支
PS D:\demo\vue-button-test> git subtree add --prefix=/subtree https://gitee.com/***/npm_test.git master
git fetch https://gitee.com/goloving/npm_test.git master
From https://gitee.com/goloving/npm_test
* branch master -> FETCH_HEAD
error: Invalid path 'C:/Program Files/Git/subtree/README.md'
error: Invalid path 'C:/Program Files/Git/subtree/index.js'
error: Invalid path 'C:/Program Files/Git/subtree/package.json'
error: Invalid path 'C:/Program Files/Git/subtree/plugins/lib/button.vue'
fatal: C:/Program Files/Git/subtree: 'C:/Program Files/Git/subtree' is outside repository
PS D:\demo\vue-button-test> git subtree add --prefix=subtree https://gitee.com/goloving/npm_test.git master
prefix 'subtree' already exists.
PS D:\demo\vue-button-test> git subtree add --prefix=subtree https://gitee.com/goloving/npm_test.git master
git fetch https://gitee.com/goloving/npm_test.git master
From https://gitee.com/goloving/npm_test
* branch master -> FETCH_HEAD
Added dir 'subtree'
PS D:\demo\vue-button-test>
遇到3个报错:
(1)Working tree has modifications. Cannot add.
实际上工作树没有更改,解决:关闭终端,重新开启终端即可
(2)--prefix=/subtree 应该是相对路径,不是绝对路径,所以写 /subtree 匹配到 C:/Program Files/Git/ 去了
(3)--prefix=subtree 刚开始以为必须新建这个目录,发现不是的,新建会报错,删除即可
这样的命令,把subtree即npm_test项目的代码下载到--prefix所指定的subtree目录中,并在demo项目里自动产生一个commit(就是把npm_test目录的内容提交到demo项目里)
我们再用一个demo2项目做同样的操作
2、像往常一样更新代码
大家在demo项目里各种提交commit,其中有些commit会涉及到subtree目录的更改,正如前面提到的,这是没任何关系的,大家也不会感受到有任何不一样。
比如我们在 npm-test 项目里button按钮后随便加个文字:subtree,看下图 demo 项目生效了
然后我们把更改的所有内容 git 提交到 demo 项目仓库,我们可以看到下面全部的 commit 记录
3、提交更改到子项目的Git服务器
关键的地方来了: 当维护这个demo项目 subtree 的人希望把最近这段时间对demo项目subtree目录的更改提交到subtree对应的npm_test项目的 Git 服务器上时,他执行一段类似于这样的命令:
cd demo项目的路径
git subtree push --prefix=用来放subtree项目的相对路径 subtree项目git地址 xxx分支
PS D:\demo\vue-button-test> git subtree push --prefix=subtree https://gitee.com/***/npm_test.git master
git push using: https://gitee.com/goloving/npm_test.git master
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 384 bytes | 384.00 KiB/s, done.
Total 5 (delta 2), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.3]
To https://gitee.com/goloving/npm_test.git
adbf133..bbe8c7b bbe8c7b22181acc371c7118f6392fafa9448ce4f -> master
PS D:\demo\vue-button-test>
Git 会遍历所有的commit,从中找出针对subtree目录的更改,然后把这些更改记录提交到subtree对应的npm_test项目的Git服务器上
然后我们就可以看到 npm_test 项目上多个对button更改的记录
4、更新子项目新的代码到父项目
现在subtree npm_test项目有新代码了,demo2项目也想使用这些新代码,维护demo2项目这个Subtree的人只要执行
cd demo2项目路径
git subtree pull --prefix=subtree项目的路径 subtree项目git地址 xxx分支
PS D:\其他\big-data-view1> git subtree pull --prefix=subtree https://gitee.com/goloving/npm_test.git master
Working tree has modifications. Cannot add.
PS D:\其他\big-data-view1> git subtree pull --prefix=subtree https://gitee.com/goloving/npm_test.git master
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 5 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
From https://gitee.com/goloving/npm_test
* branch master -> FETCH_HEAD
Merge made by the 'recursive' strategy.
subtree/plugins/lib/button.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
PS D:\其他\big-data-view1>
当有未提交记录时会报这个错误:Working tree has modifications. Cannot add. 解决:将未提交记录全部提交即可
接下来我们就可以看到 demo2 项目里的 button 按钮就更新为最新的了
这样就可以将demo2项目里subtree目录里的内容更新为npm_test项目xxx分支的最新代码了。