Skip to content

Diff viewer hangs on "Loading diff..." with many untracked files (per-file git subprocess in Vcs.diff) #34916

Description

@liangrud

Description

The diff viewer (GET /vcs/diff, mode: "git") hangs on "Loading diff..." for minutes when the working tree has many untracked files. It does eventually return, but the per-file git subprocess spawn makes it impractically slow. Most severe on Windows where each git subprocess spawn is ~150-165ms.

Root cause is in packages/opencode/src/project/vcs.ts:

  • Vcs.difftrack(git, cwd, ref, options) (vcs.ts:384). When there is no HEAD yet (freshly git init'd repo with no commits), ref is undefined, so track takes the if (!ref) branch (vcs.ts:230) and passes an empty batch (emptyBatch()) to files().
  • In files() (vcs.ts:178) the for loop iterates every untracked item. For each, patchForItem (vcs.ts:148) finds no batched patch and falls through to nativePatch (vcs.ts:162) → git.patchUntracked (vcs.ts:130), which spawns one git diff --no-index /dev/null <file> subprocess per file.
  • The MAX_TOTAL_PATCH_BYTES cap (vcs.ts:187) does not break the loop — it only swaps the patch for emptyPatch (vcs.ts:157). The stat line (vcs.ts:179) still calls git.statUntracked (another per-file subprocess) for every added item even after cap, since the cap check happens after it.

This is not fixed by having a HEAD either: diffAgainstRef (vcs.ts:201) merges untracked ?? items into the list (vcs.ts:214-217), but batchPatches (vcs.ts:96) only batch-patches tracked changes via git.patchAll (git diff --patch HEAD). Untracked items still miss the batch and fall through to per-file patchUntracked.

So any repo with a large number of untracked files (e.g. node_modules not yet gitignored, generated/synced caches) makes the diff viewer effectively unusable, regardless of HEAD presence.

Steps to reproduce

  1. In a directory with many untracked files (700+), run git init but create no commit.
  2. Start opencode, open the diff viewer (/diff or command palette → "Open diff viewer").
  3. It stays on "Loading diff..." for several minutes. With ~700 files on Windows it is ~2+ minutes; with ~4000+ it never practically returns.

Per-file commands Vcs.diff ends up running:

git diff --no-index --numstat -- /dev/null <file>                                              # statUntracked
git diff --no-index --patch --no-ext-diff --no-renames --unified=3 -- /dev/null <file>        # patchUntracked

Operating System

Windows 11 (each git subprocess spawn ~150-165ms here, which compounds the problem; less severe but still linear on macOS/Linux)

Suggested fix directions (optional)

  • Batch untracked-file patches in a single git invocation instead of per-file git diff --no-index (e.g. feed multiple paths, or diff the whole untracked set against an empty tree once).
  • In files(), break the loop once capped is true (or at least skip git.statUntracked for capped items).
  • Make the stat line (vcs.ts:179) respect capped before spawning git.statUntracked.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions