【问题标题】:Trying to understand `git diff` and `git mv` rename detection mechanism试图了解`git diff`和`git mv`重命名检测机制
【发布时间】:2017-09-16 16:39:58
【问题描述】:

这是another question I asked before的后续。

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

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

【问题讨论】:

  • 为什么修改文件以添加abc 最终会被报告为一个新文件和一个已删除的具有另一个名称的文件?你也重命名了文件吗?您一直在说并提到“重命名”,但您的问题实际上并没有表明您正在重命名文件。请明确说明您在做什么。
  • 文件在编辑之前被重命名,这是第一次发生在line 34
  • 但问题中没有提到这一点。如果有先前的信息(即使,如果这是您要发布后续问题的问题),您需要在此处发布。 Stack Overflow 不是一个线程讨论板,每个问题都应该独立存在。
  • 对于小文件,重命名检测总是很不稳定。

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


【解决方案1】:

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

要计算两个文件 S(源)和 D(目标)的相似度指数,Git:

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

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

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

  • 从当前文件偏移量开始(初始为零)
  • 读取 64 个字节或直到 '\n' 字符,以先到者为准
  • 如果文件声称是文本并且在'\n'之前有一个'\r',则丢弃'\r'
  • 使用链接文件中显示的算法对生成的最多 64 个字节的字符串进行散列

现在 SD 都有哈希表,每个可能的哈希 hi 都会出现 nS 次在 SnD 次在 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 中较大的一个的字节大小。

注意有一个更早的计算:

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),所以默认minimum_score,当您使用默认-M50% 时,是30000(60000 的一半)。但是,除了 CR-before-LF 吃掉的情况之外,这种特殊的捷径不应该影响更昂贵的相似性计算的结果。

[编辑:现在已过时:] git status 始终使用默认值。没有旋钮可以更改阈值(也没有重命名队列中允许的文件数)。如果有代码将转到 here,设置差异选项的 rename_score 字段。 在 Git 版本 2.18.0 之前,无法控制 git status。在 Git 2.18.0 及更高版本中,git statusgit diff 具有相同的--find-renames 选项。 Git 配置中的status.renames 选项启用任何默认检测,如果未设置,git status 服从diff.renames 设置;见the git config documentationthe git status documentation

【讨论】:

    猜你喜欢
    • 2011-06-21
    • 2011-12-07
    • 2019-08-12
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    • 1970-01-01
    • 2012-11-28
    相关资源
    最近更新 更多