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~1
或 HEAD^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:
列出所有可从 HEAD
访问的提交(使用名称 HEAD
是 in-built,git rebase
本身的一部分)无法从C
(来自你的论点C
),减去一些我们可以稍后描述的提交。以“反向拓扑”顺序列出它们,即,较旧的必需提交在较新的之前。
在这种情况下,列表是空的:没有从 HEAD
开始的提交——附加到 branch1
,branch1
指向提交 C
——那是也无法从提交 C
访问。
建立一个指向B
的临时分支(来自你的论点B
)。
对于第 1 步中列出的所有提交,复制这些提交,构建这个新的临时分支。
完成后,更改名称 branch1
以指向复制的最终提交。
由于没有提交被复制,临时分支仍然指向B
,所以名称branch1
被移动到指向B
。
您可以尝试通过将 C
以外的内容命名为“不复制的内容”来解决此问题。问题是您只获得 one 提交 ID 用于“不复制的内容”。您可以指定 B
,但 Git 将尝试复制 D
和 J
(以其他顺序:D
首先,然后 J
)。您可以指定 D
,但 Git 将尝试复制 B
、A
和 I
!
另一个遗留问题是我们在上面提到的第 1 步中,Git 省略了某些提交。具体来说,它省略了 all 合并提交——它根本不会尝试复制 C
——而且它还省略了已经在上游的提交(通过我赢得的机制不要尝试在这里描述;它不适用于您的情况,因此并不重要)。这意味着即使你可以以某种方式说“复制 C
但不复制 B
或 D
”,Git 也会抛出 C
列表和仍然什么都不复制。
这就是为什么我们需要一种替代机制:使用 git cherry-pick
的“手动”变基。我们可以cherry-pick D
本身。这意味着 re-doing 任何冲突解决方案。或者,我们可以使用 git cherry-pick -m 1 <specifier-for-C>
到 cherry-pick 合并 D
的 效果 并进行任何冲突解决,这就是 TL;DR 中的方法上面的部分。
我有以下提交历史:
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~1
或 HEAD^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:
列出所有可从
HEAD
访问的提交(使用名称HEAD
是 in-built,git rebase
本身的一部分)无法从C
(来自你的论点C
),减去一些我们可以稍后描述的提交。以“反向拓扑”顺序列出它们,即,较旧的必需提交在较新的之前。在这种情况下,列表是空的:没有从
HEAD
开始的提交——附加到branch1
,branch1
指向提交C
——那是也无法从提交C
访问。建立一个指向
B
的临时分支(来自你的论点B
)。对于第 1 步中列出的所有提交,复制这些提交,构建这个新的临时分支。
完成后,更改名称
branch1
以指向复制的最终提交。由于没有提交被复制,临时分支仍然指向
B
,所以名称branch1
被移动到指向B
。
您可以尝试通过将 C
以外的内容命名为“不复制的内容”来解决此问题。问题是您只获得 one 提交 ID 用于“不复制的内容”。您可以指定 B
,但 Git 将尝试复制 D
和 J
(以其他顺序:D
首先,然后 J
)。您可以指定 D
,但 Git 将尝试复制 B
、A
和 I
!
另一个遗留问题是我们在上面提到的第 1 步中,Git 省略了某些提交。具体来说,它省略了 all 合并提交——它根本不会尝试复制 C
——而且它还省略了已经在上游的提交(通过我赢得的机制不要尝试在这里描述;它不适用于您的情况,因此并不重要)。这意味着即使你可以以某种方式说“复制 C
但不复制 B
或 D
”,Git 也会抛出 C
列表和仍然什么都不复制。
这就是为什么我们需要一种替代机制:使用 git cherry-pick
的“手动”变基。我们可以cherry-pick D
本身。这意味着 re-doing 任何冲突解决方案。或者,我们可以使用 git cherry-pick -m 1 <specifier-for-C>
到 cherry-pick 合并 D
的 效果 并进行任何冲突解决,这就是 TL;DR 中的方法上面的部分。