Merging vs. rebasing
git rebase
命令以其神奇的 Git hocus 而闻名,初学者应该远离它,但如果谨慎使用,它实际上可以让开发团队的生活变得更加轻松。在本文中,我们将对比 git rebase
与相关的 git merge
命令,并介绍将变基融入一般的 Git 工作流的所有可能机会。
Conceptual overview
要了解 git rebase
,首先要明白的是它解决的问题与 git merge
一样。这两个命令都是将一个分支的变更集成到另一个分支—只是两者的方式截然不同。
设想一下,您开始在一个专用分支中处理新功能,然后其他团队成员使用新的提交更新 main
分支。这会生成新拷贝的历史记录,对于使用 Git 作为协作工具的人来说,这一切应该都不陌生。
现在,假设 main
中的新提交与您处理的功能相关。要将新的提交并入您的 feature
分支中,有两个选项:合并或变基。
相关资料
如何移动完整的 Git 存储库
查看解决方案
了解 Bitbucket Cloud 的 Git
The merge option
最简单的选择是使用以下方法将 main
分支合并到 feature 分支中:
git checkout feature
git merge main
或者,您可以把它压缩成一行:
git merge feature main
这会在 feature
分支中创建一个新的“合并提交”,将两个分支的历史记录联系在一起,从而为您提供一个看起来像这样的分支结构:
合并是不错的选择,因为它是一种非破坏性的操作。现有分支不会得到任何更改。这避免了变基操作的所有隐患(后面将会讨论)。
另一方面,这也意味着每次需要并入上游变更时,feature
分支将会产生一个无关的合并提交。如果 main
非常活跃,这可能会污染您的功能分支的历史记录。虽然可以使用高级 git log
选项来缓解此问题,但可能会让其他开发人员难以理解项目的历史记录。
The rebase option
作为合并的替代方法,您可以使用以下命令将 feature
分支变基为 main
分支:
git checkout feature
git rebase main
这会移动整个 feature
分支,以在 main
分支的节点开始,从而有效地将所有新提交并入 main
中。但是,变基并不使用合并提交,而是为原始分支中的每个提交创建全新的提交来重写项目历史记录。
变基的主要优势在于您可以获得更干净的项目历史记录。首先,它不像 git merge
一样需要不必要的合并提交。其次,如上图所示,变基还会产生完美的线性项目历史记录—您可以在没有任何新拷贝的情况下,始终按照 feature
的提示找到项目的源头。这可以让您更轻松地使用 git log
、git bisect
和 gitk
等命令导航项目。
但是,对于这种清晰的提交历史记录,存在两个需要权衡的地方:安全性和可追溯性。如果您不遵循变基的黄金法则,重写项目历史记录可能会对您的协作工作流造成潜在危害。另外,变基会丢失合并提交所带来的上下文—您无法看到上游变更何时被并入功能。
Interactive rebasing
交互式变基可让您在提交移动到新分支时对提交进行更改。这甚至比自动变基更强大,因为这可以让您完全控制分支的提交历史记录。通常情况下,可用它来清理混乱的历史记录,然后再将功能分支合并到 main
中。
要开始交互式重基会话,请将 i
选项传递给 git rebase
命令:
git checkout feature
git rebase -i main
这将打开一个文本编辑器,列出所有即将移动的提交:
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
这个列表准确地定义了执行变基后分支的样子。通过更改 pick
命令和/或重新排序条目,您可以使分支的历史记录看起来像您想要的任何样子。例如,如果第二次提交修复了第一次提交中的一个小问题,则可以使用 fixup
命令将它们压缩为一次提交:
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
当您保存并关闭文件时,Git 将根据您的指令执行变基操作,生成如下所示的项目历史记录:
像这样清除不重要的提交,可以让功能的历史记录更容易理解。这是 git merge
完全无法做到的事情。
The golden rule of rebasing
在了解什么是变基之后,最重要的是要学习何时不使用它。git rebase
的黄金法则是永远不要在公有分支上使用它。
例如,想一下如果您将 main
变基到 feature
分支,会发生什么:
变基会将 main
中的所有提交移到 feature
的顶端。问题在于这只发生在您的存储库中。所有其他开发人员仍在使用原始 main
分支。由于变基会产生全新的提交,Git 会认为您的 main
分支的历史记录与其他所有人的历史记录有所不同。
同步两个 main
分支的唯一方法是将它们重新合并在一起,从而产生一个额外的合并提交和两组包含相同变更的提交(原始提交和变基分支的提交)。不用说,这是一个非常令人困惑的情况。
所以,在您运行 git rebase
之前,一定要问问自己:“还有其他人在看这个分支吗?”如果答案是肯定的,请暂停操作,开始考虑一种非破坏性的方法来进行变更(例如,git revert
命令)。如果没有,您可以随意重写历史记录。
Force-pushing
如果您尝试将变基的 main
分支推送回远程存储库,Git 将阻止您这样做,因为它与远程 main
分支冲突。但是,您可以通过使用 --force
标记来强制推送,如下所示:
# Be very careful with this command! git push --force
这将覆盖远程 main
分支,以匹配来自您的代码库的变基分支,并让您团队的其余成员感到非常困惑。所以,只有当您确切知道您在做什么时,才应该非常小心地使用此命令。
适合使用强制推出的时机之一是,您将私有功能分支推送到远程代码库(例如,用于备份目的)之后,执行了本地清理。这就像是在说“哎呀,我真的不想推送这个功能分支的原始版本。我要推送当前版本。”再次重申,没有人正在处理来自功能分支的原始版本的提交,这一点很重要。
Workflow walkthrough
可以将变基合并到您现有的 Git 工作流程中,具体合并程度由您的团队决定。在本节中,我们将探讨变基在功能开发的各个阶段可以带来的好处。
在任何利用 git rebase
的工作流程中,第一步都是为每个功能创建一个专用分支。这为您提供了必要的分支结构,可以安全地使用变基:
Local cleanup
将变基纳入工作流程的最佳方法之一是清理本地正在进行的功能。通过定期执行交互式变基,可以确保功能中的每一次提交都集中且有意义。这样您就可以写代码,而不必担心将其分解成单独的提交——您可以在事后修复它。
调用 git rebase
时,您有两个针对新基准的选项:功能的父分支(例如,main
)或功能中更早的提交。我们在交互式变基这一部分看到了第一个选项的示例。当您只需要修复最近几个提交时,后一个选项是很好的选择。例如,以下命令仅对最后 3 个提交进行交互式变基。
git checkout feature git rebase -i HEAD~3
通过指定 HEAD~3
作为新基准,您实际上并没有移动分支—您只是以交互的形式重写其后的 3 个提交。请注意,这将不会将上游变更并入到 feature
分支。
如果您想要使用此方法重写整个功能,则可以使用 git merge-base
命令查找 feature
分支的原始基准。以下内容返回原始基准的提交 ID,然后您可以传递给 git rebase
:
git merge-base feature main
使用这种交互式变基的是将 git rebase
引入到工作流中的好方法,因为它仅影响本地分支。其他开发人员可以看到的只是您的成品,也就是一个干净、易于追踪的功能分支历史记录。
但是,这仅适用于私有功能分支。如果您通过一个功能分支与其他开发人员进行协作,则该分支是公有分支,您不能重写其历史记录。
git merge
没有替代方法来使用交互式变基清理本地提交。
Incorporating upstream changes into a feature
在概念概述部分中,我们看到了功能分支如何使用 git merge
或 git rebase
合并 main
分支的上游变更。合并是一个安全的选项,它可以保留存储库的整个历史记录,而变基通过将功能分支移到 main
分支的顶部来创建线性历史记录。
git rebase
的这种用法类似于本地清理(并且可以同时执行),但是在此过程中它合并了来自 main
的上游提交。
请注意,变基到远程分支而不是 main
分支上是完全合理的。当与其他开发人员在同一功能上进行协作时,可能会发生这种情况,您需要将他们的变更并入到您的代码库中。
例如,如果您和另一位名为 John 的开发人员向 feature
分支添加了提交,则在从 John 的存储库中获取远程 feature
分支后,您的存储库可能如下所示:
您可以使用与从 main
中集成上游变更完全相同的方法解决此新拷贝:将本地 feature
与 john/feature
合并,或将本地 feature
变基到 john/feature
节点。
请注意,这种变基并不违反变基的黄金法则,因为只有您的本地 feature
提交会被移动,之前的所有内容都保持不变。这就像在说:“把我的变更添加到 John 已经完成的事情上。”在大多数情况下,这比通过合并提交与远程分支同步更直观。
默认情况下,git pull
命令会执行合并,但您可以通过传递 --rebase
选项来强制它将远程分支与变基集成。
Reviewing a feature with a pull request
如果您在代码评审过程中使用拉取请求,那么在创建拉取请求后需要避免使用 git rebase
。当您提出拉取请求后,其他开发人员将会查看您的提交,这意味着它是一个公有分支。重写其历史记录将使 Git 和您的队友无法跟踪任何添加到该功能的后续提交。
来自其他开发人员的任何变更都需要使用 git merge
而不是使用 git rebase
并入。
因此,在提交您的拉取请求之前,通过交互式变基清理代码通常是一个好办法。
Integrating an approved feature
在您的团队批准了一项功能后,您可以选择在将功能变基到 main
分支的节点,然后使用 git merge
将功能集成到主基准代码中。
这与将上游变更并入到功能分支中类似,但是由于您不被允许在 main
分支中重写提交,您最终必须使用 git merge
来集成该功能。但是,通过在合并之前执行变基,可确保合并将以快进模式进行,从而形成完美的线性历史记录。这也使您有机会在拉取请求期间添加任何后续提交。
如果您完全不习惯使用 git rebase
,可以随时在临时分支中执行变基。这样,如果您不小心弄乱了功能的历史记录,则可以检出原始分支,然后重试。例如:
git checkout feature
git checkout -b temporary-branch
git rebase -i main
# [Clean up the history]
git checkout main
git merge temporary-branch
摘要
以上就是您在开始变基分支之前真正需要了解的内容。如果您喜欢干净的线性历史记录且没有不必要合并提交,那么您应该在集成其他分支的变更时使用 git rebase
而不是 git merge
。
另一方面,如果您想保留项目的完整历史记录并避免重写公共提交的风险,您可以坚持使用 git merge
。这两个选项都是完全有效的,但至少现在您可以选择利用 git rebase
的好处。
分享此文章
下一主题
推荐阅读
将这些资源加入书签,以了解 DevOps 团队的类型,或获取 Atlassian 关于 DevOps 的持续更新。