git rebase 与 git merge 弄的明明白白

950 阅读6分钟

相信大多朋友经常会遇到合并分支的问题,作为网上应该有很多类似的文章了,本文将会简单暴力的从最常用最实用的角度,将rebase和merge弄的明明白白

前言

首先要理解的是git rebase和git merge解决了同样的问题。这两个命令都旨在将更改从一个分支集成到另一个分支,它们只是以不同的方式进行。

试想一下当你开始在专用分支中开发新功能时另一个团队成员以新提交更新master分支会发生什么?这会出现分叉历史记录,对于使用Git的任何人来说都应该很熟悉。

image.png

现在,我们来说说当master新提交与你正在开发的功能相关。要将新提交合并到你的feature分支中,你有两个选择:merge或rebase。

Merge

最简单的是将master分支合并到feature分支中:

git checkout feature

git merge master

简化为一行:

git merge master feature

此操作在feature分支中会创建一个新的“merge commit”

image.png

合并很好,因为它是一种非破坏性的操作。现有分支结构不会以任何方式更改。这避免了rebase的所有潜在缺陷(下面会说)。

另一方面,这也意味着每次在上游更改时feature都需要合并,且有无关的合并提交。如果master改动非常频繁,你的分支历史记录会非常的乱。尽管可以使用高级git log选项减轻此问题的影响,但它可能使其他开发人员难以理解项目的历史更改记录。

Rebase

作为merge的替代方法,你可以使用以下命令将feature分支rebase到master分支上:

git checkout feature

git rebase master

这会将整个feature分支移动到master分支的顶端,从而有效地整合了所有master的新提交。但是,rebase不是使用merge commit,而是通过为原始分支中的每个提交创建全新的提交来重写项目历史记录。

image.png

rebase的主要好处是可以获得更清晰的项目历史记录。首先,它消除了不必要的git merge产生的merge commit。其次,正如在上图中所看到的,rebase也会产生完美线性的项目历史记录 - 你可以从feature分支顶端一直跟随到项目的开始而没有任何的分叉。这使得它比命令git log,git bisect和gitk更容易导航项目。

但是,对这个原始的提交历史记录有两个权衡:安全性和可追溯性。如果你不遵循rebase的一些规则,重写项目历史记录可能会对你的协作工作流程造成灾难性后果。其次rebase会丢失merge commit提供的上下文 - 你无法看到上游更改何时合并到功能中。

rebase黄金法则

所谓黄金法则,一句话说:git rebase的黄金法则是永远不要在公共分支使用它。

例如,想想如果你把master分支rebase到你的feature分支会发生什么:

image.png

rebase将master所有提交移动到feature顶端。问题是这只发生在你的仓库中。所有其他开发人员仍在使用原始版本master。由于rebase导致全新的提交,Git会认为你的master分支的历史与其他人的历史不同。

同步两个master分支的唯一方法是将它们合并在一起,从而产生额外的合并提交和两组包含相同更改的提交(原始提交和来自rebase分支的更改)。这将是一个非常令人困惑的情况。

因此,在你运行git rebase之前,总是问自己,“还有其他人在用这个分支吗?”如果答案是肯定的,那就把你的手从键盘上移开,考虑使用非破坏性的方式进行(例如,git revert命令)

rebase实操

rebase 使用的最多的就是以下两种场景:

  • 合并多次 commit 提交
  • 合并其他分支代码

1. 合并多次 commit 提交

为什么要合并多次commit?
答:开发一个功能时,由于太菜难免修修补补产生多次commit,都是同一功能,提交太多commit没必要,不利于追溯,看着一长串提交记录毫无意义好吧,所以最好合并一下下

例如,我们基于ceshi分支开发,开发过程中共产生了 3 次commit

image.png

此时,我想合并这 3 次commit,在远程仓库上只体现 1 次commit,怎么做呢?
执行以下命令来实现:

git rebase -i [startpoint] [endpoint]

// eg:
git rebase -i 0845a3584bdc2c6718dd4a2d663478848191ee6d 22d18e4fb994abbabb90fb3ba107332c1d5ecfac

git rebase -i [startpoint] [endpoint]
-i的含义是:弹出交互式的界面让用户编辑完成合并操作。
[startpoint]指的是合并区间的起点;[endpoint]指的是合并区间的终点,默认是当前分支HEAD所指向的commit
注意:这里的区间是一个前开后闭的区间,(commit_id1,commit_id2]。

回车,进入下一交互界面:

image.png

这里列出了很多命令 具体操作看自己情况上述交互界面中
输入i,进入INSERT编辑模式, 可用squash进行修改合并 完成修改后按esc退出编辑,输入:wq保存退出 git log 查看发现已经合并了

image.png

然后push分支肯定会失败,那是因为此刻基于 ceshi 分支进入了临时分支

image.png

不要慌,按以下步骤解决:

image.png

基于无名的临时分支创建dev-temp分支:git checkout -b ceshi1\

切回ceshi分支:git checkout ceshi\

执行git rebase ceshi1

如果有冲突请解决

一切顺利,执行git push 即可

当然,也可以合并多次提交至另一分支,可自行学习git rebase --onto的用法

2. 合并其他分支

合并其他分支有git rebasegit merge两种方式, merge就不多赘述

接着上文,我们在ceshi分支开发某个功能的同时,团队其他成员也在开发同一项目的1和2功能,完成后提交合并至dev分支:

image.png

此时,ceshi分支是落后于当前dev分支的,因为dev分支已经有最新的 1和2功能

image.png

如何合并包含新提交内容的dev分支呢?很简单,在当前分支执行git rebase dev

  1. rebase内部会把ceshi分支里的commit取消掉;
  2. 把取消的commit保存在.git/rebase目录下;
  3. ceshi分支更新到最新的dev分支(这里好好体会下变基的意义);
  4. 把保存的commit应用到dev分支上。

image.png

以上就完成了合并,查看ceshi分支 git log看下commit记录如上

但发生代码冲突的场景更加常见,如果开发A功能B功能时都修改了同一文件,例如你之前开发A功能时也改了README.md内容,那可能就产生冲突(conflict)了

  1. 先在IDE(我用的是VSCode)手动处理冲突,并保存;
  2. 处理完冲突后执行git add .,注意:执行git add .后不需要再执行git commit
  3. 继续执行rebase命令:git rebase --continue,如果还有冲突,重复上面的步骤,直到完成分支合并,如下图;
  4. 记住,在 rebase过程中一旦处于懵逼状态,可随时终止当前rebase操作:git rebase --abort,想明白了重新再来。

结束