Repository navigation
Conversation
|
Review note: I see two blocking issues before this should merge.
|
0cc4251 to
ed86670
Compare
|
Follow-up to my self-review note above — both blocking issues are now addressed in 1. The passthrough branch now forwards stdout and stderr independently of if !result.stdout.is_empty() {
print!("{}", result.stdout);
}
if !result.stderr.is_empty() {
eprint!("{}", result.stderr);
}
return Ok(result.exit_code);2. The trigger is now purely flag-driven via Programmatic consumers still get raw passthrough — they just have to signal intent with a real flag ( cargo fmt / clippy --all-targets / test --bin rtk: 1908 passed, 0 warnings. |
Task #10 — the bug that sabotaged this entire dev session, and the fork's copy of upstream rtk-ai#1918 (PR rtk-ai#1981 pattern). Problems fixed in run_diff(): - --name-only / --name-status / --raw / -z / --check / --exit-code / --quiet / -p / -U<n> / --diff-filter / --word-diff / etc. went down the compact path: a prepended `git diff --stat` run polluted output and a literal "--- Changes ---" header was injected — breaking every shell pipeline, script, and tool parsing the output (including this fork's own council CLI and the Law 2 gate). - exit-code semantics: `git diff --exit-code` exits 1 when differences exist — that is a result, not a failure. The old code treated exit!=0 as failure and DROPPED stdout (the actual diff). - passthrough output was trim()'d and println!'d — not byte-faithful. New behaviour: - is_passthrough_diff(): any machine-output flag → raw passthrough, byte-faithful stdout (print!, no trim), faithful exit code. - diff_exit_is_error(): only exit >1 is an error for git diff. - Plain `git diff` (no machine flags) keeps the compact token-saving format — that is the product; programmatic consumers now never see it. End-to-end verified against this repo's own working diff: contextcrawler git diff --name-only → exactly "src/cmds/git/git.rs" contextcrawler git diff --exit-code --stat → stat output + exit 1 Law 2: agy council review, quorum 1/1, ZERO findings (codex peer excluded from review until its repo-write access is sandboxed, task #11). Tests: 6 new (passthrough matrix, exit semantics), git module 265/265. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…umers Issues rtk-ai#1918 / rtk-ai#1869 / rtk-ai#1081: 'rtk git diff' and 'rtk diff' were emitting decorative output that broke 'git apply', 'patch', shell loops over '--name-only', and exit-code-based predicates. Because the PreToolUse hook silently rewrites 'git diff' to 'rtk git diff' for agents, those decorations silently distorted any patch/script workflow. Two surfaces are fixed: 1. 'rtk git diff' (proxy for 'git diff') - Detect machine-friendly flags (--name-only, --name-status, --check, --exit-code, --raw, -z, --no-color, --numstat, --shortstat, --dirstat, --patch-with-raw, --patch-with-stat, --output[=...]). - When any is present: emit raw 'git diff' output verbatim, with byte-for-byte fidelity (print! not println!) so trailing newlines are preserved for 'git apply'. - Forward stdout AND stderr regardless of exit code -- 'git diff --exit-code' returns 1 *with* the patch on stdout when files differ, so gating stdout emission on 'result.success()' would silently drop content in exactly the programmatic case this PR is trying to preserve. - Propagate git's actual exit code rather than forcing 0. Deliberately does NOT consult 'stdout.is_terminal()'. Agent runtimes (Claude Code, Codex) always capture stdout through a pipe, so an '!is_terminal' trigger would silently disable every RTK compaction on the exact code path where token savings matter most. Trigger is purely flag-driven. 2. 'rtk diff' (RTK's GNU-diff-like file compare) - Returns 0 when files are identical, 1 when they differ, 2 on missing-file / I/O error -- the GNU diff exit-code contract. - The '[ok] Files are identical' status now goes to stderr, so 'rtk diff a b > patch.diff' produces an empty patch on identical files (which is what scripts expect) rather than text in stdout. Eight new tests cover: machine-friendly flag detection (name-only, name-status, stat variants, raw, -z, exit-code, output[=...]); normal args bypass passthrough; identical files exit 0; different files exit 1; missing file exits 2. Fixes rtk-ai#1918, rtk-ai#1869 Refs rtk-ai#1081 (duplicate of rtk-ai#1918)
ed86670 to
c542ad1
Compare
|
Rebased this PR onto current develop and resolved conflicts in the git diff path. Resolution notes:
Local verification completed:
|
KuSh
left a comment
There was a problem hiding this comment.
Requesting changes. There is a good PR in here, but it needs a rebase and a narrower scope, and its headline claim doesn't currently hold.
I reviewed the real PR head (c542ad1) fetched from pull/1981/head. The branch is conflicting with develop and roughly three months behind it.
The headline issue. The commit says "Fixes #1918" and the PR body says the patch now applies, but flagless rtk git diff is still decorated: the passthrough trigger is flag-only (is_terminal was deliberately dropped), so a bare git diff still falls into the compact path at git.rs:233-249 and emits --stat header + Changes: + truncated compact_diff. rtk git diff > p.patch; git apply therefore still fails, which is #1918's exact repro. No test covers the flagless case — the one new integration test uses --exit-code, which takes the passthrough branch.
Scope. Since this was opened, #2473 has landed the exit-2 half of what this PR does, with the same semantics. That leaves two genuinely unique contributions here that I'd like to keep: the machine-friendly flag passthrough, and moving the identical-files verdict off stdout. Narrowing to those would make this reviewable; as a four-file PR spanning both it's competing with itself.
Details inline. Two more that don't anchor cleanly:
--quietis missing from the allowlist (git.rs:111).rtk git diff --quieton a clean tree hitsprintln!("{}", result.stdout.trim())and emits a bare newline, sotest -z "$(rtk git diff --quiet)"fails despite the flag meaning "disable all output".- Exact string matching misses value-taking spellings.
--stat=200and--dirstat=files,10match neitherwants_statnor the new list, so they get theChanges:decoration instead of the stat table. You handled the prefix form for--output=, so the pattern was clearly considered — it just needs applying to the rest. tests/git_diff_contract_test.rs:28isn't isolated from global git config orHOMEthe way its sibling tests are (they setLC_ALL/HOME). A developer withdiff.externalset breaks it, and the spawned binary writes to their real tracking database.
Things I checked and found fine, so you don't re-litigate them: std::process::exit does flush stdout, so print! without an explicit flush is safe; decode_process_output keeps valid UTF-8 verbatim, so the byte-fidelity claim holds for text patches; and forwarding stderr alongside stdout and returning result.exit_code in the passthrough branch are correct for --stat/--no-compact.
| return Ok(2); | ||
| } | ||
| }; | ||
| let raw = format!("{}\n---\n{}", content1, content2); |
There was a problem hiding this comment.
This is the most important one: the branch is based on ee9e2f8, which predates never_worse being added to diff_cmd::run.
Current develop (0de9610) has let shown = never_worse(&raw, &rtk); in this exact block, plus the use crate::core::guard::never_worse; import. This PR head has neither, so merging as-is silently removes the guarantee that rtk diff never emits more tokens than the raw input — a cross-cutting invariant, not a local one.
Needs a rebase onto current develop with the guard reapplied. Worth doing before anything else here, since the rebase will also resolve the conflict and may change how the rest of the diff reads.
| if exit_code == 0 { | ||
| // Match GNU diff: silent on identical files (advisory message to | ||
| // stderr only). Exit code 0. | ||
| eprintln!("[ok] Files are identical"); |
There was a problem hiding this comment.
The comment above says "Match GNU diff: silent on identical files", but this moves [ok] Files are identical to stderr rather than removing it. Those aren't the same thing: rtk diff a b 2>&1 | patch puts the decorative line straight back into the patch stream, which is the failure mode the PR is trying to prevent.
If the goal is GNU parity, print nothing at all on the identical path and let exit 0 carry the information. If the goal is keeping a human-readable signal, that's defensible too — but then the comment should say so rather than claiming silence.
Worth flagging that this interacts with #3469: on current develop the "identical" verdict is also emitted for byte-different files (CRLF-vs-LF, trailing newline). Whatever this PR does with the verdict should assume that distinction will soon exist.
| || a == "--exit-code" | ||
| || a == "--raw" | ||
| || a == "-z" | ||
| || a == "--no-color" |
There was a problem hiding this comment.
--no-color is in a list documented as "flags that change git diff output into a non-unified format", but it doesn't — it produces exactly the same unified diff without ANSI escapes.
Including it means anyone whose alias or wrapper passes --no-color (common in scripts, and some CI images set it globally) silently gets all RTK compaction disabled. That's a large, invisible behaviour change for a flag that has nothing to do with output shape.
Suggest dropping it from this list. If the intent was to make sure colour codes don't reach the parser, that belongs upstream in how the command is built rather than in the passthrough predicate.
|
Thanks for this, and sorry it sat so long. Both files this touches were rewritten since it was opened: the decision to pass git output through raw now runs through the argument tokenizer in src/cmds/git/git.rs, and the file comparison in diff_cmd.rs was rebuilt by #3519 on 2026-09-02 and #3268 on 2026-09-08, which also added the byte level identity and invisible difference handling. develop also deliberately does not gate on is_terminal, since the hook always pipes rtk's stdout, so the TTY based passthrough would not be accepted as written. Four cases from this PR are still open on develop and are worth separate issues or narrow PRs on the current code: |
Summary
`rtk git diff` and `rtk diff` were emitting decorative output that broke `git apply`, `patch`, shell loops over `--name-only`, and exit-code-based predicates. Because the `PreToolUse` hook silently rewrites `git diff` to `rtk git diff` for hooked agents, those decorations silently distorted any patch / script workflow that used to work. Issue #1918 (and its duplicates #1081 / #1869) ask for the POSIX/git contract to be honored.
Two surfaces are fixed:
`rtk git diff` (proxy for `git diff`)
`rtk diff` (RTK's GNU-diff-like file compare)
Reproduction
```bash
rtk git diff round-trip
mkdir -p /tmp/g && cd /tmp/g && git init -q
git config user.email t@t.t && git config user.name t
printf '1\n2\n3\n' > f && git add f && git commit -q -m x
printf '1\n2x\n3\n' > f
rtk git diff > rtk.patch
git stash -q
git apply --check rtk.patch && echo "applies"
Before this PR: 'No valid patches in input'
After this PR: 'applies'
rtk git diff --name-only
Before this PR: 'f\n\n--- Changes ---\n f (...) ...'
After this PR: 'f'
rtk diff exit codes
printf 'a\n' > /tmp/a && printf 'b\n' > /tmp/b
rtk diff /tmp/a /tmp/b; echo "exit=$?"
Before this PR: exit=0 (silently wrong)
After this PR: exit=1 (GNU diff convention)
rtk diff /tmp/nonexistent /tmp/a; echo "exit=$?"
Before this PR: exit=1
After this PR: exit=2 (GNU diff convention)
```
Behavior changes
The compact-diff experience for interactive `rtk git diff` users is unchanged.
Test plan
Out of scope (deferred to a follow-up)
Fixes #1918, #1869
Refs #1081 (duplicate of #1918)