logicdiff
v0.1.0
Published
A whitespace- and reflow-blind diff: folds respacing AND line re-wrapping that git diff -w can't, and tells you if a change is logical or just formatting. Zero dependencies.
Maintainers
Readme
logicdiff
A whitespace- and reflow-blind diff. A pull request reindents a file and
rewraps a few long lines, and now git diff shows 80 changed lines — but did
anything actually change? logicdiff answers that: it folds away pure
formatting (respacing and line reflow) and shows only the logical changes.
logicdiff old.js new.js
# only formatting differs - no logical change (a line diff would show 80 changed lines)
logicdiff a.js b.js
# --- a.js
# +++ b.js
# -42: const total = price * qty;
# +51: const total = price + qty;
#
# 1 token removed, 1 added across 2 logical lines (78 lines folded as reflow/whitespace)Exit 0 when the change is formatting-only (or identical), 1 when there's a
real logical change — so CI can ask "is this PR just a reformat?" Zero
dependencies, language-agnostic, also on PyPI (pipx run logicdiff) — the two
builds produce byte-for-byte identical output.
Why not git diff -w?
git diff -w (ignore-all-space) folds respacing — but it is still
line-anchored, so it cannot fold reflow. Re-wrap a function signature across
three lines and git diff -w still shows 1 removed + 3 added, even though not a
single token changed. That exact gap is GitHub discussion #20610
("Ignore Format Changes in Diff"), open and unanswered for years.
difftastic solves it beautifully with per-language tree-sitter parsing — but
it's a multi-megabyte binary, needs a grammar for each language (config/log/DSL
files fall back to text), and it's a display tool with no "is this
formatting-only?" exit code.
logicdiff is the lightweight middle ground: zero-config, zero-dependency,
language-agnostic (works on any text — code, YAML, logs, DSLs), folds both
whitespace and reflow, and gives a one-shot CLI answer plus a CI exit code.
How it works
It tokenizes each file into a sequence of tokens — a token is a run of
[A-Za-z0-9_] or a single punctuation character, and whitespace is dropped.
So a+b, a + b, and a +\n b all become the same token stream [a, + , b]:
respacing and line breaks become invisible. It then runs the canonical
Myers diff on the token streams. If the streams are equal, the change is
formatting-only. If not, the changed tokens are mapped back to their line
numbers and shown.
Because it has no language parser, whitespace inside string literals is also
ignored — x = "a b" and x = "a b" are "formatting only", exactly like
git diff -w. That's a deliberate, documented limitation, not a bug.
Usage
logicdiff old new # human diff (or "only formatting differs")
logicdiff old new --stat # just the counts, machine-friendly key=value
logicdiff old new --json # structured output (byte-identical both builds)
logicdiff old new -q # no output, exit code only (the CI gate)
cat new | logicdiff old - # - reads stdin--color=auto|always|never, --max-tokens N (bail over N tokens, default 2,000,000).
Two wildly dissimilar inputs (a huge edit distance) also bail with exit 2 instead of
risking the heap — logicdiff is for spotting a real change inside a reformat, not for
diffing unrelated files.
Exit codes: 0 identical or formatting-only · 1 logical changes · 2 error.
# CI: warn when a PR is more than a reformat
- run: logicdiff "$BASE" "$HEAD" -q || echo "::warning::real code change, review carefully"Install
npm i -g logicdiff # or npx logicdiff
pip install logicdiff # Python build, identical behaviourNode ≥ 18 or Python ≥ 3.8. No dependencies.
License
MIT
