试图理解`git diff`和`git mv`重命名检测机制

时间:2017-09-16 16:39:58

标签: git github git-diff git-status git-mv

这是another question I asked before的后续内容。

在进行编辑之前,最初创建的文件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给出here的一个好答案/评论在一定程度上解释了这一点,并考虑了git diff重命名检测阈值git status

由于我们在两种情况下都添加了4个字节,但是以不同的方式或者换行符与此有关,所以Git的行为是否应该完全相同?

1 个答案:

答案 0 :(得分:5)

据我所知,Git的“相似性指数”计算并未记录在the source, starting with diffcore-delta.c以外的任何地方。

要计算两个文件 S (源)和 D (目标)的相似性索引,Git:

  • 读取两个文件
  • 计算文件 S
  • 的所有块的哈希表
  • 计算文件 D
  • 的所有块的第二个哈希表

这两个哈希表中的条目只是该哈希值实例的出现次数(另外,如下所述,是块的长度)。

文件块的哈希值由以下公式计算:

  • 从当前文件偏移量开始(最初为零)
  • 读取64个字节或直到'\n'个字符,以先发生者为准
  • 如果声明文件是文字且'\r'之前有'\n',则弃置'\r'
  • 使用链接文件
  • 中显示的算法对生成的64字节字符串进行哈希处理

现在有 S D 的哈希表,每个可能的哈希 h i 出现< em> n S S n D D >(要么可能为零,尽管代码会跳过两个零哈希值)。如果 D 中出现次数小于或等于 S -ie中出现的次数, n D ≤ n S -then D “从 S复制 n D 次。如果 D 中的出现次数超过 S 中的次数(包括 S 中的数字为零时),则 D < / em>具有 n D - n S 出现散列块的“文字添加”, D 还会复制所有 n S 原始事件。

每个散列块保留其输入字节数,并且这些字节数乘以“块”的副本数或添加数以获得复制或添加的字节的数量。 (删除,其中 D 缺少 S 中存在的项目,这里只有间接影响:字节副本和添加计数变小,但Git没有专门计算删除本身。)

src_copied中计算的这两个值(literal_addeddiffcore_count_changes)会移交给function estimate_similarity in diffcore-rename.c。它完全忽略literal_added计数(此计数用于决定如何构建packfile增量,但不用于重命名评分)。相反,只有src_copied数字很重要:

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

其中max_size是两个输入文件 S D 中较大者的字节大小。

请注意,有一个早期的计算:

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;

如果两个文件已经更改 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),因此当您使用默认minimum_score时,默认-M50%为30000(60000的一半)。然而,除了CR-before-LF吃的情况之外,这种特殊的捷径不应该影响更昂贵的相似度计算的结果。

同样,git status始终使用默认值。没有旋钮可以更改阈值(也不包括重命名查找队列中允许的文件数)。如果有代码将转到here,请设置差异选项的rename_score字段。