desktop: Projects shows non-ASCII file names octal-escaped and renders their diff empty
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Observed behavior
In the Projects PR view ("Files changed"), files whose names contain umlauts or any other non-ASCII characters appear with an octal-escaped, quoted path:
```
"Beppo-Auftr\303\244ge/B1 \342\200\224 Ergebnis.md"
```
Worse, the **diff body of those files is empty**. The file row correctly shows `+158 −0`, but no hunks are rendered below it. There is no error message — the user sees a file that the counter says changed, containing no visible change.
## Expected behavior
```
Beppo-Aufträge/B1 — Ergebnis.md
```
followed by the full diff.
## Reproduction (verified, git 2.x, platform independent)
```bash
mkdir repro && cd repro && git init -q .
mkdir "Beppo-Aufträge"
printf 'hallo\n' > "Beppo-Aufträge/B1 — Ergebnis.md"
git add -A && git commit -qm init
printf 'zeile2\n' >> "Beppo-Aufträge/B1 — Ergebnis.md"
git add -A && git commit -qm second
git diff --numstat HEAD~1..HEAD
# 1 0 "Beppo-Auftr\303\244ge/B1 \342\200\224 Ergebnis.md"
P=$(git diff --numstat HEAD~1..HEAD | cut -f3)
git diff HEAD~1..HEAD -- "$P"
# empty output, exit 0
```
That second call is exactly what the desktop code does.
## Cause
By default (`core.quotepath=true`) git octal-escapes every byte >= 0x80 in path output and wraps the path in quotes.
`desktop/src-tauri/src/commands/project_git_exec.rs` runs every `git` invocation under a sealed configuration:
- `GIT_CONFIG_NOSYSTEM=1`
- `GIT_CONFIG_GLOBAL=/dev/null`
- a fixed list of keys injected via `GIT_CONFIG_KEY_n` / `GIT_CONFIG_VALUE_n` in `configure_git_auth` (`credential.helper`, `core.hooksPath`, `core.fsmonitor`, `protocol.*`)
`core.quotepath` is **not** in that list — the string does not occur anywhere in the repository. And because the system and global configs are disabled, a user cannot work around it from their own `.gitconfig` either.
The follow-on failure is in `desktop/src-tauri/src/commands/project_git_diff.rs`, `diff_from_repo` (around line 363):
```rust
let numstat = run_git(&["diff", "--numstat", range], Some(repo_dir), auth)?;
let files = parse_numstat(&numstat) // path taken verbatim
.into_iter()
.map(|(path, additions, deletions)| {
let patch = run_git(&["diff", /* ... */ range, "--", &path], Some(repo_dir), auth)
.unwrap_or_default(); // <-- matches nothing
```
The quoted string is handed straight back to `git diff` as a pathspec. Git matches nothing and exits **0 with empty stdout**, so the failure is silent, and `unwrap_or_default()` swallows it on top.
## Affected call sites
| File / line | Call | Effect |
|---|---|---|
| `project_git_diff.rs:363` | `git diff --numstat ` | **main bug**: escaped path in the UI + empty patch |
| `project_git.rs:391` | `git log --name-only --diff-filter=ACMRT` | keys of `latest_commit_by_path` are escaped |
| `project_git.rs:455` | same (second path) | same |
| `project_git.rs:471` | `git ls-files ... -z` | **correct** (`-z` suppresses quoting) |
| `project_git.rs:596` | `git status --porcelain` | harmless (boolean evaluation only) |
| `project_git_workflow.rs:605` | `git diff --name-only --diff-filter=U` | harmless (boolean evaluation only) |
**Secondary effect of 391/455 vs. 471:** `parse_worktree_files` splits the `ls-files -z` output on `\0` (raw UTF-8 paths) and looks those up in `latest_commit_by_path`, whose keys come from `log --name-only` and are escaped. For non-ASCII files the lookup misses, so the file browser shows no "last commit" information (author/date/message).
## Second, independent bug in the same code: renames
`parse_numstat` (`project_git_diff.rs:151`) takes the third tab-separated field as the path. For a detected rename, `git diff --numstat` emits:
```
0 0 alt.md => neu.md
```
Rename detection has been on by default since git 2.9 and the call does not pass `--no-renames`. `alt.md => neu.md` does not match anything as a pathspec either, so **a renamed file renders an empty patch even with pure ASCII names.** Verified in the same repro setup.
## Notes
- No existing issue: searched issues and PRs for `quotepath`, `non-ASCII`, `umlaut`, `diacritic`, `octal`, `numstat` — no hits.
- Not to be confused with #5989 (Windows `MAX_PATH` in the Projects repo browser) or #5070 (message renderer corrupts non-ASCII to `?`). Both are different code paths.
- There is no test coverage for non-ASCII paths under `desktop/src-tauri/src/commands/`.
Contributor guide
Research direction
Start with desktop/src-tauri/src/commands/project_git_diff.rs, especially parse_numstat and diff_from_repo, then inspect git invocation and path handling in project_git_exec.rs and project_git.rs. Run the supplied non-ASCII reproduction and verify behavior for renamed files. Done means Projects displays the original UTF-8 paths, renders their complete diffs, and preserves last-commit metadata for affected files.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, rust
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100