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

5
这是 之前我问过的另一个问题 的跟进。
在被编辑之前,最初创建的文件 something 被重命名为 somethingelse,可以 在这里 观察到。
git mv something somethingelse

文件somethingelse在第二次vim编辑之前被重命名为something
git mv somethingelse something

基本上在以下代码部分中:

# 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

我认为这是在第一行添加了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给出了一个好的回答/评论,从考虑git status git diff 重命名检测门限方面,某种程度上解释了这个问题。

既然我们两种情况都添加了4个字节,为什么Git的行为不同,难道换行符与此有关吗?


为什么修改文件添加“abc”会被报告为一个新文件和另一个名称的已删除文件?你也重命名了文件吗?你一直在说“重命名”,但是你的问题实际上并没有显示你正在重命名文件。请明确说明你正在做什么。 - Lasse V. Karlsen
文件在编辑之前被重命名,这是在line 34第一次发生的。 - Christian Heinrichs
1
但是这个问题中没有提到这一点。如果有先前的信息(即使这是在你发布后续问题的那个问题中),你需要在这里发布它。Stack Overflow不是一个线程式讨论板,每个问题都应该独立解决。 - Lasse V. Karlsen
对于小文件来说,重命名检测总是会有些不可靠。 - o11c
1个回答

11
Git的“相似度指数”计算,据我所知,除了从diffcore-delta.c开始的源代码之外,没有任何其他文档记录。
为了计算两个文件S(源)和D(目标)的相似度指数,Git执行以下操作:
  • 读取两个文件
  • 计算文件S的所有块的哈希表
  • 计算文件D的所有块的第二个哈希表
这两个哈希表中的条目仅是该哈希值实例出现的次数(加上下面注明的块长度)的计数。
文件块的哈希值由以下计算得出:
  • 从当前文件偏移量开始(初始为零)
  • 读取64个字节或直到出现'\n'字符,以先到者为准
  • 如果文件被声明为文本且在'\n'之前有'\r',则舍弃'\r'
  • 使用链接文件中显示的算法对最多64个字节的结果字符串进行哈希处理
现在,既然SD都有哈希表,每个可能的哈希值hiS中出现nS次,在D中出现nD次(其中一个可能为零,尽管代码会跳过这两个零哈希值)。如果D中的出现次数小于或等于S中的出现次数 - 即nD ≤ nS - 那么D将从S中“复制”nD次。如果D中的出现次数超过S中的出现次数(包括当S中的出现次数为零时),那么D就会“字面添加”nD - nS个哈希块的出现次数,并且还会复制所有nS个原始出现次数。
每个哈希块都保留其输入字节数,这些字节数乘以“块”的复制次数或添加次数,得到复制或添加的字节数。(删除操作中,D 中缺少的项存在于 S 中,对此只有间接影响:字节复制和添加计数变小,但 Git 并不特别计算删除本身。)
diffcore_count_changes 中计算出的这两个值(src_copiedliteral_added)将传递给 function estimate_similarity in diffcore-rename.c。它完全忽略了 literal_added 计数(该计数用于决定如何构建 packfile deltas,但与重命名评分无关)。相反,只有 src_copied 数量才有意义。
score = (int)(src_copied * MAX_SCORE / max_size);

其中max_size是输入文件SD中较大的那个文件的大小(以字节为单位)。

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

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 总是使用默认值。没有旋钮可以更改阈值(也没有文件数量限制在重命名查找队列中)。如果有的话,代码将会在 这里,设置 diff 选项的 rename_score 字段。 直到 Git 版本 2.18.0,无法控制 git status 的此项功能。在 Git 2.18.0 及更高版本中,git status 具有与 git diff 相同的 --find-renames 选项。Git 配置中的 status.renames 选项启用任何默认检测,如果未设置,则 git status 遵守 diff.renames 设置;请参阅 git config 文档git status 文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接