在日常开发中,Git 默认的文本对比足以满足大多数源码文件的需求。但当项目包含结构化文档(如 JSON、YAML、XML)或二进制资产(如图片、PDF)时,开发者往往希望看到更具可读性的差异,甚至希望把外部工具的解析结果直接交给 Git 展示。Git 提供的 diff driver(亦称自定义 diff 驱动)正是解决这一痛点的低层 API。通过 diff driver,我们可以把任意文件的 “人类可读” 表示交给 Git,从而在 git diff、git log -p 甚至 IDE 的 diff 视图中获得统一的对比体验。
1. 什么是 diff driver
diff driver 本质上是 Git 配置文件中的一组指令,告知 Git 在比较特定类型的文件时使用哪个外部程序(command)或转换过滤器(textconv)。它由两个核心概念组成:
- 注册:在
git config中声明一个以diff.<name>开头的配置块,提供command(外部 diff 程序)或textconv(把文件转为可比较的文本)路径。 - 绑定:在
.gitattributes文件里通过diff=<name>属性把某个文件名模式(比如*.json)映射到已经注册的 driver。
这样,当 Git 遇到匹配的文件时,会先调用对应的转换或外部程序,再把结果交给内部的 diff 引擎生成统一的差异报告。
2. 注册自定义 diff driver
在任意层级(全局 --global、仓库局部 --local)执行 git config 即可完成注册。最常见的两种方式是:
2.1 使用 textconv(文本转换)
# 将 JSON 文件pretty‑print后进行比较
git config diff.json.textconv "jq -S ."
上述命令会在当前仓库的 .git/config 中生成:
[diff "json"]
textconv = jq -S .
jq -S . 读取文件内容,输出键排序后的 JSON 文本。Git 会把原始文件的压缩形式转换为易读的展开形式,再进行 diff。
2.2 使用 command(完整外部 diff 程序)
如果你希望自行实现差异生成逻辑(比如调用 diff -u 之外的自定义算法),可以指定 command:
git config diff.custom.command "/path/to/my-diff-script"
此时 Git 会把两个文件路径作为前两个参数传递给脚本,脚本的标准输出即为差异结果。脚本的签名应为:
my-diff-script old-file new-file
3. 把 driver 绑定到文件类型
仅注册 driver 不会自动生效,需要在仓库根目录的 .gitattributes(或子目录的 .git/info/attributes)中添加映射:
*.json diff=json
*.xml diff=xml
*.png diff=image
*.json diff=json:告诉 Git 遇到 JSON 文件时使用名为json的 driver(即前一步注册的diff.json)。*.png diff=image:示例中假设我们对图像文件使用自定义 driver。
提示:
.gitattributes必须纳入版本库,以便团队成员共享文件类型的 diff 行为;而 diff driver 的实现(脚本或工具)则需要每位成员自行安装或配置。
4. 触发方式
- 属性触发:只要文件匹配
.gitattributes中的diff=…,执行git diff、git log -p、git show等命令时会自动调用对应的 driver。 - 手动触发:使用
git diff --ext-diff可强制使用所有已注册的属性驱动,即使没有在.gitattributes中声明也能生效。
5. 处理二进制文件与特殊文件类型
5.1 使用 binary 属性
Git 默认会把未声明的文件视作文本。若要彻底禁止比较二进制文件,可以在 .gitattributes 中直接标记为二进制:
*.png binary
这样 git diff 只会提示 “Binary files a/foo.png and b/foo.png differ”,不再尝试显示内容。
5.2 使用 textconv 转换二进制为可读文本
如果你想在 diff 中看到二进制的结构化信息(例如 PNG 的尺寸、颜色模式),可以写一个小型脚本把元数据打印出来,然后将其注册为 textconv:
# image-meta 脚本示例(依赖 ImageMagick 的 identify)
#!/usr/bin/env bash
identify -verbose "$1" | grep -E '^(Image:|Geometry:|Colorspace:|Depth:)'
# 注册
git config diff.image.textconv "/path/to/image-meta"
这样当比较两张 PNG 时,Git 会把元数据差异呈现出来,而不是直接报 “Binary files differ”。
5.3 使用外部 command 处理特殊格式
对于像 PDF、Office 文档等只能抽取文本的文件,可以使用 pandoc、pdftotext 等工具:
git config diff.pdf.command "pandoc -t plain"
Git 会先调用 pandoc 把 PDF 转为纯文本,再进行 diff。
6. 配置 diff 算法与行为
diff driver 本身不限制使用的算法,但可以在全局或 driver 级别覆盖一些行为:
[diff "json"]
textconv = jq -S .
algorithm = histogram
wsErrorHighlighting = all
algorithm:可取myers、minimal、patience、histogram,影响差异块的选择策略。wsErrorHighlighting:开启空格错误高亮,适合代码审查场景。
如果想对所有文本文件统一配置,也可以在 ~/.gitconfig 的 [diff] 段落设置全局选项。
7. 完整示例:JSON 文件的可读 diff
- 编写转换脚本(
~/bin/json-pretty.sh):
#!/usr/bin/env bash
# 将 JSON 规范化并排序键
jq -S . "$1"
记得 chmod +x ~/bin/json-pretty.sh。
- 注册 driver:
git config diff.json.textconv "~/bin/json-pretty.sh"
- 在仓库中绑定:
echo '*.json diff=json' >> .gitattributes
git add .gitattributes
- 验证:
git diff HEAD~1 -- *.json
你会看到原本紧凑的一行 JSON 被展开为多行、键排序后的可读形式。
同理,如果想对图片、PDF、YAML 等实现类似可读 diff,只需把对应的转换工具路径放入 textconv,并在 .gitattributes 中声明即可。
8. 常见坑与最佳实践
- 路径必须可执行:无论是
textconv还是command,都必须具备执行权限;Windows 环境下注意使用.bat、.exe或 WSL 路径。 - 使用绝对路径或
$PATH内工具:相对路径在不同机器上可能失效,推荐把脚本放在项目根目录的scripts/子目录并使用$PWD变量。 - 本地 vs 全局配置:若团队成员需要统一的 diff 行为,最好在项目的
CONTRIBUTING.md中说明安装步骤,或把脚本随仓库一起发布(放在.git/hooks/之外的目录),让每位开发者自行配置。 - 返回值与错误处理:自定义脚本返回非零状态码会导致 diff 失败,建议捕获异常并输出空文本,以免阻断
git diff。 - 调试技巧:使用
GIT_TRACE=1 git diff可以看到实际的驱动调用参数,帮助定位问题。
9. 小结
自定义 diff driver 为 Git 打开了处理结构化文档和二进制资产的钥匙。通过 git config 注册 diff.<name>.textconv 或 diff.<name>.command,再在 .gitattributes 中把特定文件模式映射到该 driver,即可在 git diff 系列命令中得到统一且可读的差异展示。结合 algorithm、wsErrorHighlighting 等细粒度选项,还能进一步调优差异块的生成策略。掌握以上注册、绑定与调参技巧后,你完全可以为项目中的任何文件类型构建专属的 diff pipeline。详细参数可参阅 Git 官方文档 diff-config(中文手册亦可查询)。