合并基于另一个功能分支的功能分支
Merging a feature branch that is based off another feature branch
几天前,我有一个具有完全线性历史的 master
分支。然后我创建了一个功能分支,我们称之为 feat/feature-a
。我在那个分支上工作,然后提交它进行代码审查以合并到 master
.
在审查 feat/feature-a
期间,我想开发另一个依赖于 feat/feature-a
引入的代码的功能。所以我从 feat/feature-a
分支创建了一个 feat/feature-b
分支。
当我在 feat/feature-b
上工作时,feat/feature-a
被合并到了 master 中。所以现在master有feat/feature-a
介绍的代码了。我现在想将 feat/feature-b
合并到 master 中,但是我遇到了很多看起来像这样的合并冲突:
<<<<<<< HEAD
=======
// Some code that was introduced by feat/feature-b
>>>>>>> c948094... My commit message from feat/feature-b
我的猜测是,因为我将 feat/feature-a
更改纳入了我的 feat/feature-b
分支,所以我现在正在尝试 "duplicate" 那些以合并冲突结尾的更改。
我可以手动解决这些问题,但它们在数十个文件中多次存在,所以如果有更好的解决方案,我想知道。
总结:使用git rebase --onto <target> <limit>
作为,如果你有一个真正的合并,这不应该发生。这就是我所说的 "real merge" 的意思,以及如果您绘制相关提交图,分支的外观图。我们从这样的事情开始:
...--E---H <-- master
\
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
这里有两个提交(尽管确切数量无关紧要)在 feat/feature-b
上 仅 ,称为 I
和 J
这里; both 功能分支上有两个提交,称为 F
和 G
;并且在 master
上有一个 仅 的提交,称为 H
。 (提交 E
及更早版本在 所有三个 分支上。)
假设我们在 master
上进行真正的合并以引入 F
和 G
。看起来像这样,以图形形式表示:
...--E---H--K <-- master
\ /
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
请注意,真正的合并 K
具有作为其父提交历史指针的提交 H
(在 master
上)和 G
(在 [=31= 上) ]). Git因此知道,后来,合并J
意味着"start with G
"。 (更准确地说,提交 G
将是此后一次合并的 合并基础 。)
合并就可以了。但这不是以前发生的事情:相反,进行合并的人使用了 so-called "squash merge" 功能。虽然 squash-merge 带来与实际合并相同的 更改 ,但它根本不会产生合并。相反,它会生成一个复制已合并的 however-many-it-was 提交的工作的单个提交。在我们的例子中,它复制了 F
和 G
的工作,所以它看起来像这样:
...--E---H--K <-- master
\
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
请注意 K
到 G
之间缺少 back-pointer。
因此,当你去合并(realorsquash-not-really-a-"merge")feat/feature-b
,Git认为应该以 E
开头。 (从技术上讲,E
是合并基础,而不是早期实际合并案例中的 G
。)如您所见,这最终会给您带来合并冲突。 (通常它仍然 "just works" 无论如何,但有时 - 就像在这种情况下 - 它不会。)
这对未来来说也许很好,但现在的问题是如何解决它。
你想在这里做的是复制排他性-feat/feature-b
提交到新提交,之后K
。也就是我们希望图片是这样的:
I'-J' <-- feat/feature-b
/
...--E---H--K <-- master
\
F--G <-- feat/feature-a
\
I--J [no longer needed]
最简单的方法是 rebase 这些提交,因为 rebase 意味着 复制。问题是一个简单的 git checkout feat/feature-b; git rebase master
将复制 太多 提交。
解决方案是告诉git rebase
哪个提交复制。您可以通过将参数从 master
更改为 feat/feature-a
(或提交的原始哈希 ID G
——基本上,任何标识第一个 1 提交 而不是 进行复制)。但这告诉 git rebase
将它们复制到它们已经存在的位置;所以那不好。所以 new 问题的解决方案是添加 --onto
,这样可以将 "where the copies go" 部分与 "what to copy" 部分分开:
git checkout feat/feature-b
git rebase --onto master feat/feature-a
(假设您仍然有名称 feat/feature-a
指向提交 G
;如果没有,您将不得不找到其他方式来命名提交 G
——您可能希望绘制自己的图表 and/or 或仔细查看 git log
输出,以找到提交哈希)。
1"First" Git-style 向后的方式,也就是说。我们从最近的提交开始,并按照连接向后返回到较旧的提交。 Git 一切都是倒着做的,所以在这里倒着思考是有帮助的。 :-)
简单模型如下所示:
X -> MA <master
\ /
A <feature-a
这里,feature-a可能已经通过rebase被压缩成一个commit,但是merge还是真正的merge。那么,你有
X -> MA -> MB <master
\ / /
A ---> B <feature-b
其中feature-b是基于feature-a任意压缩后的,也正常合并。这种情况应该 正常工作 ,因为 git 可以看到 A
是 B
的祖先并且您已经合并了它。
为了比较,这不会干净地工作:
X -> MA -> ... <master
|\ /
| As <feature-a
| |
| ^--squash------<--
\ \
A0 -> A1 -> ... -> An -> B <feature-b
您在合并 feature-a 之前将 A0..n 压缩为 As,但是 feature-b 是从 An.
分支出来的
现在 git 不知道 As 和 A0..n 是如何相关的,所以合并和(简单的)变基都不会自动工作。如果您想使用 rebase --onto
来解决这种情况,请参阅 torek 的优秀答案。
几天前,我有一个具有完全线性历史的 master
分支。然后我创建了一个功能分支,我们称之为 feat/feature-a
。我在那个分支上工作,然后提交它进行代码审查以合并到 master
.
在审查 feat/feature-a
期间,我想开发另一个依赖于 feat/feature-a
引入的代码的功能。所以我从 feat/feature-a
分支创建了一个 feat/feature-b
分支。
当我在 feat/feature-b
上工作时,feat/feature-a
被合并到了 master 中。所以现在master有feat/feature-a
介绍的代码了。我现在想将 feat/feature-b
合并到 master 中,但是我遇到了很多看起来像这样的合并冲突:
<<<<<<< HEAD
=======
// Some code that was introduced by feat/feature-b
>>>>>>> c948094... My commit message from feat/feature-b
我的猜测是,因为我将 feat/feature-a
更改纳入了我的 feat/feature-b
分支,所以我现在正在尝试 "duplicate" 那些以合并冲突结尾的更改。
我可以手动解决这些问题,但它们在数十个文件中多次存在,所以如果有更好的解决方案,我想知道。
总结:使用git rebase --onto <target> <limit>
作为
...--E---H <-- master
\
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
这里有两个提交(尽管确切数量无关紧要)在 feat/feature-b
上 仅 ,称为 I
和 J
这里; both 功能分支上有两个提交,称为 F
和 G
;并且在 master
上有一个 仅 的提交,称为 H
。 (提交 E
及更早版本在 所有三个 分支上。)
假设我们在 master
上进行真正的合并以引入 F
和 G
。看起来像这样,以图形形式表示:
...--E---H--K <-- master
\ /
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
请注意,真正的合并 K
具有作为其父提交历史指针的提交 H
(在 master
上)和 G
(在 [=31= 上) ]). Git因此知道,后来,合并J
意味着"start with G
"。 (更准确地说,提交 G
将是此后一次合并的 合并基础 。)
合并就可以了。但这不是以前发生的事情:相反,进行合并的人使用了 so-called "squash merge" 功能。虽然 squash-merge 带来与实际合并相同的 更改 ,但它根本不会产生合并。相反,它会生成一个复制已合并的 however-many-it-was 提交的工作的单个提交。在我们的例子中,它复制了 F
和 G
的工作,所以它看起来像这样:
...--E---H--K <-- master
\
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
请注意 K
到 G
之间缺少 back-pointer。
因此,当你去合并(realorsquash-not-really-a-"merge")feat/feature-b
,Git认为应该以 E
开头。 (从技术上讲,E
是合并基础,而不是早期实际合并案例中的 G
。)如您所见,这最终会给您带来合并冲突。 (通常它仍然 "just works" 无论如何,但有时 - 就像在这种情况下 - 它不会。)
这对未来来说也许很好,但现在的问题是如何解决它。
你想在这里做的是复制排他性-feat/feature-b
提交到新提交,之后K
。也就是我们希望图片是这样的:
I'-J' <-- feat/feature-b
/
...--E---H--K <-- master
\
F--G <-- feat/feature-a
\
I--J [no longer needed]
最简单的方法是 rebase 这些提交,因为 rebase 意味着 复制。问题是一个简单的 git checkout feat/feature-b; git rebase master
将复制 太多 提交。
解决方案是告诉git rebase
哪个提交复制。您可以通过将参数从 master
更改为 feat/feature-a
(或提交的原始哈希 ID G
——基本上,任何标识第一个 1 提交 而不是 进行复制)。但这告诉 git rebase
将它们复制到它们已经存在的位置;所以那不好。所以 new 问题的解决方案是添加 --onto
,这样可以将 "where the copies go" 部分与 "what to copy" 部分分开:
git checkout feat/feature-b
git rebase --onto master feat/feature-a
(假设您仍然有名称 feat/feature-a
指向提交 G
;如果没有,您将不得不找到其他方式来命名提交 G
——您可能希望绘制自己的图表 and/or 或仔细查看 git log
输出,以找到提交哈希)。
1"First" Git-style 向后的方式,也就是说。我们从最近的提交开始,并按照连接向后返回到较旧的提交。 Git 一切都是倒着做的,所以在这里倒着思考是有帮助的。 :-)
简单模型如下所示:
X -> MA <master
\ /
A <feature-a
这里,feature-a可能已经通过rebase被压缩成一个commit,但是merge还是真正的merge。那么,你有
X -> MA -> MB <master
\ / /
A ---> B <feature-b
其中feature-b是基于feature-a任意压缩后的,也正常合并。这种情况应该 正常工作 ,因为 git 可以看到 A
是 B
的祖先并且您已经合并了它。
为了比较,这不会干净地工作:
X -> MA -> ... <master
|\ /
| As <feature-a
| |
| ^--squash------<--
\ \
A0 -> A1 -> ... -> An -> B <feature-b
您在合并 feature-a 之前将 A0..n 压缩为 As,但是 feature-b 是从 An.
分支出来的现在 git 不知道 As 和 A0..n 是如何相关的,所以合并和(简单的)变基都不会自动工作。如果您想使用 rebase --onto
来解决这种情况,请参阅 torek 的优秀答案。