Git 合并提交变基

Git rebase with merged commits

不确定这是否重复,但花了将近一天的时间试图找出一个巧妙的解决方案,但仍找不到答案。

我的 git 回购状态现在是这样的:

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

我一直在研究 feature-branch 并且提交 CD 意外地合并到 master(复杂的故事)。其他在 repo 上工作的人恢复了这些提交,因为它破坏了构建。

现在,在提交 E 之后,我想用当前的 master 重新设置功能分支。但问题是,由于提交 C & D 已经存在于 master 中,这些提交在变基时没有被包含。

来自 git 手册页:

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

我现在如何包含提交 CD(可能使用新的 SHA),因为更改已被提交 X 还原?

您可以使用 git cherry-pick。在您的特定示例中,命令将是:

in master# git cherry-pick topic~3..topic

在功能分支中执行,

git rebase master

这将通过忽略 C D 来改变 master 和结果的基础。(A---B---C---D---X---F--E 在 feature-branch 中)

然后 git reset --soft <sha of F> 暂时摆脱提交 E.

接下来git stash重置提交更改(即E)

然后在同一个feature-branch执行以下,

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

最后 git stash pop 并执行 git commit 这样你就会找回你的 E。

这将达到您的预期。

我在我的机器上测试,下面是 git log --onlinefeature-branch

中得到的结果
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

tl;dr :你可以用像

这样的命令做你想做的事
git rebase --onto master feature-branch~3 feature-branch

(其中 feautre-branch~3 在示例中有效,但通常是解析为提交 B 的任何表达式 - 例如 B 的提交哈希等)

要了解其工作原理,请继续阅读...


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

I had been working on the feature-branch and the commits C & D got merged to master accidentally(complicated story). Others working on the repo reverted back those commits since it breaks the build.

(强调)。现在合并可以做两件事之一 [1],但复制提交 CD 不是其中之一。默认行为看起来像这样:

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

由于 master 在意外合并时显然处于 B,默认行为将是 master 的 "fast-forward" 到现有的 [=21] =] 提交。就好像 feature-branch 是在 D.

之后才创建的

(我也做了一些符号更改。!CD 表示恢复 CD 的合并(不需要注释)。我发现这种绘图方式这些行更具可读性。但重要的一点是如何表示 CD...)

另一种可能性,如果您使用了 --no-ff 选项(或者如果 master 在合并前与 feature-branch 发生分歧)看起来更像

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

同样 merge 不会重复 CD;相反,它会创建一个 "merge commit" M,将 CD 的更改合并到 master 的历史记录中(并使 CD "reachable" 来自 master).

在这两种情况下,feature-branchmaster 之间的 "merge base" 在合并后是 D。所以你必须解决两个问题:文档描述的 "duplicate patch id" 问题;并且合并基础处于排除您的提交的位置 - 因为 rebase 甚至不会考虑复制已经可以从上游访问的提交。[2]

通常只解决 "merge base" 问题很有用。最后,我们将了解为什么您不必为了您的目的而这样做,但为了完整起见,您应该这样做:

首先找到解析为提交 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 摧毁)。当然,这是假设 "fast-forward" 的情况;如果你有一个合并提交,它看起来会有所不同,但结果是一样的。

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

你想要的是阻止补丁ID重复检查;虽然似乎没有实际的选择,但您可以通过让 git 不知道在哪里寻找重复项来做到这一点。不给master作为上游,而是给commitB作为上游;然后变基 --onto master.

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

不仅 git 没有理由在您发出此命令时考虑 CD,而且因为您不再使用 master 作为在上游,master-to-feature-branch 合并基础不再重要。你明确地说 B 是你的上游,所以这同时解决了这两个问题 - 这就是为什么,正如我上面提到的,你最终不必担心 rebase -f 命令。


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

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

这在某些特定情况下很有用,但通常我不推荐它,因为 git "loses track" 因为 CD 已经存在"accounted for" 在 master 中。在您的情况下,这恰好会很好地解决问题-这就是为什么我知道您没有这样做并且没有在答案的正文中包含这种可能性的原因。但在大多数情况下,这会使分支机构之间的未来交互变得更加困难。


[2] 这些问题看似相似,实则截然不同。在一种情况下,C 本身已经可以从您要重新定位的上游访问;在另一种情况下,它不是,但是 另一个引入相同更改的提交 是。