block / block/buzz

desktop: Projects shows non-ASCII file names octal-escaped and renders their diff empty

Open
#6,309 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.