有两个相对容易的解决方案。一个是尝试rebase,这可能有效。但如果不行,还有另外一种更强大的方法。下面是第二种方法以及为什么要这样做:
git checkout topic; git rebase --onto next next@{1}
发生了什么:如何使用rebase
如果您以稍微不同的方式绘制图形,我认为视图会更清晰。以下是原始图形稍作重绘后的样子:
A
\
F
\
K
使用git checkout next; git rebase master
命令后,以下是您的代码所处的状态:
A---B---C---D---E <-- master
\ \
| F'-G'-H'-I'-J' <-- next
\
F---G---H---I---J [was "next", but not anymore]
\
K---L---M <-- topic
这是因为 rebase 通过
复制 提交来工作,并进行一些轻微(或重大)的更改。新提交
F'
是原始提交
F
的副本,
G'
是
G
的副本,等等。
如果您现在运行
git checkout topic; git rebase next
,它通常可以正常工作,但并非总是如此。这是因为默认情况下,
rebase
根据单个参数选择要复制的提交以及要复制到的位置。这有点复杂,因此让我们分两步来看。
当您说
git rebase next
时,单个参数是
next
:副本将放置在
next
的尖端提交之后(现在是
J'
)。这部分完全简单明了:那个标识符
next
找到
--onto
符合复制的提交。(我拼写成
--onto
而不只是 "onto" 有一个原因 :-))
复制的内容比较棘手,有两种方式。首先,
next
指定不应该复制的内容,而当前分支(
topic
)指定要复制的内容。Git 获取从
topic
可达到的所有提交列表 - 这是提交
A
、
B
、
F
、
G
、
H
,以及一直到
L
的提交。然后它会排除从
next
可达的所有提交 - 这是提交
A
、
B
、
C
、
D
、
E
、
F'
(但不是
F
)、
G'
(但不是
G
)等等,直到
J'
。一些被排除的提交不在包含的列表中并不重要:我们只是排除了那些在包含列表中的提交。
不幸的是,这使得提交
F
、
G
、
H
、
I
和
J
在待复制的提交堆栈中。这就是事情出错的地方,正如您所注意到的那样。但是,在大部分时间里,它似乎仍然可以正常工作。当它正常工作时发生了什么?为什么它不总是有效?答案:rebase也会排除“补丁等价提交”。在进行变基时,Git会扫描“排除”列表,看是否有任何提交与“包括”列表中的提交执行相同操作。由于F'是F的副本,在补丁等价意义上很可能与F“相同”。G与G'、H与H'也是如此。
只要提交与其变基后的版本补丁相同,它们就会被排除。
当这个条件成立(通常情况下是成立的)时,你可以简单地输入git checkout topic; git rebase next并让它自动完成。只有当提交的修改量过大导致复制后的提交不再补丁等价时,才会失败。作为一个经验法则,如果Git能够单独完成第一次变基,那么它也能够单独完成第二次变基。主要是当它停止处理冲突并要求你手动解决时,补丁才会变得“非补丁等价”。但使用更长的git rebase格式处理这些问题很容易。
git rebase next
我们可以写成:
git rebase --onto next <something>
根据我们绘制的图表,这里需要使用<something>
来指定提交J
。也就是说,我们需要找到next
在重新设置之前所指向的提交记录。现在它指向了J'
,但它曾经指向过J
。我们需要Git排除那些next
曾经能够到达的提交记录:A
、B
、F
、G
、H
、I
和J
。如果Git无法通过补丁等效性自动找到它们,我们可以手动告诉Git。
这仍然存在一个问题:如何拼写标识提交J
的名称。一种方法是使用原始的哈希值,这总是有效的,但有点繁琐(不仅要找到,而且要复制,尽管剪切和粘贴对于复制哈希值非常有效)。不过再次查看重画的图表,看一下“是“next”,但不再是”的部分。如果我们只能告诉Git查找next
的前一个值。
但等等!Git有reflogs!我们可以告诉它做到这一点!next
的前一个值是next@{1}
。(每当我们更改标签next
,例如添加新的提交记录,该数字就会增加,但我们此时所做的仅是一个rebase
,因此它仍然是next@{1}
。)因此,此时我们只需要执行以下操作:
git checkout topic; git rebase --onto next next@{1}
(根据您的shell不同,如果要使用花括号,可能需要将其放在引号中,例如'next@{1}'
或next@\{1\}
等)。
1有一些例外情况。例如,在这种情况下,如果 git diff E F'
与 git diff B F
的产生的更改集不同,由于diff选择了不同的半相关匹配项,比如空行或闭合括号行,那么就会出现问题。然而,处理这些问题的答案与处理由于冲突导致手动应用变基的答案相同。
git checkout topic; git rebase next
,但是我想知道是否有更好的解决方案。有时我需要解决一些我觉得本可以避免的问题。 - Jan Matějka