【问题标题】:Why does git merge allow me to lose a line为什么 git merge 让我丢了一行
【发布时间】:2017-07-12 06:44:51
【问题描述】:

我们最近遇到了一些与 git 的合并问题,虽然它可能做了“正确的事情”(TM),但这并不是我所期望的。我已将问题简化为位于 https://github.com/geretz/merge-quirk 的小型公共 github 存储库。

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

git merge 检测并声明冲突。

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.

但是,如果天真/信任的开发人员在冲突标记的 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);
    }
}

合并-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 无法弄清楚如何自动解决更改时会发生合并冲突。你,开发者,必须真正阅读它并弄清楚如何解决。
  • 我建议也将merge.conflictStyle 设置为diff3:这显示了Git 在原始(公共基础)文件中看到的内容,与合并的两个端点,即HEAD / @987654329 @和其他/--theirs。请注意,如果您现在正在查看冲突,但尚未解决,您也可以使用 git checkout --conflict=diff3 quirk.c 将其内容替换为新的冲突合并,但这次使用所选样式。
  • @torek diff3 不能解决根本问题。 diff3 共同祖先块中也缺少 thisLineMightDisapper 调用,仅出现在 HEAD 块中。
  • 好吧,看看 Dietrich Epp 的回答;但以上是评论,而不是答案,因为当我也可以看到原始基础时,我发现更容易推理 Git 看到(并因此做了)什么。 Git 只知道将碱基与两个技巧匹配,并将它认为的变化结合起来。我还要在这里指出,即使 Git 认为它正确解决了两个差异,也不能保证它确实
  • @torek 感谢您为我指出正确的方向(git 匹配两个提示的基础)。由于 merge-dst 重新格式化,因此在两个不同的大块中添加和删除了 thisLineMightDisapper 代码。删除显然没有冲突,因此合并的一部分“有效”,添加与不包含该行的不同块冲突,因此该函数仅出现在一个冲突块中。

标签: git merge git-merge


【解决方案1】:

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

                            if (thisLineMightDisapper(42)) {

                    if (thisLineMightDisapper(42))
                    {

不一样,而 git 通常会像他们一样行事是非常错误的。第二个在 master 和 merge-src 中保持不变,并在 merge-dst 中删除,通过引入虚假匹配与添加的冲突块分开。所以git认为删除没有冲突并自动合并它。

当像这里一样,即使git checkout -m --conflict diff3 令人困惑时,您也可以拉出合并正在查看的帅哥

$ 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,如果你用错误的方法尝试它不会有好的结果。所以它就在这里:你需要一个稍微不同的锤头,一个稍微不同的合并选项。在更常见的情况下,忽略空间更改会导致标签损坏。

【讨论】:

    猜你喜欢
    • 2020-04-20
    • 2021-04-16
    • 2022-01-10
    • 2012-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多