撤销 Git 后合并的方法

3

我有两个分支(我们称它们为A和B)。

A是我的常规开发分支,B是一个长期运行的特性分支。
为了保持B最新,A经常被合并到B中,但是B将来会被合并到A中,但我们还没有到那一步。

大约一周前,一个开发人员意外地将B合并到了A中,然后立即通过运行git revert撤销了合并。

从那时起,A已经进行了许多更改,包括其他特性分支的合并。

今天,我尝试像往常一样将A合并到B中,但现在出现了问题。

合并正在从B中删除许多在B中添加的文件(可能是因为在运行git revert时从A中删除了这些文件),并添加了一些从B中删除的文件(可能是因为git revert添加了这些文件)。

它还撤销了应该在B中的许多更改。

我的问题是是否有任何方法可以干净地解决这个问题?
我可以重写历史记录,因为我们有一个小团队,我们可以协调这个。

我尝试将A重置回坏合并之前的状态,并挑选自那时以来对它进行的更改,但由于已经进行了许多特性分支合并,这很困难(特性分支包含坏合并提交和撤销)。

3个回答

2
假设您想容忍丑陋的历史 ID 来恢复还原操作,则最简单的解决方案是重新还原还原操作。
git revert SHA1-of-revert

如果您想清理它,稍后的合并应该是完全没有问题的,前提是您已经合并了所有基于错误还原的功能分支(包括未来的分支)。

git rebase --interactive SHA1-of-revert~ 

然后删除还原的行。这将给您一个完全线性的历史记录。如果您想保留功能分支的合并,请添加--preserve-merges选项。

git rebase --interactive --preserve-merges SHA1-of-revert~

如果我撤销了还原,那么A中将再次出现B的内容,而这并不是我现在想要的... - Kyle
好的,我想我可能真的解决了这个问题。所以我在一个新的分支上执行了git revert BAD_COMMIT_SHA,这个分支是基于A的(我们称之为C)。然后我将C合并到B中,并删除了C。我觉得现在一切都正常了。下次当我尝试再次将A合并到B时,我会看看会发生什么。 - Kyle

1
所以问题在于,即使您还原了合并提交,Git 也无法以有意义的方式跟踪此操作(用于将来的合并)。因此,现在 Git 认为 A 中包含了 B 的大部分内容,并且无法进行有意义的合并。
有几种方法可以解决此问题。虽然可能有一些聪明的方法不需要重写历史记录,但我想到的两种方法都需要重写历史记录。显然,所有重写历史记录的注意事项都适用于此处。
方法一:删除 A 上的合并
第一种方法涉及从 A 的历史记录中删除合并。虽然此解决方案会产生更干净的历史记录,但它可能比第一种方法影响更大,特别是如果您在合并后分支出了 A。
(摘自 https://dev59.com/03M_5IYBdhLWcg3wiDqA)
这个问题最简单的解决方法是交互式变基。运行git rebase -i <sha>(其中<sha>是有问题的合并冲突的SHA),将允许您选择性地删除有问题的提交。只要在此过程中也删除还原提交,这应该相对容易。

方法2:用新分支替换B

另一个选项是创建B的新版本,以便git将它们分别跟踪。假设您的树如下所示:
E -- F -- G -- H (branch A)
  \
   \
     I -- J -- K (branch B)

我相信以下代码可以解决这个问题:
git rebase -f <sha of E> B

这将创建一个B的副本,该副本从未合并到A中。
注意:对于上述任何一种方法,如果您在坏的合并之后的任何时候分支(根据方法1或2选择的A或B)会出现一些奇怪的情况。具体来说,该分支将被创建为旧版本的分支,而不是更新后的版本。
要解决此问题,对于处于此状态的每个分支,您(或另一位开发人员)都需要重新基于初始提交的新版本。如果这是一个问题,我可以在这里提供更详细的描述。

感谢提供的信息。不幸的是,方法1不适用于我,在合并后有一些分支从A出来。我认为方法2还不错,正如@Jonathan.Brink所建议的那样。当我尝试这个方法时,遇到了一些很难解决的rebase冲突。最终,恢复撤销似乎是最好的解决办法。 - Kyle

1
我会尝试将分支B变基到分支A上。
在这一步之前,为了防止后期发现错误,最好先保存当前进度。首先创建一个临时分支:
git branch savePoint

在进行变基之前,我强烈建议您阅读一两篇关于变基的教程,例如this one

现在运行此命令:

git rebase A

这将在分支A的顶部重新播放B分支上的每个提交(从不在A上的最旧提交开始)。
如果检测到代码冲突,请按Git提供的说明操作,这类似于解决合并冲突。
当重新基础完成并且您已经测试和验证了工作情况后,您需要像这样强制推送您的更改,因此在推送命令中使用-f选项。
最终结果就像是有人在派生A之后立即编写了B上的所有代码。使用类似于gitk的工具来可视化树形结构,以确保您理解这一点。
如果您觉得重新基础出了问题,请简单地重置回您的保存点:
git reset --hard savePoint

非常重要的提示:这个变基操作会清理你的历史记录,但是所有从分支 B 拉取代码的人现在都将“不同步”。

这意味着他们的 B 分支头部将无法被新的远程 B 分支头部到达。

他们可以通过以下方式与新重写的 B 分支“同步”:

git checkout B
git fetch
git rebase origin/B

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