维基百科解释了自动重命名检测:
简单地说,给定一个修订版N的文件,一个同名的文件 修订版N-1是其默认祖先。但是,当没有 在修订版N-1中命名相同的文件,Git搜索存在的文件 仅在修订版N-1中与新文件非常相似。
重命名检测显然归结为类似的文件检测。这个算法记录在哪里吗?很高兴知道自动检测到哪种变换。
答案 0 :(得分:84)
Git跟踪文件内容,而不是文件名。因此,重命名文件而不更改其内容很容易被git检测。 (Git不跟踪,但执行 检测 ;使用git mv
或git rm
和git 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 94b82d5 的 commit 9dd29db、commit 6623a52、commit 05d2c61、Elijah 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)中最后一次碰撞,并且因为处理器变得更快了,而且因为人们进行了丑陋的合并,导致了问题并将其报告给邮件列表(表明人们愿意花更多的时间等待)。
从那时起:
merge.renameLimit
设置为 32767 以上,一旦代码支持,以获得正确的选择最后一点可能值得多解释一下:
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 94b82d5 的 commit 9dd29db、commit 6623a52、commit 05d2c61、Elijah Newren (newren
)(2021 年 7 月 15 日)。
(2021 年 7 月 28 日于 Junio C Hamano -- gitster
-- 被 commit 268055b 合并)
doc
:阐明重命名/复制限制的文档签字人:Elijah Newren
<块引用>文档中的一些地方暗示重命名/复制检测始终是二次的,或者所有(未配对的)文件都涉及重命名/复制检测的二次部分。
以下两次提交各自引入了一个例外:
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.renameLimit
和 merge.renameLimit
都没有指定,
当前默认为 1000。
如果重命名检测关闭,则此设置无效。
diff.renameLimit
现在包含在其 man page 中:
diff-options
和 -M
选项涉及一些初步步骤
可以廉价地检测重命名/副本的子集,然后是
比较所有剩余的详尽后备部分
所有相关来源的未配对目的地。
(对于重命名,
只有剩余的未配对来源是相关的;对于副本,所有
原始来源是相关的。)
对于 N 个源和目标,这个详尽的检查是 -C
。
这个选项
防止重命名/复制检测的详尽部分
如果涉及的源/目标文件数,则运行
超过指定数量。
默认为 O(N^2)
。