【问题标题】:git merge conflict saying deleted, but it's notgit merge 冲突说已删除,但事实并非如此
【发布时间】:2016-11-18 06:44:07
【问题描述】:

我正在尝试解决 git merge(git 版本 2.9)中的冲突 - 冲突表明“其他”分支中的文件已被删除,但事实并非如此。这是一个(相当)简短的可重复配方:

cd /some/new/folder/somewhere
git init

echo 'a on master' > a
git add a
git commit -m 'add a on master'

mkdir f
git mv a f/
git commit -m 'move a to f on master'

git checkout -b hotfix HEAD^

echo 'new content a on hotfix' > a
mkdir f
git add a
git mv a f/
git commit -m 'move a to f on hotfix with different content'

git checkout master
git merge hotfix

Git 现在提示 f/a 在 hotfix 分支中被删除,但显然不是这样?

CONFLICT (rename/delete): f/a deleted in hotfix and renamed in HEAD. Version HEAD of f/a left in tree.
Automatic merge failed; fix conflicts and then commit the result.

我预计 f/a 会出现这样的“正常”冲突:

<<<<<<< HEAD
a on master
=======
new content a on hotfix
>>>>>>> hotfix

我不明白为什么 git 将冲突提示为重命名/删除冲突?

感谢您的帮助。

【问题讨论】:

    标签: git merge-conflict-resolution


    【解决方案1】:

    我不明白为什么 git 将冲突提示为重命名/删除冲突?

    Git 已检测到重命名。更准确地说,它检测到一个,但没有另一个。

    我使用了您的示例脚本并得到了与您相同的结果:

    CONFLICT (rename/delete): f/a deleted in hotfix and renamed in HEAD.
     Version HEAD of f/a left in tree.
    Automatic merge failed; fix conflicts and then commit the result.
    

    这里有两个关键:git merge 如何执行合并,以及git diff 如何工作。让我们先从第一个开始。

    合并(作为动词)如何工作

    合并的目标合并两个不同开发线的两组更改(通常由两个不同的人进行,但在我们的例子中,一个人“戴着两顶不同的帽子”,一次一顶,好像是这样)。这些更改必须从某个共同的起点开始,Git 称之为 merge base

    为了执行这个合并,Git 必须找到合并基础。对于像这样的常规合并,合并基础是 HEAD(当前提交)和目标提交之间共享的提交。在我的特殊情况下,名为hotfix 的目标解析为提交哈希b45a155...

    $ git rev-parse hotfix
    b45a15547101d836d84dbdf4758d71dc91c93353
    

    而 HEAD 是 2ca7d2d...:

    $ git rev-parse HEAD
    2ca7d2d15d4d537edb828a7f3bfff3a2182630ec
    

    这两个提交的合并基础是初始提交d763d32 add a on master

    $ git merge-base --all HEAD hotfix
    d763d32af0cafdb0378b96b25e56fd70d63213d1
    $ git log --graph --decorate --oneline --all
    * b45a155 (hotfix) move a to f on hotfix with different content
    | * 2ca7d2d (HEAD -> master) move a to f on master
    |/  
    * d763d32 add a on master
    

    我们实际上并不需要所有这些哈希值,但有时很高兴看到它们以具体的形式出现。关键是,我们有两组不同的变化:“我们做了什么”从d763d32 变为2ca7d2d,“他们做了什么”从d763d32 变为b45a155

    我们可以通过运行git diff 找到其中的第一个:

    $ git diff d763d32 2ca7d2d    # using raw IDs
    $ git diff hotfix...master    # using the special "..." syntax
    

    这就是“我们所做的”。一会儿我会展示它。

    接下来,我们(或 Git)可以通过再次运行 git diff 来找到其中的第二个:

    $ git diff d763d32 b45a155    # using raw IDs
    $ git diff master...hotfix    # using the syntax again
    

    git diff 如何执行差异

    这个解释在重要的时候会变得冗长和复杂,最终它真的很重要。让我们把它外包给another StackOverflow answer。不过,总而言之,Git 会尝试检测重命名。能否检测到它们,取决于很多细节。

    但是,在我们的特殊情况下,发生的事情是 Git 在从 merge-base 到 tip-of-master 时检测到重命名:

    $ git diff hotfix...master
    diff --git a/a b/f/a
    similarity index 100%
    rename from a
    rename to f/a
    

    这里的三点语法告诉git diff 找到两个指定提交的合并基(hotfix 的尖端和master 的尖端),然后将该合并基与第二个提交进行比较,即师傅的小费。它检测到重命名:原始文件a 与新文件f/a 100% 相同。因此,我们有一个重命名。

    第二个diff,虽然……啊,有问题:

    $ git diff master...hotfix
    diff --git a/a b/a
    deleted file mode 100644
    index 81d07e3..0000000
    --- a/a
    +++ /dev/null
    @@ -1 +0,0 @@
    -a on master
    diff --git a/f/a b/f/a
    new file mode 100644
    index 0000000..158795c
    --- /dev/null
    +++ b/f/a
    @@ -0,0 +1 @@
    +new content a on hotfix
    

    合并库中a 的旧内容与提示提交中f/a 的新内容相差太大。 Git 不仅 没有 没有找到重命名,而且 永远不会 找到重命名:文件相差太大。一点都不像原来的样子。

    重命名检测通常比这效果更好

    在实践中,当文件被重命名时,它们往往会保留大部分甚至全部的原始内容,就像您的 merge-base-to-master-tip 更改中发生的那样。但是,如果它们没有保留“足够”,Git 不会检测到重命名。

    幸运的是,自从我在 2012 年 1 月写 this answer 以来,Git 已经存在了很长时间。它今天仍然适用——您可以使用 -X rename-threshold=<em>number</em> 在合并期间调整重命名检测阈值级别——几乎每个人都有比 1.7.4 更新的 Git。然而,它的缺点仍然存在。您可能还想阅读我在 2016 年 4 月写的 this other answer

    如果你有很多文件并且需要自动重命名检测,你可能需要花哨。如果您只有一个文件,您可以简单地手动合并文件,进行自己的“重命名检测”,使用git merge-file 生成合并结果。只需将三个修订(base、HEAD 和其他)提取到临时文件中,然后使用git merge-file 合并这三个以产生您想要的结果。用正确的版本替换 Git 有点蹩脚的版本,然后 git add 就可以了。

    【讨论】:

      【解决方案2】:

      很好的问题。

      你的例子有一个缺点:

      文件a 由一行组成,因此编辑此行意味着“两个文件之间存在 100% 的差异”。
      git 重命名检测算法永远不会将两个 100% 不同的文件检测为重命名。

      如果你改变你的例子:

      • 在您的初始文件中写入 4 行 a
      • 修改hotfix上的内容时只编辑第一个

      合并不会触发任何冲突,并会生成包含修补程序修改的f/a


      希望这意味着在你的真实场景中,git 说created vs deleted 的情况是有限的。

      如果手动处理它们是可行的,这将是一种查看您通常的 3 路合并的方法:

       # find the merge-base :
       $ git merge-base master hotfix
       3e12717099f3dc7b83b3d267e5cbd580ec8e69a1
      
       # get the content you expect for "ours", "base" and "theirs" :
       $ git show master:f/a > f/a.ours
       $ git show hotfix:f/a > f/a.theirs
      
       # here is the manual part : I don't know how to have an automatic detection
       # of the correct base file :
       $ git show 3e12717:a  > f/a.base
      
       # start a 3-way merge :
       $ meld f/a.ours f/a.base f/a.theirs
      
       # if you are satisfied with the content in a.base,
       # and want to use it as your new a :
       $ mv f/a.base f/a
       $ git add f/a
      

      【讨论】:

        猜你喜欢
        • 2017-12-06
        • 1970-01-01
        • 1970-01-01
        • 2019-05-18
        • 2017-02-15
        • 2012-09-20
        • 1970-01-01
        • 1970-01-01
        • 2019-04-11
        相关资源
        最近更新 更多