在没有额外提交消息的情况下将多个功能分支合并到主分支中?
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)
假设你有一个 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)