在 Git 中重新执行被撤销的合并操作

340
我遇到了一个问题:在Git中,我有一个特定于问题的分支28s,将其合并到了通用的develop分支中。结果发现操作太快了,因此使用git-revert撤销了合并。现在,我需要将28s合并回develop,但是git-merge命令看到了原始的合并,并欣然宣布所有内容都已经合并。那么我该怎么办?创建'Revert "Revert "28s -> develop""'提交吗?这似乎不是一个好方法,但我暂时想不到其他方法。
树形结构如下图所示: Git log output

4
这是 GitX(http://gitx.frim.nl)的网站。 - Toms Mikoss
9个回答

225

你需要“撤销撤销”。根据最初的撤销方式,可能并不像听起来那么简单。请查看这个主题的官方文档

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

允许:

---o---o---o---M---x---x-------x-------*
              /                       /
      ---A---B-------------------C---D
但它是否总是有效?当然,你可以还原一个合并,从技术角度来看,git非常自然地完成了这个操作,并没有出现什么问题。它只是将其视为从“合并前状态”到“合并后状态”的更改,就是这样。没有复杂的东西,也没有什么奇怪的东西,也没有真正危险的东西。Git甚至不需要考虑就能做到这一点。
因此,从技术角度来看,还原合并没有任何问题,但从工作流程的角度来看,这通常是应该尽量避免的事情。
例如,如果你发现一个问题被合并到主分支中,与其还原合并,不妨尝试非常努力地将问题追溯到已经合并的分支上并修复它,或者尝试还原导致它的单个提交。是的,这更加复杂,而且并不总是有效的(有时答案是:“糟糕,我真的不应该合并它,因为它还没有准备好,我真的需要撤消所有的合并”。),那么你就应该还原合并,但当你想重新进行合并时,你现在需要通过还原还原来重新进行合并。

19
好的链接(+1)。我冒昧复制了你答案中的一部分内容,这样读者可以立即看到相关的选项。如果您不同意,请随时恢复。 - VonC
6
我们遇到了一个需要这样做的情况,并发现乐趣并没有在这里停止。那是一个长时间运行的分支,已经合并,所以我们需要继续更新它。我的方法在这里:http://tech.patientslikeme.com/2010/09/29/dealing-with-git-merge-revisions/ - jdwyah
3
@jdwyah,那个链接好像失效了,但这篇文章听起来很有趣。这里有一个Archive.org的镜像,但是缺少图片:https://web.archive.org/web/20111229193713/http://tech.patientslikeme.com/2010/09/29/dealing-with-git-merge-revisions - Alex KeySmith
17
博客文章已被恢复,感谢:http://blog.jdwyah.com/2015/07/dealing-with-git-merge-revisions.html - jdwyah
2
@jdwyah的帖子当前链接为https://blog.jdwyah.com/2015/07/07/dealing-with-git-merge-revisions.html - Christian Long
显示剩余5条评论

82

假设您有这样的历史记录

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

其中A、B是失败的提交记录,而W则是M的回退

在我开始修复发现的问题之前,我会将W提交记录cherry-pick到我的分支中

git cherry-pick -x W

然后我在我的分支上还原了 W 的提交

git revert W 

我可以继续修复。

最终的历史记录可能如下:

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

当我提交PR时,它会清楚地显示PR是撤销还原并添加了一些新的提交。


6
这似乎有些有帮助,但细节太少了(最后一个图中的C、D是什么)以至于更令人沮丧而不是有用。 - Isochronous
8
@等时C和D似乎是修复A和B引入的问题的提交。 - Thomas
@Thomas 确切无误 - Maksim Kotlyar
很好,你在这种情况下强调使用PR是有益的,因为它可以在合并回主分支之前提供最终的“健全性检查”。 - Willem van Ketwich
1
你为什么要从原始版本W重新开始你的分支?也就是说,让你的主题从W继续,接着是W`、C和D?这样可以消除一些重复。 - Erik Carstensen

56

如果你想撤销之前的撤销操作,同时又不希望太大程度上影响工作流程:

  • 创建本地开发分支(develop)的垃圾箱备份
  • 在开发分支(develop)的本地备份上撤销之前的撤销操作
  • 将备份分支合并到你的特性分支中,并将你的特性分支推送到git服务器。

当你准备好时,你的特性分支应该可以像平常一样被合并。唯一的缺点是你的历史记录会有一些额外的合并/撤销提交记录。


1
为了防止任何进一步的混淆,我还创建了一个“垃圾”副本我的特性分支,并将撤销的develop合并到其中。 - jhhwilliams
4
谢谢!这是唯一一个真正解释如何做而不是说你不应该这样做的答案。非常有用。 - Emmy
谢谢,我的情况非常简单,所以这完全能为我完成任务。如果您遇到更复杂的情况,其他答案也很棒。 - subelsky

28

要在GIT中还原一个还原:

git revert <commit-hash-of-previous-revert>

1
在我的工作分支中使用它来撤销撤销,然后新的PR进行开发。现在git看到了之前被撤销的所有更改。谢谢。 - Danstan
救命的回退方法!现在要撤销原始回退的回退。 - jtlz2

6

如果你不想使用git-revert命令,那么在devel分支中可以使用以下命令来丢弃(撤销)错误的合并提交(而不是简单地撤销它)。

git checkout devel
git reset --hard COMMIT_BEFORE_WRONG_MERGE

这也会相应地调整工作目录的内容。请注意
  • 在develop分支中保存您的更改(因为错误合并),因为它们也将被git-reset擦除。在指定为git reset参数的提交之后的所有提交都将消失!
  • 此外,如果您的更改已经从其他存储库中提取,则不要执行此操作,因为重置将重写历史记录。

我建议在尝试此操作之前仔细研究git-reset手册页面。

现在,在重置后,您可以在develop中重新应用您的更改,然后执行

git checkout devel
git merge 28s

这将是一个真正的合并,从28s合并到devel,就像最初的那个一样(现在已经从git的历史记录中删除)。


13
对于不太熟悉Git但想要遵循这些说明的人,请注意:在结合“reset --hard”和“push origin”的过程中需要小心。同时,请注意,向origin进行强制推送可能会对GitHub上的开放PR造成严重问题。 - funroll
非常有帮助,可以解决私有Git服务器上的一些合并问题。谢谢! - mix3d
1
这种技术很实用,但也有可能会带来不可逆的影响。在使用时需要审慎考虑,但若使用得当,能够为你省去很多麻烦(以及一些糟糕的历史记录)。 - siliconrockstar

2
  1. 在原始合并提交之前创建新的分支 - 称其为 'develop-base'
  2. 在 'develop-base' 顶部执行交互式变基 'develop'(即使它已经在顶部)。在交互式变基期间,您将有机会从git历史中删除合并提交和反向合并的提交,即删除这两个事件。

此时,您将拥有一个干净的 'develop' 分支,可以像往常一样合并您的功能分支。


注:'Original Answer' 翻译成 "最初的回答"。

2
如果开发分支与他人共享,这种方法会产生问题。 - PSR
这个问题可以通过翻转操作并强制在相反的方向上进行rebase来改善。取出被还原的分支中的更改,将它们重放到还原之上,生成一个新的分支,该分支可以在合并流下游合并,这样你就会得到一个稍微干净一些的历史记录,而不是那种难以跟踪的还原还原。 - AJ Henderson

2
我建议您按照以下步骤撤销一个撤销,比如SHA1。
git checkout develop #go to develop branch
git pull             #get the latest from remote/develop branch
git branch users/yourname/revertOfSHA1 #having HEAD referring to develop
git checkout users/yourname/revertOfSHA1 #checkout the newly created branch
git log --oneline --graph --decorate #find the SHA of the revert in the history, say SHA1
git revert SHA1
git push --set-upstream origin users/yourname/revertOfSHA1 #push the changes to remote

现在为分支users/yourname/revertOfSHA1创建PR。

1
我在遇到相同问题时发现了这篇文章。我觉得通过硬重置等方式太过危险。我可能会误删某些东西,而且无法恢复。
相反,我查看了我想要回退到的提交,例如git checkout 123466t7632723。然后将其转换为分支git checkout my-new-branch。然后删除我不需要的分支。当然,这只有在您能够放弃您搞砸的分支时才有效。

1
git reflog 可以在硬重置时保护你数月时间,以防你后来发现需要丢失的提交。reflog 仅限于您的本地 repo。 - Todd

0
如有需要,您可以创建一个新分支并从被撤销的拉取请求中 挑选 提交(将提交 SH 与空格隔开,这样它就会成为一个命令),这样您就可以再次合并而不会丢失文件/更改。

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