fix(host-service): pin git's diff prefixes so the Changes pane can parse every patch
What & why
Fixes #7596.
With diff.mnemonicPrefix = true (or diff.noprefix = true, or explicit diff.srcPrefix/diff.dstPrefix) in your gitconfig, every file in the Changes pane's Staged and Unstaged sections renders Unable to load diff. Against base and Commits keep working, which is what made it look category-specific.
buildDiffPatch shells out to git diff without pinning any of the prefix config, so git honours whatever the user carries. The renderer parses that patch text, and @pierre/diffs recognises a section header only in git's default shape:
// @pierre/diffs 1.3.6, dist/constants.js:18
const ALTERNATE_FILE_NAMES_GIT = /^diff --git (?:"a\/(.+?)"|a\/(.+?)) (?:"b\/(.+?)"|b\/(.+?))$/;
A header it rejects yields a section with an empty file name, so byPath.get(member.file.path) in useDiffCodeViewItems misses and the file falls through to the placeholder. Comparing two commits is the one case mnemonic prefixes coincide with a/+b/ — hence Against base and Commits surviving.
The fix pins the output-affecting config at the invocation (git -c ... diff) rather than teaching the parser everyone's settings. It goes in runDiff, the single funnel both the category diff and the untracked --no-index calls pass through, so no call site can forget it. -c rather than --default-prefix, which says the same thing in one flag but only from git 2.45 and exits non-zero as an unknown option before that; unknown config keys are ignored by every git version, so this has no version floor.
Also fixed: non-ASCII filenames, which need no unusual config at all. core.quotePath defaults to true, so git emits diff --git "a/caf\303\251.txt" "b/caf\303\251.txt". That regex matches, but captures the escaped literal, while the sidebar's status walk uses -z and reports the raw café.txt. The lookup misses and you get the same placeholder with no console error. core.quotePath=false closes it on the same line of config.
Both are one root cause — git output the parser can't join back to a path — so they're fixed together.
How I tested it
Every header form git can emit, joined to the renderer's own lookup. Ran the real buildDiffPatch against temp repos, fed the result to the real parsePatchFiles, and did the byPath.get(statusPath) lookup useDiffCodeViewItems does:
Before:
| config | header emitted | byPath lookup |
|---|---|---|
| default | diff --git a/tracked.txt b/tracked.txt | HIT, renders |
diff.mnemonicPrefix=true | diff --git i/tracked.txt w/tracked.txt | MISS, placeholder |
diff.noprefix=true | diff --git tracked.txt tracked.txt | MISS, placeholder |
default, café.txt | diff --git "a/caf\303\251.txt" "b/caf\303\251.txt" | MISS, placeholder |
After:
| config | header emitted | byPath lookup |
|---|---|---|
| default | diff --git a/tracked.txt b/tracked.txt | HIT, renders |
diff.mnemonicPrefix=true | diff --git a/tracked.txt b/tracked.txt | HIT, renders |
diff.noprefix=true | diff --git a/tracked.txt b/tracked.txt | HIT, renders |
default, café.txt | diff --git a/café.txt b/café.txt | HIT, renders |
Regression tests added to diff-patch.test.ts, one per knob plus the non-ASCII case, covering unstaged, staged and the untracked --no-index path. All four fail on main and pass with the change:
(fail) diff.mnemonicPrefix does not reach the unstaged or staged header
Expected to contain: "diff --git a/tracked.txt b/tracked.txt"
Received: "diff --git i/tracked.txt w/tracked.txt ..."
(fail) diff.noprefix does not strip the header's prefixes
(fail) diff.srcPrefix and diff.dstPrefix do not replace the header's prefixes
(fail) a non-ASCII path is reported raw, as the status walk reports it
Ran: bun run lint clean, bun run lint:fix no changes, diff-patch.test.ts 8/8 pass, and the whole @superset/host-service suite at 1992 pass / 5 fail.
All five failures are pre-existing and unrelated; I checked each rather than assuming:
- 3 filesystem-permission tests (
removeDirectoryTreex2,workspaceCleanup.destroy) fail withENOTEMPTYon a read-only directory, i.e. localrmsemantics. - 2 adversarial
getGitStatusPartialtests. I re-ran that directory with my change stashed: it fails the same ones on baseline. Nothing importsdiff-patch.tsfrom the status path either. bun run typecheckreports 22 errors, all inpackages/auth/packages/trpc(better-authInferAPItypings), with an identical count before and after this change. My install ended up with two copies of@better-auth/core@1.6.22under different hashes, which is what makes the plugin types nominally incompatible. Local install artifact.@superset/workspace-fsfailed one test in the fullbun run testrun and passed on re-run. Flake; this diff doesn't touch it.
No screenshot, and I want to be upfront about why. I brought up the full local stack and drove the dev app over CDP, but a project registered through projects.openFromPath doesn't surface in the v2 sidebar or workspace list in a freshly seeded dev org, and "Open from folder" goes through a native dialog CDP can't drive, so I couldn't reach the Changes pane to capture before/after. That's dev-environment plumbing rather than anything about this fix, and I'd rather say so than substitute a screenshot of a synthetic harness for one of the real pane. The tables above test the same join the pane depends on, with the real patch builder and the real parser.
(Unrelated, happy to file separately if useful: ./.superset/setup.local.sh fails at "Seed dev account" on a fresh install with Cannot find package 'babel-plugin-macros'. It's an optional peer of @lingui/core, so nothing installs it, but seed-dev.ts pulls in the Lingui macro at runtime. bun add -d babel-plugin-macros unblocks it.)
Checklist
- PR title follows conventional commits (
type(scope): subject) -
bun run lintpasses;bun run typecheckhas the same 22 pre-existing failures with and without this change (see above) - "Allow edits from maintainers" — branch PR, not a fork
Summary by cubic
Fixes the Changes pane showing "Unable to load diff" for staged and unstaged files whenever a user's gitconfig changes git's diff header format, or when a file name contains non-ASCII characters (#7596).
Bug Fixes
- Pins
diff.mnemonicPrefix,diff.noprefix, anddiff.srcPrefix/diff.dstPrefixat therunDiffinvocation so every patch uses thediff --git a/f b/fheader shape@pierre/diffsrecognizes. - Sets
core.quotePath=falseso non-ASCII paths are emitted raw, matching the-zstatus walk's report. - Uses
-cconfig flags rather than--default-prefix, which exits non-zero on git versions before 2.45. - Adds regression tests for each knob and the non-ASCII case across unstaged, staged, and
--no-indexpaths.
Summary by CodeRabbit
- Bug Fixes
- Improved diff display reliability when custom Git prefix settings are configured.
- Non-ASCII file paths now appear correctly in diff headers without unwanted quoting.
- Staged and unstaged diffs consistently use the expected file header format.