在没有额外提交消息的情况下将多个功能分支合并到主分支中?

Merging multiple feature branches into master without additional commit message?

假设你有一个 master 分支:

A--B--C

功能 1 分支:

A--B--C--D

功能 2 分支:

A--B--C--E

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

master 的最终历史应该是这样的:

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

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

背景

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

场景,如git所见

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

请注意,此图上的角度没有任何意义,它们只是二维布局。

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

fast-forward合并

默认情况下,git 将尽可能“fast-forward”历史记录。这意味着它只是移动分支指针而根本没有触及任何提交。

这是您在合并第一个功能分支时看到的内容:git fast-forwards 指向提交 D 的“主”指针,并保留其他所有内容:

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

我们也可以这样画(记住角度不代表任何东西):

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

合并提交

当我们开始合并第二个特性分支时,我们不能再fast-forward——将“master”指向提交E会丢失提交D。所以git的另一个选项是创建一个“合并提交”——一个有多个 parent 的提交。然后“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 merge --no-ff 强制 git 对之前的合并执行此操作,这会给我们更多类似的东西:

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

变基

那么,我们如何创造这样的历史呢?

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

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

在 fast-forward 特征 1 之后,我们得到了这个:

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

如果我们现在git rebase master feature2,git 将创建一个新版本 所有提交的新版本 可从 feature2 访问但尚未从 master 访问的提交。它将尝试创建应用相同更改的提交,默认情况下复制提交消息甚至原始作者和时间戳,但它们将有新的 parents.

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

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

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

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

重绘:

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