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