Git 中 rebase 和 merge 用法经验谈

kenticny

Git 作为一个开发的基础工具,使用方法应该是每一个开发者必备的技能。由于 Git 的功能过于强大,导致很多人的使用方法都不是一样的,而且每个公司或者每个团队都有可能在日常工作中形成自己特有的 Git 管理流程,这些都是根据自己团队的特点和工作方式制定的,并没有孰优孰劣之分,毕竟是工具,能够有效的帮助完成自己的任务才是最重要的。

要了解 Git 命令功能的最简单的方法就是 git –help,这里我就不禁又要插一句闲话了,关于 Git 工具是使用命令行,还是使用GUI,这个问题就像“豆腐脑是咸的好还是甜的好”一样,之前还看过一个视频推荐大家使用GUI,然后各种列举命令行的劣势。我个人觉得这个大可不必,作为一个工具,每个人要选择自己最合适的方式使用,千万不要存在鄙视链,就像代码编辑器一样,有人喜欢用IDE,有人喜欢vim/emacs,不存在谁比谁更高级的说法,只要能最高效的完成工作就可以了。但是有一点:如果你还不了解 Git 命令具体功能的时候,最好不要使用封装度过高的GUI工具。因为这很容易让初学者误解 Git 的工作流程。

比较

通过 git --help 可以了解到两个命令的功能:

1
2
3
4
5
$ git --help
...
merge Join two or more development histories together
rebase Reapply commits on top of another base tip
...

很明显,两个命令的功能是不一样的,merge 的重点在于将 commit 合并,rebase 的重点在于重新设置base点。这两个命令的具体流程我们这里就不浪费篇幅说了,大家可以通过 git 的 help 文档来看,也可以随便百度一片文章,基本都会有那几张介绍流程的图片的。如果有不熟悉的可以先去简单了解一下。

很多关于 rebase 和 merge 相关的文章都会对这两个命令做比较,而且也有很多带节奏的人总会说要使用 rebase 而不要使用 merge,原因竟然是 rebase 的提交记录线很流畅,看着舒服,而 merge 会产生一个额外的合并 commit,有强迫症的人看了会不舒服。(WTF???)

这里我需要好好的杠一下,首先,使提交记录看起来流畅明了确实是 rebase 命令的一个特点,但是这绝对不应该是我们使用它的原因。关于 merge 操作产生合并 commit 的问题,我们从下面几个问题讨论下:

merge 一定会产生一个合并的commit吗?

相信大多数人肯定都遇到过,合并之后没有产生合并 commit 的情况,所以 merge 操作并不是一定产生一个新的 commit,那么什么时候会产生新的 commit 呢?这里需要介绍一个概念,fast-forward(快进模式),如果从 A 分支上出 B 分支,然后在 B 分支上工作,然后将 B 分支合并入 A 分支,如果此时 A 分支任然停留在出 B 分支时的状态,即没有新的 commit 加入,那么此时 B 合并入 A 会议快进模式合并,即不产生新的合并 commit。

merge 产生 commit 真的不好吗?

这里我只想说我的观点。git log 是开发者操作 git 的日志记录,也就是说每一条 log 都是有意义的,每一条 log 都可以追踪到一个操作,比如合并产生的 commit,就是记录了这里是一个合并操作。其实有很多团队的 Git 管理流程里面是要求开发者在提交的时候加 –no-ff 参数的,也就是要求在合并时必须要写清楚合并的内容;在 GitHub 或者 GitLab 中,大多数多人维护的项目也都是通过 PR 或者 MR 来合并的。而且合并分支的回滚也是通过 merge 的 commit 来进行操作的,所以说在很多情况下 merge 产生的 commit 是十分必要的。

这么说 merge 在任何情况下也不会有问题吗?

能有时候,有一些 merge commit 是可以避免的:比如在本地的分支 A 上开发完成后,要 push 到远端,然后发现远端已经有新的 commit 加入了,那么这是如果 pull 操作,则会产生一个本地合并远端的 commit。如果在本地 A 提交之前 pull 远端 A,那么这个 commit 是可以避免产生的,这也就是 rebase 这个命令的作用了。

还有就是跨分支合并的时候也会有问题:比如从 A 分支处 B 分支,然后在 B 分支上开发后,又在 B 分支上出 C 分支,C 分支开发完成后,如果优先于 B 分支合并到 A 分支,那么这时候其实就是把不属于 C 分支的内容合并到 A 分支了,此时也可能会出现问题。

Rebase 出现

首先我们看这个命令叫 rebase,根据英文语法,re开头的单词一般都表示“重新…”;那么如果要 rebase,首先得知道 base 是什么。我们可以简单的理解为:在 A 分支的 commit1 处出的分支 B,那么 B 就是 base A 分支的 commit1 产生的分支。

下面我们简单了解下 rebase 的工作原理,假设我们现有 A, B 两分支,当前在 B 分支上,当我们执行 git rebase A 时,git 做了什么呢?

  1. git 会以 A 分支为基础,然后将 B 分支上和 A 分支有差异的 commit 一个接一个的合并到 A 分支上。
  2. 在 rebase 过程中,由于 B 分支上的 commit 是重新合并到 A 分支上的,所以 rebase 后的 commit ID 是重新生成了,跟合并前的 commit ID 是不同的。

了解了工作原理之后,我们可以上面 merge 可能存在的问题用 rebase 怎么解决。假设在本地分支 A 开发完成提交以后,push 时发现远程分支已经领先,那么这里我们可以在本地分支执行 git pull –rebase,这条命令等同于 git fetch && git rebase,此时就不会产生那条合并的 commit 了。

rebase 可以解决一切问题吗?

正如我们上面提到的,任何事物都有两面性,能够剔除掉一些无实际意义的 commit,这是rebase 最明显的一个特点,但是 rebase 要想完全替代 merge,我个人任务这也是十分不现实的事情。从上面提到的 rebase 的工作原理中提到了,rebase 会将目标分支上的 commit 一个接一个的合并到 base 分支上,这里就有一个有趣的问题,在 B 分支上执行 git rebase A,假设此时 A 上有新的改动,而且 B 分支上也对相同位置做了改动,那么就会产生冲突,由于 rebase 会每个 commit 单独进行合并,那么也就是说需要每个commit 分别解决冲突,加入 B 分支上有10个 commit 都改了这个地方,那么就需要解决10次冲突了….想想有点可怕。

那么对于 rebase 的使用场景,我个人的习惯是,在本地分支同步远程分支时使用 rebase;在本地分支的 base 分支有改动的情况下,使用 rebase 进行同步分支。其余情况,使用 merge 进行合并。

最后有一点需要特别注意,如果当前分支存在基于这个分支的子分支,那么千万不要对这个分支进行rebase,为什么?我举一个简单的栗子!你出门买菜,回来发现家搬走了….所以为了避免这种情况,通常建议工作分支要基于锁定的分支出,这样就可以避免你的分支到时候找不到家了。

  • 本文标题:Git 中 rebase 和 merge 用法经验谈
  • 本文作者:kenticny
  • 创建时间:2018-05-07 17:40:57
  • 本文链接:https://luyun.io/2018/05/07/compare-git-rebase-and-merge/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论