撤销已推送的合并操作

135

我执行的步骤:

我有两个分支 branch1 和 branch2,

$git branch --Initial state
$branch1

$git checkout branch2
$git pull origin branch1 --Step1

我解决了冲突并提交了

$git commit -m "Merge resolved"

然后

$git checkout branch1
$git merge branch2
$git push origin branch1

我现在意识到,在步骤1时,自动合并删除了一些代码,并且更改的代码已被推送。现在我想回到最初的状态,以撤消所有更改。需要一些即时帮助吗?


Git revert不是你想要的吗? - Frederick Cheung
1
它会给出以下消息:致命错误:提交b4a758b36a5bde9311061fe7b56e4f47859de052是一个合并,但没有给出-m选项。@FrederickCheung - Bijendra
查看关于 -m 选项的手册。 http://www.kernel.org/pub/software/scm/git/docs/git-revert.html 简而言之,您可以使用 -m 1 或 -m 2。这将选择要还原到哪个父版本。 - Ilya Ivanov
我使用了 "git revert -m 1 SHA" 命令,现在所有更改都作为要提交的更改出现在我的本地。 - Bijendra
1
在进行还原操作时,vim出现了错误导致退出,我运行了git config --global core.editor /usr/bin/vim,这解决了问题,然后还原成功运行以解决问题。谢谢。 - Bijendra
6个回答

157

您可以根据官方指南还原合并,但这会让Git错误地认为已经合并的提交仍然存在于目标分支上。

基本上,您需要执行以下操作:

git revert -m 1 (Commit id of the merge commit)

46
在处理 1 时需要谨慎。它代表合并提交的 第一个 父提交。但是,如果某人(假设地)“不小心”将主分支合并到另一个分支上,并将主分支快进到合并提交位置上,则需要使用 -m 2 来撤销主分支上的合并。 - Krzysztof Jabłoński
2
小心那个1。它使我的存储库出了问题,部分地撤消了合并,而我想完全撤消合并。最终我撤消了撤消! - Javad

65

尝试使用git reflog <branch>来查找合并之前分支的位置,使用git reset --hard <commit number>来恢复旧版本。

Reflog会显示分支的旧状态,因此您可以将其返回到任何您喜欢的更改集。

确保在使用git reset时处于正确的分支

要更改远程存储库历史记录,可以使用git push -f,但不建议这样做,因为有人可能已经下载了您推送的更改。


2
我已经将代码推送到远程分支,如何从那里还原代码? - Bijendra
3
如果你已经进行了推送,你不希望进行本地历史记录重写,就像Ilya所描述的那样。 - ms-tg
4
抱歉,我错过了那个东西...有时候你可以使用git push -f来重写远程历史记录。这取决于你的远程仓库配置和其他团队成员(如果他们不因为这个强制推送而杀了你)。 - Ilya Ivanov
这必须要非常小心地完成,但是它可能会很有用。如果你能与所有团队成员确认,并且没有人撤销了它,或者他们也会重置自己的本地副本。否则,不行! - Basya

36

第一种选择是使用 git revert

git revert -m 1 [sha-commit-before-merge]

git revert 命令可以撤销更改但保留版本记录。因此,您将无法在同一分支上继续工作,因为您无法再查看合并分支和功能分支之间的实际差异。 如果只有您正在推送更改到该分支,请使用以下方法完全删除历史记录,并非常小心地执行。

git reset --hard [sha-commit-before-merge]
git push [origin] [branch] --force

7
使用了第二种方法,效果非常好。这也使得我的 GitHub 合并请求像之前一样干净。谢谢。 - Raj Rajeshwar Singh Rathore
第二种方法真是救命稻草,因为它没有创建一个撤销合并提交,只是将最新的提交回退了。 - undefined

6
给定答案的更多解释,包括一个图形表示和一个逐步示例:git revert -m 1 <merge commit id>
撤销合并提交不像git revert <commit-hash>那样直观,因为Git在查找合并提交时会混淆其两个父提交。为了指定所需的父提交,请使用-m标志。由于Git无法自动确定哪个父提交是主干线,哪个是要取消合并的分支,因此必须指定。

git commit graph

master 分支中合并了 iss53 分支,创建了一个 Merge Commit,即 C6C6 有两个父级,即 C5C4

需要还原 C6 并将存储库返回到其在 C4 处的状态。因此必须指定用于还原命令的父级。

  • 请检查 git log,(这里用图表中的代码名称代表实际提交哈希值

    > git log
    
    commit C6
    Merge: C4 C5
    Author: Mozz <mozz@example.com>
    Date:   Wed Feb 29 23:59:59 2020 +0100
    
    Merge branch 'iss53' to master
    ...
    
  • git log 输出中,记录下与 Merge: - - 相关的父级 ID。它将以 Merge: parent1 parent2 的格式出现,这里是 Merge: C4 C5

  • C4 提交在 master 分支上,我们需要还原到该提交,即需要使用父级 1 和 -m 1(使用 git log C4 确认之前的提交以确认父分支)。

  • 切换到进行合并的分支(这里是 master 分支,我们的目标是从中删除 iss53 分支)

    使用 -m 1 标志执行 git 还原。

    # 还原到 master 分支中的 C4
    git revert C6 -m 1
    
    # C6 - 是合并提交哈希值
    

对于其他情况,如果需要,请恢复到C5

# revert to C5 in iss53 branch
git revert C6 -m 2

# General
git revert <merge commit id> -m 1 (reverts to parent1)
git revert <merge commit id> -m 2 (reverts to parent2)
# remember to check and verify the parent1 and parent2 with git log command.

实际例子

在一个只有main分支的现有项目上创建了一个名为revert-test的新分支,现在提交图看起来像这样。

git tree before

(为了以图形方式查看提交,请在git log 后使用 --graph [SO ans ref] 或使用这个更加交互式的 VS Code 扩展 - git graph)

现在,我添加了一些新文件,修改了现有文件,并在每个分支上创建了单独的提交,然后将它们推送到了源。现在的图形如下所示:

git tree after commit

然后,从GitHub创建了一个拉取请求,并将revert-test分支合并到main分支。

git tree after merge

我想撤销合并提交并返回到main分支中的上一个提交 - 即12a7327

请注意,合并提交 - 2ec06d9现在有两个父提交 - main中的12a7327revert-test中的15bde47,现在检查git log

> git log

commit 2ec06d9d315a3f7919ffe4ad2c2d7cec8c8f9aa3 (HEAD -> main, origin/main, origin/HEAD)
Merge: 12a7327 15bde47
Author: Akshay <63786863+akshay@users.noreply.github.com>
Date:   Sun Feb 5 00:41:13 2023 +0530

    Merge pull request #1 from Akshay/revert-test
    
    Revert test

要撤销合并提交并返回到 12a7327,需要执行以下操作:

# To the First parent
git revert 2ec06d9 -m 1

现在,提交消息将显示在编辑器中,指定详细信息,请检查和验证。

git revert commit verify message

这样就创建了一个还原提交,它会执行合并提交的相反更改。

revert commit

最后推送更改,现在合并提交的更改已经消失,日志将会像这样。

final git tree


1

如另一个答案中所提到的,对合并进行git revert的主要问题在于Git仍然认为所有先前的提交都已合并(它只更改了代码)。如果以后尝试合并该分支,您会发现那些先前的提交丢失了。

更好的方法(显然要与团队协商,以便他们不会继续在有问题的分支上工作)是将git reset回到早期的提交。它不必是硬重置,因为这仅涉及当前工作更改的处理方式,所以这部分由您决定。

如果您想在重置之前在旧的提交上进行更改,还可以从先前的提交创建临时分支。在这种情况下,您将重置为临时分支上的提交。

您将无法在未拉取上游更改的情况下推送,但是显然您不希望拉取您尝试撤销的更改。因此,您需要执行强制推送(或使用带租约的强制推送以防止在此期间推送任何更改)。

附注:作为VS Code和Git Graph的粉丝,我发现通过右键单击本地分支标签名称并选择“推送分支...”来执行强制推送很容易,选择强制或带租约的强制,并勾选“设置上游”框。


1
在我的情况下,我将我的分支(称为:my-branch)与另一个功能分支(feature-branch)合并,但不是主分支。 因此,我的分支历史记录如下:
my-branch (before merge)

---master----m1----m2----m3---m4

在将其与另一个 feature-branch 合并后,该分支在主分支上有提交 f1, f2,变成了这样:
my-branch (after merge)

---master----m1----m2----f1----f2----m3---m4----mergecommit

这可能是因为在我的分支上工作时,我在进行了两次提交后从主分支合并了一次,或者两个分支中的一个可能没有与主分支保持同步。所以在这种情况下 git revert -m 1 不起作用,因为它会留下那些在中间的 f1f2 提交。
解决方案很简单,在正常情况下有效,其中我们没有中间提交:
git rebase -i HEAD~6

根据您想要更改的过去提交数量,使用适当的数字而不是6。 现在打开Vim编辑器,将不需要的提交标记为drop,然后使用:wq保存并退出。 验证日志:

git log --oneline 

强制推送
git push -f

现在远程分支应该处于先前的状态。

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