马春杰杰博客
致力于深度学习经验分享!

Git subtree[添加/拉取/修改]

文章目录
[隐藏]

虽然比较起来,还是submodule更加适合我,不过subtree也有它的优点,正好看到一篇非常不错的介绍subtree的文章,就在这里备份一下,等下次我用的时候再根据需要更新该文。

subtree

1.简介

subtreesubmodule的作用是一样的,但是subtree出现得比submodule晚,它的出现是为了弥补submodule存在的问题:

  • 第一:submodule不能在父版本库中修改子版本库的代码,只能在子版本库中修改,是单向的;
  • 第二:submodule没有直接删除子版本库的功能;

subtree则可以实现双向数据修改。官方推荐使用subtree替代submodule

2.创建子库

首先创建两个版本库:git_subtree_parentgit_subtree_child然后在git_subtree_parent中执行git subtree会列出该指令的一些常见的参数:

image-20200330112616987

3.建立关联

首先需要给git_subtree_parent添加一个子库git_subtree_child:

**第一步:**添加子库的远程地址:

添加完成后,父版本库中就有两个远程地址了:

image-20200330113223780

这里的subtree-origin就代表了远程仓库git_subtree_child的地址。

**第二步:**建立依赖关系:

该命令表示将远程地址为subtree-origin的,子版本库上master分支的,文件克隆到subtree目录下;

注意:是在某一分支(如master)上将subtree-origin代表的远程仓库的某一分支(如master)作为子库拉取到subtree文件夹中。可切换到其他分支重复上述操作,也就是说子库的实质就是子分支。

--squash是可选参数,它的含义是合并,压缩的意思。

  • 如果不增加这个参数,则会把远程的子库中指定的分支(这里是master)中的提交一个一个地拉取到本地再去创建一个合并提交;
  • 如果增加了这个参数,会将远程子库指定分支上的多次提交合并压缩成一次提交再拉取到本地,这样拉取到本地的,远程子库中的,指定分支上的,历史提交记录就没有了。

image-20200330114203889

拉取完成后,父版本库中会增添一个subtree目录,里面是子库的文件,相当于把依赖的子库代码拉取到了本地:

image-20200330114316257

此时查看一下父版本库的提交历史:
image-20200330114500554

会发现其中没有子库李四的提交信息,这是因为--squash参数将他的提交压缩为一次提交,并由父版本库张三进行合并和提交。所以父版本库多出了两次提交。

随后,我们在父版本库中进行一次推送:

image-20200330114730534

结果远程仓库中多出了一个存放子版本库文件的subtree目录,并且完全脱离了版本库git_subtree_child,仅仅是属于父版本库git_subtree_parent的一个目录。而不像使用submodule那样,是一个点击就会自动跳转到依赖子库的指针

  • subtree的远程父版本库:

image-20200330115004586

  • submodule的远程父版本库:

image-20200329203746236

submodulesubtree子库的区别为:

image-20200408224805624

4.同步子库变化

在子库中创建一个新文件world并推送到远程子库:
image-20200330115440136

在父库中通过如下指令更新依赖的子库内容:

image-20200330115726052

此时查看一下提交历史:

image-20200330115755340

发现没有子库李四的提交信息,这都是--squash的作用。子库的修改交由父库来提交。

5.参数--squash

该参数的作用为:防止子库指定分支上的提交历史污染父版本库。比如在子库的master分支上进行了三次提交分别为:abc,并推送到远程子库。

首先,复习一下合并分支时遵循的三方合并原则:

image-20200408003842196

当提交46需要合并的时候,git会先寻找二者的公共父提交节点,如图中的2,然后在提交2的基础上进行246的三方合并,合并后得到提交7

父仓库执行pull操作时:如果添加参数--squash,就会把远程子库master分支上的这三次提交合并为一次新的提交abc;随后再与父仓库中子库的master分支进行合并,又产生一次提交X。整个pull的过程一共产生了五次提交,如下图所示:

image-20200420103912282

存在的问题:

由于--squash指令的合并操作,会导致远程master分支上的合并提交abc与本地master分支上的最新提交2,找不到公共父节点,从而合并失败。同时push操作也会出现额外的问题。

最佳实践:要么全部操作都使用--squash指令,要么全部操作都不使用该参数,这样就不会出错。

错误示范:

为了验证,重新创建两个仓库AB,并通过subtreeB设置为A的子库。这次全程都没有使用参数--squash,重复上述操作:

  • 首先,修改子库文件;
  • 然后,通过下列指令,在不使用参数--squash的情况下,将远程子库A变化的文件拉取到本地:

image-20200330141920474

此时查看提交历史:

image-20200330142000915

可以看到子库儿子的提交信息污染了父版本库的提交信息,验证了上述的结论。

所以要么都使用该指令,要么都不使用才能避免错误;如果不需要子库的提交日志,推荐使用--squash指令。

补充:echo 'new line' >> test.txt:表示在test.txt文件末尾追加文本new line;如果是一个>表示替换掉test.txt内的全部内容。

6.修改子库

subtree的强大之处在于,它可以在父版本库中修改依赖的子版本库。以下为演示:

进入父版本库存放子库的subtree目录,修改子库文件child.txt,并推送到远程父仓库:

image-20200330121429186

此时远程父版本库中存放子库文件的subtree目录发生了变化,但是独立的远程子库git_subtree_child并没有发生变化。

  • 修改独立的远程子库:可执行以下命令,同步地修改远程子版本库:

    • 1

    如下图所示,父库中的子库文件child.txt新增的child2内容,同步到了独立的远程子库中:

    image-20200330125911158

  • 修改独立的本地子库:回到本地子库git_subtree_child,将对应的远程子库进行的修改拉取到本地进行合并同步:image-20200330144044823由此无论是远程的还是本地的子库都被修改了。

实际上使用subtree后,在外部看起来父仓库和子仓库是一个整体的仓库。执行clone操作时,不会像submodule那样需要遍历子库来单独克隆。而是可以将整个父仓库和它所依赖的子库当做一个整体进行克隆。

存在的问题

父版本库拉取远程子库进行更新同步会出现的问题:

  • 子仓库第一次修改:经历了上述操作,本地子库与远程子库的文件达到了同步,其中文件child.txt的内容都是child~4。在此基础上本地子库为该文件添加child5~6image-20200330145702019然后推送到远程子库。
  • 父仓库第一次拉取:随后父版本库通过下述指令,拉取远程子库,与本地父仓库git_subtree_parent中的子库进行同步:

    结果出现了合并失败的情况:

    image-20200330145839093

    我们查看冲突产生的文件:

    image-20200330145922152

    发现父版本库中的子库与远程子库内容上并无冲突,但是却发生了冲突,这是为什么呢?

    探究冲突产生的原因之前我们先解决冲突,先删除多余的内容:

    image-20200330150141430

    随后执行git add命令和git commit命令标识解决了冲突:

    image-20200330150312944

    image-20200330150406317

    解决完冲突后将该文件推送到独立的远程子库,发现文件并没有发生更新,也就是说git认为我们并没有解决冲突:

    image-20200330150747452

  • 子仓库第二次修改与父仓库第二次拉取:再次修改本地子库的文件并推送到对应的远程仓库,父版本库再次将远程子库更新的文件拉取到本地进行同步:image-20200330151140092这次却成功了!为什么同样的操作,有的时候成功有的时候失败呢?
解决方案

原因出现在--squash指令中。实际上,--squash指令把子库中的提交信息合并了,导致父仓库在执行git pull操作时找不到公共的父节点,从而导致即使文件没有冲突的内容,也会出现合并冲突的情况。其实不使用--squash也会有这种问题,问题的根本原因仍然是三方合并时找不到公共父节点。我们打开gitk

image-20200330154944300

从图中不难看出,当使用subtree时,子库与父库之间是没有公共节点的,所以时常会因为找不到公共节点而出现合并冲突的情况,此时只需要解决冲突,手动合并即可。

不使用subtree时,普通的版本库中的各分支总会有一个公共节点:

image-20200330160206258

**再次强调:**使用--squash指令时一定要小心,要么都使用它,要么都不使用。

7.抽离子库

git subtree split

当开发过程中出现某些子库完全可以复用到其他项目中时,我们希望将它独立出来。

  • **方法一:**可以手动将文件拷贝出来。缺点是,这样会丢失关于该子库的提交记录;
  • **方法二:**使用git subtree split指令,该指令会把关于独立出来的子库的每次提交都记录起来。但是,这样存在弊端:
    • 比如该独立子库为company.util,当一次提交同时修改了company.utilcompany.server两个子库时。
    • 通过上述命令独立出来的子库util只会记录对自身修改的提交,而不会记录对company.server的修改,这样在别人看来这次提交就只修改了util,这是不完整的。
本文最后更新于2021年3月31日,已超过 1 年没有更新,如果文章内容或图片资源失效,请留言反馈,我们会及时处理,谢谢!

如果你对这篇文章有什么疑问或建议,欢迎下面留言提出,我看到会立刻回复!

打赏
未经允许不得转载:马春杰杰 » Git subtree[添加/拉取/修改]
超级便宜的原生ChatGPT4.0

留个评论吧~ 抢沙发 评论前登陆可免验证码!

私密评论
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址(选填,便于回访^_^)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

登录

忘记密码 ?

切换登录

注册