简短版本:
对
git
的源代码进行了仔细研究,在哪里可以找到git
用于将内容块与特定跟踪路径名相关联的启发式的完整描述?
详细版本:
在下面的(Unix)shell演示交互中,两个文件a
和b
是“git-commit
”,然后对它们进行修改以便(有效)转移大多数a
的内容都是b
,最后这两个文件再次被提交。
要注意的关键是第二个git commit
的输出以行结尾
rename a => b (99%)
即使没有重命名文件(通常意义上)(!?!)。
在展示演示之前,这个简短的描述可能会让您更容易理解。
文件a
和b
的内容是通过组合三个辅助文件../A
,../B
和../C
的内容生成的。符号上,a
和b
的状态可以表示为
../A + ../C -> a
../B -> b
在第一次提交之前,
../A -> a
../B + ../C -> b
在第二个之前。
好的,这是演示。
首先,我们显示辅助文件../A
,../B
和../C
的内容:
head ../A ../B ../C
# ==> ../A <==
# ...
#
# ==> ../B <==
# ###
#
# ==> ../C <==
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
(以#
开头的行对应于到终端的输出;实际输出行没有前导#
。)
接下来,我们创建文件a
和b
,显示其内容并提交它们
cat ../A ../C > a
cat ../B > b
head a b
# ==> a <==
# ...
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
#
# ==> b <==
# ###
git add a b
git commit --allow-empty-message -m ''
# [master (root-commit) 3576df7]
# 2 files changed, 8 insertions(+)
# create mode 100644 a
# create mode 100644 b
接下来,我们修改文件a
和b
,并显示其新内容:
cat ../A > a
cat ../B ../C > b
head a b
# ==> a <==
# ...
#
# ==> b <==
# ###
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
最后,我们提交修改后的a
和b
;请注意git commit
的输出:
git add a b
git commit --allow-empty-message -m ''
# [master 25b806f]
# 2 files changed, 2 insertions(+), 8 deletions(-)
# rewrite a (99%)
# rename a => b (99%)
我将此行为合理化如下。
根据我的理解,git
将目录结构信息(例如它正在跟踪的文件的路径名)视为 secondary 信息 - 或元数据(如果您愿意),将其关联起来使用它跟踪的主要信息,即各种内容块。
由于文件的内容和名称(包括路径名)可能会在提交之间发生变化,因此git
必须使用启发式方法将路径名与内容块相关联。但就其性质而言,启发式方法并不能保证100%的工作时间。这种启发式方法的失败采用历史的形式,并不忠实地表示实际发生的事情(例如,即使没有文件被重命名,通常也会报告文件重命名。)
对此解释的进一步确认(即,某些启发式算法正在发挥作用)是,AFAICT,如果传输的块的大小不够大,git commit
的输出将不包括{{ 1}}行。 (我在本文末尾对FWIW进行了此案例演示。)
我的问题是:对
rewrite/rename
的源代码缺乏了解,在哪里可以找到git
用于将内容块与特定跟踪路径名相关联的启发式的完整描述?< / p>
除了辅助文件git
比以前短一行之外,第二个演示在各个方面与第一个演示相同。
../C
答案 0 :(得分:7)
正如您所注意到的,Git使用启发式方法执行重命名检测,而不是被告知发生了重命名。事实上,git mv
命令只是在新文件路径上添加一个添加项并删除旧文件路径。因此,通过将添加的文件的内容与先前提交的已删除文件的内容进行比较来执行重命名检测。
首先,收集候选人。任何新文件都可以重命名目标,任何已删除的文件都可以重命名源。此外,重写更改被破坏,使得与其先前版本相差50%以上的文件既可能是重命名源,也可能是重命名目标。
接下来,检测到相同的重命名。如果重命名文件而不进行任何更改,则该文件将以相同方式进行哈希处理。只需在不读取文件内容的情况下对索引中的散列进行比较就可以检测到这些,因此从候选列表中删除这些内容将减少您需要执行的比较次数。
最后,执行相似性比较。每个候选文件中的每一行都经过散列并在排序列表中收集。长行分为60个字符。假设它们对相似性匹配没有太大贡献,可以剥离仅空白行。将来自每个候选源的线散列与来自每个候选目标的线散列进行比较。如果两个列表的相似度为60%,则视为重命名。
答案 1 :(得分:2)
...没有仔细研究git的源代码,在哪里可以找到git用来将内容块与特定跟踪路径名相关联的启发式的完整描述?
根据你所说的“完整”,我不认为你能找到这样的东西。 (特别是,如何计算“百分比”?是按行,字符/字节还是其他东西?做一个面向字的差异会改变一些东西吗?)但是魔法全部都在git diff
里面,它在那里每次要显示差异时动态计算;并且启发式方法有几个控制旋钮,可以提供强有力的线索:
--no-renames
即使配置文件提供,也请关闭重命名检测 默认情况下这样做。
-B[<n>][/<m>], --break-rewrites[=[<n>][/<m>]]
将完全重写更改分为删除和创建对。 这有两个目的:
它会影响相当于a的总重写的更改方式 文件不是作为一系列删除和插入混合在一起的 很少的一行恰好在文本上与上下文匹配,但是 作为单个删除所有旧的后跟单个 插入所有新的东西,数字m控制这个方面 -B选项(默认为60%)。
-B/70%
指定更少 超过30%的原始应保留在Git的结果中 认为它是一个完全重写(即否则产生的补丁 将一系列删除和插入混合在一起 上下文行)。与
-M
一起使用时,完全重写的文件也被视为 重命名的来源(通常-M
只考虑一个文件 作为重命名的来源消失了,数字n控制 -B选项的这一方面(默认为50%)。-B20%
指定 添加和删除的变化与20%或更多相比 文件的大小有资格作为可能的选择 重命名的来源为另一个文件。
等等;请参阅documentation for git-diff。