fix(loaders): restore a/b prefixes on noprefix patch input#240
fix(loaders): restore a/b prefixes on noprefix patch input#240mo wants to merge 2 commits intomodem-dev:mainfrom
Conversation
Greptile SummaryThis PR fixes the long-standing "No files match" blank-screen for
Confidence Score: 3/5The core noprefix fix works for the common case, but can corrupt patches from files with The
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["patchText input"] --> B["stripTerminalControl + replaceAll CRLF"]
B --> C["normalizeGitPatchPrefixes"]
C --> D{"line starts with 'diff --git '?"}
D -- yes --> E["rewriteGitDiffHeader"]
E --> F{"changed?"}
F -- yes --> G["blockNeedsPrefix = true"]
F -- no --> H["blockNeedsPrefix = false"]
D -- no --> I{"blockNeedsPrefix && starts with '--- '?"}
I -- yes --> J["rewriteUnifiedFileLine (prepend a/)"]
I -- no --> K{"blockNeedsPrefix && starts with '+++ '?"}
K -- yes --> L["rewriteUnifiedFileLine (prepend b/) — blockNeedsPrefix should be cleared here"]
K -- no --> M["pass through unchanged"]
L --> D
D -- end --> N["parsePatchFiles"]
N --> O["normalizePatchChangeset → Changeset"]
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
src/core/loaders.ts:201-203
**`blockNeedsPrefix` is never cleared after the `+++` header, corrupting removals of `--` comment lines**
Once the `+++ ` file header is rewritten, `blockNeedsPrefix` stays `true` for the rest of the block. In a unified diff, a removed line whose content starts with `-- ` (SQL/Lua/Haskell/Ada comments are exactly this) produces a diff body line that begins with `--- ` (three dashes + space). This line is indistinguishable from the file header marker by the current `startsWith("--- ")` check, so it gets an `a/` prefix prepended — corrupting the diff before it reaches the parser. A minimal repro: any noprefix diff that removes a SQL comment like `-- drop table` will have a content line `--- drop table`, which becomes `--- a/drop table` after this rewrite. The parser will reject the mangled patch, and `normalizePatchChangeset` will swallow the error and return `files: []`.
Setting `blockNeedsPrefix = false` immediately after the `+++ ` line is processed is sufficient; the `---`/`+++` pair is always the last pair of header lines before the first `@@` hunk marker in any valid git diff output.
```suggestion
if (blockNeedsPrefix && line.startsWith("+++ ")) {
blockNeedsPrefix = false;
return rewriteUnifiedFileLine(line, "+++ ", "b/");
}
```
Reviews (1): Last reviewed commit: "fix(loaders): restore a/b prefixes on no..." | Re-trigger Greptile |
| if (blockNeedsPrefix && line.startsWith("+++ ")) { | ||
| return rewriteUnifiedFileLine(line, "+++ ", "b/"); | ||
| } |
There was a problem hiding this comment.
blockNeedsPrefix is never cleared after the +++ header, corrupting removals of -- comment lines
Once the +++ file header is rewritten, blockNeedsPrefix stays true for the rest of the block. In a unified diff, a removed line whose content starts with -- (SQL/Lua/Haskell/Ada comments are exactly this) produces a diff body line that begins with --- (three dashes + space). This line is indistinguishable from the file header marker by the current startsWith("--- ") check, so it gets an a/ prefix prepended — corrupting the diff before it reaches the parser. A minimal repro: any noprefix diff that removes a SQL comment like -- drop table will have a content line --- drop table, which becomes --- a/drop table after this rewrite. The parser will reject the mangled patch, and normalizePatchChangeset will swallow the error and return files: [].
Setting blockNeedsPrefix = false immediately after the +++ line is processed is sufficient; the ---/+++ pair is always the last pair of header lines before the first @@ hunk marker in any valid git diff output.
| if (blockNeedsPrefix && line.startsWith("+++ ")) { | |
| return rewriteUnifiedFileLine(line, "+++ ", "b/"); | |
| } | |
| if (blockNeedsPrefix && line.startsWith("+++ ")) { | |
| blockNeedsPrefix = false; | |
| return rewriteUnifiedFileLine(line, "+++ ", "b/"); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/loaders.ts
Line: 201-203
Comment:
**`blockNeedsPrefix` is never cleared after the `+++` header, corrupting removals of `--` comment lines**
Once the `+++ ` file header is rewritten, `blockNeedsPrefix` stays `true` for the rest of the block. In a unified diff, a removed line whose content starts with `-- ` (SQL/Lua/Haskell/Ada comments are exactly this) produces a diff body line that begins with `--- ` (three dashes + space). This line is indistinguishable from the file header marker by the current `startsWith("--- ")` check, so it gets an `a/` prefix prepended — corrupting the diff before it reaches the parser. A minimal repro: any noprefix diff that removes a SQL comment like `-- drop table` will have a content line `--- drop table`, which becomes `--- a/drop table` after this rewrite. The parser will reject the mangled patch, and `normalizePatchChangeset` will swallow the error and return `files: []`.
Setting `blockNeedsPrefix = false` immediately after the `+++ ` line is processed is sufficient; the `---`/`+++` pair is always the last pair of header lines before the first `@@` hunk marker in any valid git diff output.
```suggestion
if (blockNeedsPrefix && line.startsWith("+++ ")) {
blockNeedsPrefix = false;
return rewriteUnifiedFileLine(line, "+++ ", "b/");
}
```
How can I resolve this? If you propose a fix, please make it concise.…'t rewritten Once the `+++ ` file header is processed, any subsequent `--- ` line in the hunk body is content (the deletion marker `-` plus a line whose original content begins with `-- `, e.g. SQL/Lua/Haskell comments). Without clearing `blockNeedsPrefix`, those deletion lines were rewritten with an `a/` prefix, silently corrupting the deletion content (`-- drop table users;` → `-- a/drop table users;`). The line count still added up so the file usually still rendered, just with the wrong text on the left side of the diff. Adds a regression test that fails on the previous commit and passes here. Reported by greptile-apps in review of modem-dev#240.
|
Thanks @greptile-apps — confirmed real bug, fixed in bf21291. For anyone tracking, the corruption I could reproduce is slightly different from "the file disappears": with the original commit a noprefix patch that deletes a SQL |
|
The corruption you described is a subtler failure mode than the outright The fix in bf21291 is exactly right: clearing Thanks for the thorough follow-up and for including the credit in the commit message. Tip: You can customize Greptile's behavior for this repo with |
Patches reaching `normalizePatchChangeset` from `hunk pager` and `hunk patch` come from a `git` process Hunk does not invoke itself, so the user's `diff.noprefix=true` (or repo-level equivalents) flow through unchanged. `@pierre/diffs` requires `a/` and `b/` on `diff --git`, `---`, and `+++` headers and throws a `TypeError` on the first noprefix header, which the outer `try/catch` swallows into a zero-file changeset. The visible symptom is "No files match the current filter." in the pager TUI. The git-backed paths already force `diff.noprefix=false` via `DIFF_PREFIX_NORMALIZATION_ARGS`. This change extends the same guarantee to the patch path by rewriting noprefix headers back into canonical `a/X b/Y` form before parsing. Per-block tracking ensures we only rewrite `---`/`+++` lines for files whose `diff --git` line actually needed rewriting, so paths inside an `a/`-named directory that already arrived with prefixes are left alone. The one case left untouched is a rename of unquoted paths that contain spaces (e.g. `diff --git old name.txt new name.txt`), which is genuinely ambiguous in noprefix output even to git itself.
…'t rewritten Once the `+++ ` file header is processed, any subsequent `--- ` line in the hunk body is content (the deletion marker `-` plus a line whose original content begins with `-- `, e.g. SQL/Lua/Haskell comments). Without clearing `blockNeedsPrefix`, those deletion lines were rewritten with an `a/` prefix, silently corrupting the deletion content (`-- drop table users;` → `-- a/drop table users;`). The line count still added up so the file usually still rendered, just with the wrong text on the left side of the diff. Adds a regression test that fails on the previous commit and passes here. Reported by greptile-apps in review of modem-dev#240.
|
@benvinegar let me know if you have any questions about this PR, I'd be happy to explain the problem it fixes more in depth if necessary |
Summary
hunk pagerandhunk patchshow "No files match the current filter." for any input produced by agit diffthat ran withdiff.noprefix=true. The diff itself reaches Hunk fine — the parser just rejects it.Repro (in any repo, in a TTY):
Same symptom via the patch path:
git -c diff.noprefix=true diff HEAD~1 | hunk patch -Root cause
@pierre/diffsprocessFile()runs:ALTERNATE_FILE_NAMES_GITrequires literala/andb/prefixes:With
diff.noprefix=true, git emitsdiff --git src/x.ts src/x.ts(noa//b/). The regex fails,nameisundefined,.trim()throwsTypeError, andparsePatchFilesrethrows because Hunk passesthrowOnError: true. The outertry/catchinnormalizePatchChangesetswallows the throw and returnsfiles: []— hence the empty review.Why it only affects the patch path
src/core/git.tsalready defends git-backed reviews withDIFF_PREFIX_NORMALIZATION_ARGS:That covers
hunk diff,hunk show,hunk stash showbecause Hunk runs git itself. It can't cover the patch path, where the patch text was already produced by an outer git process (the user'score.pager = hunk pager, or a literalgit diff | hunk patch).There is also a hint of this in the existing test suite:
loads colorized git patch files like the real pager stdin streamshells out togit diff --no-index --color=alwaysand feeds the result toloadAppBootstrap. On any machine withdiff.noprefix=trueset globally, that test fails today (Expected length: 1, Received length: 0).Fix
Add
normalizeGitPatchPrefixes()tosrc/core/loaders.tsand run it insidenormalizePatchChangesetbeforeparsePatchFiles. The helper:diff --gitblock.diff --githeaders in noprefix form (quoted or unquoted, single- or multi-token paths) and rewrites them to canonicala/X b/Y.---/+++lines in that block. That keeps it safe for paths that legitimately live inside ana/-named directory and were already prefixed (diff --git a/a/inner.ts b/a/inner.ts)./dev/nulluntouched on the unified-diff lines.diff --git old name.txt new name.txt). Git itself can't unambiguously round-trip that withdiff.noprefix=true, so we don't try to.Tests
Three new tests in
src/core/loaders.test.tsexercise the patch path directly viakind: "patch":previousPathrecovered.a/-named directory is preserved unchanged.The previously-failing
loads colorized git patch files like the real pager stdin streamtest passes on machines that havediff.noprefix=trueset globally.Test plan
bun run typecheckbun test src/core/— 140 pass, 0 failbun test src/core/loaders.test.ts— 37 pass, 0 fail (3 new)bun run lint— 0 warnings, 0 errorsbun run formatgit config --global diff.noprefix true && git diff HEAD~3..HEAD | hunk pagernow renders the diff instead of the empty-state message.