为什么 git merge 让我少了一行

Why does git merge allow me to lose a line

我们最近遇到了一些与 git 的合并问题,虽然它可能 'The Right Thing'(TM) 但不是我所期望的。我已将问题减少到位于 https://github.com/geretz/merge-quirk.

的小型 public github 存储库

quirk.c 存在于 master 上。 merge-src 和 merge-dst 都是从 master 上的同一版本分支出来的,master 是最终合并的共同祖先。 merge-src 添加注释和一些代码。 merge-dst 重新格式化代码并更改现有注释但没有添加的注释或代码。

git 合并检测并声明冲突。

git clone https://github.com/geretz/merge-quirk.git
cd merge-quirk
git checkout merge-dst
git merge origin/merge-src
Auto-merging quirk.c
CONFLICT (content): Merge conflict in quirk.c
Automatic merge failed; fix conflicts and then commit the result.

但是,如果 naive/trusting 开发人员在标记为 quirk.c 的冲突中选择 origin/merge-src 代码块,则 thisLineMightDisapper (sic) 函数调用将会丢失。

在我的心智模型中,如果 thisLineMightDisapper 函数调用同时存在于 HEAD 和 origin/src 的冲突状态中,它应该存在于两个冲突块中,如果它不存在于冲突状态中,它应该存在在两个冲突块之外。为什么它只出现在 HEAD 块内?

<<<<<<< HEAD
    // a few comment lines - change 1
    // that existed - change 2
    // in the common ancestor - change 3
    // that get changed - change 4
    if(1)
    {
        if (f(1, 2))
        {
            if (thisLineMightDisapper(42))
=======
    // a few comment lines
    // that existed 
    // in the common ancestor
    // added this line in merge-src branch
    // that get changed
    if(1) {
            if (f(1, 2))
>>>>>>> origin/merge-src
            {
                t = time(0);
            }
        }
    }

    if (anotherFunction(1,2))
    {
        t = time(0)
        f(0);
    }
}

文件

master/quirk.c

    // a few comment lines
    // that existed 
    // in the common ancestor
    // that get changed
    if(1) {
            if (f(1, 2))
            {
                if (thisLineMightDisapper(42)) {
                    t = time(0);
                }
            }
    }
}

merge-src/quirk.c

    // a few comment lines
    // that existed 
    // in the common ancestor
    // added this line in merge-src branch
    // that get changed
    if(1) {
            if (f(1, 2))
            {
                if (thisLineMightDisapper(42)) {
                    t = time(0);
                }
            }
    }

    if (anotherFunction(1,2))
    {
        t = time(0)
        f(0);
    }
}

merge-dst/quirk.c

    // a few comment lines - change 1
    // that existed - change 2
    // in the common ancestor - change 3
    // that get changed - change 4
    if(1)
    {
        if (f(1, 2))
        {
            if (thisLineMightDisapper(42))
            {
                t = time(0);
            }
        }
    }
}

这里发生的事情是您将语义与文本标识混淆了。 Git 将移动和拆分的结果视为完全不同的线。你最终得到一个冲突的大块和一个不冲突的大块,并且在冲突的大块中添加的一行与在不冲突的大块中删除的一行(非常)混淆地相似:即使它们在语义上相同,

                            if (thisLineMightDisapper(42)) {

                    if (thisLineMightDisapper(42))
                    {

不一样,而git假装它们是一样的通常是非常错误的。第二个在 master 和 merge-src 中未受影响,在 merge-dst 中被删除,通过引入虚假匹配与冲突的 hunk 分开。所以git认为删除没有冲突并自动合并。

当像这里一样,即使 git checkout -m --conflict diff3 令人困惑时,您也可以将 hunks merge 与

一起查看
$ sh -xc 'git diff ...MERGE_HEAD; git diff MERGE_HEAD...'
+ git diff ...MERGE_HEAD
diff --git a/quirk.c b/quirk.c
index c149623..79dc4a2 100644
--- a/quirk.c
+++ b/quirk.c
@@ -2,6 +2,7 @@
    // a few comment lines
    // that existed 
    // in the common ancestor
+   // added this line in merge-src branch
    // that get changed
    if(1) {
            if (f(1, 2))
@@ -11,4 +12,10 @@
                }
            }
    }
+
+   if (anotherFunction(1,2))
+   {
+       t = time(0)
+       f(0);
+   }
 }
+ git diff MERGE_HEAD...
diff --git a/quirk.c b/quirk.c
index c149623..0de7516 100644
--- a/quirk.c
+++ b/quirk.c
@@ -1,14 +1,16 @@

-   // a few comment lines
-   // that existed 
-   // in the common ancestor
-   // that get changed
-   if(1) {
-           if (f(1, 2))
+   // a few comment lines - change 1
+   // that existed - change 2
+   // in the common ancestor - change 3
+   // that get changed - change 4
+   if(1)
+   {
+       if (f(1, 2))
+       {
+           if (thisLineMightDisapper(42))
            {
-               if (thisLineMightDisapper(42)) {
-                   t = time(0);
-               }
+               t = time(0);
            }
+       }
    }
 }
+ git diff ...MERGE_HEAD

稍加研究就会告诉你 git 将一个单独的大括号识别为常见内容,使其将

-               if (thisLineMightDisapper(42)) {
-                   t = time(0);
-               }
+               t = time(0);

作为无冲突的更改。

这里对我有用的一件事是

git merge --abort
git merge -Xignore-space-change origin/merge-src

这在识别更改边界时正确地忽略了重新格式化。与将任何东西组合在一起一样,将源代码更改组合在一起有时需要 exactly the right tool,如果你用错误的方式尝试它,结果不会很好。所以它就在这里:你需要一个稍微不同的锤子头,一个稍微不同的合并选项。在更常见的情况下,忽略 space 更改会导致选项卡损坏。