我在 Git 中有一个异常的合并冲突。这是一个我从未打算保留的测试合并,但我想了解 为什么 Git 将其报告为冲突。
我已使用十六进制编辑器在合并两侧获取受影响文件的 SHA-1 哈希值,并由此确认文件在合并两侧字节方向相同从至少 7 行之前到至少 28 行之后,假定的冲突。这意味着实际上不存在冲突,并且不可能是空格、行尾、奇怪的 Unicode 规范化或不显示字符的问题。
冲突看起来是这样的:
<<<<<<< HEAD
#### Where To Put Untracked Intermediate Files
=======
#### Where To Put Untracked Intermediate Files
>>>>>>> theirs
根据@torek 的建议,我也尝试过使用 merge.conflictstyle=diff3
,这会产生不同的标记,但本质上没有冲突:
<<<<<<< HEAD
This is a preceding big long line, added by both sides, simplified for this post.
||||||| 87377e6
=======
This is a preceding big long line, added by both sides, simplified for this post.
>>>>>>> theirs
<<<<<<< HEAD
||||||| 87377e6
#### Where To Put Intermediate Files
=======
#### Where To Put Untracked Intermediate Files
>>>>>>> theirs
<<<<<<< HEAD
#### Where To Put Untracked Intermediate Files
This is an edited big long line which I have simplified for this post.
||||||| 87377e6
This is a big long line which I have simplified for this post.
=======
This is an edited big long line which I have simplified for this post.
>>>>>>> theirs
(我为这篇文章更改了一些行,但这样不相同的行就保持不相同。)
仔细检查发现,在三个冲突之间,上面的HEAD
和theirs
等同于同一行。在这种情况下,冲突标记的排列与我稍后注意到的 git-diff 行顺序差异一致,但其他方面没有任何意义。
以下是复制它的过程,从新存储库的空目录中从头开始,手头有 base.txt
、ours.txt
和 theirs.txt
——稍后会详细介绍:
$ git init
Initialized empty Git repository in //bla/bla/bla/bla/bla/repo/.git/
$ cp ../test-files/base.txt .
$ mv base.txt file.txt
$ git add file.txt
$ git commit -m "Set base"
[main (root-commit) 30f2d24] Set base
1 file changed, 2169 insertions(+)
create mode 100644 file.txt
$ git checkout -b theirs
Switched to a new branch 'theirs'
$ rm file.txt
$ cp ../test-files/theirs.txt .
$ mv theirs.txt file.txt
$ git commit -am "Set theirs"
[theirs d5abe2c] Set theirs
1 file changed, 81 insertions(+), 46 deletions(-)
$ git switch main
Switched to branch 'main'
$ rm file.txt
$ cp ../test-files/ours.txt .
$ mv ours.txt file.txt
$ git commit -am "Set ours"
[main a7cde04] Set ours
1 file changed, 217 insertions(+), 49 deletions(-)
$ git merge theirs --no-ff
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.
此时 file.txt
中有两个地方带有冲突标记(使用 merge.conflictstyle=diff3
时更多)。第一个位于第 408 行,如帖子顶部所示。第二个在第 2353 行的文件末尾附近,并且有合理的解释。
我已经使用 gitk 确认所有提交中的文件模式都是 100644
。
为了试图解释发生了什么,在上述过程之后,我运行了这些:
git diff :1:file.txt :2:file.txt > 1-2.txt
git diff :1:file.txt :3:file.txt > 1-3.txt
git diff :2:file.txt :3:file.txt > 2-3.txt
文件 2-3.txt
不包含字符串 Where To Put Untracked Intermediate Files
。
1-2.txt
和 1-3.txt
do 包含字符串,分别作为 @@ -372,34 +389,42 @@
和 @@ -372,34 +388,42 @@
块的一部分。请注意,这些大块头比报告为冲突的区域要长得多。
我在十六进制编辑器中打开 ours.txt
和 theirs.txt
,仔细定位这些大块头的起点和终点,并获取它们的 SHA-1。 哈希值相同。
不过,我确实找到了一个可能的线索:
大块头的 a/
和 b/
文件在 1-2.txt
和 1-3.txt
之间显示相同的内容,但它们的方式 在 diff 输出中呈现的并不完全相同。尽管它们的顺序在功能上是相同的,但有 7 行的跨度没有以相同的顺序列出:
对于 1-2.txt
这些行,加上以下为清楚起见,如下所示:
-#### Where To Put Intermediate Files
-This is a big long line which I have simplified for this post.
+#### Where To Put Untracked Intermediate Files
+
+This is an edited big long line which I have simplified for this post.
对于 1-3.txt
,它们如下:
+
-#### Where To Put Intermediate Files
+#### Where To Put Untracked Intermediate Files
-This is a big long line which I have simplified for this post.
+This is an edited big long line which I have simplified for this post.
1-2.txt
、1-3.txt
和 2-3.txt
文件在运行和不运行 merge.conflictstyle=diff3
时是相同的。
git diff
是否承诺将使用一组功能等效的排序中的哪一行排序?git merge
是否假定如果 git diff
的文字文本输出对于一个大块头不相同,那么大块头是一个冲突? base.txt
、ours.txt
和 theirs.txt
在我的原始测试中是密切相关的 ~2300 行 Markdown 文件。
我最初在一个实际的存储库中遇到了这个冲突,在那里重复了两个分支之间的挑选和合并。我在不打算提交的情况下运行 git merge --squash
时遇到了它,以查看分支收敛了多少。起初我认为冲突是由于挑选樱桃(它可以做这样有趣的事情),但我越想越没有意义,因为当双方更改文件时同理,这不应该是冲突。
因此,在 merge
抱怨之后,在做出任何解决方案之前,我运行了这些:
git show :1:the-actual-file.md > base.txt
git show :2:the-actual-file.md > ours.txt
git show :3:the-actual-file.md > theirs.txt
在新的存储库中重现合并非常有效地排除了诸如挑选樱桃之类的复杂性作为解释。
ours.txt
和 theirs.txt
有许多不冲突的变化,我无法仅根据异常冲突在一个微不足道的例子上重现。但是,我能够在三个文件的“清理”版本上重现,其中所有行都被替换为它们的 SHA‑1 十六进制字符串,这会产生相同的冲突模式和 git diff
如上图所示。有兴趣的可以下载:base.txt、ours.txt 和 theirs.txt。