我在分支上工作,我最近重构了我的文件夹结构,很多文件在这里和那里移动,许多文件也被重命名。但是当我将 master(旧结构)合并到我当前的分支时,git 能够理解文件的位置并自动合并代码而不会发生冲突。这怎么可能?
这个“如何”有两个部分,可以这样总结:
对于提交,Git不在乎。它只是制作快照。你告诉 Git 使用的任何文件名来保存你告诉 Git 使用的任何文件内容——所有这些都在你运行时存储在你的索引中git commit,这就是为什么你必须一直保存git add文件,将它们复制到旧的版本在索引的Git只是冻结所有这些文件,用他们的指数有现在,当你运行git commit。这些冻结的文件是提交的快照。
换句话说,在您重新排列所有内容后git mv(这会更改索引和工作树中的名称),并git add根据需要更新内容,然后git commit,您将获得具有新名称和任何更新内容的新快照。旧快照保持原样:所有现有快照都被永久冻结,或者至少,只要提交本身存在。(默认设置是它们永远存在。可以删除提交,但这只是简单的,直到您将它们传播到其他存储库,之后即使您删除它们,它们也会继续从其他存储库重新感染您的存储库来自你自己的。)
为了比较——包括合并——Git 必须发现/检测重命名的文件。Git 通过比较文件的内容来做到这一点。
这里的第二个要点中的声明实际上有点夸张,但让我们通过说明 with 来看看它是如何工作git diff的,至少在我们在这里关心的模式中 - 比较两次提交。请记住,每次提交都代表所有文件的完整快照。我们将找到两个提交的哈希 ID,然后运行:
git diff --find-renames <hash of earlier commit> <hash of later commit>
Run Code Online (Sandbox Code Playgroud)
此时 Git 会做的是提取两个提交中的每一个。(“提取”尽可能地短路,这通常很多:Git 通常可以直接就地检查冻结的提交。但这只是速度优化;您可以将其视为 Git 将两个提交完全提取到一个临时工作区。)让我们在这里将较早的提交称为旧的,将稍后的提交称为新的。1 的工作git diff是告诉您如何将旧变新。这不一定是进行更改的任何人所做的,只是一些会产生相同结果的指令集。
为了找到这些说明,Git 将:
首先,在old和new 中找到所有具有完全相同名称的文件。Git 假设如果old有一个名为的文件,README而new有一个名为 的文件README,则它们必须是“相同”的文件。这些文件是成对的:它们现在暂时被排除在外。Git 还没有弄清楚如何更改配对文件,它只是将它们配对。
(您可以使用该-B选项在此处插入一个步骤,以插入具有该选项的命令。但我们现在将忽略它,因为它只会使这变得复杂。)
现在,如果有未配对的文件,这些文件代表从旧文件中丢失和/或在新文件中突然出现的文件......或者是吗?也许他们是被那个文件重命名,即有一些姓名o老和一些不同的名称为N新。在这里,Git为每个可能的文件配对计算一个相似性索引号。
出于速度目的,Git 可以非常快速地配对任何两个100% 相同的文件(一个来自old,一个来自new)。这通常会大大缩小必须以困难方式进行比较的文件池。
最后,Git 归结为未配对的文件,即使它们不是 100%、逐位相同,它也应该考虑配对。Git 现在对每对文件进行完整的相似性索引计算(使用与Git 用于在包文件中进行增量压缩相同的类似 xdelta 的代码)。(这需要真正提取所有这些文件的数据。)将获得最佳配对分数的文件配对在一起,如果分数超过您选择的最小值,默认为“50% 相似”。
在所有这些额外工作之后仍未配对的文件要么被删除,要么重新创建。(如果您添加--find-copies或--find-copies-harder,这里会引入更多的复杂性,但同样,我们将在此处忽略它们。)
既然文件已经配对了,即现在git diff知道,比方说,文件README.md在旧大多匹配文件README.rst在新的,所以这两个文件必须真的是一个单一的文件,有一个身份,而不是两个不同的文件用两个身份——现在Git 比较每个配对的文件以生成指令:
-: 从旧的文件版本中删除这一行+: 将此行添加到旧文件的版本中如果您按照所有说明进行操作,包括顶部给出的任何“重命名此文件”说明,则会将在old 中找到的文件更改为在new 中找到的文件。
1如果愿意,您可以反转散列 ID。然后 Git 会告诉你如何把新的提交变成旧的提交。
git merge使用git diff当合并两个提交时,Git 使用提交图——通过查看每个提交的父散列或散列将所有单独的提交连接到一个大 DAG 形成的有向无环图——来找到最好的共同祖先提交。此提交是两个指定提交的合并基础。
该git merge命令实际上运行了两个 git diff命令。两者都已--find-renames启用,相似度阈值默认为 50%。您可以使用-X find-renames=<number>更改此阈值,以允许更多或更少名称不匹配的文件配对。
这两个差异是:
git diff --find-renames <hash of merge base> <hash of HEAD commit>
Run Code Online (Sandbox Code Playgroud)
和:
git diff --find-renames <hash of merge base> <hash of other commit>
Run Code Online (Sandbox Code Playgroud)
两个差异都根据需要在内部git diff.
添加-B告诉 Git打破自动配对的文件:仅仅因为README在两次提交中都命名了一个文件并不意味着这实际上是同一个文件。例如,如果您重命名README为old/README,然后重命名new/README为README?在这种情况下,Git 将在我之前提到的那个步骤中对自动配对的文件进行相似度计算。如果相似度太低,Git 会破坏配对。后来,如果相似不算非常低,而且配对保持断开,Git会重新加入两个文件,所以-B需要2个号码,不只是一个。
merge 命令不允许您提供-B参数。(可以说,它应该。)
如果您使用--find-copies或--find-copies-harder,Git 将查看部分或全部源(“旧”)文件,以查看是否从中复制了新创建的目标(“新”)文件。它们使用相同的相似性索引。这一步发生在重命名检测之后,有时只会将修改后的文件视为可能的来源,同样是因为它的计算成本很高。
合并命令也不允许您指定查找副本选项。