mnestorov的评论提到使用--rebase-merges
与你试图解决的实际问题相关,请考虑使用它(但你还没有真正描述出问题在哪里,我会在下面记录下似乎偏离轨道的地方)。如果-r
有效,那么你已经完成了。如果你的git版本较旧,则可能没有-r
/--rebase-merges
选项。如果是这样,最好的答案是升级你的Git。
更多关于rebase的内容
让我们更多地谈论一般情况下的rebase,从这里开始:
有没有办法让rebase采用cherry-pick的-m#
行为来进行pick
?
没有:即使有,也不会起作用,至少不是普遍的。下面是原因。
当您在此处使用-m
选项将合并提交“复制”到非合并,普通提交中。 -m
选项使Git将合并提交视为普通提交,具有单个父项,并且-m
标志告诉它将哪个父项称为“the” 单一的父项。但是,合并提交的目的通常是从两个父项中结合工作1。
同时,git rebase
的目的是重复复制某些提交,随后放弃原始提交,转而使用新的副本。无法复制合并提交 - cherry-pick的-m
并不这样做;它代替产生一个普通提交-因此rebase通常会丢弃合并提交。我将展示下面的如何以及为什么这是标准rebase运作的正确方法。
git rebase -i --onto myBranch myBranch
请注意,
--onto
参数和
git rebase
文档中称为
upstream
的另一个参数默认相同,因此更简单的写法如下:
git rebase -i myBranch
此操作要复制的提交集合,不超过以下操作所产生的提交集合:
git log myBranch..HEAD
那就假设我们有以下情况,其中更新的提交向右,我们当前在分支
topic
上:
G--H <-- topic (HEAD)
/
...--E--F--I--J <-- myBranch
运行
git rebase myBranch
,无论是否带有
--interactive
参数,告诉Git:
首先,列出那些从HEAD
(即topic
)可以到达的提交,减去任何同样可以从myBranch
到达的提交。这会导致Git在内部列出提交
G
和
H
。它们是
复制的候选者。
如果这些提交是最终要复制的提交,且其他简化假设成立,则结果将是:
G--H [abandoned]
/
...--E--F--I--J <-- myBranch
\
G'-H' <-- topic (HEAD)
其中G'
和H'
是原始提交G
和H
的副本,这些副本各有两个重要的不同之处:
G'
的父级是J
,而不是F
。 G'
中的文件包含与G
相对于F
所做的更改相同的更改,因此G'
中存储的快照与G
中的快照不同。
H'
的父级是G'
,而不是G
,其快照以类似的方式不同。
也就是说,由于每个提交都保存了完整的快照,因此我们需要在复制的提交中让快照与原始提交的快照不同。 新快照中的差异使得G'
与J
进行比较与G
与F
进行比较产生了大致相同的差异(diff)。 当然,链接(在Git中总是向后)也不同,因此这些副本排在myBranch
的最后一个提交之后。
1如果有章鱼合并(octopus-merge),则会从多个父级中合并工作,而罕见的-s ours
合并将完全丢弃一个父级的内容,因此这些特殊情况更加特殊。一般来说,不应在这些情况下使用变基(rebase)。
变基(rebase)有意不做什么
假设在我们的原始两个提交G
和H
中:
G--H <-- topic (HEAD)
/
...--E--F--I--J <-- myBranch
从G
到H
的更改与从I
到J
的更改完全相同。例如,这两个提交都修复了在README
文件中拼写错误的相同单词,除此之外没有做任何其他更改。
当我们运行git rebase myBranch
时,Git仍然列出提交记录G
和H
。但它还会查看提交记录I
和J
,并针对每个提交记录计算出Git所谓的一个补丁ID(请参见git patch-ID文档)。该补丁ID告诉Git:提交H
是提交J
的重复提交。因此,Git会删除要复制的提交记录H
。
因此,当我们说rebase列出myBranch..HEAD
以获取要复制的候选提交记录时,这些只是候选者。其中一些候选项会被有意自动排除。在这种特殊情况下,只有提交记录H
是有意被排除的,最终rebase的结果将是:
G--H [abandoned]
/
...--E--F--I--J <-- myBranch
\
G' <-- topic (HEAD)
Git基本上认为提交H
已经应用。因此,它完全放弃了它。
Git还会与其称为"分岔点"的东西进行相当复杂的协作。分叉点代码的目标是发现被有意删除的提交并在变基期间自动删除它们。这段代码通常做正确的事情,但也可能出现问题。2 在这种情况下,修补程序ID和分叉点代码似乎都没有影响到你,但还有一个更大的特殊情况,值得单独讨论。
2它可能会出错的事实使我认为它不一定是正确的默认值。这也适用于“已应用上游”修补程序ID案例。特别是,交互式变基真的应该在其指令页中包括这些提交,预选操作应为“删除”,并注明为什么要删除它们。但是今天还没有这样。
合并
到目前为止,我们绘制的图片都很简单。但是假设我们的topic
分支提交看起来像这样:
I--J
/ \
G--H M--N <-- topic (HEAD)
/ \ /
/ K--L
/
...--E--F--------------O--P <-- myBranch
当我们运行以下代码时:
git log myBranch..topic
我们将看到提交
N
和
M
,然后按某种顺序看到提交
I
到
L
,其中
I
在
J
后面显示,但与
K
和
L
的顺序是随机的,而且
K
在
L
后面显示,但对于
I
和
J
的顺序也是随机的。 然后我们将看到提交
H
,接着是
G
,这就是列表的结尾。
(如果我们添加
--topo-order
,那么列表的顺序会更受限制。rebase 代码在内部添加
--topo-order
。我们仍然不知道
L
或
J
谁会先出现,但一旦我们得到其中一个,我们将完成整个行,然后再去另一行。没有
--topo-order
,我们可以看到
N
、
M
、
L
、
J
、
K
、
I
、
H
、
G
这样的结果。)
你的问题有点偏离了。 git rebase 命令将自动完全删除合并提交
M
,原因有两个:
- cherry-pick(以及旧的 git format-patch / git am 方法)无法复制合并提交;
- 标准 rebase 的结果不应复制合并提交。
因此,您将不会在提交
M
上看到
pick
命令。如果要看到它,您必须手动插入,这是一个错误的做法。为了理解这一点,让我们看看 Git 在不使用
pick <hash-of-M>
时如何处理这种情况,并进行常规(非 --rebase-merges)重新派生。
序列首先列出要复制的提交。假设它们按以下顺序显示,在谨慎地反转它们的同时舍弃合并提交:
G-H-I-J-K-L-N
。如果在复制阶段一切顺利,结果将是:
I--J
/ \
G--H M--N [abandoned]
/ \ /
/ K--L
/
...--E--F--O--P <-- myBranch
\
G'-H'-I'-J'-K'-L'-N' <-- topic (HEAD)
换句话说,
git rebase
已经
摊平了合并。但是合并代码
M
的目的是将
I-J
和
K-L
分支上的工作
合并在一起。我们不需要那个合并,因为将
K
复制到
K'
的过程如下:
- 对于
H
与K
提交之间的每个变化,在从I'
中提取的内容中进行相同的更改;
- 然后将其作为新提交
K'
提交。
也就是说,提交
K'
不是基于
H
或
H'
,而是基于
I'
。它
已经包含了另一个分支上的工作。同样地,当Git将
L
复制到
L'
时,它会将其复制到一个
已经包含了另一个分支上的工作的提交上。因此,没有必要创建分支。重新定位操作可以完全将其
摊平。
3请记住,Git是反向工作的,因此列表总是首先显示N
。我们需要将N
放在最后,因此重新定位会将列表反转。
--rebase-merges
选项
摊平合并的想法并不总是一个好主意。有时它不起作用。例如,一系列操作如下:
I--J
/
...--H
\
K--L
通常,两个分支上的更改相对较少,这使得"将分支平铺"变得容易且效果良好。但是如果该系列在每个分支上有大量提交:
o--o--...(1000 commits)...--o--tip1
/
...--o
\
o--o--....................--o--tip2
在这种情况下,合并两个tip提交的合并可能会有很多工作。删除合并是不可行的。
或者,也许我们只是喜欢保留合并。合并代表着重要的东西,我们希望未来的代码考古学家能够看到它。
好吧,“复制”合并确实是不可能的。Cherry-pick的-m选项不会这样做。如果我们在平整事情之后用cherry-pick -m“复制”合并:
I--J
/ \
G--H M--N <-- topic
/ \ /
/ K--L
/
...--E--F--O--P <-- myBranch
\
G'-H'-I'-J'-K'-L' <-- HEAD
我们只需通过I-J
或通过K-L
获得已有的更改,因此再次引入这些更改是多余的。为了“正确地”复制合并操作,我们必须先创建一个分支:
I--J
/ \
G--H M--N <-- topic
/ \ /
/ K--L
/
...--E--F--O--P <-- myBranch
\
\ I'-J' <-- temp-label-1
\ /
G'-H'
\
K'-L' <-- temp-label-2, HEAD
接着,我们必须选择正确的分支最新版本作为HEAD
提交,并且要重新再次运行git merge
以创建M'
:
reset-to temp-label-1
merge temp-label-2
如果合并成功,我们现在将拥有:
I--J
/ \
G--H M--N <-- topic
/ \ /
/ K--L
/
...--E--F--O--P <-- myBranch
\
\ I'-J' <-- temp-label-1
\ / \
G'-H' M' <-- HEAD
\ /
K'-L' <-- temp-label-2
我们现在可以选择
N
的哈希值来生成
N'
:
I--J
/ \
G--H M--N <-- topic
/ \ /
/ K--L
/
...--E--F--O--P <-- myBranch
\
\ I'-J' <-- temp-label-1
\ / \
G'-H' M'-N' <-- HEAD
\ /
K'-L' <-- temp-label-2
然后我们就完成了这个复杂的重新基础分支合并的操作,可以移动分支标签topic
并且删除任何临时标签:
I--J
/ \
G--H M--N [abandoned]
/ \ /
/ K--L
/
...--E--F--O--P <-- myBranch
\
\ I'-J'
\ / \
G'-H' M'-N' <-- topic (HEAD)
\ /
K'-L'
这是
git cherry-pick --rebase-merges
命令的作用。为了达到这个结果,它需要一些额外的命令和插入临时标签的能力。(注意,在进行cherry-pick操作序列重置
HEAD
之前,
K
复制到
K'
时,也会有一个
H'
的临时标签。您将在指令说明书中看到所有这些标签和重置操作,该说明书需要知道何时创建各种标签以及如何移动
HEAD
。)
-r
(--rebase-merges
) 选项来运行交互式变基了吗? - mnestorov