【问题标题】:How to fix multiple faulty merges?如何修复多个错误的合并?
【发布时间】:2014-03-28 21:35:51
【问题描述】:

我们的 Git 存储库中有一个相当复杂的情况。我们有两个团队在开发同一个软件。一个团队正在对 3.0 版本进行维护,而另一个团队正在做一个长期运行的项目,这将导致 3.1 版本很快发布。

3.1 团队从他们自己的分支开始,他们决定合并来自 3.0 分支的所有更改,每次从 3.0 分支发布新版本时,通常是每隔一周。因此,当 3.0 团队发布他们的第一个版本时,提交 R1 并将其合并到 3.1 中,从而产生 M1。

  3.1---o---o---M1
 /             /
3.0---o---o---R1

两周后,3.0 团队在 R2 发布了他们的第二个版本,3.1 团队将其合并,产生了 M2。

  3.1---o---o---M1---o---o---M2
 /             /            /
3.0---o---o---R1---o---o---R2

这持续了一段时间,但最终 3.0 团队进行了重构,并在提交 R3 中发布了它。在这次重构中,3.0 团队将一个大的 Constants.java 文件拆分为两个较小的文件:Constants.javaMoreConstants.java。 (这些不是实际的文件名。)Constants.java 文件先前已被两个团队更改并成功合并。但是,当 3.1 团队尝试将 R3 合并到他们的分支时,这种重构引起了冲突。 3.1 团队决定他们不想解决这个冲突,他们认为他们可以通过忽略重构来“推迟”解决,坚持使用他们自己的版本。这意味着他们不仅没有将MoreConstants.java 添加到他们的存储库中,而且还意味着他们没有合并现在正在导入MoreConstants.java 而不是Constants.java 的文件中的更改。他们所做的是:

$ git merge 3.0
$ git status

然后,对于列出的每个冲突,他们决定是否要解决它。如果他们不想解决冲突,他们就这样做了

$ git checkout --ours <file they wanted to ignore>
$ git add <file they wanted to ignore>

这导致在 3.1 分支上提交 C3:

  3.1---o---o---M1---o---o---M2---o---o---C3
 /             /            /            /
3.0---o---o---R1---o---o---R2---o---o---R3

从那时起,他们每次都继续使用“--ours”-trick,因为他们每次都希望忽略冲突。同样重要的是要注意 3.1 团队自己不断更改 Constants.java 文件。所以随着时间的推移,情况变成了这样:

  3.1---o---o---M1---o---o---M2---o---o---C3---o---o---C4---o---o---C5
 /             /            /            /            /            /
3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5

现在两个分支都有不同版本的Constants.java,有一个共享的祖先,这是在 R2 之前某处的提交。此时,我们可以通过运行查看那个祖先是什么

$ git merge-base --all C5 R5

现在是复杂的部分。 3.1 团队几乎完成了产品 3.1 版的创建。 3.0-团队将接管。 3.1 发布后,3.0 团队将立即发布 3.2。因此,从 3.0 分支创建了一个新的 3.2 分支:

  3.1---o---o---M1---o---o---M2---o---o---C3---o---o---C4---o---o---C5
 /             /            /            /            /            /
3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5
                                                                   \
                                                               3.2  A5

当然,版本 3.2 需要合并 3.0 和 3.1 的所有更改。所以我们做了一个合并:

$ git checkout 3.2
$ git merge 3.1

导致:

  3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5
 /             /            /            /            /            /  \
3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5   \
                                                                   \    \
                                                               3.2  A5---A6

现在令人讨厌的惊喜是:3.0 团队在 A6 提交中遗漏了许多修复。事实证明,丢失的所有内容都可能与Constants.java-refactoring 有关。当然,3.2 中缺少新的 MoreConstants.java,但在 3.2 分支上也缺少导入新 MoreConstants.java 的文件的许多更改。

在阅读了How to revert a merge which used strategy=ours?How to revert a faulty merge(尤其是附录)之后,我想我明白为什么会这样了:3.1 团队通过选择“我们的”解决了很多冲突。这样,实际上他们告诉 Git 3.0 的冲突更改是“错误的”。因此,这实际上是某种形式的还原合并,如上述文档中所述。不管是不是整个合并都被恢复了,而只是其中的一部分。

两个文档都包含很多关于如何解决此问题的线索。但是,两者都专注于修复 一个 错误合并。在这个简化的示例中,我们讨论的是 三个 错误合并。实际上,我猜我们有 15 到 20 个错误的合并。

我知道 3.1 团队不应该忽视冲突,但他们确实做到了。所以我们必须找到解决办法。到目前为止,我唯一能想到的是:

1。很多樱桃采摘

我们可以从 3.1 行到 3.2 行的每一个提交中挑选,但合并的提交除外。听起来很简单,但实际上我们有大约 20 个错误合并并且合并之间的提交比简化图表中显示的要多得多,我们谈论的是数百个提交到樱桃挑选。手动执行此操作既乏味又容易出错。我认为这个解决方案只有在有办法生成所有提交的列表以自动挑选并将该列表提供给(一系列)git命令时才可行。

2。很多变基

一系列连续的rebases。考虑到错误合并的数量,这仍然容易出错且乏味,但可能比之前的建议少一点。在专业方面,这可能会导致更清晰的历史。我认为流程如下:

$ git checkout x3
$ git rebase --no-ff 3.0

  3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5
 /             /            /            /            /            /  \
3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5   \
 \                                                                 \    \
  \                                                            3.2  A5---A6
   \
    3.1'--o--o--M1'--o--o--M2'---o---x3'

$ git merge R3

  3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5
 /             /            /            /            /            /  \
3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5   \
 \                                      \                          \    \
  \                                      \                     3.2  A5---A6
   \                                      \
    3.1'--o--o--M1'--o--o--M2'---o---x3'---D3

$ git checkout x4
$ git rebase --no-ff R3

  3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5
 /             /            /            /            /            /  \
3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5   \
 \                                      \ \                        \    \
  \                                      \ C3'--o--x4'         3.2  A5---A6
   \                                      \     
    3.1'--o--o--M1'--o--o--M2'---o---x3'---D3   

$ git merge D3

  3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---x5--C5
 /             /            /            /            /            /  \
3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5   \
 \                                      \ \                        \    \
  \                                      \ C3'--o--x4'--D4     3.2  A5---A6
   \                                      \             /
    3.1'--o--o--M1'--o--o--M2'---o---x3'---D3----------o   

等等……

3。生成补丁

第三种解决方案可能是生成“补丁”,仅包含在某些提交之间所做的更改。当我们再次可视化这棵树时:

  3.1---o---o---M1---o---o---M2--y2--o--x3--C3--y3--o--x4--C4--y4--o--x5--C5
 /             /            /              /              /              /  
3.0---o---o---R1---o---o---R2--o---o--o---R3--o---o---o--R4--o---o---o--R5   
                                                                         \    
                                                                     3.2  A5

这意味着创建包含在以下“范围”中所做的所有更改的补丁:

  • [y2 - x3],
  • [y3 - x4]
  • [y4 - x5]

然后,我们可以将这些补丁应用到 A5 之上,而不是进行合并。

问题

所以这些是我的问题:

  1. 什么是这个问题的最佳解决方案。它可能是我提出的解决方案之一(变体),完全不同。
  2. 有没有办法以(或多或少)自动化的方式执行解决方案 1,以防止错误并加快处理速度?
  3. 解决方案 2 中的计划是否可行?还是我在这里遗漏了什么?
  4. 解决方案 3 怎么样?有没有办法让这个过程自动化?

【问题讨论】:

  • Argggh ...你简直让我的头爆炸了!
  • 对不起。最近在我们的团队中发生了多次头部爆炸...... ;-)

标签: git merge git-merge git-rebase git-checkout


【解决方案1】:

我们可以从 3.1 行到 3.2 行的每一个提交中挑选,但合并的提交除外。 ...我认为这个解决方案只有在有一种方法可以生成所有提交的列表以自动挑选樱桃并将该列表提供给(一系列)git命令时才可行。

您可以将一系列提交传递给cherry-pick,例如git cherry-pick 16234..351be,你也可以指定-m 1 只跟随第一个父母,这样你就可以正确地传递合并提交,所以你不需要跳过它们。当您在每个步骤中解决合并冲突时,它们应该在未来得到解决,因此希望您在每个冲突步骤中都会遇到常规合并冲突,并且事情会更加自动化并且不易出错。

【讨论】:

  • 感谢您的回复。我不确定我是否完全理解为什么会这样,但我们会试一试!
  • 祝你好运!我很想知道它是否有效。我认为在那篇长篇文章中我跟随了你的回购中发生的事情,但肯定有可能我弄错了。
  • 好的。所以我有一段时间没时间深入研究这个问题。但最终,我找到了最终解决这个问题的时间。我选择了樱桃采摘解决方案。但是,建议的 -m 1 选项对我们来说并没有那么好。关键是当您使用-m 1 时,git 期望范围内的每个提交都是合并提交。因此,我最终确定了所有合并,然后挑选了介于两者之间的所有提交。所以我做了很多git cherry-pick -x y2^..x3
猜你喜欢
  • 2019-11-28
  • 1970-01-01
  • 2016-02-15
  • 1970-01-01
  • 2013-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多