在Mercurial上回退合并


77

您如何扭转合并对极化分支的影响而又不至于痛苦呢?

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

您有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,修订版9)是问题的开始。

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

修订版B12是无用的,错误地反复删除了原本不打算删除的更改。

这种困境造成了很多挫败感和蓝烟,我想结束这一困境。

注意

尝试通过钩子或策略来禁止反向合并可能是显而易见的答案,我发现将这种情况搞糟的能力相当高,而且发生这种可能性的可能性很大,以至于即使采取对策,您仍然必须假设不可避免地发生这种情况,以便您可以在解决时解决它。

详细说明

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

同样,为了增加伤害,在分支A上进行了实质性更改,留下了常设问题:“使分支A的更改与分支B的更改发生冲突,分支B的更改看起来像是在变回(并退出),看起来像是在更改”在分支A上,“

关于历史重写技巧:

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

  1. 我们有9000次提交。
  2. 新鲜克隆因此需要半小时
  3. 如果某个地方甚至存在一个错误的存储库克隆,它的一种似然又会回到与原始存储库的联系,然后重新将其重新敲打。
  4. 每个人都已经克隆了该存储库,现在已经进行了几天提交了。
  5. 这样的一个克隆恰好是一个在线站点,因此“擦除该克隆并从头开始” =“ big nono”

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

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


1
哎呀,这看起来很丑...您是否也将其发布到selenic.com/mercurial/bts?这听起来像是应该解决的汞错误。
Mike G.

4
对所有人表示歉意,图片托管失败了:(我找不到原始资料
Kent Fredric

1
经过两年的发展,我想想关于这个的任何新闻都不会引起人们的兴趣,现在我想可能会有更好的方法来解决这个问题?:)
皮特·邓肯森

可以理解,“ A”分支是QA / Prod,而“ B”分支是Main / Dev?
雪橇2012年

Answers:


50

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

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

因此,具体地说,如果在BRANCH_V8上签入了某些内容,则必须将其合并到BRANCH_V9。

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

因此,情况如下图所示。

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

我们可以按以下方式解决此错误:

  1. 将您的本地目录更新为BRANCH_V8的最后一个良好状态: hg update 11
  2. 创建一个最后状态良好的新子级:
    1. 更改一些文件$EDITOR some/file.txt(这是必要的,因为Mercurial不允许空提交)
    2. 提交这些更改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的错误合并
      | / |
      o | BRANCH_V8-11-在BRANCH_V8上添加评论
      | |
      | o BRANCH_V9-10-BRANCH_V9的最后一次提交
      
  3. 将新生成的头部与发生错误合并的修订版本合并,并在提交之前丢弃所有更改。不要简单地合并两个头,因为那样您将失去在合并之后发生的重要提交!

    1. 合并:(hg merge 12忽略任何冲突)
    2. 放弃所有更改: hg revert -a --no-backup -r 14
    3. 提交更改: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-错误合并后的重要提交
      | |
      o | BRANCH_V8-12-来自BRANCH_V9的错误合并
      | \ |
      | o BRANCH_V8-11-在BRANCH_V8上添加评论
      | |
      o | BRANCH_V9-10-BRANCH_V9的最后一次提交
      | |
      

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

  4. 合并BRANCH_V8上的两个头:
    1. 合并: hg merge
    2. 提交: 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的错误合并
| | |
o | | BRANCH_V8-13-错误合并后的重要提交
| / /
o | BRANCH_V8-12-来自BRANCH_V9的错误合并
| \ |
| o BRANCH_V8-11-在BRANCH_V8上添加评论
| |
o | BRANCH_V9-10-BRANCH_V9的最后一次提交
| |

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

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

详细:

  1. 将工作目录切换到BRANCH_V9: hg update BRANCH_V9
  2. 在BRANCH_V8的最后一个良好状态下合并(即,您为修复错误合并而生成的提交)。此合并是类似于任何常规合并的合并。冲突应该像往常一样得到解决,没有任何东西要扔掉。
    1. 合并: hg merge 14
    2. commit: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丢弃错误的合并
      | | | |
      | o | | BRANCH_V8-14-在BRANCH_V8上生成提交以纠正来自BRANCH_V9的错误合并
      | | | |
      | | o | BRANCH_V8-13-错误合并后的重要提交
      | | | /
      + --- o BRANCH_V8-12-来自BRANCH_V9的错误合并
      | | /
      | o BRANCH_V8-11-在BRANCH_V8上添加评论
      | |
      o | BRANCH_V9-10-BRANCH_V9的最后一次提交
      | |
      
  3. 合并到BRANCH_V8及其修复程序的错误合并中,并丢弃所有更改:
    1. 合并: hg merge 15
    2. 还原所有更改: hg revert -a --no-backup -r 17
    3. 提交合并: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的错误合并
      | | | |
      | | o | BRANCH_V8-14-在BRANCH_V8上生成提交以纠正来自BRANCH_V9的错误合并
      | | | |
      + ----- o BRANCH_V8-13-错误合并后的重要提交
      | | |
      o --- + BRANCH_V8-12-来自BRANCH_V9的错误合并
      | / /
      | o BRANCH_V8-11-在BRANCH_V8上添加评论
      | |
      o | BRANCH_V9-10-BRANCH_V9的最后一次提交
      | |
      
  4. 从BR​​ANCH_V8合并剩余的更改:
    1. 合并: hg merge BRANCH_V8
    2. 提交: 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的最后一个良好状态下合并
| | | \
o | | | BRANCH_V8-16-合并了两个用于从不良合并中恢复的磁头
| \ | | |
| o-+ BRANCH_V8-15-从BRANCH_V9丢弃错误的合并
| | | |
| | | o BRANCH_V8-14-在BRANCH_V8上生成提交以纠正来自BRANCH_V9的错误合并
| | | |
o | | | BRANCH_V8-13-错误合并后的重要提交
| / / /
o --- + BRANCH_V8-12-来自BRANCH_V9的错误合并
| / /
| o BRANCH_V8-11-在BRANCH_V8上添加评论
| |
o | BRANCH_V9-10-BRANCH_V9的最后一次提交
| |

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


基本上,此方法的好处是您生成一个提交(在本例中为rev 15),该提交从BRANCH_V8的最后一个好点开始时仅包含错误的合并及其修复。然后,您可以将此变更集单独合并到BRANCH_V9中,并且可以安全地忽略该合并所带来的所有变更。
oenli 2010年

1
哈啊,有一个</pre>第二个到最后的代码块后失踪。花了一段时间找到,但是就在我准备放弃的时候...现在一切都显示正确了,我认为:)
sth

请注意,对于Git,可以使用相同的技术,因此您可以在不重写历史记录的情况下修复向后合并,这会引起麻烦。
oenli 2010年

4
这很棒。谢谢。在执行任何此操作之前,@ Kent应该安装一个提交钩子,以防止向后合并被提交和/或推入。解决问题的根本原因,然后处理剩余的症状。
哈维

1
我已经创建了一个汞库来演示此解决方案(无需额外的空提交)bitbucket.org/janverley/wrongmerge
2013年1

2

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

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

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


是的,做您所做的事情将是一个详尽的过程,并且不一定适合我的情况(命名分支的工作方式很奇怪),而且当您按自己的时间表安排了4个以上的提交者时,历史就是猪圈是完全没有线性的;)
肯特·弗雷德里克

2

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

hg graft r1 r2 r3

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

将对A的最后一个已知的良好版本产生新的更改。将这些新更改拉入新存储库。只是要仔细检查您是否没有错过任何内容,请对旧存储库进行hg传入。如果您看到除合并合并和r1-3以外的任何内容,请拉出它。

丢弃旧的存储库。你完成了。合并根本不在新的存储库中,您无需重写历史记录。


1

因此,您只想将一些变更集从B合并到A?像您一直在做的那样,撤消变更集已经是一个非常糟糕的主意了。

您应该使用移植扩展名,或者在第三个分支中进行通用更改以合并到A和B中。


2
因为我/ want /到这里,所以不撤消更改;),问题是无论我创建了多少分支,仍然有可能以错误的方式合并它们,从而导致此问题。(请相信我,沿着那条路走)
肯特·弗雷德里克

1

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

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

这是解决此问题的当前建议方法的图表:

[原始图像丢失]

现在,它不再是要解决的问题了,但是我仍然不得不比较差异(即:b46:b11与b46:b8,a43:a10与a43:a9)并手动编辑一些更改。

在我确定在任何存储库上都可以使用的方式之前,请不要关闭此问题/获得答案。

重要

任何尝试这些东西的人都应该克隆自己的存储库,并像沙盒一样先使用它。正如您应该对任何合并过程所做的那样,因为那样会发生错误,您可以将其丢弃并重新开始。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.