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

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个回答

2093
git revert -m 中,-m 选项指定了父级编号。这是必需的,因为合并提交具有多个父级,并且 Git 不知道哪个父级是主干,哪个父级是您想要取消合并的分支。
当您在 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”中一样。
为了更好地理解您要还原的内容,请运行“git diff <parent_commit> <commit_to_revert>”,在本例中为:
git diff 8989ee0 8f937c6

并且

git diff 7c6b236 8f937c6

214
从两个数字 8989ee07c6b236 中,应该选择哪一个?我应该如何理解这个问题? - Arup Rakshit
26
撤销后,我认为在源分支中轻松地修正代码并再次合并是不可能的。 - IsmailS
19
在谷歌搜索更好的解释时,我发现了这篇文章,认为它很好地概述了细节。阅读后,我发现我真正需要的是RESET命令,接着是强制推送。也许这会对其他人有所帮助。https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting - Funktr0n
67
如果你运行 git log 8989ee0git log 7c6b236,你就应该知道答案了。 - BMW
6
使用 "git log --merges" 命令来查看所有合并记录,使用 "git log --no-merges" 命令来查看不包含合并记录的历史记录。合并分支会将被合并分支的历史记录合并到目标分支中,使用普通的 git log 命令很难区分出来。 - Alex Punnen
显示剩余17条评论

683

这是一个完整的示例:

git revert -m 1 <commit-hash> 
git push -u origin master

git revert ... 撤销你的更改。

  • -m 1 表示您要撤消合并之前第一个父提交的树,如此答案所述。

  • <commit-hash> 是您想要撤销的合并的提交哈希。

git push ... 将您的更改推送到远程分支。


26
我认为git revert命令已经提交了所创建的提交对象。如果不想发生这种情况,你需要输入--no-commit标志。 - Delfic
2
正如 @Delfic 所提到的,提交已经由第一行管理(我需要一个 :wq 来验证它),因此第二行是不必要的。 - eka808
3
这很令人困惑。只有两行代码,没有Git提交记录。能否请有人编辑一下? - Jay Random
1
正如上面的评论所指出的那样,revert 命令包括一个提交。 - Will Sheppard
运行得非常顺利。 - Anurag Prashant
显示剩余3条评论

243
本已经告诉了你如何撤消合并提交,但是你必须非常重视它使你意识到这样做会声明你不想要由合并带来的树更改。因此,以后的合并只会引入之前被撤消的合并的祖先提交中未引入的树更改。这可能是你想要的,也可能不是你想要的。从(git-merge man page)可以看到。

一篇从文章/邮件列表消息链接过来的文章详细介绍了其中涉及的机制和考虑事项。请确保您理解,如果您撤消合并提交,您不能后来再次合并分支,并期望相同的更改再次出现。

120
如果确实需要,你可以撤销之前的撤销以恢复它们。 - dalore
8
非常有用的信息,因为在出现错误时撤销合并并修复后再次合并整个分支是一个常见的应用案例。 - Component 10
7
如果你跟我一样,后来又想要合并变更,你可以选择还原还原操作,或者挑选你撤销的变更进行单独合并。 - UnitasBrooks
在我的情况下,我不得不“撤销还原”以解决我的更改回退问题。挑选特定提交可能是一种更好的方法?下次我会尝试一下... - Steven Anderson
5
这非常重要,应该作为警告添加到其他答案中。我的看法是,你并没有真正撤销合并本身,而是撤销了合并所做的更改。 - Adam

145
您可以按照以下步骤撤销错误提交或将远程分支重置回正确的HEAD/状态。
注意:此解决方案仅适用于您自己的分支,不适用于共享分支。
1. 将远程分支检出到本地仓库。 ``` git checkout your_branch_name ```
2. 从git log中复制提交哈希(即错误提交之前的提交ID)。 ``` git log -n5 ``` 应显示如下内容: ``` commit 7cd42475d6f95f5896b6f02e902efab0b70e8038 "Merge branch 'wrong-commit' into 'your_branch_name'" commit f9a734f8f44b0b37ccea769b9a2fd774c0f0c012 "this is a wrong commit" commit 3779ab50e72908da92d2cfcd72256d7a09f446ba "this is the correct commit" ```
3. 将分支重置为在上一步中复制的提交哈希。 ``` git reset (例如:3779ab50e72908da92d2cfcd72256d7a09f446ba) ```
4. 运行`git status`以显示所有包含在错误提交中的更改。
5. 运行`git reset --hard`以撤销所有这些更改。
6. 强制将本地分支推送到远程,并注意您的提交历史记录与被污染之前一样干净。 ``` git push -f origin your_branch_name ```

21
如果与此同时有20个开发人员拉取了最新的开发分支合并,会怎样? - Ewoks
5
如果团队里有20个开发人员在使用某个开发分支,我不会强制推送更改到该分支。在这种情况下,明智的做法是进行还原提交(revert commit),而非强制推送。 - ssasi
9
当你独自工作或确定没有其他开发人员拉取了你搞砸的提交时,这是一个非常好的解决方案。 - Kyle B
1
在我错误地将一个分支拉到本地后,这正是我需要的。谢谢! - GardenRouteGold
2
这个答案默认显示在顶部,但是它是危险的。许多人会直接按照步骤操作而不阅读底部的注释。我试图把注释放在顶部,但看起来编辑队列已满。 - Abbas Mashayekh
显示剩余2条评论

100
git revert -m 1 <merge-commit>

55
这个回答缺乏很多细节。也许这正是它好的原因。 - Gustavo Straube
3
那是讽刺话还是你真的认为答案很好?它对我实际上起作用了。 - Sahil Dhawan
4
为什么要使用数字“1”? 根据谷歌搜索: "我们使用合并提交的SHA1哈希指定合并。-m后面跟着数字1表示我们想保留合并的父侧(我们要合并到的分支)。" 出处: https://mijingo.com/blog/reverting-a-git-merge - Pomme De Terre
1
这是答案。简单,实际有效。 - petey m
1
这个答案可以继续参考其他解释何时使用 1 以及何时不使用的答案。 - cellepo
显示剩余2条评论

52
为了让日志保持干净,好像什么都没发生过(虽然这种方法有一些缺点(由于使用 push -f)):
git checkout <branch>
git reset --hard <commit-hash-before-merge>
git push -f origin HEAD:<remote-branch>

'commit-hash-before-merge'来自合并之后的日志(git log)。


3
提示:如果您在公司中这样做,您可能没有获得许可。 - eneski
17
不要在共享版本库上使用 push -f - Baptiste Mille-Mathias
1
在进行此操作之前,请创建一个备份分支,以防万一事情不如您所预期。 - Andrew Nessin
我认为通常通过查看 git log 来确定合并前的提交哈希不是一件简单的事情,因为提交会随着时间交织在一起 - 我想知道这是否总是 MERGECOMMIT^(这似乎表明是 https://www.git-tower.com/learn/git/faq/undo-git-merge/#using-span-classmonospaced-boldgit-resetspan-to-undo-a-merge)? - John Bachir
不要在共享仓库上使用强制推送(push -f),如果你不理解它的作用。如果你理解,当然强制推送是一种必要的工具。我们可以说这是给初学者的一个好指南。 - Romain Valeri

35

所有回答已经覆盖了大部分内容,但我会加上我的五分钱。简而言之,还原合并提交非常简单:

git revert -m 1 <commit-hash>

如果您有权限,可以直接推送到“master”分支;否则,请将其推送到您的“revert”分支并创建拉取请求。

您可能会在此主题上找到更有用的信息:https://itcodehub.blogspot.com/2019/06/how-to-revert-merge-in-git.html


24
有时候,回滚最有效的方法是先后退一步再替换。 git log 使用第二个提交哈希值(完整哈希值,在列出错误之前要还原回去的那个)然后从那里重新分支。 git checkout -b newbranch <HASH> 然后删除旧分支,在其位置上复制新分支并从那里重新开始。
git branch -D oldbranch
git checkout -b oldbranch newbranch
如果已经广播过了,那么就从所有的存储库中删除旧分支,将重新做的分支推送到最核心的位置,再将其拉回所有存储库。

6
关于广播的警告应该更加明确地表达这个主意是多么糟糕。这将会破坏每个人在那个分支上的版本,并且只有当你在使用一个远程仓库(如github/bitbucket)并且只有你自己能够访问时才真正有用。 - RobbyD
不像将修改过的配置文件推送到生产环境那样糟糕。这不会破坏什么,只是重新从之前的提交中分支出来,所以这是一种把分支指针移动到早期版本的迂回方式。希望它只影响本地代码库。 - ppostma1
这是我认为最好的方法,它不会干扰尝试撤消合并提交分支的努力。 - rickvian

16
如果你想撤销一个 merge 提交,请按以下步骤进行操作。
  1. 首先,查看 git log 以找到合并提交的 ID。您还会发现与合并相关的多个父 ID(见下面的图片)。

图片描述

记下显示为黄色的合并提交 ID。父 ID 是下一行中写成 Merge: parent1 parent2 的那些。现在...

简短地说:

  1. 切换到进行合并的分支。然后只需执行 git revert <merge commit id> -m 1 命令,这将打开一个 vi 控制台以输入提交消息。编写、保存、退出,完成!

详细地说:

  1. 切换到进行合并的分支。在我的情况下,它是 test 分支,我正在尝试从中删除 feature/analytics-v3 分支。

  2. git revert 是撤消任何提交的命令。但是,当撤消一个 merge 提交时有一个讨厌的技巧。您需要输入 -m 标志,否则它将失败。从这里开始,您需要决定是否要还原分支,并使其看起来像是在 parent1parent2 上完全一样:

git revert <merge commit id> -m 1 (还原到parent2

git revert <merge commit id> -m 2(还原到parent1

你可以用 git log 查看这些父提交,以确定想要回滚的方向,这是所有混淆的根源。


我不理解父提交。我想要还原两个合并,其中一个是最后一次提交,另一个是倒数第三次提交,在它们之间有别人的另一个合并。 - Mahdiyeh

10

我从这个链接中找到了有关“如何撤消合并”的好解释,下面是我复制粘贴的解释。如果下面的链接无法使用,它可能会有所帮助。

如何撤销错误的合并 Alan(alan@clueserver.org) 说:

我有一个主分支,在此基础上我们建立了一个分支供一些开发人员进行工作。他们声称已经准备好。我们将其合并到主分支中,但它破坏了某些东西,因此我们撤消了合并。然后他们对代码进行了更改。他们将其调整到了一个可以接受的状态,我们再次合并。但检查后发现,在撤消之前所做的代码更改不在主分支中,而之后的更改却在主分支中,请求帮助从这种情况中恢复。

“合并撤消”后的历史记录如下:

---o---o---o---M---x---x---W
              /
      ---A---B

其中 A 和 B 是在较差的侧面开发中,M 是将这些早期更改合并到主干的合并,x 是与侧面分支无关的更改,已经在主干上进行了,而 W 是“撤销合并 M”(W 是否倒置 M?)。 换句话说,“diff W^..W”类似于“diff -R M^..M”。

可以使用以下命令进行这样的合并“撤销”:

$ git revert -m 1 M 在侧面分支的开发人员修复错误后,历史记录可能如下所示:

---o---o---o---M---x---x---W---x
              /
      ---A---B-------------------C---D

其中C和D是为了修复A和B中出现的问题,而在W之后,您可能已经对主线进行了其他更改。

如果您合并更新后的侧分支(其顶部为D),则A或B中所做的任何更改都不会出现在结果中,因为它们被W撤销了。这就是Alan看到的。

Linus解释了情况:

撤销一个常规提交只是有效地撤消该提交所做的事情,并且相当简单。但是,撤消合并提交还会撤消该提交更改的数据,但绝对不会影响合并对历史的影响。因此,合并仍将存在,并被视为连接两个分支的最后共享状态,未修复的合并也不会对此产生影响。因此,“撤消”取消数据更改,但在版本库历史记录上,它绝对不是“撤消”操作。因此,如果您认为“撤消”就是“撤销”,那么您总是会忽略掉撤消的这一部分。是的,它撤销了数据,但不,它没有撤销历史记录。在这种情况下,您希望首先撤消先前的撤消,这将使历史记录如下所示:

---o---o---o---M---x---x---W---x---Y
              /
      ---A---B-------------------C---D

其中,Y是W的还原。可以通过以下方式进行“还原的还原”:

$ git revert W (假设W和W..Y更改之间不存在冲突)这个操作将使历史记录等同于在历史记录中根本没有W或Y:

---o---o---o---M---x---x-------x----
              /
      ---A---B-------------------C---D

合并旁支后,不会出现早期还原和还原还原所引起的冲突。

---o---o---o---M---x---x-------x-------*
              /                       /
      ---A---B-------------------C---D

当然,对C和D所做的更改仍可能与任何x所做的更改冲突,但这只是普通的合并冲突。


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