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.
Project description
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 npm (npx 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
pip install logicdiff # or pipx run logicdiff
npm i -g logicdiff # Node build, identical behaviour
Python ≥ 3.8 or Node ≥ 18. No dependencies.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file logicdiff-0.1.0.tar.gz.
File metadata
- Download URL: logicdiff-0.1.0.tar.gz
- Upload date:
- Size: 12.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8b39551202f8ba5fc7da09df2b3e0543c3a41dbdd54f904c4ae8e515a530a35
|
|
| MD5 |
5bb19499ad28e1758a849f1bc0803517
|
|
| BLAKE2b-256 |
caf6cc449c15d63fe5bb4bd68b5a26b46f9c1e08c1b3fffc640d9c62293661e3
|
File details
Details for the file logicdiff-0.1.0-py3-none-any.whl.
File metadata
- Download URL: logicdiff-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fdc2081cbfbf388e47392b8616ee05ba52329486a24baf85c31078eb18f89f04
|
|
| MD5 |
faa579d1fba3c0bf3196c8af24e7735b
|
|
| BLAKE2b-256 |
e91947491e57bf4b02f731ea89aa08b4bfeb4ab2ec3f392adff2b1a16e8d7479
|