git merge vs rebase
我认为 git 最神奇的一点,是能够让同一项目中不同成员不受干扰地编写各自的代码。然而,当需要合并不同成员的工作的时候,就有些麻烦了。
为了实现这个目的,有两种方式:git merge
与git rebase
。这两种方法分别是什么,改用哪个?
下面用一个具体的例子来说明:
首先,假定我们已经开发了feature 1的两个任务:
然后我负责的团队需要完成feature 2的开发。我们已经完成了feature2 的部分任务,但是现在其他团队突然在feature1发现一个重要的bug,并且把它修复了,而这个修复又影响到我们feature2的开发。因此局面就变成这样:
我们在继续开发feature2之前,必须整合主团队的bug修复。这时候就有两种方式:
方法1:git merge main
这将main分支的bug修复改动合并到了我们的feature2分支,在我们的分支上额外创建了一次提交:Merge branch 'main' into feature2
。
我们继续开发,完成了feature2的开发任务,并将此分支最终合并到main分支。
此时feature2分支和main分支是完全一致的。我们发现,main 分支的 bug修复提交通过 merge 提交添加到了 feature2
分支,merge提交实际上是两个分支交汇的节点。这样的好处是保留了所有的细节,但坏处是在主分支里面会出现额外的merge提交。
方法2:git rebase main
回到这一步。同样,我们需要先把main分支的改动同步到我们这个feature2分支,再继续开发。
我们在feature2分支执行git rebase main
:
Rebase的本质是重写历史,相当于把feature2分支的改动”移植”到更新后的main分支。这一点会改动我们分支task1、task2的提交时间戳。
继续完成task3:
然后将其合并到main分支
运行git merge feature2
后,可以发现提交记录是一条直线。这是因为使用rebase后合并到main时,由于历史已线性化,会触发Fast-forward合并,不会产生额外合并提交。
应该如何选择merge或rebase?
下面这个表格总结了merge和rebase的比较:
比较项 | Git Merge | Git Rebase |
---|---|---|
提交结构 | 产生额外的合并提交 | 没有合并提交,历史线性化 |
历史记录 | 保留完整历史,包含分支结构 | 创建线性、简洁的历史 |
适用场景 | 所有协作场景,特别是公共分支 | 主要用于未推送的本地分支 |
操作安全性 | 高,不改变现有提交 | 中等,会改写提交历史 |
冲突处理 | 一次性解决所有冲突 | 可能需要逐个提交解决冲突 |
代码审查 | 可能较难追踪特定变更 | 便于逐个提交进行代码审查 |
使用建议 | 多人协作的公共分支 | 个人分支或团队内部约定 |
merge虽然会产生合并提交,让历史记录变得复杂,但是它保留了完整的分支结构,便于代码审查和问题追踪。
而rebase虽然会让历史线性化,便于管理,但是它可能会改写提交历史,导致基于原来历史开发的其他人产生误解。
如果你的分支有多个人在做,那么你rebase并强制推送到仓库,是非常不好的。因为rebase相当于改写了你这个分支的历史,会导致基于原来历史开发的其他人产生误解。
使用rebase的黄金法则是:只对「未推送」的本地分支使用 Rebase。换句话说,如果一个分支已推送到远程且可能被他人使用,那就不应该使用rebase并强制推送。
操作 | 安全边界 | 风险边界 |
---|---|---|
git rebase + 未推送 |
完全安全 | 无 |
git rebase + 强制推送 |
仅限个人分支或团队共识 | 公共分支、协作分支 |
git merge |
所有协作场景(但产生合并提交) | 无(除非项目禁止合并提交) |