Git合并diff3样式需要解释

39

我合并了两个分支,出现了冲突,我需要一些提示来确定开始和结束的位置等信息。为了更容易阅读和讨论,我已经用一些虚假数据替换了代码。

<<<<<<< HEAD
    aaaaaa
||||||| merged common ancestors
<<<<<<< Temporary merge branch 1
    bbbbbb
=======
    cccccc
>>>>>>> mybranch
    dddddd
<<<<<<< HEAD
    eeeeee
||||||| merged common ancestors
    ffffff
||||||| merged common ancestors
    gggggg
=======
>>>>>>> Temporary merge branch 2
=======
    hhhhhh
>>>>>>> mybranch

这个差异很奇怪。你能给我们展示原始分支吗?原始文件包含合并标记 <<<<<< 吗? - Ciro Santilli OurBigBook.com
2个回答

55

这个例子中展示的(带有Temporary merge branch标记的)是一个由交叉式合并冲突引起的diff3结果。我将通过一系列定义来解释这个过程。

定义

  • 合并基底(merge base):两个合并分支最近分叉的提交。当出现合并冲突时,不同的更改被应用于这些分支中的相同行。合并基底包含了这些行在任何一个分支修改前的状态。
  • 合并的共同祖先(merged common ancestors):diff3会输出一个额外的“中间”部分,显示它们在合并基底中的状态。这是两个分支的起始点。
  • 交叉式合并(criss-cross merge):指两个分支以无法进行快进合并的方式合并到彼此。下面举例说明。在交叉式合并情况下,存在多个合并基底
  • 临时合并分支(Temporary merge branch):当存在多个合并基底时,diff3尝试将它们(使用临时合并分支)合并在一起,形成一个单一的共同祖先,以显示在diff3的中间部分。当没有冲突时,这个过程很顺利,但是当出现冲突时,在合并的共同祖先部分中会看到临时合并分支的冲突标记。

交叉式合并冲突示例

每当两个分支在不同时间点合并到彼此时,就会发生交叉式合并。

m3 *
   |\
   | \
   |  * B1
   |  |
m2 *  * B0
   |\/|
   |/\|
m1 *  * A
   | /
   |/
m0 *

考虑以下事件序列:

  • m0 存在于 origin/master 分支上
  • 我创建了一个名为 feature-A 的功能分支,并提交了一次提交 A
  • 别人将 m1 提交到 master 分支上
  • 我又开始了一个建立在 A 基础上的新功能分支 feature-B
  • 我将 origin/master (m1) 合并到 feature-B 分支。发生了冲突,我解决了它。合并提交是 B0
  • 我实现了 feature-B 并将其作为提交 B1 提交
  • feature-A 准备好进行发布,有人将其合并到了 master 分支。发生了冲突。他们解决了冲突,但他们的解决方案与 B0 中的解决方案不同。合并提交是 m2
  • feature-B 准备好进行发布,有人将其合并到了 master 分支。git 尝试确定合并基础,但是 m1A 同样都符合合并基础的条件。git 在一个临时的合并分支中合并了 m1A,导致冲突。我们在“merged common ancestors”部分看到 diff3 输出,类似于 OP 的问题。

阅读输出结果

如果 diff3 关闭,则此合并冲突会简单地显示如下:

<<<<<<< HEAD
    aaaaaa
=======
    hhhhhh
>>>>>>> mybranch
首先,通过所有额外标记,您需要确定实际冲突的行,以便将其与diff3公共祖先输出区分开来。 aaaaaahhhhhh,这好多了。;-) 在两个冲突解决方案相互冲突的情况下,aaaaaa和hhhhhh是两个解决方案。 接下来,检查合并的公共祖先的内容。 对于这种合并历史,存在多个合并基地,需要多个临时合并分支,然后将它们合并在一起。当有许多合并基地和冲突时,结果可能会变得非常混乱且难以阅读。有人说不要管它,只需为这些情况关闭diff3。 还要注意,git内部可能会决定使用不同的合并策略来自动解决冲突,因此输出可能很难理解。如果可以理解,则可以理解,但要知道它并不是针对人类消费而设计的。在这种情况下,在合并mybranch到Temporary merge branch 1之间的bbbbbb和cccccc之间发生了冲突。dddddd行在临时合并分支之间没有冲突。然后,在将Temporary merge branch 2合并到HEAD时发生了独立的冲突,有多个公共祖先。 HEAD通过将ffffff和gggggg合并为eeeeee来解决了冲突,但Temporary merge branch 2通过删除(或移动)该行来解决了相同的冲突(因此没有======和Temporary merge branch 2之间的行)。 如何解决这样的冲突?虽然可能进行技术分析,但通常最安全的选择是返回并审核所有涉及冲突的分支中的历史记录,并根据您的理解手动制定解决方案。
避免所有这些
这些冲突是最糟糕的,但是有一些行为可以帮助防止它们。
1.避免交叉合并。在上面的示例中,feature-B将origin / master合并为B0。可能不需要此合并以保持与主机同步(尽管有时确实需要)。如果从未将origin / master合并到feature-B,则不会出现合并交错,并且m3将成为仅具有A作为唯一合并基地的普通冲突。
m3 *              m3 *
   |\                |\
   | \               | \
   |  * B1           |  * B1
   |  |              |  |
m2 *  * B0   VS   m2 *  |
   |\/|              |\ |
   |/\|              | \|
m1 *  * A         m1 *  * A
   | /               | /
   |/                |/
m0 *              m0 *
  • 在冲突解决方面保持一致性。在这个例子中,临时合并基础冲突只发生在m2B0的冲突解决方案不同的情况下。如果他们以相同的方式解决了冲突,m3将是一个干净的合并。然而需要意识到,这只是一个应该具有相同决策的简单十字路口合并。在其他情况下可能会有不同的处理方式。当存在超过2个合并基础和多个提交点之间的合并点时,事情变得更加复杂。也就是说,如果你在十字路口的情况下有意不一致地处理冲突,那么后面可能会遇到问题。

  • 1
    嗯,你两次添加了相同的答案吗?:http://stackoverflow.com/a/33419598/456814。 - user456814
    在第3步和第4步之间修复这个问题的方法是将A重新基于master进行变基吗?通常我不喜欢合并提交,所以我不会包含b0,但是我对这种"十字路口合并"还不熟悉,因此我正在尝试看看是否可以在没有合并提交的情况下进行这种十字路口合并。 - Tommy
    @Tommy 正确。如果您和您的团队进行变基,使得您的历史记录是线性的,那么在您的情况下,如果您的历史记录是线性的,则不可能有多个合并基础,因此不可能有交叉合并。 - Edward Anderson
    啊啊啊啊啊——机智而启迪性——阅读时的乐趣。 - gregory

    3
    这是一篇关于Git的diff3合并风格的文章。它指出,在这种风格下很难区分哪些行是被添加还是被删除的。
    如果您正在寻找特定信息,我建议您进一步明确您的问题。目前很难理解您的询问内容。

    5
    好的链接,谢谢。我对这篇文章的理解不同:在默认的冲突样式中(未显示),很难区分添加/删除,但是diff3包括|||||||标记以提供上下文,使我们能够推断出更改是添加还是删除。 - ptim

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