在协同开发时,我们不可避免的需要代码整合,一般来说,merge是我们最先接触的代码整合方法。
但是我最近发现为什么有些人不喜欢用 merge
而提倡用rebase了,因为在 merge
之后,commit
历史就会出现分叉,显得非常混乱。如下图:
这种分叉再汇合的结构总会让强迫症的我觉得很不舒服,不知道哪次改动在哪个commit记录中。
听说备受推崇的rebase能够解决这个问题,让commit历史成一条直线更加清晰。
但是,问题来了,我想解决的这个分叉虽然是我造成,但是他为什么会分叉呢?
一、merge为什么会产生分叉?
首先小明创建分支test
,在master的基础上进行了一点小改动
与此同时,在小明想要将代码合并到master前,小刚将他的代码合并到了master。
此时,小明想要将代码合并到master中,发现merge request出现了冲突,于是就回到自己的分支通过merge master into xiaoming
在自己的分支上解决冲突。
于是,分叉就出现了。此时紫色的分叉代表着xiaoming分支的改动,灰色的分支代表着master分支的改动。最后通过一个merger的commit进行合并。
无论有没有冲突,commit记录都会出现分叉。如果有冲突的话,最后的merge commit会显示冲突解决的过程;如果没有冲突的话,这里也会有一个无意义的merge commit。
xiaoming merge master后的分支如下:
graph LR
root --> 同步
同步 --> 小明的提交
同步 --> 合并小刚的提交
小明的提交 --> merge-commit
合并小刚的提交 --> merge-commit
也就是说,两个人同时对功能进行开发,如果进行代码整合,就不可避免地会造成commit
历史分叉。 两个人可能看起来还比较清晰,如果是一个大团队十几个人进行开发,那这个混乱程度想想就很绝望。
所以,我想让自己分支的commit历史变成成一条直线,是不是会更加清晰,更方便管理?
于是,我们就需要用到rebase了。
二、对rebase的理解
2.1 rebase的含义
相比较于merge(合并) 这种望文生义,通俗易懂的词语,rebase(变基) 貌似有点的过于生僻而难以理解了。
其实这个翻译还是比较准确的。rebase 的意思是,把你现在所在的 commit 以及它所在的 commit 串,以指定的⽬标分支最新提交为基础,依次重新提交⼀次。
官方的rebase解释如下: 当执行rebase操作时,git会从两个分支的共同祖先开始提取待变基分支上的修改,然后将待变基分支指向基分支的最新提交,最后将刚才提取的修改追加到基分支的最新提交的后面。
2.2 rebase操作流程
在rebase xiaoming onto master
中,待变基的分支是xiaoming
,基分支是master
。两个分支的共同祖先为 同步
这个commit
。执行这个命令的流程如下:
2.2.0. 原有xiaoming
分支上的情况
graph LR
root --> 同步
同步 --> 小明的提交
2.2.1. 提取xiaoming
分支上的修改小明的提交
graph LR
root --> 同步
小明的提交
2.2.2. xiaoming
分支指向master
分支上最新的提交合并小刚的提交
graph LR
root --> 同步
同步 --> 合并小刚的提交
小明的提交
2.2.3. 将提取xiaoming
分支出来的修改小明的提交
,追加到master
分支上最新的提交合并小刚的提交
后面。
graph LR
root --> 同步
同步 --> 合并小刚的提交
合并小刚的提交 --> 小明的提交
从上述rebase的流程图来看,就很容易就能发现为什么rebase不会产生分叉了。
2.3 rebase的缺陷
rebase后远程仓库和本地仓库会出现这样的情况。
- git远程仓库中分支情况如下:
graph LR
root --> 同步
同步 --> 小明的提交
- 本地仓库分支情况如下:
graph LR
root --> 同步
同步 --> 合并小刚的提交
合并小刚的提交 --> 小明的提交
这个时候你按照原有的习惯直接push
是一定会报错的。因为git的push操作默认是假设远端的分支和你本地的分支可以进行fast-forward操作,换句话说就是这个push命令假设你的本地分支和远端分支的唯一区别是你本地有几个新的commit,而远端没有。
所以上面这种情况下是不能进行fast-forwad
模式的合并操作的,所以当执行 git push origin xiaoming
命令时会报错。
此时没有其他人在这个分支上进行提交操作,那么可以直接进行强制推送 git push --force origin xiaoming ,–force可以直接理解为用你本地分支的状态去覆盖掉远端origin分支的状态,也就是执行过后,本地的分支什么样,远端分支就什么样
这里切记,一定要确保是在自己的分支上只有自己在开发,不能在主干分支上进行force操作,否则会直接覆盖掉别人已经提交的代码,会被打的。
如果这个分支有多人协同开发,最好不要用rebase
避免 push --force
, 如果一定要用rebase,那我更推荐另外一种更安全的命令 git push --force-with-lease origin xiaoming 使用该命令在强制覆盖前会进行一次检查如果其他人在该分支上有提交会有一个警告,此时可以避免覆盖代码的风险。
三、rebase实践
现在让我们回到最初的起点,reset xiaoming分支到未merge之前。同样的小明和小刚,同样的xiaoming和master,此时master上已经合并了小刚的提交。
xiaoming对master进行rebase,需要checkout xiaoming
然后 rebase xiaoming onto master
需要注意的是rebase仍然需要进行冲突解决(选择merge):
rebase生成了新的commit
此时需要注意的点在于本地已经将远程的记录给更改了,一定要要push --force才能推送上去
push -force
关于git rebase的操作解决 push时被拒绝,整体流程_git push rebase-CSDN博客
git rebase后无法push远程分支的问题解决_git rebase master后git push-CSDN博客
四、rebase与merge的争论
git pull --rebase的作用是什么,它与git pull有什么区别?-CSDN博客
Git 两个常用命令:Git Pull和Git Rebase|极客教程 (geek-docs.com)
总结:使用 rebase
来将远程的变更整合到本地仓库是一种更好的选择。(仅限自己的分支)
- 用
merge
拉取远程变更的结果是,每次你想获取项目的最新进展时,都会有一个多余的merge
提交。 - 而使用
rebase
的结果更符合我们的本意:我想在其他人的已完成工作的基础上进行我的更改。
参考文章
深入理解git rebase - 知乎 (zhihu.com)
git rebase详解(图解+最简单示例,一次就懂)-CSDN博客