如果整个文件夹结构发生更改,git如何跟踪更改



我正在研究分支,最近我重构了文件夹结构,很多文件被移到了这里和那里,很多文件也被重命名了。但是,当我将master(旧结构)合并到我当前的分支时,git能够理解文件在哪里,并自动合并代码而不发生冲突。这怎么可能?

这个"如何"有两个部分,可以这样总结:

  • 对于提交,Git不在乎。它只是制作快照。无论你告诉Git使用什么文件名来保存你告诉Gits使用的任何文件内容——所有这些文件在你运行git commit时都存储在你的索引中,这就是为什么你必须一直使用git add文件,将它们复制到索引中的旧版本上——当你运行git commit时,Git只是冻结所有文件,以及它们现在在索引中的任何内容。那些冻结的文件是提交的快照。

    换句话说,使用git mv重新排列所有内容(这会同时更改索引和工作树中的名称),并根据需要使用git addgit commit更新内容后,您将获得一个包含新名称和任何更新内容的新快照。旧的快照保持原样:所有现有的快照都将永远冻结,或者至少在提交本身存在的时间内冻结。(默认情况下,它们会永远存在。可以删除提交,但这很容易,直到你将它们传播到其他存储库,之后它们会不断从其他存储库重新感染你的存储库,即使你从自己的存储库中删除了它们。)

  • 对于比较(包括合并),Git必须发现/检测重命名的文件。Git通过比较文件的内容来实现这一点。

这里第二个要点中的声明实际上有点夸张,但让我们通过用git diff来说明它是如何工作的,CCD_7至少在我们这里关心的模式中比较了两个提交。请记住,每次提交都代表所有文件的完整快照。我们将找到两个提交的散列ID,并运行:

git diff --find-renames <hash of earlier commit> <hash of later commit>

Git此时要做的是提取两个提交中的每一个。("提取"会尽可能地短路,这通常是一个很大的问题:Git通常可以直接检查冻结的提交。但这只是一个速度优化;你可以认为这是Git将两个提交完全提取到一个临时工作区中。)让我们在这里称早期的提交为旧提交,后期的提交为新提交1git diff的工作是告诉你如何将旧的更改为新的。这不一定是任何做出改变的人所做的,只是一些指令集会产生相同的结果

要找到这些指令,Git将:

  • 首先,在oldnew中查找所有名称完全相同的文件。Git假设,如果old有一个名为README的文件,而new则有一个名称为README的文件,那么它们必须是"相同"的文件。这些文件是成对的:它们现在暂时不在等式中。Git还没有弄清楚如何更改配对的文件,它只是将它们配对。

  • (您可以使用-B选项在这里插入一个步骤,用于有一个步骤的命令。但我们现在将忽略它,因为它只会使这变得复杂。)

  • 现在,如果有未配对的文件,这些文件代表中丢失的文件和/或突然出现在。。。还是他们?也许它们是被重命名为的文件,在中有一些名称O,在有一些不同的名称N。在这里,Git为每个可能的文件配对计算相似性指数

    为了提高速度,Git可以非常快速地配对任何两个100%相同的文件(一个来自,一个来自)。这通常会大大缩小必须以硬方式进行比较的文件池。

    最后,Git要考虑的是未配对的文件,即使它们不是100%完全相同。Git现在对每对文件进行完全的相似性索引计算(使用Git在包文件中进行delta压缩时使用的类似xdelta的代码)。(这需要真正提取所有这些文件的数据。)无论哪个文件获得最佳配对分数,如果分数超过您选择的最小值(默认为"50%相似"),都会将其配对在一起。

  • 在所有这些额外工作之后仍然未配对的文件要么被删除,要么被重新创建。(如果添加--find-copies--find-copies-harder,这里还会引入一些复杂情况,但我们在这里会忽略它们。)

现在文件已经配对了,也就是说,现在git diff知道,比如说,旧中的文件README.md与新的文件CCD16大部分匹配,所以这两个文件必须是一个具有一个标识的单个文件,而不是两个具有两个标识的不同文件

  • -:从old中找到的文件版本中删除此行
  • +:将此行添加到old中找到的文件版本中

如果您遵循所有指令,包括顶部给出的任何"重命名此文件"指令,将把中的文件更改为


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>

和:

git diff --find-renames <hash of merge base> <hash of other commit>

两个diff在内部git diff期间根据需要对任何未配对的文件名进行相似性计算。

并发症

添加-B告诉Git自动破解配对文件:仅仅因为一个文件在两个提交中都命名为README并不意味着这真的是同一个文件。例如,如果您将README重命名为old/README,然后将new/README重命名为README,该怎么办?在这种情况下,Git将在我前面提到的步骤中对自动配对的文件进行相似性计算。如果相似度太低,Git将破坏配对。稍后,如果相似度不是太低,并且配对仍然中断,Git将重新加入两个文件,因此-B两个数字,而不仅仅是一个。

merge命令不允许您提供-B参数。(可以说,它应该这样做。)

如果使用--find-copies--find-copies-harder,Git会查看部分或全部源文件("旧"),以查看是否从中复制了新创建的目标文件("新")。这些文件使用相同的相似性索引。这一步骤发生在重命名检测之后,有时只会将修改后的文件视为可能的来源,同样是因为它的计算成本很高。

merge命令也不允许您指定查找副本选项。

最新更新