Git subtree 日常使用

来源:互联网 发布:多益网络 ceo 编辑:程序博客网 时间:2024/06/06 03:51

即使在我写完这篇文章之后,我还是对 git subtree 的行为和模糊。。。欢迎大家指正和赐教。

零、前言

既然你已经在找 git subtree 相关的东西了,那么我可以假定你的目的和大家的都差不多:把一个项目作为子项目,在多个项目中共享&维护。

还是举个例子吧:

假设你有一个在线商城项目 Shop,这个项目下有个订单模块 order,这个 order 模块很通用,你在下一个团购系统 Groupon,也要用到这个 order 模块,最简单的方式就是把整个 order 模块 copy 过来。

然而,这种 copy 方式,会很难维护:order 模块修改了一个Bug,然后又要复制更新到所有用了 order 模块的项目里。

这时就需要 git subtree 来管理这个 order 模块了:把 order 模块单独作为一个 git 仓库,在需要用到 order 模块的项目,通过使用 git subtreeorder 模块当作一个 git 项目来引入,这就解决了 order 模块的共享和维护问题。

零零、相关命令

git subtree --help 可以查看 git subtree 命令的帮助文档。

git subtree 命令中,都会用到一个参数 --prefix=<prefix>,可以简写成 -P <prefix>,本文的命令都是这样使用的。

git subtree add 添加项目作为子树

git subtree add -P <子树名> <子仓库地址> <分支>

执行以上命令后,项目下就会新创建一个名为 <子树名> 的目录。

如果提前使用 git remote add <子仓库名> <子仓库地址> 添加了子项目的远程仓库地址(建议按此方式,下文都基于此),那么也可以这样:

git subtree add -P <子树名> <子仓库名> <分支>

以上命令可以多加一个 --squash 参数:

git subtree add -P <子树名> <子仓库名> <分支> --squash

--squash 参数含义是:把 subtree 的改动合并成一次commit,这样就不用拉取子项目完整的历史记录。

git subtree pull 从子仓库拉取子树更新

fetch

git fetch <子仓库名> <分支>

pull

git subtree pull -P <子树名> <子仓库名> <分支>

以上命令也可以多加一个 --squash 参数:

git subtree pull -P <子树名> <子仓库名> <分支> --squash

--squash 参数含义同上。

git subtree push 推送子树更新到子仓库

git subtree push -P <子树名> <子仓库名> <分支>

一、场景一:把其他已有项目,添加为子树

场景假设:新建了一个团购系统项目 Groupon,也需要用到 order 模块,那么按以下步骤即可:

添加子项目的远程仓库地址

git remote add order-repo https://github.com/xxx/order.git

Groupon 项目里进行添加项目作为子树

cd Groupongit subtree add -P order order-repo master --squashgit subtree pull -P order order-repo master

二、场景二:把项目里的某个目录,分割出去作为子树维护

场景假设:现在在开发 Shop 项目有一段时间了,发现里面的 order 模块非常通用,着手使用 git subtreeorder 作为一个子项目单独维护。

2.1 方式一:分割出去,并清理 commit 记录

命令过程:

cd Shopgit subtree split -P shop -b order-branch    //1cd ../                                      //2mkdir tempDir                               //2cd tempDir                                  //2git init                                    //2git pull ../Shop order-branch               //2git remote add order-repo https://github.com/xxx/order.git  //3git push -u order-repo master                               //3cd ../              //4rm -rf tempDir      //4cd Shop                                                                                                      //5git 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 Shopgit remote add order-repo https://github.com/xxx/order.gitgit subtree add -P order order-repo master --squashgit subtree pull -P order order-repo master

2.2 方式二:直接作为子树提交到子仓库,不清理 commit 记录

git subtree --help 查看文档,git subtree push 命令本身就会进行 git subtree split 操作:

Does a split (see below) using the <prefix> 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 Shopgit remote add order-repo https://github.com/xxx/order.git  //1git subtree push -P order order-repo master  //2

流程说明:
1. 添加 order 子项目的远程仓库地址
2. order 模块作为 order 子树 push 到远程仓库

三、场景三:在现有项目里,新建了一个子模块(子目录),准备当作子树维护

场景假设:现在在开发 Shop 项目,刚新建了个 order 模块(还未提交到git),准备把这个 order 模块当作一个子项目维护。

3.1 方式一:子模块(子目录)单独提交到远程仓库,删除子模块(子目录)后,重新引入

网上这这个场景的资料比较少,以至于我一开始是先提交这个 order 模块,然后再走场景二的流程。

后来发现可以简单点:git 允许在 git 仓库的子目录下再创建 git 仓库,所以一切都好办了:

命令过程:

cd Shop/order                                               //1git init                                                    //1git add .                                                   //1git commit -m "add order ..."                                //1git remote add order-repo https://github.com/xxx/order.git       //1git push -u order-repo mastercd ../                                                      //2rm -rf order                                                //2git remote add order-repo https://github.com/xxx/order.git       //3git subtree add -P order order-repo master --squash              //3

流程说明:
1. 进入 order 模块创建仓库、提交、添加远程仓库地址、push
2. 删除 order 模块
3. 添加 order 子项目远程仓库地址,git subtree add 添加 order 子项目作为子树

3.2 方式二:先把子模块(子目录)提交,然后 git subtree push

这就和 场景二 里的 《2.2 方式二:直接作为子树提交到子仓库,不清理 commit 记录》 一样了。

四、日常一:删除子树

场景假设:突然有一天,Shop 项目不再开放下单功能,以后也仅仅做静态展示,很确定 order 子模块可以删除了,那么有以下两种删除途径。

4.1、删除子模块目录,并commit

git rm -r <子模块目录名>git commit -m "remove <子模块目录名> ..."

此方式的缺点就是 git log 里还有留着 order 子模块的 commit 历史。

4.2、使用 filter-branch

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,虽然很扯,但是不得不照做。

但是要按实际情况来:

5.1、不影响子树内的文件内容时

直接重命名子树的目录名,并提交:

cd shopmv order xiadangit add .git commit -m "reanme order -> xiadan"

只是以后 pullpsuh 子树的命令要改变少少:

git subtree pull -P xiadan order-repo master    //原来的 -P 参数值也由 order 改成 xiadangit subtree push -P xiadan order-repo master    //原来的 -P 参数值也由 order 改成 xiadan

5.1、会影响子树内的文件内容时

重命名子树后,会影响到子树内的文件内容时(比如 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 改成 xiadangit subtree push -P xiadan xiadan-repo master    //原来的 -P 参数值也由 order 改成 xiadan

好像没什么好写了 ):

附、参考资料

  • git subtree --help
  • 使用git subtree集成项目到子目录
  • Git Subtree 的介绍及使用
  • 使用git的subtree将已有项目的某个目录分离成独立项目
  • git subtree: possible to change subtree branch/path in a forked repository?
  • How to remove previously added git subtree and its history