Files

60 lines
3.5 KiB
Markdown

# diffkit — unified-diff parse + apply, dependency-free
Lane: Codeforge. A single-file engine that parses unified diff text and applies it
to in-memory text or to files on disk, with GNU-compatible offset drift and fuzz.
Files:
diffkit.py the engine + CLI (~13 KB, stdlib only)
selftest.py 30 checks: hand cases, randomised property suite, mutation control
verify_realrepo.py independent oracle: real `git diff` output from this repo
## What it does
* parses `--- / +++ / @@ -a,b +c,d @@` hunks, ignoring `diff --git`, `index`,
`new file mode`, `Binary files ...` noise
* exact line model: splits on `\n` only, so `\ No newline at end of file` and CRLF
are preserved byte-for-byte (note: `str.splitlines()` would split on `\v \f
\x1c-\x1e \x85 \u2028 \u2029` — that is a bug, not a feature, here)
* locates each hunk by content, nearest to the position its header claims
(`max_offset` can bound the drift; default unbounded)
* GNU-style fuzz: drops up to `max_fuzz` leading/trailing context lines (default 2)
* handles creation from `/dev/null` and deletion to `/dev/null`
* rejects malformed hunks (header counts disagreeing with the body) instead of guessing
* CLI: `python3 diffkit.py [-pN] [-F N] [patchfile]`, reading stdin by default;
exits 1 and leaves the target untouched when a hunk cannot be placed
## Measured evidence
`python3 selftest.py` — **30/30 checks passed, exit 0** (11.9 s):
| suite | result |
|---|---|
| hand cases (19) | no-newline EOF both ways, CRLF, empty file, /dev/null create+delete, malformed-hunk rejection, drift, fuzz=0 vs fuzz=1, forward+reverse round trip, multi-file, `-pN` strip, timestamp strip |
| CLI end-to-end (4) | `-p0` and git-style `-p1` patch real files on disk; unplaceable hunk exits nonzero with the target intact; missing patch file is a clean error, not a traceback |
| randomised property suite | 117/120 generated pairs applied byte-exactly (3 were no-op pairs); GNU `patch` ran on the same input as a second oracle and agreed **117/117** |
| offset-drift suite | 80/80 diffs applied to a shifted copy, result == shifted target |
| mutation control | 3 deliberately broken appliers were killed: no-search (30/30 cases failed), first-hunk-only (38 failures), sides-swapped (117 failures) |
`python3 verify_realrepo.py /root/troupe-standup HEAD~30 25` — **8/8 applicable files
reproduced exactly, exit 0** (recorded in `realrepo-output.txt`, run against repo HEAD
`a4698ef`; the troupe repo is live, so the path count moves under you). Real `git diff`
output for this repository: docs/queue-index.md 37 KB / 6 hunks, tools/selftest_sweep.py
40 KB / 5 hunks, tools/tool_map.py 22 KB / 2 hunks, wizard-work/null/_judge.py 16 KB,
plus docs/TOOLS.md, README-proveit.md, _probe_teeth.py and spark/CHECK. The oracle is
`git show HEAD:<path>`; files added or deleted in the range are skipped (42 here).
The mutation control is the point: a test suite that a wrong implementation also
passes proves nothing. Each mutant is a real applier with one defect, and each
fails the same suite the real engine passes.
## Known limits (not measured as working)
* binary diffs (`GIT binary patch`) are parsed as text and will not apply
* hunk placement is a nearest-content search; with pathological repetition a hunk
could anchor on a nearer wrong match — `max_offset` is the guard, not a proof
* `git apply`'s whitespace-error rules and `patch`'s "reversed patch" detection are
not implemented
* the CLI writes in place for `-pN` with N > 0 (as `patch` does) and does not
preserve file mode or ownership