如何 git 恢复通过 cherry-picking 完成的合并提交
How to git revert merge commits done by cherry-picking
我想还原这 3 个提交 295e8ce6e63e39b87548f2f52b3eb0d5139999ef
、fb690eaec7938abbd614b0b07a5364998c09f7a5
和 92ed5ce3d6b4cb70e56f74f17e80df960311b647
。我能够还原最后一个 295e8ce6e63e39b87548f2f52b3eb0d5139999ef
但无法还原底部 2。我尝试了以下操作:
$git revert fb690eaec7938abbd614b0b07a5364998c09f7a5 -m 1
$git revert 92ed5ce3d6b4cb70e56f74f17e80df960311b647 -m 1
然后我得到
Already up to date!
On branch feature
Your branch is up to date with 'origin/feature'.
nothing to commit, working tree clean
但是我的 git 日志没有恢复最后 2 次提交并且它保持不变:
commit d81838a9dc973acbe03865dce0533bd41e77860e (HEAD -> feature, origin/feature)
Author: abc
Date: Thu Nov 5 14:08:59 2020 -0800
Revert "merge PR #173"
This reverts commit 295e8ce6e63e39b87548f2f52b3eb0d5139999ef.
commit 295e8ce6e63e39b87548f2f52b3eb0d5139999ef
Author: xyz
Date: Fri Aug 28 22:30:12 2020 -0700
merge PR #173
commit fb690eaec7938abbd614b0b07a5364998c09f7a5
Merge: 3cef115 92ed5ce
Author: abc
Date: Thu Nov 5 12:29:19 2020 -0800
Merging changes from e6a35ad0b2363932ac190ec602a7fd0c8bf9f04f
commit 92ed5ce3d6b4cb70e56f74f17e80df960311b647 (temp)
Author: xyz
Date: Wed Sep 2 17:32:07 2020 -0700
readjust null values for double data type from v5
您所看到的行为几乎可以肯定是正常的。 (如果没有访问相关存储库,我无法确定。)
我可以重现非常相似的东西,其中 git revert
什么都不做:
$ git revert 6af46d5b31c241a666c90fb77ae54baac842b251
On branch master
nothing to commit, working tree clean
请注意,我不会在此处还原合并提交。您恢复合并的事实与 revert-result 无关,但 确实 解释了 为什么您处于 ,其中你要求的恢复什么都不做。无事可做,所以 Git 什么都不做。
但那是什么解释呢?为什么不再需要恢复?那么,为此,我们必须了解什么是还原,以及它是如何工作的。它实际上与git cherry-pick
直接相关:事实上,git cherry-pick
和git revert
都使用相同的代码。
理解git revert
让我们先看看 git revert
的目标,然后看看 Git 如何完成还原。请记住,在 Git 中,动词 revert——字面意思是 turn back,具有法语和拉丁语词源,源自 revertere — 并不意味着“return 到”,而是意味着更多类似于“撤消”的内容。在英语中(包括特有的美式英语,其中 -ised
拼写为 -ized
),动词 revert 通常与助动介词 到,如恢复到(以前的某个状态)。不过,这不是 Git 中 revert
的意思。 Git 的意思更像是 退出: 我们希望 Git 进行一些提交,并将其视为 更改 到一些文件集。找出哪些文件以何种方式更改后,我们希望 Git 现在 撤消 这些更改:即,采用我们拥有的状态 现在 并应用在另一个提交中发现的更改的逆转。
因此,我们希望 Git 假装提交是 更改 — 但提交实际上并不是更改!每个提交都包含每个文件的 完整快照 。幸运的是,每个提交还包含 上一个 提交的哈希 ID。每个提交都有一个唯一的编号,或 hash ID,并且每个提交都会记住上一个提交的哈希 ID。因此 Git 可以获取您在 git log
输出中看到的提交字符串:
... <-F <-G <-H
其中 H
是某个提交的哈希 ID,不仅提取 H
的快照,还提取更早的提交 G
的快照。 Git 然后可以 比较 G
-提交快照与 H
- 提交快照。对于相同的文件,这些文件没有发生任何事情。对于两个提交中都存在但不同的文件,这些文件中的不同之处就是 更改 。如果 G
中有 H
中不存在的文件,则这些文件已 删除 ,如果 H
中存在未删除的文件t 在 G
中,这些文件是 新添加的 。
这始终是 Git 将提交作为更改呈现给您的方式:遍历 提交图 ,由每个提交与其 [=567= 的连接组成].当你有:
...--F--G--H <-- somebranch
名称 somebranch
允许 Git 找到最新的提交 H
,并且 H
允许 Git 找到较早的提交 G
,它允许 Git 找到更早的提交 F
,等等。从这些快照中,Git 可以向您展示变化。 Git 简单地将 parent 提交(例如 G
)与 child 进行比较提交如 H
.
如果我们有一个像这样的长链:
...--C--D--E--F--G--H <-- somebranch
和我们在E
中所做的更改,即D
-vs-E
的区别,原来是坏主意,我们可以告诉 Git:恢复我们在 E
中所做的更改。 Git 现在将提取 D
和 E
,查看 更改的内容 ,并尝试撤消相同的更改。如果我们添加了一个新文件 newfile
,Git 可以 删除 整个文件 newfile
。如果我们向某个现有文件添加一些行,Git 可以从该文件中删除这些行。如果我们从某个文件中删除了一些行,Git 可以将这些行放回去。
但是有一个明显的问题。假设我们在提交 E
中添加了一个包含两个库例程的新文件 lib.py
。假设通过提交 H
,lib.py
有十几个库例程。 完全删除lib.py
会破坏一切。因此,git revert
必须执行的 back-out-some-changes 操作必须以某种方式进行调整。这里有许多可能的“不知何故”; Git 选择的是使用其 三向合并 引擎。
在我们了解 git revert
如何使用 Git 的合并引擎之前,我们必须首先了解 git merge
本身。
合并:合并作为动词,合并作为名词
在Git中,我们通常使用git merge
—或运行git pull
来制作Git运行git merge
—执行合并操作,这通常会导致合并提交。 merge这个词在a merge commit这个词中起到了形容词的作用,但是我们经常把merge commit简称为“a merge”。这里单词 merge 作为名词。
我们通过执行一些特定的操作来为合并(或合并提交,如果您愿意)生成快照。这些动作就是合并的过程。他们实现了合并的动词形式,合并(一些文件and/or一些提交)。确切的操作集当然很重要,但最重要的是,为了执行此合并操作,我们需要的不是两个而是 三个 输入。我们有一些原始的东西——一个提交,或者一个提交中的一个文件——以及那个原始东西的两个修改版本,我们称之为“我们的”和“他们的”。我们有 合并引擎 ,它是生成最终文件的代码,检查所有这三个输入并找出结果应该是什么。另见 three way merge in Merge (version control) in Wikipedia and/or Why is a 3-way merge advantageous over a 2-way merge?
合并单独的分支时,这一切都非常有意义。三个输入是三个提交。我们有一些看起来像这样的提交系列:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
我们在分支 br1
上,提交 J
。我们 运行 git merge br2
合并提交 L
。此过程的共同起点 是提交H
:最佳共享提交,在两个 分支上。合并引擎通过将 H
中的快照(我们都开始时使用的快照)与 J
中的快照进行比较来查看 我们 更改的内容,并比较 H
到 L
看看 他们 改变了什么。合并引擎然后将我们的更改与其更改组合,将 合并的 更改应用到 H
中的快照,并生成最终快照。
当我们使用 git merge
调用合并引擎时,git merge
提交本身使结果合并 commit M
如下所示:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
现在我们有一个合并(名词),通过合并(动词)提交 J
和 L
使用提交 H
作为共同起点。
git cherry-pick
如何使用合并引擎
想象一下我们有这样的 branch-y 情况:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
也就是说,我们正在 br1
上做一些工作。在这一点上,我们意识到我们需要的正是我们或其他人在提交 L
中所做的,因为它修复了一些错误。但是我们还没有准备好得到commitK
的效果,所以我们不想mergebr2
完全。相反,我们想检查提交 L
,查看提交 L
中的 更改,并获取 相同的更改 为了我们自己。
这就是 git cherry-pick
所做的:它比较 K
和 L
中的快照,看看 L
中发生了什么变化,然后它使 same 更改我们在提交 J
时的位置,并进行新的提交。但是 方式 它这样做有点偷偷摸摸:它使用与 git merge
.
相同的合并引擎
假设我们将提交 K
指定为(假的!)merge base 或 shared common commit,并使用 commit J
作为我们当前的提交,因为它是。同时我们使用 commit L
作为“他们的”提交。也就是说,这些是唯一“有趣”的提交:
J <-- br1 (HEAD)
K--L <-- br2
我们有 Git,通过它的合并引擎,比较 K
和 L
看看他们改变了什么:这些是我们想要的改变 将 添加到我们的提交中。然后我们还有 Git 比较 K
与 J
,看看“我们改变了”什么:这些是我们希望 保留的与 K
的差异.
就这两组更改不冲突而言,它是安全的——好吧,就像 Git 可以自动化一样安全,无论如何——采取从 L
变化而来。如果他们 做 冲突,Git 将声明合并冲突,暂停操作,并允许我们解决冲突。
如果一切顺利,Git 在最后做一个普通的 non-merge 提交:
I--J--L' <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
其中 L'
是 L
的副本。如果不是,Git 因合并冲突而暂停。 我们 修复冲突,运行 git cherry-pick --continue
或 git commit
提交 L'
.
还原只是 cherry-pick 向后完成
如果我们有:
J <-- br1 (HEAD)
K--L <-- br2
并比较 K
-vs-L
看看他们改变了什么,比较 K
-vs-J
看看我们改变了什么,我们得到一个 cherry-pick操作。但是如果我们 Git 选择提交 L
作为“基本”提交,K
作为“他们的”,并且 J
作为“我们的”?那么我们想要保留的变化是从 L
到 J
的变化,以及从 L
到 K
的变化——即变化的逆他们实际上做了——ar我们想要 添加 .
的更改
无论我们过去是否提交 K
和 L
,这都有效!如果它们是——如果我们有这样的东西:
...o--o--K--L--o--o--o--J <-- branch (HEAD)
那么L
-vs-J
就是我们想要保留的一切,而L
-vs-K
就是我们要“加”的。它已经是 K
-vs-L
的诗句,所以它将“撤消”或 退出 从 K
到 [=76] 的更改=].
所以git revert
像以前一样简单地应用合并引擎,以child提交(L
)作为共同起点, parent 提交 reverse (K
) 作为他们的更改,我们的提交 J
作为我们的提交。如果一切顺利,Git 自己进行新的提交:
...o--o--K--L--o--o--o--J--Γ <-- branch (HEAD)
其中提交 Γ
“撤消”L
(gamma 看起来像 upside-down L
)。
这什么时候什么都不做?
无论是 cherry-pick 还是还原,当我们要求 Git 到 add 的更改是 已经。例如,假设我们有:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
与原始 cherry-pick 示例相同。进一步假设提交 L
修复了提交 K
中的错误,但是提交 I
and/or J
已经修复了相同的错误(也许也做了更多的事情)。如果我们 运行 git cherry-pick br2
要求 Git 在此处复制提交 L
,Git 将比较 K
-vs-L
看看他们改变了什么,然后比较 K
和 J
看看 我们 改变了什么。 我们改变的已经包含了一切,没有什么可以添加。 cherry-pick 只是停止。由于实施的一个怪癖——cherry-pick 以 运行 内部提交结束——你还会收到有关没有任何内容可提交的消息。
还原也是如此:如果我们要求还原 L
,但在我们工作的某个地方,我们 已经实现了 ,没有什么可做的。 git revert
尝试提交,但没有任何内容可提交,您得到了您看到的消息。
为什么还原合并很特别?
在某些方面,恢复或 cherry-pick 合并 一点也不 特别。但有一种方法是。请记住,合并提交有 两个 以前的快照,并且 git cherry-pick
和 git revert
都需要找到 the [=您在 git
命令中命名的 child 提交的 567=]。合并没有 the parent:它有 two parents(或更多,在章鱼合并,但我们不会去那里。
因此,您必须命名 您希望 Git 使用的 parent 作为 merge-base (cherry-pick) 或“他们的”提交(恢复)。那就是你命令行中的-m 1
。
我们必须考虑的另一件事是合并快照中的内容。请记住,合并提交,例如本例中的 M
:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
是在 H
的基础上加上 添加 all H
-vs- J
到 all 从 H
-vs-L
.
的变化
为此,如果我们愿意,我们可能会添加更多提交:
I--J
/ \
...--G--H M--N--O <-- br1 (HEAD)
\ /
K--L <-- br2
现在假设我们要求 Git 恢复 M
,使用 J
作为 parent。 Git 将:
- 将
M
视为合并的starting-point;
- 将
O
视为“我们的”提交,因此找到 M
-vs-O
作为 keep 的内容;和
- 将
J
视为“他们的”提交,因此找到 M
-vs-J
作为 add.
如果我们不添加任何提交,这会简化一点:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
Git 现在将 M
视为基础提交和我们的提交:
- 找到
M
-vs-M
我们应该 keep (什么都没有);
- 找到
M
-vs-J
作为他们更改的应该添加到这里。
将 M
-vs-J
添加到 nothing,当然会提供将快照转换为M
回到 J
中的那个。所以结果是:
I--J
/ \
...--G--H M--J' <-- br1 (HEAD)
\ /
K--L <-- br2
其中 J'
表示新提交中的 快照 与提交 J
中的快照匹配。就 source snapshots 而言,我们刚刚撤消了整个合并。
现在,如果提交 L
本身是先前合并的结果怎么办?也就是说,如果不是上面的,我们开始于:
I--J
/ \
...--G--H M <-- br1 (HEAD)
| /
: _--L <-- br2
:: /
:..
H
和 L
之间有一些大的混乱历史,所以 L
是一个合并。 不管我们是如何到达那里的,L
中的 快照 结合了从 H
到那一点的所有工作.如果我们现在使用 J
作为 parent 恢复 M
,以获得新的提交 J'
,提交 J'
已撤消 一切 我们通过 L
引入,包括任何 mrges 到达 L
。因此,恢复历史记录中从 H
到 L
的所有合并现在无效。
我认为这就是您的情况。 Git 无事可做,因为无事可做。
我想还原这 3 个提交 295e8ce6e63e39b87548f2f52b3eb0d5139999ef
、fb690eaec7938abbd614b0b07a5364998c09f7a5
和 92ed5ce3d6b4cb70e56f74f17e80df960311b647
。我能够还原最后一个 295e8ce6e63e39b87548f2f52b3eb0d5139999ef
但无法还原底部 2。我尝试了以下操作:
$git revert fb690eaec7938abbd614b0b07a5364998c09f7a5 -m 1
$git revert 92ed5ce3d6b4cb70e56f74f17e80df960311b647 -m 1
然后我得到
Already up to date!
On branch feature
Your branch is up to date with 'origin/feature'.
nothing to commit, working tree clean
但是我的 git 日志没有恢复最后 2 次提交并且它保持不变:
commit d81838a9dc973acbe03865dce0533bd41e77860e (HEAD -> feature, origin/feature)
Author: abc
Date: Thu Nov 5 14:08:59 2020 -0800
Revert "merge PR #173"
This reverts commit 295e8ce6e63e39b87548f2f52b3eb0d5139999ef.
commit 295e8ce6e63e39b87548f2f52b3eb0d5139999ef
Author: xyz
Date: Fri Aug 28 22:30:12 2020 -0700
merge PR #173
commit fb690eaec7938abbd614b0b07a5364998c09f7a5
Merge: 3cef115 92ed5ce
Author: abc
Date: Thu Nov 5 12:29:19 2020 -0800
Merging changes from e6a35ad0b2363932ac190ec602a7fd0c8bf9f04f
commit 92ed5ce3d6b4cb70e56f74f17e80df960311b647 (temp)
Author: xyz
Date: Wed Sep 2 17:32:07 2020 -0700
readjust null values for double data type from v5
您所看到的行为几乎可以肯定是正常的。 (如果没有访问相关存储库,我无法确定。)
我可以重现非常相似的东西,其中 git revert
什么都不做:
$ git revert 6af46d5b31c241a666c90fb77ae54baac842b251
On branch master
nothing to commit, working tree clean
请注意,我不会在此处还原合并提交。您恢复合并的事实与 revert-result 无关,但 确实 解释了 为什么您处于 ,其中你要求的恢复什么都不做。无事可做,所以 Git 什么都不做。
但那是什么解释呢?为什么不再需要恢复?那么,为此,我们必须了解什么是还原,以及它是如何工作的。它实际上与git cherry-pick
直接相关:事实上,git cherry-pick
和git revert
都使用相同的代码。
理解git revert
让我们先看看 git revert
的目标,然后看看 Git 如何完成还原。请记住,在 Git 中,动词 revert——字面意思是 turn back,具有法语和拉丁语词源,源自 revertere — 并不意味着“return 到”,而是意味着更多类似于“撤消”的内容。在英语中(包括特有的美式英语,其中 -ised
拼写为 -ized
),动词 revert 通常与助动介词 到,如恢复到(以前的某个状态)。不过,这不是 Git 中 revert
的意思。 Git 的意思更像是 退出: 我们希望 Git 进行一些提交,并将其视为 更改 到一些文件集。找出哪些文件以何种方式更改后,我们希望 Git 现在 撤消 这些更改:即,采用我们拥有的状态 现在 并应用在另一个提交中发现的更改的逆转。
因此,我们希望 Git 假装提交是 更改 — 但提交实际上并不是更改!每个提交都包含每个文件的 完整快照 。幸运的是,每个提交还包含 上一个 提交的哈希 ID。每个提交都有一个唯一的编号,或 hash ID,并且每个提交都会记住上一个提交的哈希 ID。因此 Git 可以获取您在 git log
输出中看到的提交字符串:
... <-F <-G <-H
其中 H
是某个提交的哈希 ID,不仅提取 H
的快照,还提取更早的提交 G
的快照。 Git 然后可以 比较 G
-提交快照与 H
- 提交快照。对于相同的文件,这些文件没有发生任何事情。对于两个提交中都存在但不同的文件,这些文件中的不同之处就是 更改 。如果 G
中有 H
中不存在的文件,则这些文件已 删除 ,如果 H
中存在未删除的文件t 在 G
中,这些文件是 新添加的 。
这始终是 Git 将提交作为更改呈现给您的方式:遍历 提交图 ,由每个提交与其 [=567= 的连接组成].当你有:
...--F--G--H <-- somebranch
名称 somebranch
允许 Git 找到最新的提交 H
,并且 H
允许 Git 找到较早的提交 G
,它允许 Git 找到更早的提交 F
,等等。从这些快照中,Git 可以向您展示变化。 Git 简单地将 parent 提交(例如 G
)与 child 进行比较提交如 H
.
如果我们有一个像这样的长链:
...--C--D--E--F--G--H <-- somebranch
和我们在E
中所做的更改,即D
-vs-E
的区别,原来是坏主意,我们可以告诉 Git:恢复我们在 E
中所做的更改。 Git 现在将提取 D
和 E
,查看 更改的内容 ,并尝试撤消相同的更改。如果我们添加了一个新文件 newfile
,Git 可以 删除 整个文件 newfile
。如果我们向某个现有文件添加一些行,Git 可以从该文件中删除这些行。如果我们从某个文件中删除了一些行,Git 可以将这些行放回去。
但是有一个明显的问题。假设我们在提交 E
中添加了一个包含两个库例程的新文件 lib.py
。假设通过提交 H
,lib.py
有十几个库例程。 完全删除lib.py
会破坏一切。因此,git revert
必须执行的 back-out-some-changes 操作必须以某种方式进行调整。这里有许多可能的“不知何故”; Git 选择的是使用其 三向合并 引擎。
在我们了解 git revert
如何使用 Git 的合并引擎之前,我们必须首先了解 git merge
本身。
合并:合并作为动词,合并作为名词
在Git中,我们通常使用git merge
—或运行git pull
来制作Git运行git merge
—执行合并操作,这通常会导致合并提交。 merge这个词在a merge commit这个词中起到了形容词的作用,但是我们经常把merge commit简称为“a merge”。这里单词 merge 作为名词。
我们通过执行一些特定的操作来为合并(或合并提交,如果您愿意)生成快照。这些动作就是合并的过程。他们实现了合并的动词形式,合并(一些文件and/or一些提交)。确切的操作集当然很重要,但最重要的是,为了执行此合并操作,我们需要的不是两个而是 三个 输入。我们有一些原始的东西——一个提交,或者一个提交中的一个文件——以及那个原始东西的两个修改版本,我们称之为“我们的”和“他们的”。我们有 合并引擎 ,它是生成最终文件的代码,检查所有这三个输入并找出结果应该是什么。另见 three way merge in Merge (version control) in Wikipedia and/or Why is a 3-way merge advantageous over a 2-way merge?
合并单独的分支时,这一切都非常有意义。三个输入是三个提交。我们有一些看起来像这样的提交系列:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
我们在分支 br1
上,提交 J
。我们 运行 git merge br2
合并提交 L
。此过程的共同起点 是提交H
:最佳共享提交,在两个 分支上。合并引擎通过将 H
中的快照(我们都开始时使用的快照)与 J
中的快照进行比较来查看 我们 更改的内容,并比较 H
到 L
看看 他们 改变了什么。合并引擎然后将我们的更改与其更改组合,将 合并的 更改应用到 H
中的快照,并生成最终快照。
当我们使用 git merge
调用合并引擎时,git merge
提交本身使结果合并 commit M
如下所示:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
现在我们有一个合并(名词),通过合并(动词)提交 J
和 L
使用提交 H
作为共同起点。
git cherry-pick
如何使用合并引擎
想象一下我们有这样的 branch-y 情况:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
也就是说,我们正在 br1
上做一些工作。在这一点上,我们意识到我们需要的正是我们或其他人在提交 L
中所做的,因为它修复了一些错误。但是我们还没有准备好得到commitK
的效果,所以我们不想mergebr2
完全。相反,我们想检查提交 L
,查看提交 L
中的 更改,并获取 相同的更改 为了我们自己。
这就是 git cherry-pick
所做的:它比较 K
和 L
中的快照,看看 L
中发生了什么变化,然后它使 same 更改我们在提交 J
时的位置,并进行新的提交。但是 方式 它这样做有点偷偷摸摸:它使用与 git merge
.
假设我们将提交 K
指定为(假的!)merge base 或 shared common commit,并使用 commit J
作为我们当前的提交,因为它是。同时我们使用 commit L
作为“他们的”提交。也就是说,这些是唯一“有趣”的提交:
J <-- br1 (HEAD)
K--L <-- br2
我们有 Git,通过它的合并引擎,比较 K
和 L
看看他们改变了什么:这些是我们想要的改变 将 添加到我们的提交中。然后我们还有 Git 比较 K
与 J
,看看“我们改变了”什么:这些是我们希望 保留的与 K
的差异.
就这两组更改不冲突而言,它是安全的——好吧,就像 Git 可以自动化一样安全,无论如何——采取从 L
变化而来。如果他们 做 冲突,Git 将声明合并冲突,暂停操作,并允许我们解决冲突。
如果一切顺利,Git 在最后做一个普通的 non-merge 提交:
I--J--L' <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
其中 L'
是 L
的副本。如果不是,Git 因合并冲突而暂停。 我们 修复冲突,运行 git cherry-pick --continue
或 git commit
提交 L'
.
还原只是 cherry-pick 向后完成
如果我们有:
J <-- br1 (HEAD)
K--L <-- br2
并比较 K
-vs-L
看看他们改变了什么,比较 K
-vs-J
看看我们改变了什么,我们得到一个 cherry-pick操作。但是如果我们 Git 选择提交 L
作为“基本”提交,K
作为“他们的”,并且 J
作为“我们的”?那么我们想要保留的变化是从 L
到 J
的变化,以及从 L
到 K
的变化——即变化的逆他们实际上做了——ar我们想要 添加 .
无论我们过去是否提交 K
和 L
,这都有效!如果它们是——如果我们有这样的东西:
...o--o--K--L--o--o--o--J <-- branch (HEAD)
那么L
-vs-J
就是我们想要保留的一切,而L
-vs-K
就是我们要“加”的。它已经是 K
-vs-L
的诗句,所以它将“撤消”或 退出 从 K
到 [=76] 的更改=].
所以git revert
像以前一样简单地应用合并引擎,以child提交(L
)作为共同起点, parent 提交 reverse (K
) 作为他们的更改,我们的提交 J
作为我们的提交。如果一切顺利,Git 自己进行新的提交:
...o--o--K--L--o--o--o--J--Γ <-- branch (HEAD)
其中提交 Γ
“撤消”L
(gamma 看起来像 upside-down L
)。
这什么时候什么都不做?
无论是 cherry-pick 还是还原,当我们要求 Git 到 add 的更改是 已经。例如,假设我们有:
I--J <-- br1 (HEAD)
/
...--G--H
\
K--L <-- br2
与原始 cherry-pick 示例相同。进一步假设提交 L
修复了提交 K
中的错误,但是提交 I
and/or J
已经修复了相同的错误(也许也做了更多的事情)。如果我们 运行 git cherry-pick br2
要求 Git 在此处复制提交 L
,Git 将比较 K
-vs-L
看看他们改变了什么,然后比较 K
和 J
看看 我们 改变了什么。 我们改变的已经包含了一切,没有什么可以添加。 cherry-pick 只是停止。由于实施的一个怪癖——cherry-pick 以 运行 内部提交结束——你还会收到有关没有任何内容可提交的消息。
还原也是如此:如果我们要求还原 L
,但在我们工作的某个地方,我们 已经实现了 ,没有什么可做的。 git revert
尝试提交,但没有任何内容可提交,您得到了您看到的消息。
为什么还原合并很特别?
在某些方面,恢复或 cherry-pick 合并 一点也不 特别。但有一种方法是。请记住,合并提交有 两个 以前的快照,并且 git cherry-pick
和 git revert
都需要找到 the [=您在 git
命令中命名的 child 提交的 567=]。合并没有 the parent:它有 two parents(或更多,在章鱼合并,但我们不会去那里。
因此,您必须命名 您希望 Git 使用的 parent 作为 merge-base (cherry-pick) 或“他们的”提交(恢复)。那就是你命令行中的-m 1
。
我们必须考虑的另一件事是合并快照中的内容。请记住,合并提交,例如本例中的 M
:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
是在 H
的基础上加上 添加 all H
-vs- J
到 all 从 H
-vs-L
.
为此,如果我们愿意,我们可能会添加更多提交:
I--J
/ \
...--G--H M--N--O <-- br1 (HEAD)
\ /
K--L <-- br2
现在假设我们要求 Git 恢复 M
,使用 J
作为 parent。 Git 将:
- 将
M
视为合并的starting-point; - 将
O
视为“我们的”提交,因此找到M
-vs-O
作为 keep 的内容;和 - 将
J
视为“他们的”提交,因此找到M
-vs-J
作为 add.
如果我们不添加任何提交,这会简化一点:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
Git 现在将 M
视为基础提交和我们的提交:
- 找到
M
-vs-M
我们应该 keep (什么都没有); - 找到
M
-vs-J
作为他们更改的应该添加到这里。
将 M
-vs-J
添加到 nothing,当然会提供将快照转换为M
回到 J
中的那个。所以结果是:
I--J
/ \
...--G--H M--J' <-- br1 (HEAD)
\ /
K--L <-- br2
其中 J'
表示新提交中的 快照 与提交 J
中的快照匹配。就 source snapshots 而言,我们刚刚撤消了整个合并。
现在,如果提交 L
本身是先前合并的结果怎么办?也就是说,如果不是上面的,我们开始于:
I--J
/ \
...--G--H M <-- br1 (HEAD)
| /
: _--L <-- br2
:: /
:..
H
和 L
之间有一些大的混乱历史,所以 L
是一个合并。 不管我们是如何到达那里的,L
中的 快照 结合了从 H
到那一点的所有工作.如果我们现在使用 J
作为 parent 恢复 M
,以获得新的提交 J'
,提交 J'
已撤消 一切 我们通过 L
引入,包括任何 mrges 到达 L
。因此,恢复历史记录中从 H
到 L
的所有合并现在无效。
我认为这就是您的情况。 Git 无事可做,因为无事可做。