Lor*_*nja 2 git version-control diff git-branch
git diff --no-index --no-prefix --summary -U4000 目录1 目录2
这按预期工作,因为它返回两个目录之间所有文件的差异。添加的文件按预期输出,删除的文件也会产生预期的 diff 输出。
但是,由于 diff 将文件路径考虑为文件名的一部分,因此两个不同目录中具有相同名称的文件会导致 diff 输出带有重命名标志,而不是更改。
有没有办法告诉 git 不考虑 diff 中的完整文件路径,而只查看文件名,就好像文件来自同一目录一样?
有没有办法让 git 真正知道不同目录中同一文件的副本是否确实被重命名?我不明白如何,除非它有一种以某种方式比较文件 md5 的方法(可能是一个错误的猜测,哈哈)。
使用分支而不是目录可以轻松解决此问题吗?如果可以,上面列出的命令的分支版本是什么?
这里有多个问题,其答案相互交织。让我们从重命名和复制检测开始,然后继续讨论分支。
\n\n\n\n\n但是,由于 diff 将文件路径考虑为文件名的一部分,因此两个不同目录中具有相同名称的文件会导致 diff 输出带有重命名标志,而不是更改。
\n
这不太正确。(下面的文字旨在解决您的第 1 项和第 2 项。)
\n\n尽管您正在使用--no-index(大概是为了使 Git 在存储库外部的目录上工作),但 Git\ 的 diff 代码在所有情况下的行为方式都是相同的。为了比较(比较)两棵树中的两个文件,Git 必须首先确定文件身份。也就是说,有两组文件:“左侧”或源树(第一个目录名)中的文件,以及“右侧”或目标树(第二个目录名)中的文件。左侧的一些文件与右侧的一些文件是相同的文件。左边的一些文件是不同的文件,没有对应的右边文件,即它们已被删除。最后,右侧的一些文件是新的,即已创建。
“同一文件”的文件不必具有相同的路径名。在本例中,这些文件已被重命名为.
\n\n以下是它的详细工作原理。请注意,使用 时,“完整路径名”会有所修改git diff --no-index dir1 dir2:“完整路径名”是去掉dir1和dir2前缀后剩下的内容。
比较左侧树和右侧树时,具有相同完整路径名的文件通常会自动被视为“同一文件”。我们将所有这些文件放入“要比较的文件”队列中,并且没有一个文件会显示为被重命名。请注意这里的“通常”一词\xe2\x80\x94,我们稍后会再讨论这一点。
\n\n这给我们留下了两个剩余的文件列表:
\n\nNa\xc3\xafvely,我们可以简单地声明所有这些源端文件都已被删除,并且所有这些目标文件都已被创建。您可以指示git diff以这种方式进行操作:设置--no-renames标志以禁用重命名检测。
或者,Git 可以继续使用更智能的算法:设置--find-renamesand/or-M <threshold>标志来执行此操作。在 Git 版本 2.9 及更高版本中,重命名检测默认处于启用状态。
现在,Git 如何确定源文件与目标文件具有相同的标识?他们有不同的道路;a/b/c.txt左侧对应哪个右侧文件?它可能是d/e/f.bin、 或d/e/f.txt、 或a/b/renamed.txt,等等。实际的算法比较简单,过去并没有将最终名称组件生效(我不确定现在是否如此,Git 正在不断发展):
如果源文件和目标文件的内容完全匹配,则将它们配对。因为 Git 对内容进行哈希处理,所以这种比较速度非常快。a/b/c.txt我们可以通过哈希 ID 将左侧的文件与右侧的每个文件进行比较,只需查看所有文件的哈希 ID。因此,我们首先遍历所有源文件,找到匹配的目标文件,将新的文件对放入 diff 队列中,并将它们从两个列表中拉出。
对于所有剩余的源文件和目标文件,运行高效但不适合git diff输出的算法来计算“文件相似度”。至少<threshold>与某个目标文件相似的源文件会导致配对,并且该文件对将被删除。默认阈值为 50%:如果您启用了重命名检测但未选择特定阈值,则此时仍在列表中且相似度为 50% 的两个文件将进行配对。
任何剩余文件都将被删除或创建。
现在我们已经找到了所有配对,git diff继续比较配对的相同身份文件,并告诉我们已删除的文件已被删除,并且新创建的文件已创建。如果相同标识文件的两个路径名不同,git diff则表示该文件已重命名。
任意文件配对代码很昂贵(即使同名配对代码非常便宜),因此 Git 对进入这些配对源和目标列表的名称数量有限制。该限制是通过配置的git config diff.renameLimit。多年来,默认值一直在攀升,现在有数千个文件。您可以将其设置为0(零)以使 Git 始终使用其自己的内部最大值。
上面我说了,一般情况下,同名的文件会自动配对。这通常是正确的做法,因此它是 Git 的默认设置。然而,在某些情况下,名为 的左侧文件a/b/c.txt实际上与名为 的右侧文件无关,例如a/b/c.txt它实际上与右侧文件相关。a/doc/c.txt我们可以告诉 Git打破“差异太大”的文件配对。
我们看到上面使用“相似性索引”来形成文件配对。例如,相同的相似性索引可用于拆分文件: 。-B20%/60%这两个数字相加不必等于 100%,您实际上可以省略其中之一或两者:如果您设置模式,则每个数字都有一个默认值-B。
第一个数字是可以将默认已配对文件放入重命名检测列表的点。使用 时-B20%,如果文件有 20% 不相似(即只有 80% 相似),则该文件将进入“重命名源”列表。如果它从未被视为重命名,它可以与其自动目标 \xe2\x80\x94 重新配对,但此时,第二个数字(斜杠后面的数字)生效。
第二个数字设置了配对肯定被破坏的点。例如-B/70%,如果文件有 70% 不相似(即只有 30% 相似),则配对就会中断。(当然,如果该文件作为重命名源被拿走,则配对已经被破坏。)
除了通常的配对和重命名检测之外,您还可以要求 Git 查找源文件的副本。运行所有常用的配对代码(包括查找重命名和断开配对)后,如果您指定了-C,Git 将查找实际从现有源复制的“新”(即未配对)目标文件。有两种模式,具体取决于您是否指定-C两次或添加:一种仅考虑已修改的--find-copies-harder源文件(即单一情况),另一种模式考虑每个源文件(即两个或案例) )。请注意,在这种情况下,“源文件已修改”意味着源文件已经在配对队列\xe2\x80\x94 中,如果不是,则根据定义,它未“修改”\xe2\x80\x94并且其相应的目标文件具有不同的哈希 ID(同样,这是一个成本非常低的测试,有助于保持单个选项的廉价)。-C-C--find-copies-harder-C
\n\n\n使用分支而不是目录可以轻松解决此问题吗?如果可以,上面列出的命令的分支版本是什么?
\n
分支在这里没有区别。
\n\n在 Git 中,术语“分支”是不明确的。请参阅“分支”到底是什么意思? 但是,对于git diff,分支名称只是解析为单个提交,即该分支的尖端提交。
我喜欢这样画 Git 的分支:
\n\n...--o--o--o <-- branch1\n \\\n o--o--o <-- branch2\nRun Code Online (Sandbox Code Playgroud)\n\n每个小轮os 代表一次提交。在 Git 中,这两个分支名称只是指针:它们指向一个特定的提交。名称branch1指向顶行最右边的提交,名称branch2指向底行最右边的提交。
在 Git 中,每个提交都指向其父级(大多数提交只有一个父级,而合并提交只是具有两个或多个父级的提交)。这就是形成我们也称为“分支”的提交链的原因。分支名称直接指向链的尖端。1
\n\n当你跑步时:
\n\n$ git diff branch1 branch2\nRun Code Online (Sandbox Code Playgroud)\n\nGit 所做的就是将每个名称解析为其相应的提交。例如,如果branch1名称提交1234567...和branch2名称提交89abcde...,这只是做同样的事情:
$ git diff 1234567 89abcde\nRun Code Online (Sandbox Code Playgroud)\n\n实际上,Git 甚至不关心这些是否是提交。Git 只需要左侧或源树和右侧或目标树。这两个树可以来自一个提交,因为一个提交命名了一棵树:任何提交的树都是您进行该提交时拍摄的源快照。它们可以来自分支,因为分支名称命名一个提交,它命名一棵树。其中一棵树可以来自 Git 的“索引”(又名“暂存区”,又名“缓存”),因为索引基本上是一棵扁平树。2 其中一棵树可以作为您的工作树。一棵或两棵树甚至可以不受 Git 的控制(因此有标志--no-index)。
如果您运行git diff --no-index /path/to/file1 /path/to/file2,Git 将简单地比较这两个文件,即将它们视为一对。这完全绕过了所有配对和重命名检测代码。如果没有大量摆弄--no-renames、--find-renames、--rename-threshold等选项可以解决问题,您可以显式比较文件路径,而不是目录(树)路径。对于大量文件来说,这当然会很痛苦。
1超过该点可能会有更多提交,但它仍然是其链的尖端。此外,多个名称可以指向单个提交。我把这种情况画成:
\n\n...--o--o <-- tip1\n \\\n o--o <-- tip2, tip3\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,多个分支名称“后面”的提交实际上位于所有这些分支上。因此,两个底行提交都在 和tip2分支上tip3,而两个顶行提交都在所有三个分支上。尽管如此,每个分支名称都会解析为一个且只有一个提交。
2事实上,为了进行新的提交,Git 只需使用 将索引(就像现在一样)转换为一棵树git write-tree,然后进行命名该树的提交(并使用当前提交作为其父级,并且具有作者和提交者,以及提交消息)。事实上 Git 使用现有索引,这就是为什么您必须git add在提交之前将更新的工作树文件放入索引中。
有一些方便的快捷方式可以让您告诉git commit将文件添加到索引,例如git commit -a或git commit <path>。这些可能有点棘手,因为它们并不总是产生您可能期望的索引。例如,请参阅的--includevs--only选项。git commit <path>它们还通过将主索引复制到新的临时索引来工作;这可能会产生令人惊讶的结果,因为如果提交成功,临时索引将复制回常规索引。