恢复合并而不取消之后完成的提交

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 已经在这个分支中并且在它们之上还存在这些提交的反向提交。

这意味着您将永远无法合并提交 567

虽然有一些机制可以让您更改提交 ID,方法是执行变基或仅为了更改 ID 而进行微不足道的更改,但这会导致相同更改发生合并冲突。就个人而言,我不推荐这种方法。

由于您没有提及您当前拥有的分支,我建议您首先在现有提交上创建一些分支(b7bkp),以使它们在提交后保持可见变化:

        +----- b7
        v
5 - 6 - 7       +-------- bkp
        |       v
M - N - O - P - Q
                ^
                +-------- bQ

因为您可能有一个指向 Q 的分支,您可以在下面的命令中使用它而不是 bQ

然后 git rebase 在提交 N 之上提交 PQ 以获得您想要的结构。

命令是:

# 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

旧的 OPQ 提交仍然存在,只要分支 bkp 或任何其他指向的分支,它们仍然可以访问Q 仍然存在。如果您对更改感到满意,您可以删除 bkp 和任何其他指向您拥有的 Q 的分支。

命令是:

git branch -D bkp

您可能不想删除 b7,除非您已经有另一个分支指向提交 7。在这种情况下,您甚至不需要创建 b7.

此操作后,repo 如下所示:

        +----- b7
        v
5 - 6 - 7

M - N - P'- Q'
            ^
            +-------- bQ

请注意,提交 P'Q' 不同于原始提交 PQ

警告

如果提交 OPQ 已经推送到远程存储库(通过 bQ 分支),再次推送 bQ将失败。可以强制推送 (git push --force origin bQ),但此操作会使您的同事感到困惑,因为他们已经获取了 bQ 分支的当前位置(它包含 OPQ 提交)。

如果你真的需要表演这个特技,一定要通知大家这个变化。

在这种情况下更好的方法是 git revert -m 提交 O。这会在 Q 之上创建一个新的提交,它引入了取消由提交 O 引入的更改的更改。虽然很丑,但这是最安全的解决方案。

您可以重写分支历史,也可以还原合并。各有利弊。

首先让我们从您的当前状态图的稍微修改的副本开始,它反映了更多正在发生的事情。

A -- B -- C <--(branch>
           \
  M -- N -- O -- P -- Q <--(master)

你没有显示任何引用,所以我假设 master 指向 Q。如果您在 branch 没有分支,您可能应该创建一个(除非您永久放弃 ABC 的更改)。另外,一个小的符号点,但我已经将所有提交切换为字母(因为这有时会更清楚)。


重写历史

所有关于历史重写的常见警告都适用于这种方法。这主要意味着,如果 master 已经被推到其他人已经 "seen" 提交 O 作为 master 历史的一部分,那么你将不得不与他们协调成功重写历史。重写会使他们的 master 副本处于糟糕状态,他们必须从中恢复,如果他们以错误的方式这样做——如果你没有传达正在发生的事情,他们可能会这样做——那么你的工作可能撤消。有关详细信息,请参阅 git rebase 文档中的 "Recovering from upstream rebase",因为无论您是否实际使用 rebase 命令执行重写,这都是适用的条件。

如果你确实想重写,rebase 是最简单的方法。您将需要提交 NO 的 ID,或者解析为提交 NO 的表达式。在此示例中,我们可以将 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,它将跳过提交 ABC.

解决这个问题的一种方法是使用 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)

现在合并 branchmaster 你可以说

git checkout master
git revert master~2
git merge branch