If you've ever tried enforcing clang-format on a large legacy C++ codebase, you've hit this problem:
mirrors-clang-format reformats entire files — your PR becomes 80% formatting noise, 20% actual change
git clang-format --staged has the right idea, but breaks in CI — the staging area is empty when pre-commit runs with --from-ref/--to-ref
So you either reformat everything (noisy) or nothing (useless).
The fix: format only changed lines
darker solved this problem for Python/Black years ago. The idea is simple: diff the current changes, extract the added-line ranges, and only run the formatter on those ranges.
I built the same thing for C++: clang-format-inc.
How it works
git diff -U0 --cached --
└─ parse added-line ranges: { "foo.cpp": [(5, 8), (20, 22)] }
└─ clang-format --lines=5:8 --lines=20:22 -i foo.cpp
The --lines flag tells clang-format to reformat only those ranges. Everything outside the diff is untouched.
In CI, it reads PRE_COMMIT_FROM_REF/PRE_COMMIT_TO_REF (set automatically by pre-commit) and diffs those two refs instead.
Setup
.pre-commit-config.yaml
repos:
- repo: https://github.com/goyaladitya05/clang-format-inc
rev: v1.0.0
hooks:
- id: clang-format-inc clang-format installs automatically — no system dependency needed.
What else it can do
Check mode — report without fixing (useful for CI that shouldn't auto-commit):
clang-format-inc --check
Diff mode — print exactly what would change:
clang-format-inc --diff
Skip generated files:
clang-format-inc --exclude 'third_party/|generated/'
Parallel processing for large jobs:
clang-format-inc --workers 4
Why not just use clang-format-diff.py?
LLVM ships a script that does something similar. Two reasons I didn't use it:
It's Apache-2.0 — bundling it into an MIT package adds license complexity
It's a vendored file that needs tracking against upstream LLVM releases
clang-format-inc reimplements the diff-parsing logic in ~30 lines of pure Python with full test coverage.
Links
📦 PyPI
📖 Docs
🐙 GitHub
Top comments (0)