恢复合并而不取消之后完成的提交
Revert merge without cancel commits done after
我有点问题。我需要取消我的远程存储库上的合并而不取消之后完成的提交。请参阅下面我的模型和我的期望。
目前我有这个型号:
我想要这个:
我查看了文档并在网上搜索,但没有找到任何智能解决方案。你对我有什么想法吗?我会注意我未来的合并!
非常感谢!
一种方法是在 O
之前的最后一次提交上进行硬重置,该提交不属于分支 5 - 6 - 7
,即本例中的 N
。然后挑选所有必需的提交,即 P
& Q
。示例如下:
git reset --hard N #This resets the branch to N & removes all commits of merged branch `5 - 6 - 7`
git cherry-pick P #Manually add the 2 commits you want back.
git cherry-pick Q
另一种方法是使用以下命令还原合并提交:
git revert -m 1 O . #this is alphabet O - merge commit id. Not Numeric zero.
这将在 Q
之上添加一个新提交 - 我们将其命名为 O'
,其中
O'
是 O
的反向提交
警告: 如果您以后尝试在 5 - 6 - 7
分支中进行一些更改并再次合并它 - 它不会合并提交 5
, 6
, 7
因为这些提交 ID 已经在这个分支中并且在它们之上还存在这些提交的反向提交。
这意味着您将永远无法合并提交 5
、6
、7
。
虽然有一些机制可以让您更改提交 ID,方法是执行变基或仅为了更改 ID 而进行微不足道的更改,但这会导致相同更改发生合并冲突。就个人而言,我不推荐这种方法。
由于您没有提及您当前拥有的分支,我建议您首先在现有提交上创建一些分支(b7
和 bkp
),以使它们在提交后保持可见变化:
+----- b7
v
5 - 6 - 7 +-------- bkp
| v
M - N - O - P - Q
^
+-------- bQ
因为您可能有一个指向 Q
的分支,您可以在下面的命令中使用它而不是 bQ
。
然后 git rebase
在提交 N
之上提交 P
和 Q
以获得您想要的结构。
命令是:
# Preparations
# Create the branch `b7` to keep the commit `7` reachable after the change
git branch b7 7
# Create the branch `bkp` for backup (the commit `Q` will be discarded)
git branch bkp Q
# The operation
# Checkout `bQ` to start the change
git checkout bQ
# Move the `P` and `Q` commits on top of `N`
# Change "2" and "3" in the command below with the actual number of commits
# 2 == two commits: P and Q, 3 == 2 + 1
# Or you can use commit hashes (git rebase --onto N O)
git rebase --onto HEAD~3 HEAD~2
现在存储库如下所示:
+----- b7
v
5 - 6 - 7 +-------- bkp
| v
M - N - O - P - Q
|
P'- Q'
^
+-------- bQ
旧的 O
、P
和 Q
提交仍然存在,只要分支 bkp
或任何其他指向的分支,它们仍然可以访问Q
仍然存在。如果您对更改感到满意,您可以删除 bkp
和任何其他指向您拥有的 Q 的分支。
命令是:
git branch -D bkp
您可能不想删除 b7
,除非您已经有另一个分支指向提交 7
。在这种情况下,您甚至不需要创建 b7
.
此操作后,repo 如下所示:
+----- b7
v
5 - 6 - 7
M - N - P'- Q'
^
+-------- bQ
请注意,提交 P'
和 Q'
不同于原始提交 P
和 Q
。
警告
如果提交 O
、P
和 Q
已经推送到远程存储库(通过 bQ
分支),再次推送 bQ
将失败。可以强制推送 (git push --force origin bQ
),但此操作会使您的同事感到困惑,因为他们已经获取了 bQ
分支的当前位置(它包含 O
、P
和 Q
提交)。
如果你真的需要表演这个特技,一定要通知大家这个变化。
在这种情况下更好的方法是 git revert -m
提交 O
。这会在 Q
之上创建一个新的提交,它引入了取消由提交 O
引入的更改的更改。虽然很丑,但这是最安全的解决方案。
您可以重写分支历史,也可以还原合并。各有利弊。
首先让我们从您的当前状态图的稍微修改的副本开始,它反映了更多正在发生的事情。
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q <--(master)
你没有显示任何引用,所以我假设 master
指向 Q
。如果您在 branch
没有分支,您可能应该创建一个(除非您永久放弃 A
、B
和 C
的更改)。另外,一个小的符号点,但我已经将所有提交切换为字母(因为这有时会更清楚)。
重写历史
所有关于历史重写的常见警告都适用于这种方法。这主要意味着,如果 master
已经被推到其他人已经 "seen" 提交 O
作为 master
历史的一部分,那么你将不得不与他们协调成功重写历史。重写会使他们的 master
副本处于糟糕状态,他们必须从中恢复,如果他们以错误的方式这样做——如果你没有传达正在发生的事情,他们可能会这样做——那么你的工作可能撤消。有关详细信息,请参阅 git rebase
文档中的 "Recovering from upstream rebase",因为无论您是否实际使用 rebase
命令执行重写,这都是适用的条件。
如果你确实想重写,rebase 是最简单的方法。您将需要提交 N
和 O
的 ID,或者解析为提交 N
和 O
的表达式。在此示例中,我们可以将 master~3
用于 N
,将 master~2
用于 O
。
git rebase --onto master~3 master~2 master
这将获取可从 master
访问但无法从 O
访问的更改,并在 N
上重播它们;然后将 master
移动到重写的提交。
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q <--(master@{1})
\
P' -- Q` <--(master)
旧的提交仍然存在(而且,正如我在这里展示的那样,reflog 仍然可以到达它们 - 暂时在本地)。因为大多数工具不遵循 reflog,所以您可能会看到更像
的内容
A -- B -- C <--(branch>
M -- N -- P' -- Q` <--(master)
事实上,在 reflog 过期后,这正是将保留的内容(如果您在此期间不采取措施来保留旧提交)。在这一点上,要推动 master
你必须做一个强制推动。最安全的方法是
git push --force-with-lease
人们通常只推荐 -f
选项,但这不太安全,因为它可能会破坏您在远程 master
上不知道的提交。无论如何,在强制推送之后,任何其他拥有 master
副本的人都必须从 "upstream rebase" 条件中恢复过来。
进行重写的其他方法(例如通过重置然后挑选)在功能上是等效的(除了一些奇怪的边缘情况),但它们更加手动,因此更容易出错。值得重申的是,即使这些替代方案可能不使用 rebase
命令,"upstream rebase" 情况仍然适用于完全相同的方式。
没有重写
如果重写历史记录不可行——在广泛共享的回购协议中通常是这种情况——另一种方法是恢复合并提交。这将创建一个新提交,其中 "undoes" 合并引入的更改。要在合并提交上使用 revert
,您必须提供 -m
选项(它告诉 revert
哪个父级要恢复 到 ;如果您重新尝试撤消合并的效果这通常是 -m 1
).
同样,您需要 O
的 ID 或解析为 O
的表达式;我们将在示例中使用 master~2
。
git checkout master
git revert -m 1 master~2
现在你有
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q -- !O <--(master)
其中 !O
反转 O
应用于 N
的更改。
如其他地方所述,git 将 branch
视为 "already accounted for" - 它不会跟踪 !O
的更改旨在作为 revert/rollback O
或类似的东西。因此,如果您稍后想说 git merge branch
,它将跳过提交 A
、B
和 C
.
解决这个问题的一种方法是使用 rebase -f
。例如,在还原之后你可以说
git rebase -f master~3 branch
并且在 O
处合并之前可从 branch
访问但无法从 master
访问的所有提交都将被重写。当然,这是对branch
的改写。由于您可能一直在使用 revert
方法来避免重写 master
,因此您可能也不想重写 branch
。 (如果你确实重写了 branch
,并且如果 branch
与其他 repos 共享,那么你将不得不 push --force-with-lease
并且其他用户将不得不从上游 rebase 中恢复。)
另一个选项,在您想要将 branch
合并回 master
的位置,是 "revert the revert"。假设您恢复合并后已经过去了一段时间,并且您有
A -- B -- C -- D -- E <--(branch>
\
M -- N -- O -- P -- Q -- !O -- R -- S <--(master)
现在合并 branch
到 master
你可以说
git checkout master
git revert master~2
git merge branch
我有点问题。我需要取消我的远程存储库上的合并而不取消之后完成的提交。请参阅下面我的模型和我的期望。
目前我有这个型号:
我想要这个:
我查看了文档并在网上搜索,但没有找到任何智能解决方案。你对我有什么想法吗?我会注意我未来的合并!
非常感谢!
一种方法是在 O
之前的最后一次提交上进行硬重置,该提交不属于分支 5 - 6 - 7
,即本例中的 N
。然后挑选所有必需的提交,即 P
& Q
。示例如下:
git reset --hard N #This resets the branch to N & removes all commits of merged branch `5 - 6 - 7`
git cherry-pick P #Manually add the 2 commits you want back.
git cherry-pick Q
另一种方法是使用以下命令还原合并提交:
git revert -m 1 O . #this is alphabet O - merge commit id. Not Numeric zero.
这将在 Q
之上添加一个新提交 - 我们将其命名为 O'
,其中
O'
是O
的反向提交
警告: 如果您以后尝试在 5 - 6 - 7
分支中进行一些更改并再次合并它 - 它不会合并提交 5
, 6
, 7
因为这些提交 ID 已经在这个分支中并且在它们之上还存在这些提交的反向提交。
这意味着您将永远无法合并提交 5
、6
、7
。
虽然有一些机制可以让您更改提交 ID,方法是执行变基或仅为了更改 ID 而进行微不足道的更改,但这会导致相同更改发生合并冲突。就个人而言,我不推荐这种方法。
由于您没有提及您当前拥有的分支,我建议您首先在现有提交上创建一些分支(b7
和 bkp
),以使它们在提交后保持可见变化:
+----- b7
v
5 - 6 - 7 +-------- bkp
| v
M - N - O - P - Q
^
+-------- bQ
因为您可能有一个指向 Q
的分支,您可以在下面的命令中使用它而不是 bQ
。
然后 git rebase
在提交 N
之上提交 P
和 Q
以获得您想要的结构。
命令是:
# Preparations
# Create the branch `b7` to keep the commit `7` reachable after the change
git branch b7 7
# Create the branch `bkp` for backup (the commit `Q` will be discarded)
git branch bkp Q
# The operation
# Checkout `bQ` to start the change
git checkout bQ
# Move the `P` and `Q` commits on top of `N`
# Change "2" and "3" in the command below with the actual number of commits
# 2 == two commits: P and Q, 3 == 2 + 1
# Or you can use commit hashes (git rebase --onto N O)
git rebase --onto HEAD~3 HEAD~2
现在存储库如下所示:
+----- b7
v
5 - 6 - 7 +-------- bkp
| v
M - N - O - P - Q
|
P'- Q'
^
+-------- bQ
旧的 O
、P
和 Q
提交仍然存在,只要分支 bkp
或任何其他指向的分支,它们仍然可以访问Q
仍然存在。如果您对更改感到满意,您可以删除 bkp
和任何其他指向您拥有的 Q 的分支。
命令是:
git branch -D bkp
您可能不想删除 b7
,除非您已经有另一个分支指向提交 7
。在这种情况下,您甚至不需要创建 b7
.
此操作后,repo 如下所示:
+----- b7
v
5 - 6 - 7
M - N - P'- Q'
^
+-------- bQ
请注意,提交 P'
和 Q'
不同于原始提交 P
和 Q
。
警告
如果提交 O
、P
和 Q
已经推送到远程存储库(通过 bQ
分支),再次推送 bQ
将失败。可以强制推送 (git push --force origin bQ
),但此操作会使您的同事感到困惑,因为他们已经获取了 bQ
分支的当前位置(它包含 O
、P
和 Q
提交)。
如果你真的需要表演这个特技,一定要通知大家这个变化。
在这种情况下更好的方法是 git revert -m
提交 O
。这会在 Q
之上创建一个新的提交,它引入了取消由提交 O
引入的更改的更改。虽然很丑,但这是最安全的解决方案。
您可以重写分支历史,也可以还原合并。各有利弊。
首先让我们从您的当前状态图的稍微修改的副本开始,它反映了更多正在发生的事情。
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q <--(master)
你没有显示任何引用,所以我假设 master
指向 Q
。如果您在 branch
没有分支,您可能应该创建一个(除非您永久放弃 A
、B
和 C
的更改)。另外,一个小的符号点,但我已经将所有提交切换为字母(因为这有时会更清楚)。
重写历史
所有关于历史重写的常见警告都适用于这种方法。这主要意味着,如果 master
已经被推到其他人已经 "seen" 提交 O
作为 master
历史的一部分,那么你将不得不与他们协调成功重写历史。重写会使他们的 master
副本处于糟糕状态,他们必须从中恢复,如果他们以错误的方式这样做——如果你没有传达正在发生的事情,他们可能会这样做——那么你的工作可能撤消。有关详细信息,请参阅 git rebase
文档中的 "Recovering from upstream rebase",因为无论您是否实际使用 rebase
命令执行重写,这都是适用的条件。
如果你确实想重写,rebase 是最简单的方法。您将需要提交 N
和 O
的 ID,或者解析为提交 N
和 O
的表达式。在此示例中,我们可以将 master~3
用于 N
,将 master~2
用于 O
。
git rebase --onto master~3 master~2 master
这将获取可从 master
访问但无法从 O
访问的更改,并在 N
上重播它们;然后将 master
移动到重写的提交。
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q <--(master@{1})
\
P' -- Q` <--(master)
旧的提交仍然存在(而且,正如我在这里展示的那样,reflog 仍然可以到达它们 - 暂时在本地)。因为大多数工具不遵循 reflog,所以您可能会看到更像
的内容A -- B -- C <--(branch>
M -- N -- P' -- Q` <--(master)
事实上,在 reflog 过期后,这正是将保留的内容(如果您在此期间不采取措施来保留旧提交)。在这一点上,要推动 master
你必须做一个强制推动。最安全的方法是
git push --force-with-lease
人们通常只推荐 -f
选项,但这不太安全,因为它可能会破坏您在远程 master
上不知道的提交。无论如何,在强制推送之后,任何其他拥有 master
副本的人都必须从 "upstream rebase" 条件中恢复过来。
进行重写的其他方法(例如通过重置然后挑选)在功能上是等效的(除了一些奇怪的边缘情况),但它们更加手动,因此更容易出错。值得重申的是,即使这些替代方案可能不使用 rebase
命令,"upstream rebase" 情况仍然适用于完全相同的方式。
没有重写
如果重写历史记录不可行——在广泛共享的回购协议中通常是这种情况——另一种方法是恢复合并提交。这将创建一个新提交,其中 "undoes" 合并引入的更改。要在合并提交上使用 revert
,您必须提供 -m
选项(它告诉 revert
哪个父级要恢复 到 ;如果您重新尝试撤消合并的效果这通常是 -m 1
).
同样,您需要 O
的 ID 或解析为 O
的表达式;我们将在示例中使用 master~2
。
git checkout master
git revert -m 1 master~2
现在你有
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q -- !O <--(master)
其中 !O
反转 O
应用于 N
的更改。
如其他地方所述,git 将 branch
视为 "already accounted for" - 它不会跟踪 !O
的更改旨在作为 revert/rollback O
或类似的东西。因此,如果您稍后想说 git merge branch
,它将跳过提交 A
、B
和 C
.
解决这个问题的一种方法是使用 rebase -f
。例如,在还原之后你可以说
git rebase -f master~3 branch
并且在 O
处合并之前可从 branch
访问但无法从 master
访问的所有提交都将被重写。当然,这是对branch
的改写。由于您可能一直在使用 revert
方法来避免重写 master
,因此您可能也不想重写 branch
。 (如果你确实重写了 branch
,并且如果 branch
与其他 repos 共享,那么你将不得不 push --force-with-lease
并且其他用户将不得不从上游 rebase 中恢复。)
另一个选项,在您想要将 branch
合并回 master
的位置,是 "revert the revert"。假设您恢复合并后已经过去了一段时间,并且您有
A -- B -- C -- D -- E <--(branch>
\
M -- N -- O -- P -- Q -- !O -- R -- S <--(master)
现在合并 branch
到 master
你可以说
git checkout master
git revert master~2
git merge branch