Quote the Index line like the other file-name headers - #705
Conversation
formatPatch runs the file name through quoteFileNameIfNeeded on every header
line it writes -- rename from/to, copy from/to, --- and +++ -- except Index:,
which got the raw name. A name needing quotes then produced a patch parsePatch
could not read back:
createPatch('x\n--- evil', 'foo\n', 'bar\n')
-> parsePatch throws: Missing "+++ ..." file header for evil
parsePatch gains the matching unquoteIfQuoted, the same inverse it already
applies to rename from/to.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2de3afdf68
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (headerMatch) { | ||
| index.index = line.substring(headerMatch[0].length).trim(); | ||
| // Inverse of the quoteFileNameIfNeeded applied to the `Index:` line by formatPatch | ||
| index.index = unquoteIfQuoted(line.substring(headerMatch[0].length).trim()); |
There was a problem hiding this comment.
Preserve raw Index filenames from older jsdiff output
For a literal filename such as "report", previous versions of formatPatch emitted Index: "report" while quoting the ---/+++ names as "\"report\"". This unconditional decoding now parses such an older jsdiff patch with index === 'report' but oldFileName === '"report"', so consumers that use index to select the target file can load the wrong file. Restrict unquoting to representations that could have been produced by the new quoting behavior, or reconcile the index with the parsed file headers.
Useful? React with 👍 / 👎.
formatPatchwrites the file name on seven header lines. Six of them run it throughquoteFileNameIfNeeded—rename from,rename to,copy from,copy to,---and+++.Index:(src/patch/create.ts:416) emitted it raw, twelve lines above the---line that quotes it.So
createPatchcan produce a patch thatparsePatchcannot read back:And a quieter version with no throw —
createPatch('with\nnewline.txt', …)round-tripsoldFileNamecorrectly but yieldsindex === 'with'.The parse side gets
unquoteIfQuoted, which is the inverse this file already applies torename from/rename toatsrc/patch/parse.ts:129. Both halves reuse helpers that were already here; nothing new is introduced.What this argument does not rest on
I checked, and neither GNU
diffnorgitemits anIndex:line at all — it is a CVS/svn-ism — andgit applyignores it. So there is no external reference saying the line should be quoted. The case here is purely that jsdiff quotes this name everywhere else it writes it, and that its own parser chokes on its own output when it doesn't.Testing
Two tests in the existing
headers handlingblock, deliberately split so each pins one side: the create-side asserts the emitted text, the parse-side parses a hardcoded string.master(expected 'Index: x\n--- evil…' to equal 'Index: "x\n--- evil"…'andexpected '"x\n--- evil"' to equal 'x\n--- evil') and pass here.libesm/: revertingcreate.tsalone fails only the create-side test, revertingparse.tsalone fails only the parse-side test.yarn lintis clean apart from the pre-existing warning on theTODOatparse.ts:188.createPatch/parsePatch/applyPatchover file names containing a space, a quote, a backslash, a tab,éand한글, plus content lines that mimic diff syntax — no regressions.One thing I could not run, honestly
I could not get a meaningful number out of the
nyc100% gate.yarn run-mochapasses--require ./runtime, and on Node 20, 22 and 25 that fails before any test runs — the path has no extension for the ESM resolver, andruntime.jsusesrequireinside a"type": "module"package. This happens on a clean checkout too, so it is not from this change; I worked around it locally with a.cjsshim to run the suite at all. Undernycthe instrumentation then never attaches and every file reports 0%, base included, so the figure is meaningless rather than failing.The structural argument in the meantime: this changes two existing lines and adds no branch, both call pre-existing helpers, and each changed line is proven executed because reverting it turns exactly one test red.
unquoteIfQuoted's false branch is already taken by every ordinaryIndex: foopatch. Worth confirming on whatever Node versionyarn testactually runs for you — and you may want that--requireline to point at a.cjsfile regardless.Left alone
The trailing-space file name quirk (
'tail '→'tail') is real but is the case you explicitly deferred to a major release atparse.ts:188-196.AI assistance disclosure: this patch was found and written with Claude Code. Every quoted output above is verbatim from running it against
masterand against this branch.