即使在我写完这篇文章之后,我还是对 git subtree
的行为和模糊。。。欢迎大家指正和赐教。
既然你已经在找 git subtree
相关的东西了,那么我可以假定你的目的和大家的都差不多:把一个项目作为子项目,在多个项目中共享&维护。
还是举个例子吧:
假设你有一个在线商城项目 Shop
,这个项目下有个订单模块 order
,这个 order
模块很通用,你在下一个团购系统 Groupon
,也要用到这个 order
模块,最简单的方式就是把整个 order
模块 copy 过来。
然而,这种 copy 方式,会很难维护:order
模块修改了一个Bug,然后又要复制更新到所有用了 order
模块的项目里。
这时就需要 git subtree
来管理这个 order
模块了:把 order
模块单独作为一个 git 仓库,在需要用到 order
模块的项目,通过使用 git subtree
把 order
模块当作一个 git 项目来引入,这就解决了 order
模块的共享和维护问题。
git subtree --help
可以查看 git subtree
命令的帮助文档。
git subtree
命令中,都会用到一个参数 --prefix=
,可以简写成 -P
,本文的命令都是这样使用的。
git subtree add -P <子树名> <子仓库地址> <分支>
执行以上命令后,项目下就会新创建一个名为 <子树名>
的目录。
如果提前使用 git remote add <子仓库名> <子仓库地址>
添加了子项目的远程仓库地址(建议按此方式,下文都基于此),那么也可以这样:
git subtree add -P <子树名> <子仓库名> <分支>
以上命令可以多加一个 --squash
参数:
git subtree add -P <子树名> <子仓库名> <分支> --squash
--squash
参数含义是:把 subtree 的改动合并成一次commit,这样就不用拉取子项目完整的历史记录。
先 fetch
:
git fetch <子仓库名> <分支>
后 pull
:
git subtree pull -P <子树名> <子仓库名> <分支>
以上命令也可以多加一个 --squash
参数:
git subtree pull -P <子树名> <子仓库名> <分支> --squash
--squash
参数含义同上。
git subtree push -P <子树名> <子仓库名> <分支>
场景假设:新建了一个团购系统项目 Groupon
,也需要用到 order
模块,那么按以下步骤即可:
添加子项目的远程仓库地址:
git remote add order-repo https://github.com/xxx/order.git
在 Groupon
项目里进行添加项目作为子树:
cd Groupon
git subtree add -P order order-repo master --squash
git subtree pull -P order order-repo master
场景假设:现在在开发 Shop
项目有一段时间了,发现里面的 order
模块非常通用,着手使用 git subtree
把 order
作为一个子项目单独维护。
命令过程:
cd Shop
git subtree split -P shop -b order-branch //1
cd ../ //2
mkdir tempDir //2
cd tempDir //2
git init //2
git pull ../Shop order-branch //2
git remote add order-repo https://github.com/xxx/order.git //3
git push -u order-repo master //3
cd ../ //4
rm -rf tempDir //4
cd Shop //5
git filter-branch -f --index-filter "git rm -r -f -q --cached --ignore-unmatch order" --prune-empty HEAD //5 注意:这里是 order,那个子目录(-P 对应的值)
git branch -D order-branch //6
流程说明:
1. 在 Shop
项目里把 order
模块分割成一个临时分支 order-branch
2. 项目外新建一个临时目录 tempDir 并创建 git 仓库,拉取这个 order
模块的临时分支 order-branch
3. 在临时目录里,添加 order
子项目的远程仓库地址,并 push 到该远程仓库
4. 删除临时目录 tempDir
5. 清理掉原来的 order
模块的 commit 记录
6. 按需删除临时分支 order-branch
当然,把 order
模块抽离出去之后,这个 Shop
项目还是需要用到 order
模块的,重新走一遍 《场景一(把其他已有项目,添加为子树)》 的流程就可以了:
cd Shop
git remote add order-repo https://github.com/xxx/order.git
git subtree add -P order order-repo master --squash
git subtree pull -P order order-repo master
git subtree --help
查看文档,git subtree push
命令本身就会进行 git subtree split
操作:
Does a split (see below) using the supplied and then does a git push to push the result to the repository and ref.
This can be used to push your subtree to different branches of the remote repository.
所以简单点就 git subtree push
完事。
命令过程:
cd Shop
git remote add order-repo https://github.com/xxx/order.git //1
git subtree push -P order order-repo master //2
流程说明:
1. 添加 order
子项目的远程仓库地址
2. order
模块作为 order
子树 push
到远程仓库
场景假设:现在在开发 Shop
项目,刚新建了个 order
模块(还未提交到git),准备把这个 order
模块当作一个子项目维护。
网上这这个场景的资料比较少,以至于我一开始是先提交这个 order
模块,然后再走场景二的流程。
后来发现可以简单点:git 允许在 git 仓库的子目录下再创建 git 仓库,所以一切都好办了:
命令过程:
cd Shop/order //1
git init //1
git add . //1
git commit -m "add order ..." //1
git remote add order-repo https://github.com/xxx/order.git //1
git push -u order-repo master
cd ../ //2
rm -rf order //2
git remote add order-repo https://github.com/xxx/order.git //3
git subtree add -P order order-repo master --squash //3
流程说明:
1. 进入 order
模块创建仓库、提交、添加远程仓库地址、push
2. 删除 order
模块
3. 添加 order
子项目远程仓库地址,git subtree add
添加 order
子项目作为子树
这就和 场景二 里的 《2.2 方式二:直接作为子树提交到子仓库,不清理 commit 记录》 一样了。
场景假设:突然有一天,Shop
项目不再开放下单功能,以后也仅仅做静态展示,很确定 order
子模块可以删除了,那么有以下两种删除途径。
git rm -r <子模块目录名>
git commit -m "remove <子模块目录名> ..."
此方式的缺点就是 git log
里还有留着 order
子模块的 commit
历史。
git filter-branch -f --index-filter "git rm -r -f -q --cached --ignore-unmatch order" --prune-empty HEAD //注意:这里是 order,那个子模块(-P 对应的值)
此方式可以把order
子模块的 commit
历史一并删除。
场景假设:突然有一天,空降了一个领导,他对 Shop
项目里的 order
子项目的目录命名非常看不顺眼,命令我们改成拼音 xiadan
,虽然很扯,但是不得不照做。
但是要按实际情况来:
直接重命名子树的目录名,并提交:
cd shop
mv order xiadan
git add .
git commit -m "reanme order -> xiadan"
只是以后 pull
和 psuh
子树的命令要改变少少:
git subtree pull -P xiadan order-repo master //原来的 -P 参数值也由 order 改成 xiadan
git subtree push -P xiadan order-repo master //原来的 -P 参数值也由 order 改成 xiadan
重命名子树后,会影响到子树内的文件内容时(比如 Java,改一下目录名,类文件里的 import
就出问题了),那就不能像上一步那样直接改了,因为会影响到其他使用 order
子树的项目!
那就只能走 《日常一:删除子树》 流程,先删除子树,然后重新引入了!
先删除(把 order
的本地历史记录也一并删了):
git filter-branch -f --index-filter "git rm -r -f -q --cached --ignore-unmatch order" --prune-empty HEAD
然后重新引入:
git remote add xiadan-repo https://github.com/xxx/order.git // 干脆把 order 的远程仓库也命名成 xiadan-repo 吧!
git subtree pull -P xiadan xiadan-repo master //原来的 -P 参数值也由 order 改成 xiadan
git subtree push -P xiadan xiadan-repo master //原来的 -P 参数值也由 order 改成 xiadan
好像没什么好写了 ):
git subtree --help