试图理解 `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 backsomething

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 statusgit 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 字节的字符串进行哈希处理

现在 SD 都有散列 table,每个可能的散列 hiS[=中出现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_copiedliteral_added)交给了function estimate_similarity in diffcore-rename.c。它完全忽略 literal_added 计数(此计数用于决定如何构建包文件增量,但不用于重命名评分)。相反,只有 src_copied 个数字很重要:

score = (int)(src_copied * MAX_SCORE / max_size);

其中 max_size 是两个输入文件 SD.[=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_SCORE60000.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.