为什么会导致合并冲突?

7
这是我的git仓库的初始快照。

enter image description here

在分支master上,文件m1包含。
L1

在分支dev上,文件m1包含。
L1
L2

如果我尝试从主分支合并dev分支,会导致冲突。
$ git checkout master
Switched to branch 'master'

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

$ git diff
diff --cc m1
index 078f94b,9f46047..0000000
--- a/m1
+++ b/m1
@@@ -1,1 -1,2 +1,5 @@@
  L1
++<<<<<<< HEAD
++=======
+ L2
++>>>>>>> dev

尽管我在master中没有修改m1的第2行,但为什么会导致冲突?
为了验证文件的实际内容并确保这是否是由于空格造成的:
在分支master上:
git branch
  dev
* master

$ xxd m1
0000000: 4c31 0a                                  L1.

在分支 dev

$ git checkout dev
Switched to branch 'dev'

$ xxd m1
0000000: 4c31 0a4c 320a                           L1.L2.

这是我用来创建此仓库的脚本。

#!/bin/bash

mkdir git_demo
cd git_demo
git init

touch m1
git add .
git commit -m "Added file: m1"
# sleep is needed, otherwise a different repo is being created, probably because of *some* filesystem issue!
sleep 1

git branch dev
echo L1 >> m1
git add .
git commit -m "Added line L1 to m1"
# sleep is needed, otherwise a different repo is being created, probably because of *some* filesystem issue!
sleep 1

git checkout dev
echo L1 >> m1
git add .
git commit -m "Added line L1 to m1"
# sleep is needed, otherwise a different repo is being created, probably because of *some* filesystem issue!
sleep 1

echo L2 >> m1
git add .
git commit -m "Added line L2 to m1"
# sleep is needed, otherwise a different repo is being created, probably because of *some* filesystem issue!

gitg --all
git checkout master
git merge dev

你的流程是快速向前合并的一个完美示例,这真的很奇怪。请问您可以发布一下创建存储库时的确切说明吗? - Maroun
1
@MarounMaroun 或者如果我上传一个未经修改的存储库的ZIP文件,你会觉得更容易吗?我有一个原始副本用于测试目的。而且我不认为这是快进式合并。相反,我期望 merge 跳过重复提交(我故意引入的)并应用其他提交。 - sherlock
你的 CRLF 设置是什么?可能是它。 - CodeWizard
@MarounMaroun,它不能是快进,每个分支都有一个在另一个上不存在的提交。 - Jonathan Wakely
你找到避免这个问题的方法了吗?一些同事遇到了像这样的冲突问题,真的很烦人! - Abdelilah El Aissaoui
显示剩余8条评论
3个回答

3
答案是由于2个分支没有任何merge-base提交,所以存在冲突。以下是如何用较少的步骤生成问题。
创建孤立分支(孤立分支是没有历史记录的分支)。

enter image description here

你可以看到这里它们不共享同一棵树。
[enter image description here]

enter image description here

enter image description here


是的,解压缩(Unix),然后你就可以看到我在那里得到了什么。有一些隐藏的字符,我很快就会找出来的 :-) - CodeWizard
连stash也不起作用,这个文件有些奇怪。 - CodeWizard
唯一的“修复”方法是添加文件并提交,但这不是我们想要的。 - CodeWizard
当然不是。我对合并的行为感到惊讶。 - sherlock
半小时后回来检查。 - CodeWizard
显示剩余8条评论

1
因为共同祖先是空的。
在主分支中,您向一个空文件添加了一行。在开发分支中,您向一个空文件添加了两行。
即使其中一行是共同的,也无所谓,您必须选择要采取哪一方;一条线还是两条线的那一方。

从这次讨论中,我可以看出即使是资深用户,Git 的 merge 行为仍然大部分被误解了。有太多相互冲突的理论了!然而,我仍然无法理解 3-way merge 是如何改善这种情况的。我认为 Git 应该能检测到 master - ancestordev - ancestor 之间的 L2 没有变化。后者应该占据优势。只有当同一行发生更改时才会发生冲突,难道不是这样吗? - sherlock
但是主分支和祖先之间有一个变化。祖先是一个空文件。如果祖先与主分支完全相同,那么你就是正确的。 - Edward Thomson
@Holmes.Sherlock:顺便说一下,在这种情况下避免合并冲突的正确工作流程是:在dev中添加L1行。将dev合并到master。在dev中添加L2行。如果您在不使用合并的情况下将L1行添加到master,则会出现合并冲突。 - slebetman
@slebetman 在 master 和公共的 ancestor 中,第二行都是空的。如果git的合并算法是-感知的话,那么它应该会优先考虑在dev中唯一的更改。 - sherlock
@Holmes.Sherlock:这正是问题所在。Git的合并算法不是基于行的,而是基于历史记录的。 - slebetman
显示剩余6条评论

0

这很容易重新创建:

% git init                                                                                                                                                                                                        [8:33:13]
Initialized empty Git repository in /home/martin/tmp/gitte/.git/
% touch m1                                                                                                                                                                                                        [8:33:16]
% git add m1                                                                                                                                                                                                      [8:33:40]
% git commit -m "Added file: m1"                                                                                                                                                                                  [8:33:48]
[master (root-commit) 72a9740] Added file: m1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 m1
% git checkout -b dev                                                                                                                                                                                           [8:34:05]
Switched to a new branch 'dev'
% echo L1 >> m1                                                                                                                                                                                                   [8:34:08]
% git commit -am "Added line L1 to file m1"                                                                                                                                                                     [8:34:29]
[dev b16538c] Added line L1 to file m1
 1 file changed, 1 insertion(+)
% git checkout master                                                                                                                                                                                             [8:34:33]
Switched to branch 'master'
% echo L1 >> m1                                                                                                                                                                                                   [8:34:38]
% git commit -am "Added line L1 to file m1"                                                                                                                                                                       [8:34:46]
[master 7b952c8] Added line L1 to file m1
 1 file changed, 1 insertion(+)                                                                                                                                                    [8:35:59]
HEAD is now at 7b952c8 Added line L1 to file m1
% gitk                                                                                                                                                                                                            [8:36:04]
% echo L2 >> m1                                                                                                                                                                                                                                                                                                                                                                                                                [8:36:28]
% git commit -am "Added line L2 to file m1"                                                                                                                                                                       [8:36:28]
[master f336d77] Added line L2 to file m1
 1 file changed, 1 insertion(+)
% git merge dev  # merge conflict!

问题的原因是两个文件中的第二行不同。在您的主分支中,第二行只是一个“EOF”,而在dev分支中,第二行是“L2”。

你确定吗?如果你在m1上查看两个分支的xxd输出,会发现0a是换行符。文件的任何版本中都没有单独的EOF。 - sherlock
请在我的原始帖子中检查xxd输出。 - sherlock
从技术上讲,没有EOF字符,所以我已经澄清了我的答案。然而,你可以清楚地看到合并冲突在第2行。文件曾经被认为是EOF,现在是L2,Git将其视为冲突。 - Martin Konecny
1
在这种情况下,当文件中添加或删除一行时,git 总是会在最后显示冲突,不是吗?而且 master 分支中的第三个字符 0a 应该与 dev 分支中的第三个字符 0a 匹配。 - sherlock
似乎只有在两个分支中都更改文件的最后一行时才会发生。在一个分支中,EOF(不是字符)被引入到第2行,在另一个分支中,L2被引入到第2行。 - Martin Konecny
master 分支上:4c31 0a。在 dev 分支上:4c31 0a4c 320a。如果你说在第二行 master 包含了 0a,那么 dev 在第二行也包含了完全相同的字节。我想知道 0a 是否被视为 LF 和 EoF 字符并受到不同的处理。 - sherlock

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