写这篇文章的契机也是因为有次组内技术分享会里面讨论过这个话题,这两个指令都是用来合并分支代码的,rebase 可以把分支记录弄成一条线性,而 merge 保留了分支树状图,以我的习惯是更倾向于 merge,因为我大部分场景用不上 rebase
我们先看个大家开发中非常常见的场景,两个人基于 master 的同一个 commit 节点各自拉起一个新的 feature 分支,其中一个人 A 有多个 commit,并率先完成任务合入 master,B 也有多个 commit ,在他完成任务准备合入 master 时,发现有冲突,这里我模拟的每个提交都是针对同一个文件,每个 commit 都会有冲突
在 A 合并 master 前的分支图如下

可以看到 feature/A 分支 和 feature/B 分支都是基于 master 最新的 commit 拉起的分支,两条分支并行开发,这里我为了跟随时间线,用顺序数字模拟 commit 信息,两条分支各自新增了 3 个 commit
现在我们把 A 通过 merge 合并到 master 上
bash
git checkout master
git merge feature/A

可以看到 A merge 到 master 后,master 和 feature/A 保持一致了
好了,开头说的场景已经出现了,这个时候你是 B 你会怎么做,要是我会选择 merge,那就先看下 merge 的表现
bash
git checkout master
git merge feature/B
由于我的每个 commit 都是针对同一个文件做出更改,所以最终汇入必然要去解决冲突
可以看到这里的冲突是让你一次性解决的,解决完冲突后提交看 分支图

可以看到,merge 会创建一个新的合并提交,把两个分支平行的合并,保留了分支历史
这里顺带解释下为啥有时候 merge 没有 mr 节点,有时候又有,其实大部分情况下都会有。比如这里的 feature/A 合入 master 就没产生 mr 节点,feature/B 后合入 master 就产生了 mr 节点
merge 节点有没有取决于 git 是否需要创建一个新的合并提交
可能你体感上是永远会有 mr 节点,因为远程合作大部分场景 master 都是一直在前进的
- fast-forward 合并
- master 还停留在你分支起点的那个提交,比如 feature/A 合入到 master 的时候,master 和 feature/A 同一个起点,这就是 fast-forward 提交,就不会产生 mr 节点
- 非 fast-forward 合并
- master 相较于你的分支有了新的提交,比如 feature/B 的起点是最初的
docs: add README,在 feature/B 准备合入 master 时,master 最新的提交却是 feature/A 的dosc: 5
- master 相较于你的分支有了新的提交,比如 feature/B 的起点是最初的
估计这个时候就小盆友要说了,我的这个 mr 节点是在我解决冲突时提交的,其实就算没有冲突,非 fast-forward 合并就是会产生 mr 节点
好,我们现在撤回 feature/B merge 到 master,我们改用 rebase 再来看下对比
撤回之后的分支图如下,master 还是停留在 docs: 5

现在使用 rebase
bash
git checkout feature/B
git rebase master
可以看到 vscode 编辑器让我去解决冲突,这还是第一个

要是每个 commit 都有冲突那就每个 commit 都要单独解决一次冲突
一旦解决冲突并 git add,再运行 git rebase --continue,git 就会继续重放下一个提交,若同一段代码在多个提交中都改过,就会感觉是在重复解决冲突
现在回过头来看 git 树

可以看到 最终的 git 记录成一条直线了,看着确实顺眼不少,并且你的分支所有提交都会提前,你也不会产生 合并 的 mr 节点 ,但是你分支上原本的 commit hash (docs:3, dosc: 4, dosc: 6)会发生变化
结论
从 git 提交记录看
- merge 保留历史记录,非线性,容易产生 mr 节点
- rebase 将自己的 commit 记录提前并更改你之前的 commit hash,线性,不产生多余的 mr 节点
从 解决冲突 看
- merge 在汇入那一刻解决所有的冲突,直接对比两个分支代码的终极形态
- rebase 会按顺序重放你的所有 commit,但凡有冲突的提交都会让你挨个去解决
用 rebase 是有使用场景的,要是不清楚无脑 merge 就好。只有你一个人使用的分支,并且还没 push 到远端,可以去使用 rebase 整理下 git 历史,否则其余所有情况都可以用 merge
git rebase -i 可以整理提交顺序,合并多个小提交,改写提交信息
rebase 一定不能在公共分支上使用,因为这会篡改共有分支的 commit hash,别人再去合代码必然导致冲突