意外地将主分支合并到开发分支并推送

5
我在一个git仓库中,我们维护一个主分支和一个开发分支。更改是在开发分支上进行的,并在发布之前合并到主分支。今天,我不小心将主分支合并到开发分支并将结果推送到开发分支。现在,在开发分支上的git log中显示了merge develop into master提交记录。我需要找到正确的方法来解决这个问题。使用git reflog我找到了最近的好的提交记录。那么,恢复到它的正确方法是什么?
由于我已经推送了代码,我想避免重写历史记录。但是,我不确定是否可以使用git revert。我不确定是否适用于“撤消错误合并” howto 和其他 SO 问题,因为我要撤消的提交本身是合并提交(https://www.kernel.org/pub/software/scm/git/docs/howto/revert-a-faulty-merge.txt)。
4个回答

11

可以使用git revert来撤销合并。但需要注意的是,这样做会使未来的“重新合并”变得更加困难。仔细阅读你提供的链接中的信息,它详细说明了如何让未来的“重新合并”工作,但要注意它谈论的是将develop合并到master,而你说你是相反地将master合并到develop

A -- B -- C -- D -- E          <-- master
      \          \
       F - G - H - M           <-- develop

在这种情况下(将master合并到develop而不是相反),您可以选择许多不同的选项,每个选项都有优点和缺点...

最简单的方法,如果可以的话我更喜欢用这个方法

(0) 什么也不做。如果合并CD不破坏develop分支,就保持现状。稍后,有人会 git checkout master 然后 git merge --no-ff develop,然后得到这个结果(比如说,I这个提交先添加到了develop):

A -- B -- C -- D -- E -- M2    <-- master
      \          \      /
       F - G - H - M - I       <-- develop

这里的merge会查找自从从master分离出来并且在B上的develop中所做的更改,它将在合并后放进FGH,跳过任何来自master(可能是全部)的M的部分,并最终放入I并生成合并提交M2(因为提交E,如果没有使用--no-ff,也会生成合并提交M2)。

一些易于实施但明显存在缺陷的方法

(1) 只需"重写历史记录":从分支develop中删除提交M。通知所有使用该分支的其他人即将发生的事情:提交M将消失,他们应该采取适当的措施来处理此事。

(2) 停止使用旧名称develop,创建一个新的分支名称develop1或其他名称:

A -- B -- C -- D -- E          <-- master
     |           \
     |             M           <-- develop
      \          /
       F - G - H               <-- develop1

这与选项(1)相同,只是提交 M 仍然存在,并带有分支标签,在其中,您具有指向提交 H 的新/不同的分支标签。您仍然需要通知所有人,但这可能会使他们的工作更轻松。当所有人都满意后,您可以稍后删除 develop

还原及其缺点

(3)还原合并。如链接文章中所述,让我们使用W来表示新的还原提交:

A -- B -- C -- D -- E          <-- master
      \          \
       F - G - H - M - W       <-- develop

“W”中有什么?它包括一切必要的内容,以“消除合并效果”。在这种情况下,这意味着撤销了在“C”和“D”中所做的任何更改。到目前为止,情况还不错,develop 中只包含来自 F, GH 的更改。问题出现在以后——如果或者当有人执行以下操作:

git checkout master
git merge develop
第二个命令将寻找masterdevelop分支在哪里分离,也就是提交B,因此它会捡起从那时起的所有更改。这些更改包括W中的更改,这将撤消CD,而这并不是你想要的。这可以在之后进行修复,但是谁合并代码必须知道这个等待他们的“删除CD”定时炸弹。
请注意,这与“什么也不做”的选项(0)相同。这里的问题是W内容。在情况(0)中,您想要提交I的内容,但在这里,您不想要W的内容。
另外一个选项:
这可能是最糟糕的选择,鉴于上面展示的情况,但我还是列出来吧:
(4)创建一个全新的分支,其中包含您要保留的提交的副本,并将其命名为develop。(或者命名为其他名称,但那样我们就回到了上面类似于develop1的情况,您可能应该只使用那个。)您是选择保留old-develop分支还是放弃它(不加标签),取决于您,但我会在图中将其画出来:
       F' - G' - H'            <-- develop
      /
A -- B -- C -- D -- E          <-- master
      \          \
       F - G - H - M           <-- old-develop

这在您提供的链接中已经有所描述。它只在更复杂的情况下才真正有用。


+1 只是为了你花费在这些图纸上的时间 ;) - Loïc MICHEL

2
您可以通过将本地分支重置到最后一个良好的提交,然后推送该提交来解决此问题:
git checkout develop
git reset --hard lastgoodcommit
git push origin develop

请注意,您的上游仓库可能默认禁止非快进式合并。如果是这样,您需要修改上游设置,执行上述推送操作,然后恢复原始设置。

标准的做法是使用push --force,但OP表示如果可能的话不想重写历史记录。 - Danica
1
当然,但“不想重写历史”和“撤销错误合并”这两个目标在很大程度上是不兼容的。问题在于,如果你除了重写历史之外做任何其他事情,那么看起来以前在master上的提交已经被合并到develop中,即使它们没有(在尝试撤消此操作后)。 - Greg Hewgill
你的意思是因为它们仍会显示在 git log 中吗?而如果你能看到还原操作,历史记录才会清晰可见? - aschmied
是的,在日志中仍会显示来自主分支的这些提交,有些工具会认为这些提交已经存在于您的开发分支中,即使稍后的还原操作已经撤销了它们的影响。如果您能够处理,最好使用强制推送操作来倒退历史记录。 - Greg Hewgill

0
我使用GIT扩展。在UI中右键单击修订版本,可以恢复到该版本的功能。

0

是的,您可以撤销合并,但需要指定哪个父分支是主线。由于您将develop合并到了master中,因此应该这样做:

git revert --mainline 1 HEAD

为确保您拥有所需的内容,您应该使用$lastgoodcommit进行差异比较,但是$lastgoodcommit应该是HEAD^。当然,如果您没有在合并之上进行提交,那么就需要将HEAD替换为合并提交。
最后,您还可以通过检出当前提交的代码来手动执行还原操作:
git checkout $lastgoodcommit -- .

'-- .' 的作用是区分普通的 checkout,其中 HEAD 被切换到指定的提交。在这个命令中,工作目录和暂存区中的所有文件都将与 $lastgoodcommit 中的文件完全相同。如果您使用 $lastgoodcommit 进行 diff,您应该会看到没有任何更改。本质上它是一个还原操作。

您也可以尝试 'git checkout --patch' 来选择性地还原代码块。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接