首页 > 解决方案 > 在没有额外提交消息的情况下将多个功能分支合并到 master 中?

问题描述

假设你有一个 master 分支:

A--B--C

特征 1 分支:

A--B--C--D

功能 2 分支:

A--B--C--E

当我们git merge Feature1进入 master 时,它合并得很好,但是当尝试合并 Feature2 时,我们会看到 vi 要求我们输入合并的提交消息。有没有办法合并这些分支而无需额外的合并提交?除了功能提交之外,它们与 master 共享相同的历史记录。

master 上的最终历史记录应如下所示:

A--B--C--D--E

取决于哪个提交日期(D 或 E)是第一个

标签: githubgit-merge

解决方案


背景

  • 在 git 中,历史是通过记录每个提交的级来建立的——通常,“正常”提交有一个父级,“合并提交”有两个父级,但实际上可以有任意数量的父级,包括零。
  • 每个提交都由其内容和元数据的哈希标识 - 包括提交者和时间,以及它的父级列表。如果没有获得新的提交哈希,您将无法更改该数据的任何部分,因此所有提交实际上都是不可变的。
  • git 中的“分支”实际上只指向一个提交,git从那里向后跟踪历史。

git看到的场景

每个提交指向其父级或父级,每个分支指向一个提交。

请注意,此图上的角度没有任何意义,它们只是将其以 2D 形式显示。

          +--D <--(feature1)
          v
A <--B <--C <--(master)
          ^
          +--E <--(feature2)

快进合并

默认情况下,git 将尽可能“快进”历史记录。这意味着它只是移动分支指针而不触及任何提交。

这就是你合并第一个特性分支时看到的内容:git 将“master”指针快进指向提交 D,而忽略其他所有内容:

             +--(master)
             V
          +--D <--(feature1)
          v
A <--B <--C 
          ^
          +--E <--(feature2)

哪个(记住角度没有任何意义)我们也可以这样绘制:

A <--B <--C <--D <--(master, feature1)
          ^
          +--E <--(feature2)

合并提交

当我们来合并第二个特性分支时,我们不能再快进了——将“master”指向提交 E 会丢失提交 D。所以 git 的另一个选择是创建一个“合并提交”——一个提交更多比一位家长。然后,“master”的指针可以指向这个新的提交。

这就是为什么在第二次合并时提示您输入消息的原因,因为 git 正在创建一个新提交(我们称之为“M2”),因此 D 和 E 都将在其历史记录中:

A <--B <--C <--D <--(feature1)
          ^    ^
          |    |
          |    M2 <--(master)
          |    |
          |    v
          +----E <--(feature2)

我们也可以这样画:

               +--(feature1)
               v
A <--B <--C <--D <--M2 <--(master)
          ^         |
          |         v
          +---------E <--(feature2)

请注意,我们也可以强制 git 在之前的合并中执行此操作,使用git merge --no-ff,这会给我们更多类似的东西:

          +----D <--(feature1)
          |    ^
          v    |
A <--B <--C <--M1 <--M2 <--(master)
          ^          |
          |          v
          +----------E <--(feature2)

变基

那么,我们如何创造一个看起来像这样的历史呢?

A <--B <--C <--D <--E <--(master)

从表面上看,我们不能:E 的父级记录为 C,而不是 D,并且提交是不可变的。但是我们可以做的是创建一个的提交,它看起来像 E,但它的父级是 D。这就是这样git rebase做的。

在快进功能 1 之后,我们有这个:

A <--B <--C <--D <--(master, feature1)
          ^
          +--E <--(feature2)

如果我们现在git rebase master feature2,git 将创建一个新版本的所有从 feature2 可访问的提交,这些提交还不能从 master 访问。它将尝试创建应用相同更改的提交,并默认复制提交消息甚至原始作者和时间戳,但它们将有新的父级。

然后它将 feature2 指向这些新的提交;在我们的例子中,结果将如下所示:

A <--B <--C <--D <--(master, feature1)
          ^    ^
          |    +--E2 <--(feature2)
          |
          +--E

原始提交 E 现在无法从任何分支访问,并将被清理。但是现在我们可以避免合并提交:新的提交 E2 处于我们可以再次快进 master 的位置:

A <--B <--C <--D <--(feature1)
               ^
               +--E2 <--(feature2)
                   |
                   + <--(master)

重绘:

               +--(feature1)
               v
A <--B <--C <--D <--E2 <--(master, feature2)


推荐阅读