起因
git diff 经典痛点:
- function process(items) {
+ async function process(items, options = {}) {
实际只加了 async 和 options 参数,但 diff 显示整行替换。
review 慢 + 看不出真正变化。
更糟:reformatter(prettier / black)改空白 → diff 一片噪音,藏住
真正逻辑改动。
difftastic (difft) 用 tree-sitter 解析语法 → 按 AST 节点 diff →
真正的"结构化" diff。
装
brew install difftastic
cargo install difftastic
单 file diff
$ difft old.js new.js
输出 side-by-side,按 AST 节点高亮变化:
old.js new.js
function process(items) { async function process(items, options = {}) {
^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
(unchanged) new new
...
只高亮变化的 token,不变的部分原样。
看一眼就知道 "加了 async" + "加了 options 参数"。
git 集成
~/.gitconfig:
[diff]
external = difft
[pager]
difftool = false
[difftool]
prompt = false
[difftool "difftastic"]
cmd = difft "$LOCAL" "$REMOTE"
或者只在需要时用:
GIT_EXTERNAL_DIFF=difft git diff
GIT_EXTERNAL_DIFF=difft git log -p
alias 一下:
alias gdt='GIT_EXTERNAL_DIFF=difft git diff'
alias glogt='GIT_EXTERNAL_DIFF=difft git log -p'
跨语言
difftastic 用 tree-sitter,支持几十种语言:
- Python / JS / TS / Rust / Go / Java / C / C++
- HTML / CSS / Markdown
- JSON / YAML / TOML
- SQL / GraphQL
每语言知道 "function" / "class" / "import" 等结构。
实际效果
reformatter run 改空白:
# 普通 git diff
- def foo(x):
- return x + 1
+ def foo(x):
+ return x + 1
# difftastic
[no changes detected]
啥都不改的格式化 → difftastic 不显,git diff 显一堆假动。
重命名变量
- def process(data):
- result = data * 2
- return result
+ def process(items):
+ output = items * 2
+ return output
普通 git diff 看不出"几个变量重命名",看到"3 行换 3 行"。
difftastic 高亮:data→items, result→output。
理解快。
大改动也清晰
// before
const x = a + b * c;
// after
const x = (a + b) * c;
普通 diff: - ... + ... 整行。
difftastic 高亮加的括号 → 一眼看出。
限制
- 性能:大 file(>1MB)解析慢。git log -p 多 commit 时累。
- 只 syntax:不是 semantic diff(不知 var rename 是同 var)。
- 某些语言 parse 错:tree-sitter parser 偶有 bug。
不 fit 时降级 git --no-external-diff diff。
与 delta 对比
delta(dandavison/delta)是另一 diff 工具:
- 漂亮 syntax highlighting(仍是行级 diff)
- side-by-side / line numbers
- 不是真 AST diff,但视觉好
| delta | difftastic | |
|---|---|---|
| 方式 | 行级 + highlighting | AST 结构 |
| 速度 | 快 | 中 |
| 噪音 (whitespace) | 仍显示 | 跳过 |
| 重命名 | 不识别 | 识别 |
我 daily git diff 用 delta(速度 + 通用),复杂 PR review 用 difftastic。
配 GitHub Actions PR review
- run: |
GIT_EXTERNAL_DIFF=difft git diff origin/main...HEAD > review.txt
- uses: actions/upload-artifact@v4
with:
name: structural-diff
path: review.txt
CI 生成结构化 diff,review 时下载看。
配 fzf
# 选 commit 看 difft
git log --oneline | fzf --preview 'GIT_EXTERNAL_DIFF=difft git show {1}'
与 IDE diff 对比
VS Code / JetBrains 内置 diff 视图也强。
- 行级 + 块对比
- inline editing
- 但不识别 AST 结构
difftastic 在 terminal / PR review 流程中 fill gap。
真实 case
某 PR refactor 1000 行:
- 普通 diff 显 800 行变化(多数是 whitespace + import 顺序)
- difftastic 显 50 行真实结构变化
review 时间从 1 小时 → 15 分钟。
看到真正改的逻辑而不是噪声。
ast-grep 替代某些场景
semantic refactor 看 ast-grep (sg):
sg --pattern 'console.log($A)' --rewrite 'logger.info($A)'
按 AST pattern 替换。
不是 diff 工具但相关:semantic understanding of code。
踩过的坑
-
git log -p 慢:每 commit difft → 100 commit 几分钟。
只在需要时用,不要全局 default。 -
alias 没生效:
GIT_EXTERNAL_DIFF=difft必须 env var,
aliasgit diff不带 env 不行。用git -c diff.external=difft diff。 -
某语言不识别:esoteric 文件 difft 不解析 → 行级 fallback。
仍可用,但失去 AST 优势。 -
side-by-side 太宽:窄终端被裁。
--display inline用 inline
模式。 -
PR tool 集成弱:GitHub PR review UI 不能用 difftastic(GitHub
是 server-side)。本地拉 PR 后 difftastic 看。
登录后参与评论。