Close

Merging vs. rebasing

git rebase 命令以其神奇的 Git hocus 而闻名,初学者应该远离它,但如果谨慎使用,它实际上可以让开发团队的生活变得更加轻松。在本文中,我们将对比 git rebase 与相关的 git merge 命令,并介绍将变基融入一般的 Git 工作流的所有可能机会。


Conceptual overview


要了解 git rebase,首先要明白的是它解决的问题与 git merge 一样。这两个命令都是将一个分支的变更集成到另一个分支—只是两者的方式截然不同。

设想一下,您开始在一个专用分支中处理新功能,然后其他团队成员使用新的提交更新 main 分支。这会生成新拷贝的历史记录,对于使用 Git 作为协作工具的人来说,这一切应该都不陌生。

分叉提交历史记录

现在,假设 main 中的新提交与您处理的功能相关。要将新的提交并入您的 feature 分支中,有两个选项:合并或变基。

数据库
相关资料

如何移动完整的 Git 存储库

Bitbucket 徽标
查看解决方案

了解 Bitbucket Cloud 的 Git

The merge option

最简单的选择是使用以下方法将 main 分支合并到 feature 分支中:

git checkout feature
git merge main

或者,您可以把它压缩成一行:

git merge feature main

这会在 feature 分支中创建一个新的“合并提交”,将两个分支的历史记录联系在一起,从而为您提供一个看起来像这样的分支结构:

Merging main into feature branch

合并是不错的选择,因为它是一种非破坏性的操作。现有分支不会得到任何更改。这避免了变基操作的所有隐患(后面将会讨论)。

另一方面,这也意味着每次需要并入上游变更时,feature 分支将会产生一个无关的合并提交。如果 main 非常活跃,这可能会污染您的功能分支的历史记录。虽然可以使用高级 git log 选项来缓解此问题,但可能会让其他开发人员难以理解项目的历史记录。

The rebase option

作为合并的替代方法,您可以使用以下命令将 feature 分支变基为 main 分支:

git checkout feature
git rebase main

这会移动整个 feature 分支,以在 main 分支的节点开始,从而有效地将所有新提交并入 main 中。但是,变基并不使用合并提交,而是为原始分支中的每个提交创建全新的提交来重写项目历史记录。

Rebasing feature branch into main

变基的主要优势在于您可以获得更干净的项目历史记录。首先,它不像 git merge 一样需要不必要的合并提交。其次,如上图所示,变基还会产生完美的线性项目历史记录—您可以在没有任何新拷贝的情况下,始终按照 feature 的提示找到项目的源头。这可以让您更轻松地使用 git loggit bisectgitk 等命令导航项目。

但是,对于这种清晰的提交历史记录,存在两个需要权衡的地方:安全性和可追溯性。如果您不遵循变基的黄金法则,重写项目历史记录可能会对您的协作工作流造成潜在危害。另外,变基会丢失合并提交所带来的上下文—您无法看到上游变更何时被并入功能。

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 分支,会发生什么:

Rebasing the main branch

变基会将 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 分支。

变基至 Head~3

如果您想要使用此方法重写整个功能,则可以使用 git merge-base 命令查找 feature 分支的原始基准。以下内容返回原始基准的提交 ID,然后您可以传递给 git rebase

git merge-base feature main

使用这种交互式变基的是将 git rebase 引入到工作流中的好方法,因为它仅影响本地分支。其他开发人员可以看到的只是您的成品,也就是一个干净、易于追踪的功能分支历史记录。

但是,这仅适用于私有功能分支。如果您通过一个功能分支与其他开发人员进行协作,则该分支是公有分支,您不能重写其历史记录。

git merge 没有替代方法来使用交互式变基清理本地提交。

Incorporating upstream changes into a feature

概念概述部分中,我们看到了功能分支如何使用 git mergegit rebase 合并 main 分支的上游变更。合并是一个安全的选项,它可以保留存储库的整个历史记录,而变基通过将功能分支移到 main 分支的顶部来创建线性历史记录。

git rebase 的这种用法类似于本地清理(并且可以同时执行),但是在此过程中它合并了来自 main 的上游提交。

请注意,变基到远程分支而不是 main 分支上是完全合理的。当与其他开发人员在同一功能上进行协作时,可能会发生这种情况,您需要将他们的变更并入到您的代码库中。

例如,如果您和另一位名为 John 的开发人员向 feature 分支添加了提交,则在从 John 的存储库中获取远程 feature 分支后,您的存储库可能如下所示:

在同一个功能分支上协作

您可以使用与从 main 中集成上游变更完全相同的方法解决此新拷贝:将本地 featurejohn/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 来集成该功能。但是,通过在合并之前执行变基,可确保合并将以快进模式进行,从而形成完美的线性历史记录。这也使您有机会在拉取请求期间添加任何后续提交。

Integrating a feature into main with and without a rebase

如果您完全不习惯使用 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 示意图

DevOps 学习路径

与 Atlassian 专家一起进行 Den 功能演示

Bitbucket Cloud 与 Atlassian Open DevOps 如何协同工作

注册以获取我们的 DevOps 新闻资讯

Thank you for signing up