diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 4f5f6b059c711..9bd6ed646640f 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -4637,6 +4637,13 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { yield* git(cwd, ["checkout", "-b", "feature/many-untracked"]); yield* writeTextFile(cwd, ".git/info/exclude", "ignored.txt\n"); yield* writeTextFile(cwd, "bulk[1]/ignored.txt", "ignored\n"); + const clean = yield* driver.statusDetailsLocal(cwd, { includeBranchChanges: true }); + assert.deepStrictEqual(clean.branchChanges, { + baseRef: "main", + insertions: 0, + deletions: 0, + }); + assert.deepStrictEqual(untrackedScanRoots, []); yield* Effect.forEach( Array.from({ length: 1_001 }, (_, index) => `bulk[1]/${index}.txt`), (file) => writeTextFile(cwd, file, "one\ntwo\n"), diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 71487a327fc1f..c6c355328b534 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -630,6 +630,11 @@ export function isNonRepositoryGitStderr(stderr: string): boolean { const normalized = stderr.toLowerCase(); return NON_REPOSITORY_STDERR_PHRASES.some((phrase) => normalized.includes(phrase)); } +// Unknown spellings count as hidden so their untracked files are still found by a full scan. +const UNTRACKED_FILES_SHOWN_CONFIG_VALUES = new Set(["normal", "all", "true", "yes", "on", "1"]); +function untrackedFilesHiddenByConfig(value: string | null): boolean { + return value !== null && !UNTRACKED_FILES_SHOWN_CONFIG_VALUES.has(value.toLowerCase()); +} function isUnbornHeadStderr(stderr: string): boolean { const normalized = stderr.toLowerCase(); return ( @@ -3738,8 +3743,16 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* detail: remembered.detail, }); } - // Empty status can mean untracked files are hidden; retain the full scan. - const untracked = yield* prepareUntrackedReviewIndexOrReason(cwd, pathspecs, undefined, false); + // Empty status means no untracked files unless config hides them; then scan the whole tree. + const untracked = + roots.length === 0 && + !untrackedFilesHiddenByConfig( + yield* readConfigValue(cwd, "status.showUntrackedFiles").pipe( + Effect.orElseSucceed(() => "no"), + ), + ) + ? { env: undefined, omitted: false } + : yield* prepareUntrackedReviewIndexOrReason(cwd, pathspecs, undefined, false); if ("unavailable" in untracked) { const count = remembered?.roots === rootsKey ? remembered.count + 1 : 1; untrackedTotalsFailures.delete(cwd);