git如何检测类似文件的重命名检测?

时间:2011-10-29 11:22:18

标签: git

维基百科解释了自动重命名检测:

  

简单地说,给定一个修订版N的文件,一个同名的文件   修订版N-1是其默认祖先。但是,当没有   在修订版N-1中命名相同的文件,Git搜索存在的文件   仅在修订版N-1中与新文件非常相似

重命名检测显然归结为类似的文件检测。这个算法记录在哪里吗?很高兴知道自动检测到哪种变换。

3 个答案:

答案 0 :(得分:84)

Git跟踪文件内容,而不是文件名。因此,重命名文件而不更改其内容很容易被git检测。 (Git不跟踪,但执行 检测 ;使用git mvgit rmgit add实际上是相同的。)

将文件添加到存储库时,文件名位于树对象中。实际文件内容将作为二进制大对象( blob )添加到存储库中。 Git不会为包含相同内容的其他文件添加另一个blob。实际上,Git不能将内容存储在文件系统中,其中散列的前两个字符是目录名,其余字符是其中的文件名。因此,检测重命名是一个比较哈希的问题。

为了检测重命名文件的微小变化,Git使用某些算法和阈值限制来查看这是否是重命名。例如,查看-M的{​​{1}}标记。还有一些配置值,例如git diff(合并期间执行重命名检测时要考虑的文件数)。

要了解git如何处理类似的文件(即,哪些文件转换被视为重命名),请探索可用的配置选项和标志,如上所述。你不需要考虑如何。要了解git如何实际完成这些任务,请查看用于查找文本差异的算法,并阅读git源代码。

算法仅适用于差异,合并和日志目的 - 它们不会影响git如何存储它们。文件内容的任何小变化都意味着为其添加了新对象。该级别没有增量或差异。当然,稍后,对象可能被打包,其中增量存储在packfiles中,但这与重命名检测无关。

答案 1 :(得分:4)

有许多算法可以检测文本之间的相似性,版本控制系统通常只使用这些算法来存储两个版本之间的差异。像WinMerge这样的工具非常智能,可以检测差异,即使在行内也是如此,所以我没有看到为什么这些算法不会用于此重命名检测的原因。

以下是关于algorithms to detect similar texts的讨论。其中一些算法可能针对自然语言进行了优化,而其他算法可能更适合源代码,但实际上它们非常相似。

答案 2 :(得分:0)

<块引用>

该算法是否记录在任何地方?

至少在 Git 2.33(2021 年第 3 季度)中有说明,其中关于“git diff -l<n>(man)diff.renameLimit 的文档已更新,并且这些限制的默认值已提高。

请参阅 commit 94b82d5commit 9dd29dbcommit 6623a52commit 05d2c61Elijah Newren (newren)(2021 年 7 月 15 日)。
(2021 年 7 月 28 日于 Junio C Hamano -- gitster --commit 268055b 合并)

<块引用>

rename:再次默认碰撞限制

签字人:Elijah Newren

<块引用>

这些是在 commit 92c57e5(“bump rename limit defaults (again)”, 2011-02-19, Git v1.7.5-rc0 -- merge)中最后一次碰撞,并且因为处理器变得更快了,而且因为人们进行了丑陋的合并,导致了问题并将其报告给邮件列表(表明人们愿意花更多的时间等待)。

从那时起:

  • Linus 一直建议内核人员设置 diff.renameLimit=0(目前映射到 32767)
  • 拥有大量重命名的代码库的人很乐意将 merge.renameLimit 设置为 32767 以上,一旦代码支持,以获得正确的选择
  • 处理器变得更快
  • 发现上次使用的计时方法可能使用了太大的示例文件。

最后一点可能值得多解释一下:

  • 使用的“平均”文件大小似乎是当时 linux 内核历史记录中的平均 blob 大小(可能是 v2.6.25 或接近它的版本)。
  • 由于较大的文件被更频繁地修改,因此此类计算权重针对较大的文件。
  • 随着时间的推移,较大的文件可能更有可能被修改,但不太可能被重命名——树中的平均和中值 blob 大小略高于历史记录中 blob 大小的均值和中值直到 linux 内核的那个版本。
  • v2.6.25 中的平均 blob 大小是历史上导致该点的平均 blob 大小的一半
  • v2.6.25 中的 blob 大小中位数约为 v2.6.25 中 blob 大小的平均大小的 40%。
  • 由于平均 blob 大小是 blob 大小中位数的两倍以上,因此不会将任何与平均值一样大的文件与任何中位数或更小的文件进行比较(因为它们的差异将超过 50%)。< /li>
  • 由于提供 O(n^2) 行为的是比较文件的数量,因此中等大小的文件应该比平均大小的文件更重要。

以上的综合影响是过去计算中使用的文件大小可能大了大约 5 倍。
结合大约 30% 的 CPU 性能改进,我们可以将限制增加 sqrt(5/(1-.3)) = 2.67 倍,同时保持原来规定的时间限制。

保持相同的大致时间限制可能对 diff.renameLimit 有意义(例如 git log -p(man) 中没有进度反馈),但体验以上表明 merge.renameLimit 可以显着扩展。
事实上,为 merge.renameLimit 设置无限制的默认设置可能是有意义的,但这可能需要结合对进度显示方式的更改。
(有关该区域的详细信息,请参阅 https://lore.kernel.org/git/YOx+Ok%2FEYvLqRMzJ@coredump.intra.peff.net/。)
现在,让我们将大概的时间限制从 10 秒提高到 1 米。

(注意:我们不想使用实际时间限制,因为获得取决于当天系统负载情况的结果感觉很糟糕,而且因为我们没有发现直到之后才能获得所有重命名我们投入了大量工作,而不仅仅是预先告诉用户涉及的文件太多。)

使用 diff.renameLimit 的原始时间限制 2 秒,并将 merge.renameLimit 从 10 秒增加到 60 秒,我使用此提交消息末尾的简单脚本(在 AWS { {1}} 报告为“Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz”):

c5.xlarge

所以让我们四舍五入到不错的偶数,并突破 N Timing 0 1.995s 0 59.973s 400->1000, 的限制。

这是 1000->7000 脚本(特别改编自 https://lore.kernel.org/git/20080211113516.GB6344@coredump.intra.peff.net/ 以避免触发 basename-guided rename detection 的线性处理):

measure_rename_perf

#!/bin/bash n=$1; shift rm -rf repo mkdir repo && cd repo git init -q -b main mkdata() { mkdir $1 for i in `seq 1 $2`; do (sed "s/^/$i /" <../sample echo tag: $1 ) >$1/$i done } mkdata initial $n git add . git commit -q -m initial mkdata new $n git add . cd new for i in *; do git mv $i $i.renamed; done cd .. git rm -q -rf initial git commit -q -m new time git diff-tree -M -l0 --summary HEAD^ HEAD 现在包含在其 man page 中:

<块引用>

git config。如果未设置,则当前默认值为 1000。

-l 现在包含在其 man page 中:

<块引用>

当前默认为 7000。


而且,仍然使用 Git 2.33(2021 年第三季度):

请参阅 commit 94b82d5commit 9dd29dbcommit 6623a52commit 05d2c61Elijah Newren (newren)(2021 年 7 月 15 日)。
(2021 年 7 月 28 日于 Junio C Hamano -- gitster --commit 268055b 合并)

<块引用>

doc:阐明重命名/复制限制的文档

签字人:Elijah Newren

<块引用>

文档中的一些地方暗示重命名/复制检测始终是二次的,或者所有(未配对的)文件都涉及重命名/复制检测的二次部分。
以下两次提交各自引入了一个例外:

  • 9027f53(“为精确重命名执行线性时间/空间重命名逻辑”,2007-10-25,Git v1.5.4-rc0 -- merge
  • bd24aa2(“git config:指导基于基本名称的不精确重命名检测”,2021-02-14,Git v2.31.0-rc1 -- merge

(作为旁注,对于复制检测,基名引导的不精确重命名检测被关闭,精确重命名只会导致源(没有目标)从二次检测中使用的文件集中删除。
因此,对于复制检测,文档更接近正确。)

避免暗示重命名/复制检测中涉及的所有文件都受完整二次算法的约束。
同时,还要注意所有这些设置的默认值。

diffcore-rename 现在包含在其 man page 中:

<块引用>

在详尽部分中要考虑的文件数 复制/重命名检测;相当于'git diff'选项git config
如果未设置,则当前默认值为 400。
如果重命名检测关闭,则此设置无效。

-l 现在包含在其 man page 中:

<块引用>

在详尽部分中要考虑的文件数 合并期间重命名检测。

如果没有指定,默认为git config的值。
如果 diff.renameLimitmerge.renameLimit 都没有指定, 当前默认为 1000。
如果重命名检测关闭,则此设置无效。

diff.renameLimit 现在包含在其 man page 中:

<块引用>

diff-options-M 选项涉及一些初步步骤 可以廉价地检测重命名/副本的子集,然后是 比较所有剩余的详尽后备部分 所有相关来源的未配对目的地。
(对于重命名, 只有剩余的未配对来源是相关的;对于副本,所有 原始来源是相关的。)

对于 N 个源和目标,这个详尽的检查是 -C

这个选项 防止重命名/复制检测的详尽部分 如果涉及的源/目标文件数,则运行 超过指定数量。
默认为 O(N^2)