什么是git的启发式,用于为文件路径分配内容修改?

kjo*_*kjo 10 git

精简版:

如果没有仔细研究git源代码,我在哪里可以找到git用于将内容块与特定跟踪路径名相关联的启发式的完整描述?


详细版本:

在下面的(Unix)shell演示交互中,两个文件ab" git-commit已经"被修改,以便(有效地)将大部分a内容传输到b最后,最后两个文件再次被提交.

要寻找的关键是第二个的输出git commit以线结束

rename a => b (99%)
Run Code Online (Sandbox Code Playgroud)

即使没有重命名文件(通常意义上)(!?!).


在演示演示之前,这个简短的描述可能会让您更容易理解.

该文件的内容ab通过组合这三个辅助文件,内容产生../A,../B../C.象征性地,状态ab可以表示为

../A + ../C -> a
../B        -> b
Run Code Online (Sandbox Code Playgroud)

就在第一次提交之前,和

../A        -> a
../B + ../C -> b
Run Code Online (Sandbox Code Playgroud)

就在第二个之前.

好的,这是演示.


首先,我们展示的辅助文件的内容../A,../B以及../C:

head ../A ../B ../C
# ==> ../A <==
# ...
# 
# ==> ../B <==
# ###
# 
# ==> ../C <==
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
Run Code Online (Sandbox Code Playgroud)

(以#对应于输出到终端的行开始;实际输出行没有前导#.)

接下来,我们创建文件ab显示其内容并提交它们

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
Run Code Online (Sandbox Code Playgroud)

接下来,我们修改文件ab,并显示其新的内容:

cat ../A      > a
cat ../B ../C > b
head a b
# ==> a <==
# ...
#
# ==> b <==
# ###
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
Run Code Online (Sandbox Code Playgroud)

最后,我们承诺修改ab; 注意输出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%)
Run Code Online (Sandbox Code Playgroud)

我将这种行为合理化如下.

据我了解,git将目录结构信息(例如它所跟踪的文件的路径名)视为辅助信息或元数据(如果您愿意),与其跟踪的主要信息相关联,即各种内容块.

由于文件的内容和名称(包括路径名)可能在提交之间发生变化,因此git必须使用启发式方法将路径名与内容块相关联.但就其性质而言,启发式方法并不能保证100%的工作时间.这种启发式方法的失败采用历史的形式,并不忠实地表示实际发生的事情(例如,即使没有文件被重命名,通常也会报告文件重命名).

对此解释的进一步确认(即,一些启发式算法正在发挥作用)是,AFAICT,如果传输的块的大小不够大,则输出git commit将不包括rewrite/rename线.(我在这篇文章的末尾包括了这个案例的演示,FWIW.)

我的问题是:没有仔细研究git源代码,在哪里可以找到git用于将内容块与特定跟踪路径名相关联的启发式的完整描述?


除了辅助文件../C比以前短一行之外,第二个演示在各方面都与第一个演示相同.

head ../A ../B ../C
# ==> ../A <==
# ...
# 
# ==> ../B <==
# ###
# 
# ==> ../C <==
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================

cat ../A ../C > a
cat ../B      > b
head a b
# ==> a <==
# ...
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# 
# ==> b <==
# ###

git add .
git commit -a --allow-empty-message -m ''
# [master (root-commit) a06a689] 
#  2 files changed, 7 insertions(+)
#  create mode 100644 a
#  create mode 100644 b

cat ../A      > a
cat ../B ../C > b
head a b
# ==> a <==
# ...
# 
# ==> b <==
# ###
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================

git add .
git commit -a --allow-empty-message -m ''
# [master 87415a1] 
#  2 files changed, 5 insertions(+), 5 deletions(-)
Run Code Online (Sandbox Code Playgroud)

Edw*_*son 7

正如您所注意到的,Git使用启发式方法执行重命名检测,而不是被告知发生了重命名.git mv实际上,该命令只是在新文件路径上添加一个add并删除旧文件路径.因此,通过将添加的文件的内容与先前提交的已删除文件的内容进行比较来执行重命名检测.

首先,候选人被收集.任何新文件都可以重命名目标,任何已删除的文件都可以重命名源.此外,重写更改被破坏,使得与其先前版本不同50%以上的文件既可能是重命名源,也可能是重命名目标.

接下来,检测到相同的重命名.如果重命名文件而不进行任何更改,则该文件将以相同方式进行哈希处理.只需在不读取文件内容的情况下对索引中的散列进行比较就可以检测到这些,因此从候选列表中删除这些将减少您需要执行的比较次数.

最后,执行相似性比较.每个候选文件中的每一行都经过散列并在排序列表中收集.长行分为60个字符.假设它们对相似性匹配没有很大贡献,可以剥离仅空白行.将来自每个候选源的线散列与来自每个候选目标的线散列进行比较.如果两个列表的相似度为60%,则视为重命名.


tor*_*rek 2

...没有深入研究 git 的源代码,我在哪里可以找到 git 用于将内容块与特定跟踪路径名关联起来的启发式方法的完整描述?

根据您所说的“完整”的含义,我认为您找不到这样的东西。(特别是,“百分比”是如何计算的?是按行,还是字符/字节,还是其他什么?做面向单词的差异会改变事情吗?)但神奇之处在于,git diff它每次都是动态计算的将显示差异;启发式有几个控制旋钮可以提供强有力的线索:

--no-renames

关闭重命名检测,即使配置文件默认这样做。

-B[<n>][/<m>], --break-rewrites[=[<n>][/<m>]]

将完整的重写更改分解为删除和创建对。这有两个目的:

  • 它影响相当于完全重写文件的更改方式,而不是作为一系列删除和插入与恰好与上下文文本匹配的极少数行混合在一起,而是作为对所有旧内容的单个删除,然后是一次插入所有新内容,数字 m 控制 -B 选项的这方面(默认为 60%)。 -B/70%指定结果中应保留少于 30% 的原始内容,以便 Git 将其视为完全重写(即,否则生成的补丁将是与上下文行混合在一起的一系列删除和插入)。

  • 与 一起使用时-M,完全重写的文件也被视为重命名的源(通常-M仅将消失的文件视为重命名的源),并且数字 n 控制 -B 选项的这方面(默认为 50 %)。 -B20%指定与文件大小的 20% 或更多相比添加和删除的更改有资格被选为重命名为另一个文件的可能来源。

等等; 请参阅git-diff 的文档