简体中文 ▾ 主题 ▾ 最新版本 ▾ gitdiffcore 最后更新于 2.44.0

名称

gitdiffcore - 调整 diff 输出

概要

git diff *

描述

git diff-indexgit diff-filesgit diff-tree 等 diff 命令可以在显示 diff 输出之前,以非传统方式处理它们发现的差异。这种处理过程统称为“diffcore 转换”。本简短说明描述了它们是什么,以及如何使用它们来生成比传统方式更容易理解的 diff 输出。

操作链

git diff-* 系列命令的工作原理是首先比较两组文件

  • git diff-index 比较“树”对象的内容与工作目录(当不使用 --cached 标志时)或“树”对象与索引文件(当使用 --cached 标志时);

  • git diff-files 比较索引文件内容与工作目录内容;

  • git diff-tree 比较两个“树”对象的内容;

在所有这些情况下,命令本身首先会选择性地通过命令行给出的任何路径规范限制这两组文件,并比较结果两组文件中对应的路径。

路径规范用于限制 diff 操作的范围。它们会移除不在指定路径名集合中的文件对。例如,如果输入的文件对集合包括

:100644 100644 bcd1234... 0123456... M junkfile

但命令调用是 git diff-files myfile,那么 junkfile 条目将从列表中移除,因为只考虑了“myfile”。

比较的结果会从这些命令传递到内部称为“diffcore”的机制中,其格式类似于不使用 -p 选项时的输出。例如:

in-place edit  :100644 100644 bcd1234... 0123456... M file0
create         :000000 100644 0000000... 1234567... A file4
delete         :100644 000000 1234567... 0000000... D file5
unmerged       :000000 000000 0000000... 0000000... U file6

diffcore 机制被馈送此类比较结果的列表(每个结果称为“文件对”,尽管此时每个结果都只涉及单个文件),并将此列表转换为另一个列表。目前有 5 种这样的转换:

  • diffcore-break

  • diffcore-rename

  • diffcore-merge-broken

  • diffcore-pickaxe

  • diffcore-order

  • diffcore-rotate

这些转换按顺序应用。git diff-* 命令找到的文件对集合用作 diffcore-break 的输入,而 diffcore-break 的输出用作下一次转换的输入。最终结果随后传递给输出例程,并生成 diff-raw 格式(参见 git diff-* 命令手册中的输出格式部分)或 diff-patch 格式。

diffcore-break:用于拆分完全重写

链中的第二个转换是 diffcore-break,由 git diff-* 命令的 -B 选项控制。它用于检测表示“完全重写”的文件对,并将该文件对拆分为表示删除和创建的两个文件对。例如,如果输入包含此文件对

:100644 100644 bcd1234... 0123456... M file0

如果它检测到文件“file0”已被完全重写,它会将其更改为

:100644 000000 bcd1234... 0000000... D file0
:000000 100644 0000000... 0123456... A file0

为了拆分文件对,diffcore-break 会检查修改前后文件内容的变化程度(即在上面的例子中,内容具有“bcd1234...”和“0123456...”作为其 SHA-1 内容 ID)。删除原始内容的数量和插入新材料的数量相加,如果超过了“中断分数”,文件对就会被拆分为两个。中断分数的默认值为原始文件和结果文件两者较小者的 50%(即,如果编辑使文件变小,则使用结果的大小;如果编辑使文件变大,则使用原始的大小),并且可以通过在“-B”选项后给出一个数字来定制(例如,“-B75”表示使用 75%)。

diffcore-rename:用于检测重命名和复制

此转换用于检测重命名和复制,由 git diff-* 命令的 -M 选项(检测重命名)和 -C 选项(同时也检测复制)控制。如果输入包含这些文件对

:100644 000000 0123456... 0000000... D fileX
:000000 100644 0000000... 0123456... A file0

且已删除文件 fileX 的内容与已创建文件 file0 的内容足够相似,那么重命名检测会将这些文件对合并并创建

:100644 100644 0123456... 0123456... R100 fileX file0

当使用“-C”选项时,已修改文件的原始内容和已删除文件(如果使用“--find-copies-harder”选项,还包括未修改文件)会被视为重命名/复制操作中源文件的候选。如果输入类似于这些关于已修改文件 fileY 和新创建文件 file0 的文件对

:100644 100644 0123456... 1234567... M fileY
:000000 100644 0000000... bcd3456... A file0

则会比较 fileY 的原始内容和 file0 的结果内容,如果它们足够相似,则会将它们更改为

:100644 100644 0123456... 1234567... M fileY
:100644 100644 0123456... bcd3456... C100 fileY file0

在重命名和复制检测中,都使用与 diffcore-break 中相同的“变化程度”算法来确定两个文件是否“足够相似”,并且可以通过在“-M”或“-C”选项后给出一个数字来定制相似度分数(默认值为 50%,例如,“-M8”表示使用 8/10 = 80%)。

注意,当开启重命名检测但关闭复制和中断检测时,重命名检测会增加一个初步步骤:首先检查文件是否在保持文件名不变的情况下跨目录移动。如果一个目录中添加了一个文件,其内容与另一个目录中已删除的同名文件足够相似,它会将它们标记为重命名,并将其从后续的二次方步骤(即成对比较所有未匹配文件以找到由最高内容相似度确定的“最佳”匹配的步骤)中排除。因此,例如,如果删除的 docs/ext.txt 和添加的 docs/ext.md 足够相似,它们将被标记为重命名,从而防止添加的 docs/ext.md(可能与已删除的 docs/ext.txt 更相似)在后续步骤中被视为重命名目标。因此,初步的“匹配相同文件名”步骤使用了稍高的阈值来标记文件对为重命名,并停止考虑其他候选者以获得更好的匹配。在此初步遍历中,每个文件最多进行一次比较;因此,如果在精确重命名检测之后目录层次结构中仍有几个 ext.txt 文件,则这些文件可能会跳过此初步步骤。

注意:当“-C”选项与 --find-copies-harder 选项一起使用时,git diff-* 命令会将未修改的文件对与已修改的文件对一起馈送给 diffcore 机制。这允许复制检测器将未修改的文件视为复制源候选者,但代价是会降低速度。如果没有 --find-copies-hardergit diff-* 命令只有在被复制的文件恰好在同一个变更集中被修改过时才能检测到副本。

diffcore-merge-broken:用于合并完全重写

此转换用于将由 diffcore-break 拆分且未被 diffcore-rename 转换为重命名/复制的文件对,重新合并为单个修改。当使用 diffcore-break 时,此操作总是会运行。

为了重新合并损坏的文件对,它使用了与 diffcore-break 和 diffcore-rename 中使用的不同的“变化程度”计算方法。它仅统计从原始文件中的删除量,不统计插入量。如果您仅从 100 行的文档中删除了 10 行,即使您添加了 910 行新内容使其成为 1000 行的新文档,您也没有进行完全重写。diffcore-break 会拆分此类情况以帮助 diffcore-rename 将此类文件对视为重命名/复制检测的候选者,但如果以这种方式拆分的文件对没有与其他文件对匹配以创建重命名/复制,则此转换会将它们合并回原始的“修改”。

“变化程度”参数可以从默认的 80%(即,除非删除了超过 80% 的原始内容,否则拆分后的文件对会合并回单个修改)进行调整,方法是给 -B 选项提供第二个数字,例如:

  • -B50/60(给 diffcore-break 50% 的“中断分数”,给 diffcore-merge-broken 使用 60%)。

  • -B/60(与上文相同,因为 diffcore-break 默认为 50%)。

注意,早期的实现将损坏的文件对留作单独的创建和删除补丁。这是一个不必要的技巧,最新的实现总是将所有损坏的文件对合并回修改中,但在发生此类完全重写时,生成的补丁输出格式会有所不同,以便于审查:显示旧版本的全部内容(以 - 为前缀),后跟新版本的全部内容(以 + 为前缀)。

diffcore-pickaxe:用于检测指定字符串的添加/删除

此转换将文件对集合限制为在原像和映射像之间以某种方式改变了指定字符串的文件对。-S<block-of-text> 和 -G<regular-expression> 选项用于指定搜索这些字符串的不同方式。

“-S<block-of-text>”用于检测其原像和映射像中指定文本块出现次数不同的文件对。根据定义,它不会检测文件内的移动。此外,当变更集整体移动文件而不影响感兴趣的字符串时,diffcore-rename 会照常介入,而 -S 会忽略该文件对(因为在该重命名检测的文件对中,该字符串出现的次数没有变化)。当与 --pickaxe-regex 一起使用时,会将 <block-of-text> 视为扩展 POSIX 正则表达式进行匹配,而不是作为字面字符串。

“-G<regular-expression>” (助记:grep) 用于检测其文本差异中存在匹配给定正则表达式的已添加或已删除行的文件对。这意味着它将检测文件内(或重命名检测所认为的同一文件)的移动,这可能是噪声。该实现运行两次 diff 并进行 grep,开销可能相当大。为了加快速度,没有 textconv 过滤器的二进制文件将被忽略。

-S-G 在不使用 --pickaxe-all 的情况下使用时,只有符合各自标准的文件对会保留在输出中。当使用 --pickaxe-all 时,如果变更集中即使有一个文件对匹配了各自的标准,整个变更集都会被保留。这种行为旨在使在整个变更集的上下文中审查更改变得更容易。

diffcore-order:用于根据文件名对输出进行排序

此转换用于根据用户(或项目)的喜好对文件对进行重新排序,由 git diff-* 命令的 -O 选项控制。

它接收一个文本文件,其中的每一行都是一个 shell glob 模式。匹配文件中靠前行 glob 模式的文件对会先于匹配靠后行的文件对输出,而不匹配任何 glob 模式的文件对将最后输出。

例如,Git 核心的一个典型顺序文件可能如下所示:

README
Makefile
Documentation
*.h
*.c
t

diffcore-rotate:用于更改输出起始路径

此转换接收一个路径名,并旋转文件对集合,使给定路径名的文件对排在第一位,并可选择丢弃其之前的路径。这用于实现 --skip-to--rotate-to 选项。如果指定路径名不在文件对集合中则为错误,但当与“git log”系列命令一起使用时报错并无益处,因为不能要求“git log”命令显示的每一个提交都会修改给定路径。因此,当与“git log”一起使用时,输出从排序结果与给定路径相同或排序在给定路径之后的第一项开始。

将此转换与 diffcore-order 结合使用会产生意想不到的结果,因为当 diffcore-order 生效时,此转换的输入很可能不是排序过的。

GIT

Git[1] 套件的一部分