Git 的分支设计大大提升了并行开发的能力,但相应的,也就要解决如何进行分支合并。毕竟分久必合,最终还是要把大家的工作合并起来,进行统一发布的。在合并时,通常有三种操作:
不同的开发者有不同的选择,前两天 HashiCorp 的创始人 Hashimoto 发了一贴 Merge vs. Rebase vs. Squash,也表达了他的观点。
我经常被问到我对 merge commits、rebase 和 squash 的看法。我已经多次回复,因此我决定记录一下,以便每当被再次问到时我都可以引用它。
我根据情况使用 merge commits,squash,rebase。我相信它们都有各自的优点,但它们的使用取决于上下文。我认为任何说某个特定策略 100% 都是正确答案的人都是错误的,但我认为在使用每种策略时都有相当大的可回旋的余地。以下是我个人专业角度的观点:
我更喜欢 merge 并且创建 merge commits,因为我认为它最能代表提交的真实历史。您可以看到 merge 点,您可以看到开发人员经历的所有 WIP 提交。您可以轻松恢复整个合并 (git revert -mN)。每 10 个 PR 中我创建的 merge commits 超过 9 个。
我还相信,只要每次提交都能编译构建,那么拥有更多的提交会让 git bisect 更好。当我把一个项目一分为二,结果却从一个类似 +2000/-500 的单个 PR 中得到了一个被 squash 的提交时,我真的是讨厌,讨厌,再讨厌。那是……一点帮助都没有。我想一分为二并提交最坏的情况也就是比如 +500/-500,这是最坏的情况了。理想情况下,我的提交更像是 +50/-50。然后我可以说「啊哈,bug 就在那里」。Squash 会破坏这些信息。如果在 1000 次 +50/-50 的提交和 1 次 squash 之间选择,每一次我都会选择前者。
使用 merge commits 策略依赖于开发人员在每次提交构建时保持良好的洁癖 (hygiene)。我 99% 的时间都遵循这条规则(我会犯错误,但我努力不犯错误)。在 OSS 中,您无法真正控制这一点,有时我最终会修复人们的提交(在进行 merge commits 之前使用交互式 rebase)。在我担任工程领导者的专业环境中,我通常希望与我一起工作的工程师保持每次提交都是可构建的。
不过,当 PR 有无数微小的 “WIP” “WIP” “WIP” 提交,但实际上是为了实现一个目标而差异相对较小时,我会 squash。这是我使用 squash 的场景。我在重写提交消息时会非常注意,让它拥有描述性。Git 和 GitHub 创建的默认 squash 提交消息并不好(它只是连接所有被 squash 的提交消息,通常是一系列 “WIP”)。
如果您有很大的差异并且有很多 “WIP”,那么我会(以交互方式)rebase,并有选择地在有意义的地方 squash 和重新排序提交。我倾向于期望开发人员这样做并保持洁癖,但不幸的是很多开发人员对 Git 的使用并不熟练。在 OSS 世界中,我会帮他们擦擦屁股。我当年担任工程经理时,我预期和我一起工作的工程师都具备这些知识。
关于最后一点,我还倾向于使用 Git GUI 客户端进行大型交互式 rebase。我对 Git CLI 非常满意,但是当我以交互方式对一个非常大的 PR(例如 50 多个提交)进行大量更改的 rebase 时,我发现使用 GUI 会很有帮助。我使用的是 macOS,所以我使用 Tower。不过,这是我真正使用 GUI 的唯一场景。
虽然 HashiCorp 是以 Hashimoto 的名字命名的,不过他本人已经不再担任领导岗位,而是做回一名工程师,捣鼓一个新的 Terminal。即使对 CLI / Terminal 操作熟练如他,依然在应付复杂的 rebase 时,要借助 GUI 工具。我个人也使用 Tower (刷了网页,发现它还正好在促销)。
说到合并采用的策略,我通常会先 rebase,squash 用的也不算少。精神上我支持 Hashimoto 的观点,但实践中,尤其是团队开发,要保持 merge commits 的洁癖对团队要求比较高。所以更实际的做法,是提倡创建小 PR,快分快合。
另外 Bytebase 也把分支功能 (Branching) 引入到了数据库变更中了,欢迎大家去试试。
更多资讯,请关注 Bytebase 公号:Bytebase