git合并冲突说删除了,但事实并非如此

时间:2016-11-18 06:44:07

标签: git merge-conflict-resolution

我试图解决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版本2.9)中的冲突 - 冲突是建议“其他”文件中的文件。分支被删除,但事实并非如此。这是一个(相当)简短的可重复配方:

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现在建议修补程序分支中的f / a 已删除,但情况显然不是这样吗?

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

我期待一个正常的&#39;像这样的f / a冲突:

select2

我无法理解为什么git会将冲突建议为重命名/删除冲突?

感谢您的帮助。

2 个答案:

答案 0 :(得分:2)

很好的问题。

您的示例中有一个缺点:

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

如果您更改示例:

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

合并不会触发任何冲突,并导致f/a包含修补程序修改。

希望这意味着在您的真实场景中,git说created vs deleted的案例数量有限。

如果手动处理它们是可行的,这里可以看到你通常的三向合并:

 # 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

答案 1 :(得分:2)

  

我无法理解为什么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的工作原理。让我们先从第一个开始。

如何合并(作为动词)

合并的目标在两个不同的开发线上组合两组更改(通常由两个不同的人制作,但在我们的例子中,由一个人制作&#34;戴着两个不同的帽子&#34;,一次一个,就像它一样)。这些更改必须从一些常见的起点开始,Git称之为合并基础

要执行此合并,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

我们实际上并不需要所有这些哈希,但有时候以具体的形式看待它们真好。关键是,我们有两组不同的变化:&#34;我们做了什么&#34;从d763d322ca7d2d,以及&#34;他们做了什么&#34;从d763d32转到b45a155

我们可以通过运行git diff

找到第一件事
$ git diff d763d32 2ca7d2d    # using raw IDs
$ git diff hotfix...master    # using the special "..." syntax

那是&#34;我们做了什么&#34;。我马上就会展示它。

接下来,我们(或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完全相同。因此,我们有一个重命名。

第二个差异虽然......啊,但是有一个问题:

$ 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更改中一样。但是,如果他们没有保留足够的&#34;但是,Git 不会检测到重命名。

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

如果您有许多文件并需要自动重命名检测,则可能需要花哨。如果您只有一个文件,则可以手动合并文件,自行执行&#34;重命名检测&#34;,使用git merge-file生成合并结果。只需将三个修订版(base,HEAD和其他版本)提取到临时文件中,然后使用git merge-file合并这三个版本以生成所需的结果。用正确的版本替换Git有点蹩脚的版本,并git add它,你就可以了。