git 合并冲突究竟是什么时候出现的

when exactly does a git merge conflict arise

我正在使用 git 来跟踪对我的 LaTeX 文档的更改。我倾向于将合著者的反馈保存在一个单独的分支中,稍后再合并。到目前为止,事情似乎神奇地正确地合并了,但我想知道合并冲突发生的确切时间,以便我可以在合并过程中获得一些真正的信任(当然我不希望文本出现时髦)。

Whosebug 上有很多问题似乎问的是同一件事,但 none 的答案非常具体。例如 this answer 指定如果对同一区域进行更改会发生冲突,但这让我想知道这些区域到底是什么。只是对同一行所做的更改,还是考虑了某些上下文?

它是逐行进行的,答案既不是也不是:上下文确实很重要,但它重要的数量是棘手的。它比你最初想象的要多,也比你想象的要少。

您可能想先浏览 this answer 到相关问题,作为背景。我现在假设我们有 base 作为(单个)合并基础(也许我们通过标记特定提交来设置名称 base,例如 git 标记基础$(git merge-base HEAD <em>other</em>)) 和 HEAD 作为我们在分支 b1 和其他分支上的提交-name b2 命名另一个提交。

接下来,我们看一下两者的区别:

git diff base HEAD
git diff base b2

如果我们看到文件 F 的所有三个版本都不同(因此 F 出现在两个输出中,并且更改不同),然后我们必须工作,本质上,diff-hunk-by-diff-hunk。在 diff hunks 重叠但进行不同更改的地方,Git 声明冲突。但是——这似乎是你的问题——"make different changes"到底是什么意思?

我认为这是最好的例子。例如:

$ git checkout b1
[checkout messages here - but I was already on b1]
$ git diff base HEAD
diff --git a/basefile b/basefile
index df781c1..e4f9e4b 100644
--- a/basefile
+++ b/basefile
@@ -4,6 +4,7 @@
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
+# added line in b1
 # 1. Redistributions of source code must retain the above copyright
 #    notice, this list of conditions and the following disclaimer.
 # 2. Redistributions in binary form must reproduce the above copyright

和:

$ git diff base b2
diff --git a/basefile b/basefile
index df781c1..c96620e 100644
--- a/basefile
+++ b/basefile
@@ -4,7 +4,6 @@
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
-# 1. Redistributions of source code must retain the above copyright
 #    notice, this list of conditions and the following disclaimer.
 # 2. Redistributions in binary form must reproduce the above copyright
 #    notice, this list of conditions and the following disclaimer in the

请注意,虽然这些更改没有触及 相同的 行,但从某种意义上说,它们也 do 触及相同的行,从某种意义上说。我添加了第 7 行(将旧的第 7 行向下推到第 8 行),并删除了旧的第 7 行。显然,这些是 "same" 行。所以:

$ git merge b2
Auto-merging basefile
CONFLICT (content): Merge conflict in basefile
Automatic merge failed; fix conflicts and then commit the result.

让我们中止合并并考虑分支 b3 的尖端(b1b3 的合并基础与 b1 的合并基础相同和 b2,在我的设置中)。

$ git merge --abort
$ git diff base b3
diff --git a/basefile b/basefile
index df781c1..e2b8567 100644
--- a/basefile
+++ b/basefile
@@ -5,7 +5,6 @@
 # modification, are permitted provided that the following conditions
 # are met:
 # 1. Redistributions of source code must retain the above copyright
-#    notice, this list of conditions and the following disclaimer.
 # 2. Redistributions in binary form must reproduce the above copyright
 #    notice, this list of conditions and the following disclaimer in the
 #    documentation and/or other materials provided with the distribution.
 $ git merge --no-edit b3
Auto-merging basefile
Merge made by the 'recursive' strategy.
 basefile | 1 -
 1 file changed, 1 deletion(-)

这次没有冲突,尽管两个 不同的帅哥 触及了相同的一般区域。第二个 diff 删除了不是 "touching" 添加行的行,因此 Git 认为这是安全的。

如果您以同样的方式进行更多实验,您会准确地发现哪些看似重叠的更改被成功组合,哪些导致了冲突。显然,直接重叠的更改(例如,同时删除原始第 42 行和插入不同的新第 42 行)将发生冲突。但所有更改始终表示为 "delete some existing line(s), though maybe zero of them" 后跟 "add some new line(s), though maybe zero of them"。 change——即使只是更改、添加或删除一行中的一个单词——也会删除非零数量的现有行并添加非零数量的新行。纯删除(一个或多个完整行)添加 零行,纯插入删除 零行。最后,它归结为: "Did both ours-and-theirs changes touch the same line number?" 上下文变得几乎无关紧要,除了删除零行或插入零行时,上下文本身 "is" 从某种意义上说,这些行。 (我不确定这种说法有多大意义,所以如果它难以理解,那是我的错。;-))

(还请记住,如果您在工作时修改 "merged so far" 文件,则在查看是否有更改时必须使用 原始基础文件的 行号touched "the same" 行。因为 "ours" 和 "theirs" 都有 相同的 基本版本,这是我们可以在这里使用的简单快捷方式。)

三向合并不是补丁

请注意,这与应用 补丁程序 不同,后者在没有通用基础版本的情况下完成。在补丁的情况下,上下文使用得更多:diff hunk header 提供了搜索上下文的位置,但由于它可能应用于文件的不同版本,上下文允许我们(和 Git) 在 不同的行 进行相同的更改,只要上下文仍然匹配。

patch 实用程序在这里使用不同的算法(一个 "maximum fuzz" 因子,看起来 +/- 那么多行)。 Git 不做模糊因子;如果必须的话,它将一直搜索到文件的开头或结尾。但是,在确定上下文无法匹配之前,它确实具有调整白色 space 的通常选项。

(当使用git apply应用补丁时,可以添加-3--3way以允许Git读取index行,这提供文件 blob 的部分或完整哈希 ID。左侧哈希 ID 是文件先前版本的哈希:请注意,在上面的所有差异中,basefile 的 "base" 版本具有 ID df781c1。如果 Git 可以从该 ID 中找到一个唯一的 blob,它可以假装那是合并基,并且只将一个合并基与 HEAD 进行比较,处理补丁本身作为 other diff,并以这种方式进行三向合并。这有时允许 git applypatch 失败的地方成功。)