如何撤销已经推送到远程的合并提交?

1725

git revert <commit_hash> 单独使用不起作用。显然,必须指定-m


3
请查看这个问题的答案:https://dev59.com/WnE95IYBdhLWcg3watQ3 - eugen
3
相关链接:撤销Git合并操作? - user456814
1
这里的链接是最好的例子,说明如何撤销合并提交:http://www.christianengvall.se/undo-pushed-merge-git/ - S.K. Venkat
1
这是一个例子,说明 git 的设计与每个人都使用的 git-flow 工作流不匹配。如果你已经检出了 develop 分支,那么你肯定想要还原引入错误的 2 次提交的特性分支,而不是多年共享的开发分支。感觉需要用 -m 1 来选择它非常荒谬。 - pkamb
2
只有一个我以前从未想过的建议 - 如果其中一个分支的提交列表很小,您可能会更舒适地还原单个提交,而不是整个提交分支。 - Sridhar Sarnobat
显示剩余2条评论
21个回答

9

当你在git log的输出中查看一个合并提交时,你会看到它的父提交在以Merge开头的行上列出:

commit 8f937c683929b08379097828c8a04350b9b8e183
Merge: 8989ee0 7c6b236
Author: Ben James <ben@example.com>
Date:   Wed Aug 17 22:49:41 2011 +0100

Merge branch 'gh-pages'

Conflicts:
    README

在这种情况下,git revert 8f937c6 -m 1 将使您得到与8989ee0相同的树形结构,而git revert -m 2 则会将树形结构恢复到 7c6b236 的状态。

为了更好地理解父ID,您可以运行:

git log 8989ee0 

并且

git log 7c6b236

选取备份分支

git checkout -b mybackup-brach

git reset --hard 8989ee0 
git push origin -u mybackup-branch

现在你已经得到了合并前的更改,如果一切正常,请切换到以前的分支并使用备份分支进行重置。

git reset --hard origin/mybakcup-branhc

9

被接受的答案和其他答案演示了如何使用 git revert 命令还原合并提交。然而,关于父提交存在一些混淆。这篇文章旨在通过图形表示和一个真实例子来澄清这个问题。

还原合并提交不像使用 git revert <commit-hash> 那么简单,因为 Git 在查看合并提交时会因其有两个父提交而感到困惑。要指定所需的父提交,请使用 -m 标志。由于 Git 无法自动确定哪个父提交是主干线,哪个是分支以进行取消合并,因此必须明确说明。

git commit graph

iss53 分支已合并到 master,创建了一个 Merge CommitC6C6 有两个父级,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 revert。

    # 要还原到主分支中的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


3
如果你想撤销刚刚推送的更改,以下是一个非常简单的解决方案:
```git git revert HEAD ```
这将创建一个新的提交,将之前的更改撤销。
commit 446sjb1uznnmaownlaybiosqwbs278q87
Merge: 123jshc 90asaf


git revert -m 2 446sjb1uznnmaownlaybiosqwbs278q87 //does the work

2

对于我来说,正确标记的答案起了作用,但我花了一些时间确定发生了什么。所以我决定添加一个简单明了的步骤,适用于像我这样的情况。

假设我们有分支A和B。你将分支A合并到分支B,并将分支B推送到自身,因此现在合并是其中的一部分。但你想回到合并之前的最后一次提交。你该怎么做?

  1. 进入你的git根目录(通常是项目文件夹),使用git log
  2. 你将看到最近提交的历史记录——提交具有提交/作者/日期属性,而合并还具有合并属性——因此你会看到它们像这样:

    commit: <commitHash> Merge: <parentHashA> <parentHashB> Author: <author> Date: <date>

  3. 使用git log <parentHashA>git log <parentHashB>——你将看到这些父分支的提交历史记录——列表中的第一个提交是最新的

  4. 获取你想要的<commitHash>,进入你的git根目录,使用git checkout -b <newBranchName> <commitHash>——这将创建一个新分支,从你在合并之前选择的最后一次提交开始。完成!

2

2
这是一个非常古老的帖子,但我认为还有另一种方便的解决方法: 我从一切正常的修订版本创建一个新分支,然后挑选需要从旧分支中挑选的所有内容并将其复制到新分支中。 因此,如果GIT历史记录如下: d c b <<<合并 a ... 我从a创建一个新分支,挑选c和d,然后新分支清除了b。 我可以再次决定在我的新分支中合并“b”。 如果“b”不再需要或仍在另一个(功能/热修补)分支中,则旧分支将变为废弃并将被删除。 唯一的问题现在是计算机科学中最难的事情之一:你如何命名新分支?;) 好的,如果您在devel中失败了,您可以按上述提到的方式创建newdevel,删除旧devel并将newdevel重命名为devel。 任务完成。 当您想要时,您现在可以再次合并更改。 它就像以前从未合并过一样....

如果您已经挑选了提交,那么再次执行合并操作会更加困难,因为它们将被重复。使用变基会更容易(变基会检测相似的提交并跳过它们:https://dev59.com/9HE85IYBdhLWcg3wwWQb#2628915,https://dev59.com/SaDia4cB1Zd3GeqPDW3q#43084547)。"我可以决定再次执行合并操作。" - VonC
嗯,是的。这个关于挑选特定提交的问题确实存在,但在这种情况下,其他提交位于旧的“死”分支中,我永远不会再次合并,并且过一段时间后也会删除旧分支。“我可以随时决定再次合并。”与“b”的合并有关。 - miwoe
我从不还原合并。我只是从一切正常的修订版本创建另一个分支。问题是,如果您想还原已经推送的提交(暗示:不重写历史记录),该怎么办? - Guildenstern

1
我发现在两个已知的端点之间创建一个反向补丁并应用该补丁可以起作用。这假设您已经从主分支创建了快照(标签),甚至是主分支的备份,例如master_bk_01012017。
假设您合并到主分支的代码分支是mycodebranch。
1. 检出主分支。 2. 在主分支和您的备份之间创建一个完整的二进制反向补丁。 git diff --binary master..master_bk_01012017 > ~/myrevert.patch 3. 检查您的补丁 git apply --check myrevert.patch 4. 用签名应用补丁 git am --signoff < myrevert.patch 5. 如果您需要在修复后再次引入此代码,则需要从还原的主分支分支并检出修复分支 git branch mycodebranch_fix git checkout mycodebranch_fix 6. 在此处,您需要找到还原的SHA密钥并还原还原 git revert [SHA] 7. 现在,您可以使用mycodebranch_fix来解决问题,提交并完成后重新合并到主分支。

1

-m1是当前正在修复的分支的最后一个父分支,-m2是已合并到此分支的原始父分支。

如果命令行让您感到困惑,Tortoise Git也可以提供帮助。


"-m1"和"-m2"? 这不是一个自包含的答案。 - Guildenstern

0

我也遇到了这个问题,它发生在一个 GitHub 仓库的主分支中已经合并了的 PR 上。

因为我只想修改一些已被修改的文件而不是整个 PR 带来的更改,所以我必须使用 git commit --am 命令对 merge commit 进行 amend

操作步骤:

  1. 切换到你想要更改/恢复某些修改后的文件的分支
  2. 根据修改后的文件进行所需修改
  3. 运行 git add *git add <file>
  4. 运行 git commit --am 并验证
  5. 运行 git push -f

它的有趣之处:

  • 它保留了 PR 的作者提交记录
  • 不会破坏 Git 树
  • 你将被标记为提交者(合并提交作者将保持不变)
  • Git 表现得好像你解决了冲突,它将删除/更改修改后的文件中的代码,就好像你手动告诉 GitHub 不按原样合并它一样

0

这对我有效 ::

  1. 检出您想要还原前一个(上一个)合并请求的分支。
  2. 打开终端。 3.运行命令 git reset --merge HEAD~1

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