rebase VS merge

目前当我们在开发结束要发版时,我们要将我们开发分支并入到主分支中,然后再让测试在预发布环境测试。预发布环境测试有时会发现新的问题,因此经常在主分支上有bug修复的额外提交,这其实有碍整洁。

要将自己的开发分支并入主分支有两种方式,一种就是直接合并一种就是变基了。

上篇中我们的分支结构图是这样的:

git_commit2

现在有两种情况我们需要考量:

  1. 远端master没有其他人更新过,本地和远端master依旧指向A3节点;
  2. 远端master已经被其他人更新了,本地和远端指向节点已经不同了。

远端master未被更新

当远端主分支没有新的更新点的时候,我们直接切换到主分支进行合并命令:

git_merge

上面的输出我们可以看到上面有一句Fast-forward,这是因为当前主分支指向的提交点是我们分支检出后新第一个提交点的直接上游,所以Git只是将主分支的执政移动到我们最后一个提交点。也就是说当我们试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做Fast-forward。

这个时候我们回顾一下我们变基的操作:

  1. 切换到master同步远端所有的更新;
  2. 从我们的开发分支检出一个新分支;
  3. 重置我们我们开发时所有的新提交点,重新提交成一个新的提交点;
  4. 和master进行变基操作,如果有冲突解决冲突;
  5. 切到master合并刚刚的分支。

我们可以看到在没有远端没有更新的情况下,变基后合并与直接合并相差的就只有提交点的数量了,所以关键情况就是当远端出现更新的时候。

远端master有了新的提交点

使用合并

我们首先也是先同步更新远端修改到本地:

git_pull

现在我们要进行合并的话,Git会根据共同祖先(A3)、主分支末端节点(C2)和特性分支末端节点(A7)进行三方合并。Git 将此次三方合并的结果做一个新的快照并且自动创建一个新的提交指向它。这个提交点的特别之处在于它有不止一个父提交。

git_merge2

A8是合并后的共同节点,从它开始分支后保有了我们开发分支和其他人开发分支的功能代码。现在我们在主分支下打印log的话,这些节点信息会按照时间顺序由近到远的展示出来。这时我们考虑下,当我们遇到问题要进行代码回退时,是不是要回退到A3点,那C1和C2的代码是不是也同时被撤销了?

PS:这里插一点,大家平时应该注意到了,我们通过git pull拉取代码时,往往会出现这样一个提交信息:

git_pull2

这是因为git pull这条命令在帮我们git fetch获取了远端代码之后,它会自动尝试帮我们进行合并提交,只有出现冲突时,它才会停下来让我们处理。

使用变基

我们再次回到我们现在的变基流程,我们变基之前都会进行一次重置操作,将多个提交点归结于一个提交点。那我们现在不这样做,直接变基会怎样呢?

首先我们还是回到当前分支状态:

git_pull

我们在开发分支运行git rebase master,同样如果出现冲突了,停下来等我们解决冲突,解决完之后,打印log我们会看到这样的情况:

git_rebase

我们的开发分支变成了这样,我们的提交点看似直接照搬接到了C2的后面,我们仔细看每个节点的名称变了(commit id)变了。它的原理是首先找到这两个分支(即当前分支、变基操作的目标基底分支 master)的最近共同祖先A3,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C2, 最后以此将之前另存为临时文件的修改依序应用(创建新的提交点)。

看当我们直接进行变基的时候,我们还是会把我们所有的提交点添加到我们的主分支master上,不过有一点好处是,不再会有那么多的分支岔路了,是一条直路通到底了。因此理论上说只要我们能记住我们每个功能的提交点或者每次我们都push了一个tag到远端仓库,那我们这种提交方式也是可以的应对的回退某一个功能的情况的。那既然变基都做了,合并一个点这么一气呵成的操作为啥不做呢,顺便记录下我们的开发信息,也就能省掉每次创建一个tag了(大家都这么懒)。。。

rebase的一个坑

讲到这一步,我们就有必要提一下我在变基指南里提到的另一种变基合成一点的操作了:

git rebase -i

开发中也有童鞋用这个操作坑了自己一下。

这个rebase命令可以让我们直接重写我们的提交历史,具体介绍参见那篇文章。但是既然是重写了我们的提交历史,那它的各个快照(snapshot)也就是节点就会受影响啊同志:

git_rebase2

我们通过git rebase -i SHA的方式合并成一个点之后,本地和远端的情况就变成了这样了。这时VSCode会跟你显示远端超前你4个版本,而你有一个新的提交点要提交了!这时你去同步会发生什么?很可能又会出现一个新的Merge点啊,一番骚操作之后你会发现:咦,我怎么反而还多了几个点出来?

因此这里我们要特别注意,在使用要遵守的一条金科玉律就是:

不要对在你的仓库外有副本的分支执行变基!因为变基会消除原先的提交点,同时又生成新的提交点!

总结

上面对我们合并和变基时会发生的情况进行了解释并花了指向图,我们为什么要定下面这些步骤来回顾下:

  1. 切换到master同步远端所有的更新;

反正都要做的,你总不能变基一帆风顺,合并一堆冲突,然后多了一个提交点你打算再来一步git commit --amend?

  1. 从我们的开发分支检出一个新分支;

为什么要检出新分支还不知道的话再看一遍~

  1. 重置我们我们开发时所有的新提交点,重新提交成一个新的提交点;

你不想重置,那你git rebase -i 了解一下?

  1. 和master进行变基操作,如果有冲突解决冲突;

记住解决了冲突,标记解决好后,要运行git rebase --continue,别自己又加一个提交点,否者你又要一脸懵逼,然后git rebase --skip

  1. 切到master合并刚刚的分支。

这是大家最爱的合并环节,就不多说了

0%