Repository navigation
fix(git-diff): emit hunk lines at column 0 so ^- anchors again - #3269
Conversation
KuSh
left a comment
There was a problem hiding this comment.
Requesting changes. The de-indent itself is correct and verified — I built the branch and confirmed rtk git diff | grep -c '^-' returns the true deletion count where it previously returned 0, that rtk's own +N -M tally and truncation notes correctly stay indented so they aren't miscounted, and that no caller re-parses this output (pipe_cmd.rs:54, gh_cmd.rs:952, glab_cmd.rs:490 all just print it). Token cost goes down slightly, so never_worse is unaffected. Full suite green.
This is the version of the column-0 fix being taken forward — the competing #3646 is closed in its favour.
What needs another pass is the contract the PR body sells. "grep -c '^-' gives you the exact deletion count" is the promise, and there are three holes in it, two of them on lines this PR edits. Details inline; the third is here because it isn't tied to a single line:
Past max_hunk_lines (100) the count silently stops being exact. Verified: a 300-deletion hunk yields grep -c '^-' = 100 while the tally correctly says +300 -300. The ... (500 lines truncated) note is deliberately indented — correctly, so the same grep doesn't count it — which means the anchored audit you're recommending has no way to see that it was truncated at all, and the note merges additions and deletions into one number. Either a per-sign note (... (N- deletions, N+ additions truncated)) or an explicit caveat in the PR body would close it honestly.
None of this makes the PR worse than the status quo — it's strictly better. It's just that the moment an audit is documented as trustworthy, people write CI gates against it.
KuSh
left a comment
There was a problem hiding this comment.
Thanks for the rework — the core fix is right, and I verified it: on ordinary git diff output ^- / ^+ anchor again, and dropping the +++ / --- guards also fixes the silent content loss I flagged earlier (a deleted line starting with --, an added ++i;). hunk_truncation_note splitting by sign is a good call, and the flush points all match the previous behaviour. The new tests cover the right things, keep1 included.
One blocking issue, one open question, one docs item.
Blocking — combined diffs (diff --cc) still emit false ^- / ^+ lines. Details inline. This one is introduced by the PR, not pre-existing: on master those header lines came out indented, so ^- did not match them.
Open question — the leading-context budget. Details inline and on the existing thread; it addresses what I asked for, but it now competes with change lines for the global max_lines budget.
Docs — docs/usage/FEATURES.md:333-341. The rtk git diff Avant / Apres block shows change lines indented with + / - / ~ under a src/main.rs (+5/-2) header. That illustration was already drifting before this PR, but this PR changes the emitted shape again (hunk bodies and @@ headers now at column 0, +5 -2 as a trailing indented line). Since the whole point of this PR is that the emitted shape is greppable, please update that block to match what compact_diff now produces — otherwise the one page a user would read to learn the format still teaches the wrong anchors.
|
Thanks — all three inline points and the truncation gap are fixed in 4efc0d9.
Leading context. I didn't want to ship a PR whose body says "nothing is dropped" while The comment. Correct on both counts — it narrated history and it sat on the Truncation past
I left |
|
Thanks — the Combined diffs. Reproduced your exact numbers on a two-file conflict with zero deletions — Leading context. You're right that the comment justified a problem that never existed — before this branch leading context was never emitted, so it couldn't crowd anything out of I took the exemption rather than dropping the feature, with one change to your suggestion. A pure exemption has a hole: a diff of many small hunks spends three exempt lines on each, so the output can overrun Measured on a five-commit diff of this repo,
So the exemption buys back 57 change lines for 0.1 points of reduction against master, which showed zero anchorable change lines at almost exactly the same byte count. Well clear of the 60% floor. Two tests pin it: one builds the same 30-hunk diff with and without leading context and asserts the change-line count is identical, the other builds 200 single-change hunks and asserts the context stays within
|
KuSh
left a comment
There was a problem hiding this comment.
Both items from my previous review are fixed, and I checked them against a build of 201b550 rather than by reading the diff:
diff --ccheader leak — my earlier repro now gives^-= 0 (was 1) and^+= 8 (was 9), with the per-file tally back to+4 -0.- Leading context displacing changes — 354 change lines on a real 5-commit diff of this repo, identical to master's 354, where the previous head gave 347. The exemption plus the diff-wide cap does exactly what it says.
- FEATURES.md — I reproduced the example verbatim; the block content is byte-accurate.
cargo test --all on this head: 2508 passed, 0 failed. I have resolved those threads.
A second pass turned up four things, one of which I would block on:
- Blocking —
compact_diffstill misreads patch/mbox input, and it lands on this PR's headline use case (gh pr diff --patch | grep -c "^-"). Inline. - Leading context keeps the first lines of a hunk, so the emitted body is not contiguous with the change and nothing says so. Inline.
- Combined diffs under-count by design — on exactly the format this PR just added support for. Inline, plus a docs note.
diff_header_pathtruncates any path containing a space. Inline.
Plus two comment-convention nits and a docs count, all inline.
One thing not worth an inline comment: exempting leading context from max_lines makes it a soft cap — output can now exceed it by up to 10% (git stash show -p passes 100, so up to 110). That is deliberate and the code comment explains it well; it just deserves a line in the PR description, since the two call sites chose those numbers as hard budgets.
Unrelated heads-up: src/cmds/git/git.rs is being renamed to src/cmds/git/git_cmd.rs on another branch. If that lands first this PR will need a rebase.
|
All four addressed in dc44930, and the mbox one is a good catch — it is exactly the case this PR was written for. Patch and mbox input. Took both of your routes, since they close different halves. The general benefit is bigger than the mbox: any trailing text after a diff, and any truncated input, now stops being read as hunk content. Leading context adjacency. Fixed with the ring buffer you described — it keeps the last three lines, so with Combined diff under-count. Widened rather than documented-as-is, at least for the tally: the marker check now reads the first The grep half I cannot fix without rewriting the line, so it is documented instead:
Comment convention. Both rewritten to state the invariant. Fair repeat: I fixed this on the FEATURES.md. The count is 24, and the stat's first line has no leading space. On the thin-illustration point you are right, and the honest version is that a two-file diff saves 11% because git's own Soft cap. Added to the PR description, with the Noted on the
|
KuSh
left a comment
There was a problem hiding this comment.
All seven comments from the last round are fixed, and I checked each against a build of dc44930 rather than by reading:
- A real
git format-patch --stdoutmbox now gives^-= 2 for its 2 real deletions (was 5), with the---/--/From:/Subject:envelope gone entirely. Ending the hunk at its declared length is the right shape for this, and the--patchpassthrough is a good belt-and-braces. -U10now emitsctx8 ctx9 ctx10— contiguous with the change.- A conflicted
my file.txtkeeps its full name; the combined tally counts+main(+5 -0, was+4 -0). - The docs example re-measures correctly (21 lines / 440 bytes raw against 24 / 392 filtered), and the third limit is accurate.
Change-line parity holds against master (390 = 390 on a real 5-commit diff of this repo) and cargo test --all gives 2508 -> 2513 passing. I have resolved all of those threads.
The rework introduces four new problems, one of which is a crash:
&line[..width]panics on non-ASCII.rtk git diff --word-diffexits 101 with empty stdout on this branch and works on master.- Combined-diff
oldaccounting is wrong in both directions, so declared-length termination — the mechanism this commit is built around — silently does not apply todiff --cc, and the reverse case drops change lines with no marker. - Buffered leading context is dropped when a hunk ends without a change line.
diff_header_pathmishandles git's quoted (non-ASCII) paths.
1 and 2 are related but independent: fixing 2 does not fix 1, since --word-diff hunks are ordinary @@ whose counts mismatch by construction. Leaving 2 unfixed means the hunk-length guard does not protect conflict diffs, which is where this PR started.
|
One thing I looked at and left: Non-regression: ordinary unified diffs are byte-identical to |
There was a problem hiding this comment.
Approving. Everything raised across the four rounds is addressed, and I verified this head by building and running it rather than by reading the diff.
What I checked on this head
--word-diff,--color-words,--word-diff=porcelainpass through untouched (exit 0);--word-diff=nonecompacts again — 25,307 bytes against 101,259 raw on a five-commit diff of this repo, so the saving that flag used to cost is back.- A real
git format-patch --stdoutmbox gives^-= 2 for its two deletions, envelope and--signature gone. - A real
diff --ccconflict:^-= 0,+5 -0counting the column-2 marker,my file.txtkeeping its space. core.quotepathpath renders asété.txt, sortk git diff | grep étéfinds it.--dst-prefix=w/namesgit.rs;diff --cc dup dupnamesdup dup.-U10leading context comes out adjacent to the change (ctx8 ctx9 ctx10).- Change-line parity against develop: 453 = 453 on a real diff, so nothing is lost relative to the old behaviour.
cargo fmt --all --check,cargo clippy --all-targets,cargo test --all— clean, 2527 passing.
The two commits I pushed (thanks for the maintainer-edit box): e224cf4 decodes git's quoting rather than only unwrapping it, gates the midpoint split on the header kind so a single-path diff --cc is not split, matches prefixes against each other so --dst-prefix reads like any other pair, and honours --word-diff=none last-one-wins. af01931 is a one-line plural fix — a hunk truncated at exactly one line over the cap said 1 deletions truncated.
Two shapes consciously accepted, both documented in the code:
- A
--no-prefixrename between two directories of equal path length reads as the shared trailing path. Space-separated paths with no prefix are ambiguous by construction; there is no signal to disambiguate on. - Lines between a hunk's declared end and the next header are dropped. That is the mbox fix working as intended, and it means
git log -p | rtk pipe --filter git-diffno longer showscommit <sha>/Author:lines as indented context. The old rendering was accidental andrtk git log -pdoes not route throughcompact_diff, so this is not worth holding the PR for — but it is a real difference for that one pipe.
Actually blocked, waiting for #3793 to be merged
compact_diff indented every hunk-body line two spaces to nest it under the filename. The content was all there, but the indent moved it off column 0, so `git diff | grep '^-'` matched nothing and an audit asking "was anything removed?" got a confident, wrong "no" while --stat reported deletions. Worth being precise about the failure, because the earlier write-up was wrong on both counts: nothing was ever dropped, and added lines were broken identically (`^+` also matched nothing). It is not a deletions-only defect and not content loss. It is one indent breaking every anchored grep. Emit hunk bodies in git's own unified shape instead. Context lines already carry git's leading space, so they stay distinct from additions. rtk's own annotations (the `+N -M` tally, `... (N lines truncated)`) keep the indent precisely so these same greps never count them as diff lines. The output still cannot be mistaken for an applyable patch: rtk drops the `diff --git`/`---`/`+++` headers, so a patch parser rejects it either way. The indent was never what made that safe.
The `+++` and `---` guards ran inside the `in_hunk` branch, where they can only match content: git emits the file headers before the first `@@`, and `in_hunk` resets to false on every `diff --git`. So an added `++i;` or a removed `-- sql comment` was dropped from the body and left out of the `+N -M` tally. A diff doing both rendered as a bare hunk header with no body and no tally, and `grep -c '^-'` answered 0 for a file that really did lose a line. Leading context now has its own budget of three lines. The `hunk_shown > 0` guard silenced every context line before a hunk's first change, and giving it `max_hunk_lines` instead would let a long run of context crowd out the change it is there to frame. The truncation note splits by sign: `... (30 deletions, 30 additions truncated)`. It stays indented so the anchored greps this output is meant to support never count it, which means a reader who trusts `grep -c '^-'` past the 100-line hunk cap needs the note to say how many deletions it did not see. One merged line count could not. The comment on the column-0 change narrated the bug's history and sat on the `@@` branch while describing the branch below it. It now states the invariant, once, where the code it governs lives.
…text Dropping the `+++` / `---` guards assumed those headers only appear before the first `@@`. That holds for `diff --git` sections. It does not hold for the `diff --cc` sections git emits for unmerged paths: `diff --cc` never matched the `diff --git` branch, so `in_hunk` stayed set from the previous section and the `--- a/z.txt` / `+++ b/z.txt` headers of every later file were printed at column 0 and counted in the tally. On a two-file conflict with no deletions, `rtk git diff | grep -c "^-"` returned 1. The state reset now fires on any `diff --` prefix, and the path falls back to the trailing token for the `diff --cc <path>` shape, so the headers land before the first `@@` again. Same repo now returns 0. Leading context no longer counts against `max_lines`, so it cannot displace change lines. Its previous justification was wrong: before this branch, leading context was never emitted, so it could not have crowded anything out of `max_hunk_lines`. The cost it does carry is on the global budget, and on a five-commit diff of this repo it cost 57 change lines (144 shown, against 201 with the exemption). A diff-wide cap of `max_lines / 10` bounds what the exemption gives back: without it, a diff of many small hunks would spend three exempt lines on every one of them. The same diff comes out at 71.7% byte reduction against 71.8% on master, which showed zero anchorable change lines. `docs/usage/FEATURES.md` showed change lines indented under a `src/main.rs (+5/-2)` header, which no version of rtk emits. It now carries real captured output, plus the two limits of the anchored audit: the 100-line per-hunk cap, and that the output is not an applyable patch.
A hunk ran until the next header, so any trailing text was read as its body. `gh pr diff --patch` routes here, and GitHub's `.patch` is an mbox: each patch carries a bare `---` before the diffstat and a `-- ` signature after the diff, both at column 0. With hunk bodies now anchoring, a two-commit mbox with two real deletions reported `grep -c "^-"` = 5. `@@ -a,b +c,d @@` declares its own line counts, so the hunk now ends when they are consumed and anything after it is outside every hunk. That covers trailing prose and truncated input too, rather than the shapes we happen to know about, and a heuristic on `---` / `-- ` could not have worked: a deleted line whose content is `- ` renders as `-- `. `--patch` also joins `has_non_diff_format_flag`, so an mbox passes through whole. Someone asking for a patch wants one that applies. Leading context keeps the last lines before the change rather than the first. With `-U10` or `--function-context` the first three sit ten lines above it, and emitting those tells the reader that ctx3 precedes the deletion when ctx10 does. A three-slot ring buffer costs the same lines. The diff-wide budget is charged when the buffer drains, so a line the ring evicted costs nothing. Combined diffs carry one marker column per parent, so a line changed against only one of them holds its marker in column 2. Those were counted as context: neither in the `+N -M` tally nor reachable by an anchored grep. The tally reads both columns now. The grep gap is real and cannot be closed without rewriting the line, so FEATURES.md states it alongside the per-hunk cap. Against `git diff --numstat` on three complete commit ranges the tally is now exact, where master under-counted one of them by +2 -1. `diff_header_path` keeps paths containing spaces: git does not quote them, so `diff --cc my file.txt` needs the whole remainder, not the last token. FEATURES.md said 15 lines for a 24-line block and showed the stat's first line with a leading space it does not have. It now also carries a measured example with real compression, since the two-file one saves 11% in a document advertising 60-90%.
…very parent Four problems from the review, one of them a crash. `&line[..width]` slices by byte index. `--word-diff`, `--color-words` and `--word-diff-regex` emit body lines with no marker column, so their content lands where the markers are read, and a leading multi-byte character split mid-character: `rtk git diff --word-diff` aborted with exit 101 and empty stdout on a file holding `école`. `run_diff`, `run_show` and `git stash show -p` have no `catch_unwind`, so the panic was fatal there. The markers are ASCII by construction, so comparing bytes removes the boundary question. A combined diff carries one marker column per parent, and a space means opposite things depending on the line: on a change line the line is in that parent unchanged, on a removal line it is absent from it. Collapsing the columns into one add/delete pair lost that, so `old` never converged on real conflict output and the hunk never closed by count -- the declared-length termination this commit's predecessor is built around silently did not apply to `diff --cc`, which is what git emits for every unmerged path. The reverse case closed early and dropped the body: `@@@ -1,2 -1,4 +1,2 @@@` over two removals from the second parent alone rendered as a bare hunk header, with no tally and no truncation note. `HunkHeader` now tracks a budget per parent and ends the hunk when every one of them and the result are spent. The leading-context ring drained only on a hunk's first change line, so a hunk that ends without one discarded what it held and rendered as a bare header. Flushing wherever the hunk closes -- and at end of input -- keeps it consistent with the buffering, inside the same diff-wide budget. `diff_header_path` mishandled git's quoted paths. Under the default `core.quotepath`, git escapes a non-ASCII path and wraps it in `"`, which removes the ` b/` the plain form splits on, so the fallback returned the whole remainder -- both paths -- as the section header. The quoted form is handled explicitly now, and the doc comment names the distinction the code makes: git quotes non-ASCII bytes, not spaces.
Four more from an adversarial pass, two of them undermining the previous
commit's own rationale.
`--word-diff`, `--word-diff-regex` and `--color-words` drop the marker column
and put `[-removed-]` / `{+added+}` inline, so a word-diff body line is
arbitrary content in the position `compact_diff` reads as a marker. The byte
slicing stopped the panic, but not the misparse: a line starting `+` counts as
an addition and consumes the wrong budget, one starting `\` is dropped as a
no-newline annotation, and a real `git show --word-diff=plain` row reading
`diff --git [-a/src/main.rs...` opens a new file section. There is no faithful
compaction of a word diff, so `run_diff`, `run_show` and `git stash show -p`
pass these modes through, the way `gh pr diff --patch` already does for mbox.
`rtk pipe --filter git-diff` has no flags to read and still misparses word-diff
text piped into it; `compact_diff` reads a unified or combined diff, and that is
now what its callers give it.
A hunk header lists one range per marker column, and a mismatch was charged
against nothing. `@@@ -1 +1 @@@` left the second parent untracked, so ` -lost`
drove the tracked budgets to zero and the hunk closed with the removal
discarded. `@@@ -1 -1 -1 +0,0 @@@` declared a third parent with no column to
spend it, so that budget never reached zero, the hunk never closed by count, and
the mbox signature and version became its body. The ranges are reconciled with
the columns now: a missing one gets `usize::MAX`, which keeps the hunk open to
the next header rather than dropping content, and an extra one is dropped.
`diff_header_path` split on the first ` b/`, which is inside the path for a file
under a directory named `x b` -- `diff --git a/x b/y b/x b/y` reported `y`. And
`--no-prefix` or a custom `--src-prefix` leaves no ` b/` at all, so `diff --git
x x` reported both paths as the section header. Anything but a rename names the
same path twice, so the halves are the same length and the separating space sits
dead centre; splitting there handles all three and does not care what the
prefixes are. A rename's halves differ in length and still fall through to the
` b/` split.
Ordinary unified diffs are byte-identical to the previous head: verified on a
26,561-line real diff of this repo and on a 390-line one.
…plitting Four findings on the section header and the word-diff guard: - Under the default `core.quotepath` git escapes a non-ASCII path one octal byte at a time. Unwrapping the quotes without decoding them printed `\303\251t\303\251.txt`, so a grep for the file by name found nothing. - A single-path `diff --cc` / `diff --combined` header was split at its midpoint like a two-path one, reading a file called `dup dup` as `dup`. - Prefixes were matched by name, so `--dst-prefix` fell through and the header carried the whole pair. Matching the halves against each other reads any prefix, named or not. - `--word-diff=none` is the mode that turns a word diff back off, leaving an ordinary unified diff. Treating it as a word diff passed the raw diff through, so a defensive `--word-diff=none` lost every saving. Modes are last-one-wins, which is what that mode is for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A hunk truncated at exactly one line over the cap rendered `... (1 deletions truncated)`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
af01931 to
75dda6c
Compare
A grep anchored to the start of a line finds nothing in
rtk git diffoutput:compact_diffindents every hunk-body line two spaces to nest it under the filename (format!(" {}", line)), which moves the content off column 0 and breaks the^anchor.Two things worth stating precisely, because both were initially diagnosed wrong on our side:
--statplus the per-file+N -Mtally report accurately.grep -c DELETED_LINE_A(unanchored) returns 1 both raw and filtered. It is a rendering issue, not data loss.^+breaks identically — measured raw^-=3 /^+=2 versus rtk^-=0 /^+=0. There is no+/-asymmetry.The cost is a silent false negative for any anchored grep over a diff: auditing "was a test expectation weakened" with
git diff tests/ | grep "^-"returns empty while--statreports deletions in the same range, and empty is exactly the answer that ends the investigation.Fix: hunk bodies emit at column 0 in git's own unified shape. rtk's own annotations keep the indent, so the same greps never count them as diff lines.
After:
2 is the exact deletion count; raw's 3 includes the
--- a/f.txtheader, so the piped grep was never a correct count even unfiltered.What the anchored audit does and does not promise
Review surfaced three holes in the original "
grep -c '^-'gives you the exact deletion count" claim. All three are closed, two by fixing the code and one by stating the limit here.Content that looks like a file header. The
+++/---guards ran inside the hunk-body branch, where they can only match content: git emits the file headers before the first@@. So an added++i;or a removed-- sql commentwas dropped from the body and left out of the tally, and a diff doing both rendered as a bare hunk header with nothing under it. The guards are gone.Combined diffs.
diff --ccsections, which git emits for unmerged paths during a merge, rebase, cherry-pick, or stash pop, never matched thediff --gitreset, soin_hunkstayed set across sections and the---/+++headers of every file after the first reached the body. Once hunk bodies emit at column 0, those headers anchor too: a two-file conflict with zero deletions returned^-= 1. The reset fires on anydiff --prefix now, so the headers land before the first@@again and the same repo returns 0.Combined diffs put the marker in column 2.
diff --cccarries one marker column per parent, so a line changed against only one of them (+ours,-theirs) does not anchor. It is counted in the+N -Mtally now, butgrep "^+"cannot see it without rewriting the line, sodocs/usage/FEATURES.mdstates the limit next to the others. Againstgit diff --numstaton three complete commit ranges the tally is exact.Past 100 change lines in a hunk, the count plateaus. A 300-deletion hunk yields
grep -c "^-"= 100 while the tally correctly says+300 -300. The truncation note carries the remainder and is split by sign —... (200 deletions truncated)— but it is indented by design, so the anchored grep cannot see it. If you gate CI ongrep -c "^-", gate on the+N -Mtally instead, or pass--no-compact.Two notes on the budgets. Exempting leading context from
max_linesmakes it a soft cap: output can exceed it by up to 10%, so thegit stash show -pcall site that passes 100 can see 110. Separately, and pre-existing ondevelop, the previous file's tally and the next file's header are pushed before the budget is checked, so a many-file diff can carry two entries pastmax_lines. And a hunk now ends when the line counts in its@@header are consumed, so trailing text after a diff — an mbox envelope, a--signature, prose — is outside every hunk rather than read as its body.gh pr diff --patchadditionally passes through unfiltered: GitHub's.patchis an mbox, not a unified diff.A hunk's declared length is per parent, and a combined hunk spends one budget per marker column. In a combined diff a space means opposite things depending on the line: on a change line the line is in that parent unchanged, on a removal line it is absent from it. Collapsing the columns into one add/delete pair lost that, so the first parent's budget never converged on real conflict output and the hunk never closed by count — everything above about keeping envelopes and signatures out of the body silently did not apply to
diff --cc, which is what git emits for every unmerged path. The reverse case closed early and dropped the body:@@@ -1,2 -1,4 +1,2 @@@over two removals from the second parent alone rendered as a bare hunk header, with no tally and no truncation note. A header whose range count disagrees with its column count is reconciled against the columns, since only a column can be charged.Word diffs pass through unfiltered.
--word-diff,--word-diff-regexand--color-wordsdrop the marker column and put[-removed-]/{+added+}inline, so a body line is arbitrary content in the position the compactor reads as a marker. Slicing those bytes as bytes rather than as astrfixes the crash on a leading multi-byte character —rtk git diff --word-diffexited 101 with empty stdout — but not the misparse: a row starting+counts as an addition and spends the wrong budget, one starting\is dropped as a no-newline annotation, and a realgit show --word-diff=plainrow readingdiff --git [-a/src/main.rs...opens a new file section. There is no faithful compaction of a word diff, sorun_diff,run_showandgit stash show -phand these modes straight through.rtk pipe --filter git-diffhas no flags to read and still misparses word-diff text piped into it;compact_diffreads a unified or combined diff, and that is now what its callers give it.Section paths are read without guessing at the separator. Under the default
core.quotepathgit escapes a non-ASCII path and wraps it in", which removes theb/the plain form splits on, so the fallback returned both paths as the section header. Andb/is not a reliable separator anyway: it appears inside the path for a file under a directory namedx b, and--no-prefixor a custom--src-prefixleaves none at all. Anything but a rename names the same path twice, so the halves are the same length and the separating space sits dead centre; splitting there covers all of those and does not care what the prefixes are. A rename's halves differ in length and fall through to theb/split, which names the destination.Leading context is also emitted now. It was silently dropped before the first
+or-of each hunk, which contradicted the "nothing is dropped" claim above. It keeps the last three lines before the change rather than the first, so the body is contiguous with it under-U10or--function-context, and is capped atmax_lines / 10across the diff and exempt frommax_linesso it cannot displace change lines. On a five-commit diff of this repo it costs 0.1 points of byte reduction against master (71.7% versus 71.8%) while taking anchorable change lines from 0 to 201.This does not make the output an applyable patch — rtk still drops the
diff --git/---/+++headers, so a patch parser rejects it regardless. Usertk proxy git diffwhen you need a faithful patch. The indent was never what made that case fail.docs/usage/FEATURES.mdis updated to real captured output; its illustration showed change lines indented under asrc/main.rs (+5/-2)header that no version of rtk emits.Testing
cargo fmt --check,cargo clippy --all-targets --all-features -- -D warnings, andcargo test --allare clean on this branch, apart from twohooks::rewrite_cmd::tests::unattestable_passthroughfailures that reproduce with this branch's changes stashed and that it does not touch.cmds::git::gitis 119 tests.Ordinary unified diffs come out byte-identical to the previous head: checked on a 26,561-line real diff of this repo, on a 390-line one, and on a 300-case generated corpus covering additions, deletions, replacements, empty rows, Unicode content, leading
+/-/\, multiple hunks and multiple files. The one intentional divergence is a hunk that ends without a change line, which now emits its buffered context instead of a bare header.