摘要:使用git rebase --onto <target> <limit>
正如Useless在评论中建议的那样,如果您进行了真正的合并,则不应发生这种情况。以下是我所说的“真正的合并”,以及有关贡献记录图形的示意图。我们从类似于这样的东西开始:
...--E---H <-- master
\
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
这里有两个提交(虽然确切的数量并不重要),它们仅存在于feat/feature-b
分支上,这里称为I
和J
;有两个提交在两个特性分支上,称为F
和G
;还有一个提交仅存在于master
分支上,称为H
。(提交E
及之前的提交存在于所有三个分支上。)
假设我们在master
上进行实际合并以引入F
和G
。以图形形式表示如下:
...--E---H--K <-- master
\ /
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
请注意真正的合并提交在其父提交历史指针中有两个提交
H
(位于
master
分支上)和
G
(位于
feat/feature-a
分支上)。因此,Git稍后知道合并
J
意味着“从
G
开始”。更准确地说,提交
G
将成为此后合并的合并基础。
这种合并方式本可以直接使用。但以前发生的事情并非如此:相反,执行合并的人使用了所谓的“压缩合并”功能。虽然压缩合并带来了与实际合并相同的
更改,但它根本不会产生任何合并结果。相反,它会产生一个重复合并了多少次提交工作的单一提交。在我们的例子中,它会重复执行从
F
和
G
进行的工作,因此看起来像这样:
...--E---H--K <-- master
\
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
请注意,K
没有从 G
回指的指针。
因此,当您尝试合并(真正的合并或压缩而不是真正意义上的“合并”)feat/feature-b
时,Git 认为它应该从 E
开始。 (在较早的真正合并情况中,技术上说 E
是合并基础,而不是像之前那样的 G
。)正如您所看到的,这会导致合并冲突。(通常它仍然可以“正常工作”,但有时就像在这种情况下一样,它不行。)
也许对于未来来说这很好,但现在问题是如何解决它。
在这里要做的是将专门针对 feat/feature-b
的提交复制到位于 K
后面的新提交中。也就是说,我们希望图片看起来像这样:
I'-J' <-- feat/feature-b
/
...--E---H--K <-- master
\
F--G <-- feat/feature-a
\
I--J [no longer needed]
最简单的方法是使用变基(rebase)这些提交,因为变基意味着复制。问题在于一个简单的git checkout feat/feature-b; git rebase master
会复制太多的提交。
解决方案是告诉git rebase
要复制哪些提交。你可以通过将参数从master
更改为feat/feature-a
(或提交G
的原始哈希ID——基本上是任何标识第一个1不需要复制的提交的东西)。但是这会告诉git rebase
将它们复制到它们已经存在的地方,所以这样做是没有用的。所以解决新的问题的方法是添加--onto
,它允许您将“副本的去处”部分与“要复制”的部分分开:
git checkout feat/feature-b
git rebase --onto master feat/feature-a
(假设您仍然拥有名称为feat/feature-a
指向提交G
;如果没有,您将不得不找到其他命名提交G
的方法 - 您可能希望绘制自己的图形和/或仔细查看git log
输出以查找提交哈希)。
1“首先”是Git风格的倒序方式。我们从最近的提交开始,向后跟随连接以查找旧的提交。Git在一切方面都是倒序的,因此在这里反向思考会有所帮助。:-)
git rebase -i master
将feat/feature-b
与主分支进行变基。我正在将feat/feature-b
上的所有提交压缩为1个提交。 - Chris Whitefeature-a
的合并是真正的合并吗?但是,如果feature-b
的合并不是真正的合并,那也会出问题。 - Uselessfeat/feature-a
被压缩成一个提交,然后合并到主分支,产生了一个合并提交。 - Chris Whitefeat/feature-a
分支上还在吗?这么说它是feat/feature-b
的父分支? - Useless