文本比较的原理
diff 只回答一个问题:要把第一段文本变成第二段,需要做哪些改动?本指南说明 diff 工具如何找到答案、如何阅读结果,以及如何准备两个版本,让结果显示你真正关心的改动,而不是噪音。
diff 显示什么
包括 LeedyBox 文本比较在内,大多数 diff 工具都按行比较文本。结果中的每一行属于三类之一:未改动(两个版本都有)、删除(只在原文中)或新增(只在新版本中)。没有单独的“修改”类别,所以改了一个词的行会出现两次,一次作为删除行,一次作为新增行:
Dear customer,
- Your order ships on Monday.
+ Your order ships on Tuesday.
Kind regards
标记沿用由来已久的惯例:- 表示删除,+ 表示新增,空格表示未改动。颜色(通常是红色和绿色)再重复一遍同样的信息。
如何匹配行
diff 工具会寻找在两段文本中以相同顺序出现的最长行序列,即最长公共子序列。这些行保留为未改动,其余的都标为删除或新增。如果原文是 A、B、C、D,新文本是 A、C、B、D,最长公共序列有三行,例如 A、B、D,于是 C 会以“在一处删除、在另一处新增”的方式表示移动。
很多时候存在多个同样长的答案,各个工具的选择方式不同。这就是为什么同一对文件在两个程序中可能得到略有不同的 diff。Git 默认使用 Myers 算法,也提供 patience 和 histogram 作为替代,它们在有很多只含右括号的行的代码中,往往能给出更易读的结果。
寻找最长公共序列的工作量,随一段文本的行数乘以另一段文本的行数而增长。LeedyBox 工具会先把开头和结尾相同的行放到一边,再比较剩下的部分;当这个乘积超过 4,000,000 时(例如大约 2,000 行对 2,000 行不同的行),就会显示提示并停止。
阅读 Git 和代码审查中的 unified diff
版本控制系统只以 unified diff 格式显示文件中改动的部分:
--- a/config.yml
+++ b/config.yml
@@ -12,4 +12,5 @@
server:
- port: 8080
+ port: 8081
+ timeout: 30
database:
--- 和 +++ 两行是旧文件和新文件的名称。块头 @@ -12,4 +12,5 @@ 的意思是:这一块在旧文件中从第 12 行开始、共 4 行,在新文件中从第 12 行开始、共 5 行。每处改动周围的几行未改动内容(默认 3 行)提供上下文。像 LeedyBox 这样显示全文的工具,所有行都可见,因此没有块头;也正因如此,它的输出不能作为补丁应用。
准备文本以便有效比较
diff 比较的是字符,不是含义。细小的不可见差异会让行变得不同,并把真正的改动淹没:
- 空格和制表符:行尾的空格,或者一个版本用制表符、另一个用空格。删除行尾空格,并让两边的缩进一致。
- 不可见字符:例如不换行空格或零宽空格,在从网页和文字处理软件复制的文本中很常见。
- Unicode 形式:像 é 这样的带重音字母,可以存为一个字符,也可以存为 e 加组合重音符。macOS 文件名或某些 PDF 中的文本常用第二种形式。
- 弯引号和破折号:文字处理软件会把 " 替换为 “ ”、把 - 替换为 –,因此粘贴的副本与原来的纯文本不再一致。
- 换行符:Windows 用 CR LF 结束一行,macOS 和 Linux 用 LF。浏览器会把文本框中的所有换行转换为 LF,因此在 LeedyBox 工具中看不到这种差异,但在直接比较文件的工具中会看到。
- 数据和代码的格式:重新缩进的 JSON 或顺序改变的键,几乎会让每一行都不同。先用相同方式格式化两个版本,例如对键排序并使用相同的缩进。
比较普通文章
在普通文档中,一整段常常就是一长行,所以只改一个词也会让整段被标为删除和新增。有两个习惯会有帮助:比较之前把每个句子放在单独的一行,这样只有改动的句子会突出;或者使用能在行内比较单词的工具,例如 git diff --word-diff 或文字处理软件自带的修订比较功能。
从 PDF 复制的文本还有它自己的陷阱:行在页面排版折行的位置断开,单词可能在行尾被连字符拆开。与源文档比较之前,先把每段的行重新接起来。
按行 diff 无法告诉你的事
- 移动:移到别处的段落会显示为一处删除、另一处新增。有些工具会单独标记移动的块,例如使用
--color-moved的 git。 - 含义:用不同措辞表达同一意思的两段文本会显示为完全不同;而只差一个字符的改动,例如少了一个 "not",看起来很小,却可能让句意完全相反。
- 二进制内容:Word 文档、PDF 和图片不是纯文本。请先提取其中的文本,或使用生成这些文件的程序自带的比较功能。
比较文档时的隐私
合同、配置文件和日志中常含有姓名、密钥或密码。有些在线比较服务会把粘贴的文本保存在服务器上,有时还会生成可分享的链接。LeedyBox 文本比较在浏览器中运行,不会发送或保存文本,但在把配置文件粘贴到任何地方之前,最好还是先删除其中的机密信息。
常见问题
我只改了一个词,为什么整段都被标为改动?
因为整段是一行,而按行 diff 比较的是整行。比较之前先把文本拆成句子或较短的行,或者使用按单词比较的工具。
可以忽略空格的差异吗?
LeedyBox 工具会精确比较每一行。请先整理两段文本,例如删除行尾空格、把制表符换成空格;比较文件时,git 提供 --ignore-space-change 等选项。
怎样比较两个 Word 文档?
Word 自带比较功能,还能显示格式的改动。如果只想比较文字内容,可以把两个文档的文本复制到文本比较工具中。
什么是三方合并(three-way merge)?
当两个人编辑了同一份原文,合并工具会把每个版本分别与共同的原文比较。不同位置的改动会自动合并;对同一行的改动会被报告为冲突,需要人工解决。