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 的持续更新。
Bitbucket 博客
DevOps 学习路径