Repository navigation
Status() and Diff() throw GitRepositoryNotFoundException for a valid repository when one submodule's git directory is missing #238
Description
What's wrong
GitCommandBuilder.CreateException classifies any failure whose diagnostic contains not a git repository as GitRepositoryNotFoundException, documented as "The path given is not inside a git working tree":
GitIntegration/GitIntegration/Builders/GitCommandBuilder.cs
Lines 236 to 249 in 65984d7
| string message = $"git exited with code {result.ExitCode}: {diagnostic.Trim()}"; | |
| // Git reports a missing working tree with a stable phrase and exit code 128. Surfacing it | |
| // as a distinct type lets callers distinguish "wrong directory" from "command failed". | |
| // | |
| // The phrase match relies on an English git, which RunCommandGitProcessRunner guarantees by | |
| // running every invocation with LC_ALL=C. That guarantee is the whole reason the match is | |
| // dependable: git translates its messages wherever the host has the catalogs installed, so | |
| // without a forced locale this would silently miss on a non-English machine and degrade to | |
| // a generic GitCommandException. Any alternative IGitProcessRunner implementation must | |
| // force the locale too, or accept that degradation. | |
| return diagnostic.Contains("not a git repository", StringComparison.OrdinalIgnoreCase) | |
| ? new GitRepositoryNotFoundException(message, result.ExitCode, result.Arguments, diagnostic) | |
| : new GitCommandException(message, result.ExitCode, result.Arguments, diagnostic); |
git uses the same phrase for a submodule's broken gitfile, for example fatal: not a git repository: sub/../.git/modules/sub. In that case the superproject is a perfectly good repository, but the library tells the caller it is not one.
Reproduction (git 2.43, verified through the library)
git init up && git -C up commit --allow-empty -m base
git clone up down
git -c protocol.file.allow=always -C down submodule add ../up sub
git -C down commit -m sub
rm -rf down/.git/modules/sub # e.g. a copy or bind mount that left out .git/modulesThen, against down:
OpenAsync(down) -> succeeds (rev-parse does not touch submodules)
Status() -> GitRepositoryNotFoundException: git exited with code 128: fatal: not a git repository: sub/../.git/modules/sub
Diff() -> GitRepositoryNotFoundException: (same)
Submodules() -> ok
Log() -> ok
Why it matters
The same GitRepository is reported as found by OpenAsync and as not found by Status(). A caller that does the obvious thing with GitRepositoryNotFoundException (show "this folder is not a git repository", offer Init, drop it from a list of repositories) acts on a false statement, and the real problem, one broken submodule, is hidden behind the wrong type.
Suggested fix
Only classify as GitRepositoryNotFoundException when git is talking about the repository the command was scoped to:
- the discovery failure forms,
not a git repository (or any of the parent directories)andnot a git repository (or any parent up to mount point …), or - a
not a git repository: <path>whose path is the repository's own git directory (for example a linked worktree whose admin directory has gone).
Any other not a git repository: <path>, such as one naming …/.git/modules/…, should stay a plain GitCommandException carrying git's message.
Acceptance criteria
- With the reproduction above,
Status()andDiff()throwGitCommandExceptionand notGitRepositoryNotFoundException, and the message still names the broken submodule path. - Running a verb against a directory that is not inside any repository still throws
GitRepositoryNotFoundException. - A unit test on
CreateExceptioncovers both diagnostic forms.
Activity
matt-edmondson commentedon Oct 17, 2026
Triage
GitIntegration/Builders/GitCommandBuilder.CreateException, which classifies git diagnosticsNotes: The exception type is wrong only in an uncommon setup: a submodule whose
.git/modules/<name>is missing. The consequence is misleading, though, because callers may offerInitor drop the repository. The fix is contained: match only the discovery forms and the repository's own git directory, and add unit tests onCreateExceptionfor both diagnostic shapes.Generated by Claude Code