使用 git rebase Interactive 来编排一系列 gitcherry-pick?

Bra*_*rad 3 git git-rebase git-cherry-pick

gitcherry-pick 允许简单地通过指示哪个合并父级应该用作基线来选择简单的合并。例如:

git cherry-pick -m 1 1234abcdef
Run Code Online (Sandbox Code Playgroud)

我有一堆提交想要挑选,其中一些可能是合并,而其他则不会。我希望能够使用 git rebase 来挑选所有这些提交,如下所示:

git rebase -i --onto myBranch myBranch 
Run Code Online (Sandbox Code Playgroud)

并将选择列表放入交互式文件中:

p 1234
p 3224
... a bunch more picks
p abcde
Run Code Online (Sandbox Code Playgroud)

而且,如果 git rebase 在这些提交中遇到合并,我想指定相当于cherry-pick 的-m 1选项,以指示应针对第一个父级选择更改。

我尝试了一些与合并相关的选项来变基,但最终总是收到错误:

commit 3c4ffe04532 is a merge but no -m option was given.
Run Code Online (Sandbox Code Playgroud)

(即使我指定 -m 来变基。)

我意识到我可以使用cherry-pick 编写一个脚本,但我喜欢现有的行为rebase -i(它运行在命令列表中,如果遇到无法处理的情况则暂停)。我非常想直接利用该逻辑,但我一直无法找到正确的方法来巧妙地使用 rebasepick命令来填补这一空白。

有没有办法让 rebase 采用cherry-pick 的-m #行为pick

为了以另一种方式陈述我的目标并帮助澄清问题 - 我想使用 git-rebase 的--i功能来协调一系列git cherry-picks,以便可以手动解决流程中的任何合并冲突,然后可以使用--continue,--abort和/或来管理流程--skip

这很有用,因为一个简单的脚本包含:

git cherry-pick -m 1 e1bed15c97f3f
git cherry-pick -m 1 6b5e6060b0e99
....
git cherry-pick -m 1 1a625d6b45faa
Run Code Online (Sandbox Code Playgroud)

可能会因如下错误而中止:

error: could not apply 6b5e6060b0e99... Implement Something... 
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

d:\src>git cherry-pick -m 1   e1bed15c97f3f
error: Cherry-picking is not possible because you have unmerged files.
hint: Fix them up in the work tree, and then use 'git add/rm <file>'
hint: as appropriate to mark resolution and make a commit.
fatal: cherry-pick failed
Run Code Online (Sandbox Code Playgroud)

谢谢!

tor*_*rek 5

mnestorov关于使用的评论--rebase-merges与此处相关;将其视为您试图解决的实际问题(您尚未真正描述:我将在下面注明事情似乎偏离轨道的地方)。git rebase可能你正在做的事情对于今天来说有点太艰难了。但我认为你正在做的正是-r设计的目的。

\n

如果-r有效,你就完成了。如果你的 Git 很旧,你可能没有-r/--rebase-merges选项。如果是这样,最好的答案是升级你的 Git。

\n

有关变基的更多信息

\n

让我们从总体上更多地讨论变基:

\n
\n

有没有办法让 rebase 采用cherry-pick 的-m #行为pick

\n
\n

不:如果有的话,无论如何它都不会起作用,至少一般情况下不会。这就是原因。

\n

当您使用-m此处的选项“复制”合并时,您将其复制到非合并的普通提交中。该-m选项使 Git 将合并提交视为具有单个父项的普通提交,并且该-m标志告诉它哪个父项称为“该”单父项。但合并提交的目的通常是合并两个父级的工作。1

\n

同时,其目的git rebase是重复复制一些提交,随后放弃原始提交并转而使用新副本。不可能复制合并提交\xe2\x80\x94cherry-pick\'s-m不会这样做;它会生成一个普通的提交,而不是 \xe2\x80\x94,因此 rebase 通常会丢弃合并提交。对于标准变基的工作方式,我将在下面展示如何以及为什么这是正确的事情。

\n
\n
git rebase -i --onto myBranch myBranch\n
Run Code Online (Sandbox Code Playgroud)\n
\n

请注意, 的参数和文档调用的--onto另一个参数默认是相同的,因此可以更简单地写为git rebaseupstream

\n
git rebase -i myBranch\n
Run Code Online (Sandbox Code Playgroud)\n

此操作要复制的提交集仅限于以下内容生成的提交集:

\n
git log myBranch..HEAD\n
Run Code Online (Sandbox Code Playgroud)\n

也就是说,假设我们有以下内容,其中较新的提交位于右侧,并且我们当前位于分支上topic

\n
          G--H   <-- topic (HEAD)\n         /\n...--E--F--I--J   <-- myBranch\n
Run Code Online (Sandbox Code Playgroud)\n

运行git rebase myBranch,无论是否带有--interactive,都会告诉 Git:首先,列出可从HEADaka访问的提交,减去可从 访问的topic所有提交。myBranch 这会导致 Git 列出提交GH内部。这些是复制的候选者

\n

如果这些是最终被复制的提交,并且其他简化假设成立,则结果将是:

\n
          G--H   [abandoned]\n         /\n...--E--F--I--J   <-- myBranch\n               \\\n                G\'-H\'  <-- topic (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

其中G\'H\'是原始提交的副本GH,每个副本都有两个重要的区别:

\n
    \n
  • 的父级G\'J,而不是F。其中的文件包含与 中所做的相同G\'的更改因此存储在中的快照与 中的不同。JGFG\'G
  • \n
  • 的父级H\'G\',而不是G,其快照的不同之处也大致相同。
  • \n
\n

也就是说,由于每个提交都保存一个完整的快照,因此我们需要复制提交中的快照与原始提交中的快照不同。新快照中的差异在于,比较 G\'vs会产生与比较vs或多或少J相同的diff。当然,Git 中总是向后移动的链接 \xe2\x80\x94 也是不同的,因此副本是在. GFmyBranch

\n
\n

1章鱼合并(如果有的话)结合了两个以上父级的工作,并且罕见的-s ours合并完全丢弃了一个父级的内容,因此这些特殊情况更加特殊;一般来说,不应在这些上使用 rebase。

\n
\n

rebase 不会故意做什么

\n

假设在我们最初的两次提交中,G并且H

\n
          G--H   <-- topic (HEAD)\n         /\n...--E--F--I--J   <-- myBranch\n
Run Code Online (Sandbox Code Playgroud)\n

从到 的更改从到更改完全相同。例如,两次提交都修复了文件中相同拼写错误单词的拼写,并且不执行任何其他操作。GH IJREADME

\n

当我们运行时git rebase myBranch,Git 仍然列出提交GH. 但它也会查看提交IJ,并且对于每个提交,Git 都会计算它所谓的补丁 ID(请参阅文档git patch-ID。此补丁 ID 告诉 Git:CommitH是 commit 的重复项J 然后 Git从要复制的提交列表中删除提交H

\n

因此,当我们说 rebase 列出myBranch..HEAD要复制的候选提交时,这些只是候选。其中一些候选人被故意自动淘汰。在这种特殊情况下,如果H故意消除 only,则变基的最终结果将是:

\n
          G--H   [abandoned]\n         /\n...--E--F--I--J   <-- myBranch\n               \\\n                G\'  <-- topic (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

Git 基本上认为 commitH已经被应用了。所以它完全放弃了它。

\n

Git 还使用一种称为分叉点代码的东西来执行一种相当复杂的舞蹈。分叉点代码的目标是发现故意删除的提交,并在变基期间自动删除它们。这段代码通常会做正确的事情,尽管它可能会失败。2 在这种情况下,补丁 ID 和分叉点代码似乎都没有让您感到困扰,但还有一个更大的特殊情况,值得单独一节。

\n
\n

2它可能会失火这一事实让我认为这不一定是正确的默认值。这也适用于“已应用上游”补丁 ID 的情况。特别是,交互式变基确实应该在其说明表中包含这些提交,其中预先选择的操作是“删除”,以及关于为什么删除它们的注释。今天的情况并非如此。

\n
\n

合并

\n

到目前为止,我们画的图很简单。但是假设我们的topic分支提交如下所示:

\n
                 I--J\n                /    \\\n            G--H      M--N   <-- topic (HEAD)\n           /    \\    /\n          /      K--L\n         /\n...--E--F--------------O--P   <-- myBranch\n
Run Code Online (Sandbox Code Playgroud)\n

当我们运行时:

\n
git log myBranch..topic\n
Run Code Online (Sandbox Code Playgroud)\n

我们将看到提交NM,然后\xe2\x80\x94以某种顺序\xe2\x80\x94I通过 提交LI显示在之后J但相对于K和随机排序L,并且K显示在之后L但相对于 和 随机I排序J。然后我们会看到 commit H,然后是G,这是列表的末尾。

\n

(如果我们添加--topo-order,列表的顺序会受到更多限制。变基代码在内部添加--topo-order。我们仍然不知道 或L是否J会排在第一位,但是一旦我们得到其中一个,我们将在之前完成整行转到另一行。如果没有,--topo-order我们就看不到N, M, L, J, K, I, H,G实例。)

\n

这就是你的问题有点偏离轨道的地方。git rebase命令将自动完全删除合并提交M,原因有两个:

\n
    \n
  • cherry-pick(以及旧的git format-patch/git am基于方法)无法复制合并;和
  • \n
  • 标准变基的结果无论如何都不应该复制合并。
  • \n
\n

所以你不会pickcommit命令M。要获得一个,您必须手动插入自己的,这是一个错误。要了解原因,让我们看看 Git 如何在没有pick <hash-of-M>in 的情况下使用常规(非--rebase-merges)变基来处理此问题。

\n

该序列首先列出要复制的提交。假设它们按此顺序出现,在git rebase小心地反转它们3同时删除合并之后:G-H-I-J-K-L-N

\n

如果复制阶段一切顺利,结果将是:

\n
                 I--J\n                /    \\\n            G--H      M--N   [abandoned]\n           /    \\    /\n          /      K--L\n         /\n...--E--F--O--P   <-- myBranch\n               \\\n                G\'-H\'-I\'-J\'-K\'-L\'-N\'  <-- topic (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

也就是git rebase合并压平了。但合并的目的M是合并和分支上的工作。我们不需要合并,因为复制到过程是:I-JK-LKK\'

\n
    \n
  • H对于-vs-提交中的每个更改K,对取自 ; 的内容进行相同的I\'更改
  • \n
  • 现在将其作为新提交提交K\'
  • \n
\n

也就是说,commitK\'不是基于Hor H\',而是基于I\'。它已经包含了其他分支的工作。同样,当 Git 复制L到时,它会复制到已经包含其他分支的工作的L\'提交上。所以不需要分支。变基操作只是将其完全压平。

\n
\n

3请记住,Git 是向后工作的,因此列表总是最先出现N。我们需要N最后一个,因此 rebase 会反转列表。

\n
\n

选项--rebase-merges

\n

这种扁平化合并的想法并不总是一个好的想法。有时效果不太好。当然,像这样的系列:

\n
       I--J\n      /\n...--H\n      \\\n       K--L\n
Run Code Online (Sandbox Code Playgroud)\n

通常两个分支上的变化相对较少,因此“压平分支”很容易并且进展顺利。但是,如果该系列的每个分支都有大量提交怎么办:

\n
       o--o--...(1000 commits)...--o--tip1\n      /\n...--o\n      \\\n       o--o--....................--o--tip2\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,合并两个提示提交的合并可能需要做很多工作。将合并展平是不切实际的。

\n

或者,也许我们只是喜欢合并。合并代表了一些重要的东西,我们希望未来的代码考古学家能够看到它。

\n

嗯,“复制”合并确实是不可能的。Cherry-pick 的-m标志不会这样做。如果我们在展平事物cherry-pick -m “复制”合并:

\n
                 I--J\n                /    \\\n            G--H      M--N   <-- topic\n           /    \\    /\n          /      K--L\n         /\n...--E--F--O--P   <-- myBranch\n               \\\n                G\'-H\'-I\'-J\'-K\'-L\'  <-- HEAD\n
Run Code Online (Sandbox Code Playgroud)\n

我们只需重新引入我们已经通过I-J或 通过获得的更改K-L为了正确“复制”合并,我们必须首先形成一个分支:

\n
                 I--J\n                /    \\\n            G--H      M--N   <-- topic\n           /    \\    /\n          /      K--L\n         /\n...--E--F--O--P   <-- myBranch\n               \\\n                \\      I\'-J\'   <-- temp-label-1\n                 \\    /\n                  G\'-H\'\n                      \\\n                       K\'-L\'   <-- temp-label-2, HEAD\n
Run Code Online (Sandbox Code Playgroud)\n

然后我们必须选择正确的分支提示作为HEAD提交,并git merge再次运行以进行M\'

\n
reset-to temp-label-1\nmerge temp-label-2\n
Run Code Online (Sandbox Code Playgroud)\n

如果合并顺利,我们现在将拥有:

\n
                 I--J\n                /    \\\n            G--H      M--N   <-- topic\n           /    \\    /\n          /      K--L\n         /\n...--E--F--O--P   <-- myBranch\n               \\\n                \\      I\'-J\'  <-- temp-label-1\n                 \\    /    \\\n                  G\'-H\'     M\'  <-- HEAD\n                      \\    /\n                       K\'-L\'  <-- temp-label-2\n
Run Code Online (Sandbox Code Playgroud)\n

我们现在可以制作:pick hash-of-NN\'

\n
                 I--J\n                /    \\\n            G--H      M--N   <-- topic\n           /    \\    /\n          /      K--L\n         /\n...--E--F--O--P   <-- myBranch\n               \\\n                \\      I\'-J\'  <-- temp-label-1\n                 \\    /    \\\n                  G\'-H\'     M\'-N\'  <-- HEAD\n                      \\    /\n                       K\'-L\'  <-- temp-label-2\n
Run Code Online (Sandbox Code Playgroud)\n

然后我们完成了这个奇特的重新合并的变基,并且可以移动分支标签topic并删除任何临时标签:

\n
                 I--J\n                /    \\\n            G--H      M--N   [abandoned]\n           /    \\    /\n          /      K--L\n         /\n...--E--F--O--P   <-- myBranch\n               \\\n                \\      I\'-J\'\n                 \\    /    \\\n                  G\'-H\'     M\'-N\'  <-- topic (HEAD)\n                      \\    /\n                       K\'-L\'\n
Run Code Online (Sandbox Code Playgroud)\n

这就是git cherry-pick --rebase-merges作用。为了实现这个结果,它需要一些额外的命令和插入临时标签的能力。(请注意,还会有一个临时标签 ,因为在复制到之前,必须在那里H\'重置挑选操作的顺序。您将在说明书中看到所有这些标签和重置,它需要知道何时制作各种标签以及去哪里走动。)HEADKK\'HEAD

\n