Rebase 与 Merge:在 Git 中集成更改

Avatar of Tobias Günther
Tobias Günther

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

本文是我们 “高级 Git” 系列的一部分。请务必关注我们的 Twitter订阅我们的新闻通讯 以了解下一篇文章!

大多数开发人员都了解在 Git 中使用分支的重要性。事实上,我已经写了一篇关于Git 中的分支策略的文章,解释了 Git 强大的分支模型、不同类型的分支以及两种最常见的分支工作流程。概括地说:为您的工作创建独立的容器,即分支,非常有帮助,也是使用版本控制系统的主要原因之一。

在这篇文章中,我们将研究集成分支。如何将新代码重新整合到现有的开发线上?有不同的方法可以实现这一点。我们“高级 Git”系列的第五集讨论了在 Git 中集成更改,即合并和变基。

在我们深入了解细节之前,务必理解这两个命令——git merge 和 git rebase——都解决了同一个问题。它们将一个 Git 分支中的更改集成到另一个分支中;只是实现方式不同。让我们从合并开始,以及它们是如何工作的。

高级 Git 系列

理解合并

要将一个分支合并到另一个分支中,可以使用 git merge 命令。假设您在一个分支 branch-B 上有一些新的提交,现在您想将此分支合并到另一个分支 branch-A 中。为此,您可以键入以下内容

$ git checkout branch-A
$ git merge branch-B

结果,Git 在您当前的工作分支(此示例中的branch-A)中创建一个新的合并提交,连接两个分支的历史记录。为了实现这一点,Git 会查找三个提交

  • 第一个是“共同祖先提交”。如果您跟踪项目中两个分支的历史记录,它们始终至少有一个共同的提交。此时,两个分支具有相同的内容。之后,它们以不同的方式发展。
  • 另外两个有趣的提交是每个分支的端点,即它们当前的状态。请记住,集成的目标是组合两个分支的当前状态。因此,它们的最新修订当然很重要。

组合这三个提交可以执行我们想要进行的集成。

诚然,这是一个简化的场景——两个分支之一(branch-A)自创建以来没有出现任何新的提交,这在大多数软件项目中是非常不可能的。因此,在此示例中,它的最后一个提交也是共同祖先

在这种情况下,集成非常简单:Git 只需将branch-B的所有新提交添加到共同祖先提交之上即可。在 Git 中,这种最简单的集成形式称为“快进”合并。然后,这两个分支共享完全相同的历史记录(并且不需要额外的“合并提交”)。

但是,在大多数情况下,这两个分支都会使用不同的提交向前移动。所以让我们来看一个更现实的例子

为了进行集成,Git 必须创建一个包含所有更改的新提交,并处理分支之间的差异——这就是我们所说的合并提交

人工提交和合并提交

通常,提交是由人类仔细创建的。它是一个有意义的单元,仅包含相关的更改,以及一个有意义的提交消息,提供上下文和注释。

现在,合并提交有点不同:它不是由开发人员创建的,而是由 Git自动创建的。此外,合并提交不一定包含“语义相关的更改集合”。相反,它的目的仅仅是连接两个(或多个)分支并系上结。

如果您想了解这种自动合并操作,则必须查看所有分支及其各自提交历史记录的历史记录。

使用变基进行集成

在我们讨论变基之前,请允许我明确一点:变基并不比合并好或坏,它只是不同。您只需合并分支就可以过上幸福的(Git)生活,甚至不必考虑变基。但是,了解变基的作用以及随之而来的优缺点确实很有帮助。也许您会在项目的某个阶段发现变基可能会有所帮助……

好的,让我们开始吧!还记得我们刚才讨论的自动合并提交吗?有些人不太喜欢这些提交,更愿意不使用它们。此外,有些开发人员希望他们的项目历史看起来像一条直线——即使在分支已集成之后,也不显示它在某些时候被拆分为多个分支的任何迹象。这基本上是在 Git 变基期间发生的事情。

变基:分步操作

让我们分步了解变基操作。场景与前面的示例相同,这是起点的示例

我们希望将branch-B中的更改集成到branch-A中——但通过变基而不是合并。为此使用的 Git 命令非常简单

$ git checkout branch-A
$ git rebase branch-B

git merge命令类似,您告诉 Git 您要集成哪个分支。让我们看看幕后发生了什么……

在第一步中,Git 将“删除”branch-A上在共同祖先提交之后发生的所有提交。别担心,它不会将它们丢弃:您可以将这些提交视为“已停放”或临时保存在安全的地方。

在第二步中,Git 应用来自branch-B的新提交。此时,暂时,这两个分支实际上看起来完全相同。

最后,那些“已停放”的提交(来自branch-A的新提交)被包含进来。由于它们位于来自branch-B的已集成提交之上,因此它们被变基了。

结果,项目历史看起来像是开发以直线进行的。没有包含所有组合更改的合并提交,并且保留了原始提交结构。

变基的潜在陷阱

还有一件事——这一点对于理解 Git 变基非常重要——那就是它会重写提交历史记录。再看一下我们最后的图表。提交 C3* 带有一个星号。虽然 C3* 与 C3 具有相同的内容,但它实际上是一个不同的提交。为什么?因为在变基之后,它有一个新的父提交。在变基之前,C1 是父提交。变基之后,父提交是 C4——它被变基到其中。

提交只有少数几个重要的属性,如作者、日期、更改集及其父提交。更改其中的任何信息都会创建一个全新的提交,并具有新的 SHA-1 哈希 ID。

像这样重写历史记录对于尚未发布的提交来说不是问题。但是,如果您正在重写已经推送到远程存储库的提交,那么您可能会遇到麻烦。也许其他人已基于原始的 C3 提交开展工作,而现在它突然消失了……

为了避免出现问题,这里有一条使用变基的简单规则:永远不要在公共分支上使用变基,即在已推送到远程存储库的提交上使用!相反,在将其集成到共享团队分支之前,使用git rebase清理您的本地提交历史记录。

整合至关重要!

归根结底,合并和变基都是有用的 Git 策略,具体取决于您想要实现的目标。合并某种程度上是非破坏性的,因为它不会更改现有历史记录。另一方面,变基可以通过避免不必要的合并提交来帮助清理您的项目历史记录。请记住,不要在公共分支中执行此操作,以免干扰其他开发人员。

如果您想深入了解高级 Git 工具,请随时查看我的(免费!)“高级 Git 工具包”:它包含一系列关于分支策略、交互式变基、Reflog、子模块等等主题的短视频。

祝您合并和变基愉快——并在我们“高级 Git”系列的下一部分中与您相见!

高级 Git 系列