Git合并提交的rebase操作

3

不确定这是否是重复问题,但我花了一整天的时间尝试找到一个简洁的解决方案,但还没有找到答案。

我的git仓库状态现在是这样的:

      C---D---E  feature-branch
     /
A---B---C---D---X---F  master
                |
         Reverts C and D

我一直在feature-branch上工作,但是由于某些复杂的原因,提交记录CD被意外地合并到了主分支。其他人在处理该代码库时撤销了这些提交记录,因为它们破坏了构建。
现在,提交记录E之后,我想将feature-branch与当前的主分支进行rebase。但问题是,由于提交记录CD已经存在于主分支中,所以在rebase过程中不会包括这些提交记录。
根据git man页面:

If the upstream branch already contains a change you have made (e.g., because you mailed a patch which was applied upstream), then that commit will be skipped. For example, running git rebase master on the following history (in which A' and A introduce the same set of changes, but have different committer information):

      A---B---C topic
     /
D---E---A'---F master

will result in:

               B'---C' topic
              /
D---E---A'---F master

我如何在提交 X 撤销更改后,包含提交 CD(可能使用新的 SHA)?

3个回答

2
tl;dr: 你可以使用类似下面的命令做你想做的事情:
git rebase --onto master feature-branch~3 feature-branch

在这个例子中,feature-branch~3 是有效的,但通常可以是任何解析为提交 B 的表达式 - 比如 B 的提交哈希等。

要了解原因,请继续阅读...


首先,我们应该稍微澄清一下您的图表。你说:

我一直在 feature-branch 上工作,意外地(复杂的故事)合并了 C & D 提交到了 master分支。其他人在回滚了这些提交,因为它们破坏了构建。

(强调添加)。现在merge操作有两种可能[1],但不会复制提交 CD。默认行为看起来像这样:

A -- B -- C -- D -- !CD -- F <-(master)
                \
                 E <-(feature-branch)

由于在意外合并时,master 显然处于 B,默认行为将是将 master 快进到现有的 D 提交。 这样就好像 feature-branch 是在 D 之后创建的。

(我还进行了一些注释更改。 !CD 表示撤消了 CD 的合并(无需注释)。 我认为这种画线方式更易读。 但重要的是如何表示 CD...)

另一个可能性是,如果您使用了 --no-ff 选项(或者如果 master 在合并之前与 feature-branch 分歧),则会更像:

       C - D -- E <--(feature-branch)
      /     \
A -- B ----- M -- !CD -- F <--(master)

再次执行merge操作不会重复创建CD,而是会创建一个“合并提交”M,将CD的更改合并到master的历史记录中(并使CDmaster可达)。

在两种情况下,合并后feature-branchmaster之间的“合并基础”都是D。因此,您需要解决两个问题:文档中描述的“重复补丁ID”问题;以及合并基础处于排除您提交的位置——因为rebase甚至不会考虑已经从上游可达的提交[2]。

通常解决“合并基础”问题很有用。最后,我们将看到为什么您不必为了您的目的而这样做,但是为了完整起见,这里是如何做:

首先找到一个解析为提交B的表达式(例如,与B相关的提交,或上述示例中的feature-branch~3)。在类似以下的命令中使用它:

git rebase -f feature-branch~3 feature-branch

这将复制提交记录 CDE,以便您拥有它们。
                 E
                /
A -- B -- C -- D -- !CD -- F <-(master)
      \          
       C' -- D' -- E' <-(feature-branch)

(其中E在技术上仍然存在,但无法访问,因此可能最终会被gc销毁)。当然,这假设是“快进”的情况;如果您有一个合并提交,则看起来会有所不同,但其结果将是相同的。

而且,现在您可以毫无问题地将feature-branch合并到master中。但是,如果您想将feature-branch rebasemaster(为了准备快进),则仍然必须解决您指出的原始问题: C'D'将被跳过作为CD的副本。

您需要的是挫败补丁ID重复检查的方法;虽然似乎没有实际的选项可用,但您可以通过防止git知道重复项的位置来实现它。不要将master作为上游给出,而是将提交B作为上游;然后--onto master重新安排。

git rebase --onto master feature-branch~3 feature-branch

不仅在执行此命令时,git没有理由考虑CD,而且因为您不再将master用作上游,所以masterfeature-branch合并基础不再重要。您明确表示B是您的上游,因此这解决了两个问题,这就是为什么,如我上面所述,最终您不必担心rebase -f命令的原因。

[1]实际上,它至少可以做第三件事。如果您指定了--squash选项,那么它将不进行真正的合并,而是在master上创建一个单独的新提交CD。这与通常创建的合并提交基本相同,只是不包括D作为父提交。

       C - D -- E <--(feature-branch)
      /     
A -- B ----- CD -- !CD -- F <--(master)

这在某些特定情况下很有用,但一般情况下我不建议这样做,因为git会“失去追踪”CD已经在master中“被考虑”。在你的情况下,这种方法可能有效,这就是为什么我知道你没有这样做并且没有在答案的主要文本中包括这种可能性的原因。但在大多数情况下,它会使分支之间的未来交互更加困难。


[2] 这些问题看起来相似,但是是不同的。在一个情况下,C已经从你要变基到的上游可达;在另一个情况下,它还没有,但是引入相同更改的另一个提交已经可达。


太好了!这正是我想要的。我想我可以使用 --onto 参数,但不知道如何在这里使用它。谢谢! - Nitheesh A S

1
您可以使用git cherry-pick来完成此操作。以您的例子为例,命令如下:
in master# git cherry-pick topic~3..topic

我现在不想将我的提交添加到主分支。我想要将“topic”与“master”进行变基。有什么方法吗? - Nitheesh A S
@NitheeshAS - 你可以在 E 处创建一个临时分支,然后将 feature-branch 重置为 master,然后从临时分支到 feature-branch 进行 cherry-pick;但这并不是必要的,因为你可以从我的建议答案中看到有更简单的方法。 - Mark Adelsberger

1
在功能分支中执行:
git rebase master

这将在主分支上进行变基并得到您所期望的结果,忽略 C D。(在 feature-branch 中为 A---B---C---D---X---F--E

然后执行 git reset --soft <F 的 sha> 以暂时删除提交 E。

接下来执行 git stash 命令以隐藏重置提交更改(即 E)。

然后在同一 feature-branch 中执行以下操作:

git cherry-pick <sha of C>
git cherry-pick <sha of D>

最后执行git stash pop并执行git commit,这样你就能恢复你的E了。
这将会达到你预期的效果。
我在我的机器上进行了测试,下面是在feature-branch中使用git log --online得到的结果。
ae4b6bb E commited
eee0a6a D commited
3141961 C commited
a44aa27 F Commited
265da4c Commited X
9e2729d D commited
84a3a9b C commited
8a543ca B commited
4e4a8ca A commited

看起来这应该可以工作,但这是一种绕远路的方式去做一个单一的rebase命令可以完成的事情(如果它是正确的rebase命令)。 - Mark Adelsberger
是的,这个可行。我想不到其他方法来达到预期的结果。 - Marlon Abeykoon
给那位点了踩的人留言,欢迎评论原因。这样我就可以解释清楚了。 - Marlon Abeykoon
不确定是谁给踩了。这个方法可以轻松地完成任务。然而,我认为使用 --onto 的解决方案才是正确的方式。 - Nitheesh A S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接