在 Mercurial 上退出向后合并

Posted

技术标签:

【中文标题】在 Mercurial 上退出向后合并【英文标题】:Backing Out a backwards merge on Mercurial 【发布时间】:2010-09-20 22:12:02 【问题描述】:

如何在不痛苦的情况下逆转合并对极化分支的影响?

这个问题困扰了我几个月,我终于放弃了。

您有 1 个存储库,其中包含 2 个 命名 分支。 A 和 B。

发生在 A 上的变化将不可避免地发生在 B 上。

直接发生在 B 上的更改绝不能发生在 A 上。

在这样的配置中,将“B”合并到“A”中会在存储库中产生一个可怕的问题,因为对 B 的所有更改都出现在 A 中,就好像它们是在 A 中进行的一样。

从这种情况中恢复的唯一“正常”方法似乎是“退出”合并,即:

 hg up -r A 
 hg backout -r BadMergeRev --parent BadMergerevBeforeOnA 

这看起来一切都很好而且很花哨,直到你决定以后在正确的方向上合并,你最终会发生各种令人讨厌的事情,并且在特定的分支 B 上被删除/注释掉的代码突然变得未删除或未注释。

到目前为止,除了“让它做它的事情,然后手动解决所有问题”之外,还没有一个可行的可行解决方案,老实说,这有点令人毛骨悚然。

这是一个澄清问题的图像:

[原图丢失]

文件 C & E (或更改 C & E ) 必须只出现在分支 b 上,而不能出现在分支 a 上。 此处的修订版 A9(分支 a,revno 9)是问题的开始。

修订版 A10 和 A11 是“回退合并”和“合并回退”阶段。

修订版 B12 是反复无常的,错误地反复删除了一个原本不应该被删除的更改。

这个困境引起了很多挫折和蓝烟,我想结束它。

注意

这可能是尝试禁止反向合并发生的明显答案,无论是使用钩子还是使用策略,我发现解决这个问题的能力相当高,而且它发生的可能性很大,即使有对策,您必须仍然假设它不可避免地会发生,以便在它发生时您可以解决它。

细化

在模型中我使用了单独的文件。这些使问题听起来很简单。这些仅代表任意更改,可能是单独的一行。

此外,雪上加霜的是,分支 A 发生了重大变化,这留下了长期存在的问题“分支 A 中的更改是否与分支 B 中刚刚出现(并退出)的变化相冲突,看起来就像在分支 A 上进行更改一样”

历史改写技巧:

所有这些追溯解决方案的问题如下:

    我们有 9000 个提交。 因此新鲜克隆需要半小时 如果存在甚至一个存储库的错误克隆某处,则它有可能重新与原始存储库联系,并将其全部破坏再次。 每个人都已经克隆了这个存储库,现在已经过去了几天,不断提交。 一个这样的克隆,恰好是一个实时站点,所以“擦除那个并从头开始”=“大诺诺”

(我承认,上面的许多内容有点愚蠢,但它们超出了我的控制范围)。

唯一可行的解​​决方案是假设人们可以并且做错所有事情,并且有办法“撤消”这种错误。

【问题讨论】:

哎呀,看起来很难看...你也把这个发到selenic.com/mercurial/bts 吗?这听起来像是一个应该解决的 Hg 错误。 向所有人道歉,图片托管失败了:(我找不到原件 任何关于这个的消息只是出于兴趣,经过 2 年的开发,我想现在可能有更好的方法来处理这个问题? :) 可以理解为'A'分支是QA/Prod,'B'是Main/Dev吗? 【参考方案1】:

好的,首先在与损坏的存储库 (hg init) 不同的目录中创建一个新的 empty 存储库。现在,将最后一个已知的良好版本拉到新存储库中;确保您不要拉出错误的合并并拉出它之前的所有内容。在旧存储库中,更新到 A 的最后一个已知良好版本,然后执行以下操作:

hg graft r1 r2 r3

其中 r1-3 是在拙劣的合并之后所做的更改。此时您可能会遇到冲突;修复它们。

这应该会产生新的变化相对于最后一个已知的良好版本的 A。将这些新更改拉入新存储库。只是为了仔细检查你没有错过任何东西,对旧存储库进行 hg 传入。如果您看到除了拙劣的合并和 r1-3 以外的任何内容,请拉动它。

扔掉旧的存储库。你完成了。合并根本不在新存储库中,您不必重写历史记录。

【讨论】:

【参考方案2】:

在与 freenode 上 #mercurial 上的一些乐于助人的人进行了多次讨论后,mpm 提供了一个似乎适用于我的测试用例的部分解决方案(我生成了一个假存储库,试图复制该场景)

但是,在我的实际存储库中,由于我不太了解的原因,它仍然不够完美。

下面是目前提出的解决此问题的方法的示意图:

[原图丢失]

现在更少要解决的问题,但我仍然需要比较差异(即:b46:b11 与 b46:b8,a43:a10 与 a43:a9)并手动编辑一些变化回来了。

在我获得适用于任何存储库的有保证的方式之前,不要关闭此问题/回答。

重要

任何尝试这些东西的人都应该首先克隆他们的存储库并像沙盒一样使用它。正如您应该对 any 合并过程所做的那样,因为这样如果出现问题,您可以将其丢弃并重新开始。

【讨论】:

【参考方案3】:

我想我找到了一个永久修复错误合并的解决方案,并且不需要您手动检查任何差异。诀窍是回到历史并生成与错误合并并行的提交。

所以我们有一个存储库,每个维护版本的单个产品都有单独的分支。就像问题中提出的情况一样,对早期版本的分支所做的所有更改(即该版本中的错误修复)最终都必须全部合并到更高版本的分支中。

特别是,如果在 BRANCH_V8 上签入某些内容,则必须将其合并到 BRANCH_V9。

现在其中一位开发人员犯了以下错误:他将 BRANCH_V9 中的所有更改合并到 BRANCH_V8(即错误方向的合并)。此外,在那次糟糕的合并之后,他在发现自己的错误之前执行了一些额外的提交。

所以情况如下图日志所示。

o BRANCH_V8 - 13 - 错误合并后的重要提交
|
o BRANCH_V8 - 12 - 从 BRANCH_V9 的错误合并
|\
| o BRANCH_V8 - 11 - 添加对 BRANCH_V8 的注释(即最后已知的良好状态)
| |
○ | BRANCH_V9 - 10 - 在 BRANCH_V9 上的最后一次提交
| |

我们可以如下修复这个错误:

    将本地目录更新到 BRANCH_V8 的最后良好状态:hg update 11 创建最后一个良好状态的新子代:
      更改一些文件$EDITOR some/file.txt(这是必要的,因为 Mercurial 不允许空提交) 提交这些更改hg commit -m "generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9" 现在的情况如下: o BRANCH_V8 - 14 - 在 BRANCH_V8 上生成提交以纠正来自 BRANCH_V9 的错误合并 | | o BRANCH_V8 - 13 - 错误合并后的重要提交 | | | o BRANCH_V8 - 12 - 从 BRANCH_V9 的错误合并 |/| ○ | BRANCH_V8 - 11 - 添加对 BRANCH_V8 的评论 | | | o BRANCH_V9 - 10 - BRANCH_V9 上的最后一次提交

    将新生成的头部与发生错误合并的修订版合并,并在提交前丢弃所有更改。 不要简单地合并两个头,因为这样你会丢失合并后发生的重要提交!

      合并:hg merge 12(忽略任何冲突) 丢弃所有更改:hg revert -a --no-backup -r 14 提交更改:hg commit -m "throwing away wrong merge from BRANCH_V9" 情况现在看起来像: o BRANCH_V8 - 15 - 从 BRANCH_V9 丢弃错误的合并 |\ | o BRANCH_V8 - 14 - 在 BRANCH_V8 上生成提交以纠正来自 BRANCH_V9 的错误合并 | | +---o BRANCH_V8 - 13 - 错误合并后的重要提交 | | ○ | BRANCH_V8 - 12 - 从 BRANCH_V9 错误合并 |\| | o BRANCH_V8 - 11 - 在 BRANCH_V8 上添加评论 | | ○ | BRANCH_V9 - 10 - 在 BRANCH_V9 上的最后一次提交 | |

    即。 BRANCH_V8 上有两个头:一个包含对错误合并的修复,另一个包含在合并后发生的 BRANCH_V8 上剩余的重要提交。

    合并 BRANCH_V8 上的两个头:
      合并:hg merge 提交:hg commit -m "merged two heads used to revert from bad merge"

BRANCH_V8 上最后的情况现在已经修正,看起来是这样的:

o BRANCH_V8 - 16 - 合并两个用于从错误合并恢复的头 |\ | o BRANCH_V8 - 15 - 从 BRANCH_V9 丢弃错误的合并 | |\ | | o BRANCH_V8 - 14 - 在 BRANCH_V8 上生成提交以纠正来自 BRANCH_V9 的错误合并 | | | ○ | | BRANCH_V8 - 13 - 错误合并后的重要提交 |/ / ○ | BRANCH_V8 - 12 - 从 BRANCH_V9 错误合并 |\| | o BRANCH_V8 - 11 - 在 BRANCH_V8 上添加评论 | | ○ | BRANCH_V9 - 10 - 在 BRANCH_V9 上的最后一次提交 | |

现在 BRANCH_V8 上的情况是正确的。剩下的唯一问题是从 BRANCH_V8 到 BRANCH_V9 的下一次合并将是不正确的,因为它也会在错误合并的“修复”中合并,这是我们在 BRANCH_V9 上不希望的。这里的技巧是在单独的更改中从 BRANCH_V8 合并到 BRANCH_V9:

第一次合并,从 BRANCH_V8 到 BRANCH_V9,在错误合并之前 BRANCH_V8 上的正确更改。 合并错误中的第二次合并及其修复,无需检查任何内容,丢弃所有更改 第三次合并 BRANCH_V8 的剩余更改。

详细说明:

    将您的工作目录切换到 BRANCH_V9:hg update BRANCH_V9 在 BRANCH_V8 的最后一个良好状态下合并(即您为修复错误合并而生成的提交)。这种合并是像任何常规合并一样的合并,即。冲突应该像往常一样解决,不需要丢弃任何东西。
      合并:hg merge 14 提交:hg commit -m "Merging in last good state of BRANCH_V8" 现在的情况是: @ BRANCH_V9 - 17 - 合并到 BRANCH_V8 的最后良好状态 |\ | | o BRANCH_V8 - 16 - 合并两个用于从错误合并恢复的头 | | |\ | +---o BRANCH_V8 - 15 - 从 BRANCH_V9 丢弃错误的合并 | | | | | ○ | | BRANCH_V8 - 14 - 在 BRANCH_V8 上生成提交以纠正来自 BRANCH_V9 的错误合并 | | | | | | ○ | BRANCH_V8 - 13 - 错误合并后的重要提交 | | |/ +---o BRANCH_V8 - 12 - 从 BRANCH_V9 错误合并 | |/ | o BRANCH_V8 - 11 - 在 BRANCH_V8 上添加评论 | | ○ | BRANCH_V9 - 10 - 在 BRANCH_V9 上的最后一次提交 | |
    合并 BRANCH_V8 上的错误合并 + 其修复,并丢弃所有更改:
      合并:hg merge 15 还原所有更改:hg revert -a --no-backup -r 17 提交合并:hg commit -m "Merging in bad merge from BRANCH_V8 and its fix and throwing it all away" 现在的情况 : @ BRANCH_V9 - 18 - 从 BRANCH_V8 合并坏合并及其修复并将其全部丢弃 |\ | o BRANCH_V9 - 17 - 合并到 BRANCH_V8 的最后良好状态 | |\ +-----o BRANCH_V8 - 16 - 合并两个用于从错误合并恢复的头 | | | | o---+ | BRANCH_V8 - 15 - 从 BRANCH_V9 丢弃错误的合并 | | | | | | ○ | BRANCH_V8 - 14 - 在 BRANCH_V8 上生成提交以纠正来自 BRANCH_V9 的错误合并 | | | | +-----o BRANCH_V8 - 13 - 错误合并后的重要提交 | | | o---+ BRANCH_V8 - 12 - 从 BRANCH_V9 错误合并 |/ / | o BRANCH_V8 - 11 - 在 BRANCH_V8 上添加评论 | | ○ | BRANCH_V9 - 10 - 在 BRANCH_V9 上的最后一次提交 | |
    合并来自 BRANCH_V8 的剩余更改:
      合并:hg merge BRANCH_V8 提交:hg commit -m "merging changes from BRANCH_V8"

最后的情况是这样的:

@ BRANCH_V9 - 19 - 合并来自 BRANCH_V8 的更改 |\ | o BRANCH_V9 - 18 - 合并 BRANCH_V8 的错误合并及其修复并将其全部丢弃 | |\ | | o BRANCH_V9 - 17 - 合并到 BRANCH_V8 的最后良好状态 | | |\ ○ | | | BRANCH_V8 - 16 - 合并两个用于从错误合并中恢复的头 |\| | | | o---+ BRANCH_V8 - 15 - 从 BRANCH_V9 丢弃错误的合并 | | | | | | | o BRANCH_V8 - 14 - 在 BRANCH_V8 上生成提交以纠正来自 BRANCH_V9 的错误合并 | | | | ○ | | | BRANCH_V8 - 13 - 错误合并后的重要提交 |/ / / o---+ BRANCH_V8 - 12 - 从 BRANCH_V9 错误合并 |/ / | o BRANCH_V8 - 11 - 在 BRANCH_V8 上添加评论 | | ○ | BRANCH_V9 - 10 - 在 BRANCH_V9 上的最后一次提交 | |

在完成所有这些步骤后,您无需手动检查任何差异,BRANCH_V8 和 BRANCH_V9 是正确的,未来从 BRANCH_V8 到 BRANCH_V9 的合并也将是正确的。

【讨论】:

基本上这种方法的好处是您生成一个提交(在本例中为 rev 15),当从 BRANCH_V8 上的最后一个好点开始时,它只包含错误的合并及其修复。然后,您可以将此变更集单独合并到 BRANCH_V9 中,并且可以安全地忽略该合并带来的所有变更。 Ahhhh 在倒数第二个代码块之后缺少</pre>。花了一段时间才找到,但就在我准备放弃的时候......现在一切都正确显示了,我想:) 请注意,对于 Git,可以使用相同的技术,这样您就可以在不重写历史记录的情况下修复向后合并,这会导致头疼。 这很棒。谢谢。在执行上述任何操作之前,@Kent 应该安装一个提交钩子,以防止向后合并被提交和/或推送。解决问题的根本原因,然后处理剩余的症状。 我创建了一个 Hg 存储库来演示这个解决方案(没有额外的空提交)bitbucket.org/janverley/wrongmerge【参考方案4】:

所以您只想将 B 中的一些变更集合并到 A 中?像您一直在做的那样退出变更集是一个非常糟糕的主意,因为您已经遭受了痛苦。

您应该使用移植扩展或拥有第三个分支,您可以在其中进行常见更改以合并到 A 和 B。

【讨论】:

不退出更改,因为我 /want/ 到这里 ;),问题是无论我创建多少分支,它仍然可能以错误的方式合并它们,导致这个问题。 (相信我,一直走这条路)【参考方案5】:

在紧要关头,您可以将存储库导出到一堆差异,编辑历史记录,然后将您想要的内容重新粘合到一个新的存储库中,因此没有损坏的风险。对你的例子来说可能还不错,但我不知道真实的历史是什么样的。

我在执行一个更简单的操作时引用了这个页面:

http://strongdynamic.blogspot.com/2007/08/expunging-problem-file-from-mercurial.html

【讨论】:

是的,做你所做的事情将是一个详尽的过程,而且它不一定适合我的情况(命名分支的工作方式很奇怪),而且历史是猪圈,当你有 > 4 个提交者都按照自己的时间表进行,根本没有线性关系;)

以上是关于在 Mercurial 上退出向后合并的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Mercurial 中重复合并分支

如何中止mercurial中的合并?

章鱼合并是否可能在mercurial中

Mercurial:在一个仓库中的分支之间合并一个文件

mercurial 的最佳实践:分支与克隆,以及部分合并?

Mercurial 无法通过命令行合并