试图理解 `git diff` 和 `git mv` 重命名检测机制
Trying to understand `git diff` and `git mv` rename detection mechanism
这是 的跟进。
在编辑之前,最初创建的文件 something
被重命名为 somethingelse
,可以观察到 here:
git mv something somethingelse
文件 somethingelse
然后在第二次 vim 编辑之前得到 renamed back 到 something
:
git mv somethingelse something
基本在以下portion of the code:
# If you add something to the first line, the rename will not be detected by Git
# However, if you instead create 2 newlines and fill line 3 with new code,
# the rename gets detected for whatever reason
printf "\nCOMMAND: vim something\n\n"
vim something
如果此时我将 abc
添加到代码中,我们将得到:
First line of code. abc
我认为是在第 1 行添加了 4 个字节,而这又将以这样的方式结束:
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: something
deleted: somethingelse
那么,如果我们在第三行加一个换行符并输入abc(也应该是4个字节,错了请指正):
First line of code.
abc
突然,Git 将检测重命名(包括编辑):
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: somethingelse -> something
@torek 给出的一个很好的 answer/comment 在一定程度上解释了这一点,考虑了 git status
的 git diff
重命名检测阈值。
不应该 Git 表现相同,因为我们在两种情况下都添加了 4 个字节,但方式不同,换行符是否与此有关?
据我所知,Git 的“相似性指数”计算没有在 the source, starting with diffcore-delta.c.
以外的任何地方记录
计算两个文件S(源)和D(目标)的相似度指数,Git:
- 读取两个文件
- 计算文件 S
的所有块的哈希值 table
- 计算文件 D
的所有块的第二个哈希值 table
这两个散列 table 中的条目只是对该散列值实例的出现次数的计数(如下所述,加上块的长度)。
文件块的哈希值由以下公式计算得出:
- 从当前文件偏移量开始(最初为零)
- 读取 64 个字节或直到
'\n'
个字符,以先到者为准
- 如果文件声称是文本文件并且在
'\n'
之前有一个 '\r'
,则丢弃 '\r'
- 使用链接文件中显示的算法对生成的最多 64 字节的字符串进行哈希处理
现在 S 和 D 都有散列 table,每个可能的散列 hi在S[=中出现nS次121=] 和 nD in D (两者都可能为零,尽管代码会直接跳过双零散列值)。如果 D 中的出现次数小于或等于 S 中出现的次数,即 nD ≤ nS—然后D”从S复制" nD 次。如果D的出现次数超过S的次数(包括S的次数是零),那么 D 的“文字加法”为 nD - nS 次哈希块,并且 D 也复制所有 nS原始事件也是如此。
每个哈希块保留其输入字节数,这些乘以“块”的副本数或添加数以获得复制的字节数或添加。 (删除,其中 D 缺少 S 中存在的项目,此处仅具有间接影响:字节复制和添加计数变小,但 Git 没有具体计算删除本身。)
在diffcore_count_changes
中计算出的这两个值(src_copied
和literal_added
)交给了function estimate_similarity
in diffcore-rename.c
。它完全忽略 literal_added
计数(此计数用于决定如何构建包文件增量,但不用于重命名评分)。相反,只有 src_copied
个数字很重要:
score = (int)(src_copied * MAX_SCORE / max_size);
其中 max_size
是两个输入文件 S 和 D.[=48 中较大者的字节大小=]
注意有一个较早的计算:
max_size = ((src->size > dst->size) ? src->size : dst->size);
base_size = ((src->size < dst->size) ? src->size : dst->size);
delta_size = max_size - base_size;
并且如果两个文件发生了变化大小“太多”:
if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
return 0;
我们甚至从未进入 diffcore-delta.c
代码来散列它们。这里的 minimum_score
是 -M
或 --find-renames
的参数,转换为比例数。 MAX_SCORE
是 60000.0
(键入 double
),因此当您使用默认 -M50%
时,默认 minimum_score
是 30000(60000 的一半)。但是,除了 CR-before-LF 进食的情况外,这个特定的快捷方式不应该影响更昂贵的相似性计算的结果。
[编辑:现在已过时:] git status
始终使用默认值。没有旋钮可以更改阈值(也没有更改重命名查找队列中允许的文件数)。如果有代码会变成 here, setting the rename_score
field of the diff options. Until Git version 2.18.0, there was no way to control this for git status
. In Git 2.18.0 and later, git status
has the same --find-renames
option as git diff
. The status.renames
option in the Git configuration enables any default detection, and if unset, git status
obeys the diff.renames
setting; see the git config
documentation and the git status
documentation.
这是
在编辑之前,最初创建的文件 something
被重命名为 somethingelse
,可以观察到 here:
git mv something somethingelse
文件 somethingelse
然后在第二次 vim 编辑之前得到 renamed back 到 something
:
git mv somethingelse something
基本在以下portion of the code:
# If you add something to the first line, the rename will not be detected by Git
# However, if you instead create 2 newlines and fill line 3 with new code,
# the rename gets detected for whatever reason
printf "\nCOMMAND: vim something\n\n"
vim something
如果此时我将 abc
添加到代码中,我们将得到:
First line of code. abc
我认为是在第 1 行添加了 4 个字节,而这又将以这样的方式结束:
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: something
deleted: somethingelse
那么,如果我们在第三行加一个换行符并输入abc(也应该是4个字节,错了请指正):
First line of code.
abc
突然,Git 将检测重命名(包括编辑):
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: somethingelse -> something
@torek 给出的一个很好的 answer/comment git status
的 git diff
重命名检测阈值。
不应该 Git 表现相同,因为我们在两种情况下都添加了 4 个字节,但方式不同,换行符是否与此有关?
Git 的“相似性指数”计算没有在 the source, starting with diffcore-delta.c.
以外的任何地方记录计算两个文件S(源)和D(目标)的相似度指数,Git:
- 读取两个文件
- 计算文件 S 的所有块的哈希值 table
- 计算文件 D 的所有块的第二个哈希值 table
这两个散列 table 中的条目只是对该散列值实例的出现次数的计数(如下所述,加上块的长度)。
文件块的哈希值由以下公式计算得出:
- 从当前文件偏移量开始(最初为零)
- 读取 64 个字节或直到
'\n'
个字符,以先到者为准 - 如果文件声称是文本文件并且在
'\n'
之前有一个'\r'
,则丢弃'\r'
- 使用链接文件中显示的算法对生成的最多 64 字节的字符串进行哈希处理
现在 S 和 D 都有散列 table,每个可能的散列 hi在S[=中出现nS次121=] 和 nD in D (两者都可能为零,尽管代码会直接跳过双零散列值)。如果 D 中的出现次数小于或等于 S 中出现的次数,即 nD ≤ nS—然后D”从S复制" nD 次。如果D的出现次数超过S的次数(包括S的次数是零),那么 D 的“文字加法”为 nD - nS 次哈希块,并且 D 也复制所有 nS原始事件也是如此。
每个哈希块保留其输入字节数,这些乘以“块”的副本数或添加数以获得复制的字节数或添加。 (删除,其中 D 缺少 S 中存在的项目,此处仅具有间接影响:字节复制和添加计数变小,但 Git 没有具体计算删除本身。)
在diffcore_count_changes
中计算出的这两个值(src_copied
和literal_added
)交给了function estimate_similarity
in diffcore-rename.c
。它完全忽略 literal_added
计数(此计数用于决定如何构建包文件增量,但不用于重命名评分)。相反,只有 src_copied
个数字很重要:
score = (int)(src_copied * MAX_SCORE / max_size);
其中 max_size
是两个输入文件 S 和 D.[=48 中较大者的字节大小=]
注意有一个较早的计算:
max_size = ((src->size > dst->size) ? src->size : dst->size);
base_size = ((src->size < dst->size) ? src->size : dst->size);
delta_size = max_size - base_size;
并且如果两个文件发生了变化大小“太多”:
if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
return 0;
我们甚至从未进入 diffcore-delta.c
代码来散列它们。这里的 minimum_score
是 -M
或 --find-renames
的参数,转换为比例数。 MAX_SCORE
是 60000.0
(键入 double
),因此当您使用默认 -M50%
时,默认 minimum_score
是 30000(60000 的一半)。但是,除了 CR-before-LF 进食的情况外,这个特定的快捷方式不应该影响更昂贵的相似性计算的结果。
[编辑:现在已过时:] Until Git version 2.18.0, there was no way to control this for git status
始终使用默认值。没有旋钮可以更改阈值(也没有更改重命名查找队列中允许的文件数)。如果有代码会变成 here, setting the rename_score
field of the diff options.git status
. In Git 2.18.0 and later, git status
has the same --find-renames
option as git diff
. The status.renames
option in the Git configuration enables any default detection, and if unset, git status
obeys the diff.renames
setting; see the git config
documentation and the git status
documentation.