我不明白为什么 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 就可以了。