Git:删除两个父提交之一

Git: Delete one of two parent commits

我有以下提交历史:

  A--B--C branch1
 /     /
I      D
 \
  \
   J branch2

我只想删除没有连接到其他地方的提交 D。

我试过 git rebase --onto B C,但出于某种原因,这也会删除 C 并生成

  A--B branch1
 /     
I      
 \
  \
   J branch2

TL;DR

将 C 的哈希 ID 保存在某处。除了使用原始 cut-and-pasted 哈希 ID 之外,最简单的方法是制作一个临时分支或标签名称,例如:

git branch save branch1

然后强制名称 branch1 指向提交 B。假设你还在 branch1——确保你也没有什么可提交的;使用 git status 检查这两个:

git reset --hard <hash-of-B>

或类似的。例如,如果 branch1 仍然指向提交 C,您可以使用 HEAD~1HEAD^1 来标识提交 B。

最后,使用git cherry-pick对提交C进行复制,但其中只有一个parent。为此,您必须指出两个 parent 中的哪个 git cherry-pick 应该被认为是 C 的(单个)parent,以便进行此复制。选择的parent将是#1;无论如何,这几乎总是正确的;在这种情况下确实如此,其中 C 是通过合并 D:

git cherry-pick -m 1 save

然后您可以随时删除名称 save(这只是为了记住提交 C 的哈希 ID)。

请注意,您可以 cherry-pick D 直接。但是,使用 git cherry-pick -m 1 <thing-to-locate-commit-C> 意味着如果您在进行合并时必须解决任何冲突,则现在不必 re-resolve 它们。

根据评论,实际的图结构是这样的:

       A--B--C   <-- branch1 (HEAD)
      /     /
...--I     D
      \   /
       \ /
        J   <-- branch2

但这对答案没有影响。

I tried git rebase --onto B C, but for some reason this deletes C as well ...

从技术上讲,它根本没有 删除 提交 C;它只是安排名称 branch1 指向提交 B (仅)。也就是说,我们可以像这样重新绘制图形:

       A---B   <-- branch1 [after rebase]
      /     \
...--I       C   <-- branch1 [before rebase]
      \     /
       \   D
        \ /
         J   <-- branch2

这样做的原因是:

git rebase --onto B C

告诉 Git:

  1. 列出所有可从 HEAD 访问的提交(使用名称 HEAD 是 in-built,git rebase 本身的一部分)无法从C(来自你的论点C),减去一些我们可以稍后描述的提交。以“反向拓扑”顺序列出它们,即,较旧的必需提交在较新的之前。

    在这种情况下,列表是空的:没有从 HEAD 开始的提交——附加到 branch1branch1 指向提交 C——那是也无法从提交 C 访问。

  2. 建立一个指向B的临时分支(来自你的论点B)。

  3. 对于第 1 步中列出的所有提交,复制这些提交,构建这个新的临时分支。

  4. 完成后,更改名称 branch1 以指向复制的最终提交。

    由于没有提交被复制,临时分支仍然指向B,所以名称branch1被移动到指向B

您可以尝试通过将 C 以外的内容命名为“不复制的内容”来解决此问题。问题是您只获得 one 提交 ID 用于“不复制的内容”。您可以指定 B,但 Git 将尝试复制 D J(以其他顺序:D 首先,然后 J)。您可以指定 D,但 Git 将尝试复制 BAI!

另一个遗留问题是我们在上面提到的第 1 步中,Git 省略了某些提交。具体来说,它省略了 all 合并提交——它根本不会尝试复制 C——而且它还省略了已经在上游的提交(通过我赢得的机制不要尝试在这里描述;它不适用于您的情况,因此并不重要)。这意味着即使你可以以某种方式说“复制 C 但不复制 BD”,Git 也会抛出 C列表和仍然什么都不复制。

这就是为什么我们需要一种替代机制:使用 git cherry-pick 的“手动”变基。我们可以cherry-pick D 本身。这意味着 re-doing 任何冲突解决方案。或者,我们可以使用 git cherry-pick -m 1 <specifier-for-C> 到 cherry-pick 合并 D 效果 并进行任何冲突解决,这就是 TL;DR 中的方法上面的部分。