difftastic:按语法做 diff(不只是行级)

起因

git diff 经典痛点:

- function process(items) {
+ async function process(items, options = {}) {

实际只加了 asyncoptions 参数,但 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。

踩过的坑

  1. git log -p 慢:每 commit difft → 100 commit 几分钟。
    只在需要时用,不要全局 default。

  2. alias 没生效GIT_EXTERNAL_DIFF=difft 必须 env var,
    alias git diff 不带 env 不行。用 git -c diff.external=difft diff

  3. 某语言不识别:esoteric 文件 difft 不解析 → 行级 fallback。
    仍可用,但失去 AST 优势。

  4. side-by-side 太宽:窄终端被裁。--display inline 用 inline
    模式。

  5. PR tool 集成弱:GitHub PR review UI 不能用 difftastic(GitHub
    是 server-side)。本地拉 PR 后 difftastic 看。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。