Skip to content

Commit eac8b3f

Browse files
committed
fix(session): skip caching failed project root lookups
A missing directory or a failed git lookup is not a stable root. Caching it pins the session key to the fallback for the process.
1 parent d303dee commit eac8b3f

2 files changed

Lines changed: 51 additions & 14 deletions

File tree

‎src/session/project-key.test.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,41 @@ exec ${shQuote(realGit)} "$@"
9898
}
9999
});
100100

101+
test("projectRootFor resolves a directory created after a missing lookup", async () => {
102+
initTemporaryGitRepo(root);
103+
await commitReadme(root);
104+
105+
const parent = realpathSync(root);
106+
const child = join(parent, "child");
107+
expect(projectRootFor(child)).toBe(child);
108+
await mkdir(child);
109+
expect(projectRootFor(child)).toBe(parent);
110+
});
111+
112+
test("projectRootFor retries a nested dir after git was unavailable", async () => {
113+
initTemporaryGitRepo(root);
114+
await commitReadme(root);
115+
116+
const nested = join(root, "nested");
117+
await mkdir(nested);
118+
const parent = realpathSync(root);
119+
const emptyBin = join(root, "empty-bin");
120+
await mkdir(emptyBin);
121+
122+
const previousPath = process.env.PATH;
123+
process.env.PATH = emptyBin;
124+
try {
125+
expect(projectRootFor(nested)).toBe(realpathSync(nested));
126+
} finally {
127+
if (previousPath === undefined) {
128+
delete process.env.PATH;
129+
} else {
130+
process.env.PATH = previousPath;
131+
}
132+
}
133+
expect(projectRootFor(nested)).toBe(parent);
134+
});
135+
101136
test("linked worktrees have distinct project roots and keys from main and each other", async () => {
102137
initTemporaryGitRepo(root);
103138
await commitReadme(root);

‎src/session/project-key.ts‎

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ function slugSegment(name: string): string {
2929
return slug.length > 0 ? slug : "project";
3030
}
3131

32-
// Repeated boot lookups of one cwd must not each spawn git. Keyed by the
33-
// resolved directory when it exists so equivalent spellings share one entry.
34-
// Missing paths keep the original string, because that string is the result.
32+
// Repeated boot lookups of one cwd must not each spawn git. Only a successful
33+
// toplevel is stored: a missing directory or a failed git lookup must be tried
34+
// again, or the session key stays on the fallback for the process lifetime.
3535
const projectRootByResolvedCwd = new Map<string, string>();
3636

3737
function resolvedCwd(cwd: string): string {
@@ -42,7 +42,7 @@ function resolvedCwd(cwd: string): string {
4242
}
4343
}
4444

45-
function projectRootUncached(cwd: string): string {
45+
function gitToplevel(cwd: string): string | undefined {
4646
try {
4747
const toplevelRaw = execFileSync(
4848
"git",
@@ -53,16 +53,15 @@ function projectRootUncached(cwd: string): string {
5353
stdio: ["ignore", "pipe", "ignore"],
5454
},
5555
).trim();
56-
if (toplevelRaw.length > 0) {
57-
const toplevelAbs = isAbsolute(toplevelRaw)
58-
? toplevelRaw
59-
: resolve(cwd, toplevelRaw);
60-
return realpathOr(toplevelAbs);
61-
}
56+
if (toplevelRaw.length === 0) return undefined;
57+
const toplevelAbs = isAbsolute(toplevelRaw)
58+
? toplevelRaw
59+
: resolve(cwd, toplevelRaw);
60+
return realpathOr(toplevelAbs);
6261
} catch {
6362
// Not a git checkout (or git unavailable) — use the workspace path.
63+
return undefined;
6464
}
65-
return realpathOr(cwd);
6665
}
6766

6867
/**
@@ -75,9 +74,12 @@ export function projectRootFor(cwd: string): string {
7574
const key = resolvedCwd(cwd);
7675
const cached = projectRootByResolvedCwd.get(key);
7776
if (cached !== undefined) return cached;
78-
const root = projectRootUncached(cwd);
79-
projectRootByResolvedCwd.set(key, root);
80-
return root;
77+
const toplevel = gitToplevel(cwd);
78+
if (toplevel !== undefined) {
79+
projectRootByResolvedCwd.set(key, toplevel);
80+
return toplevel;
81+
}
82+
return realpathOr(cwd);
8183
}
8284

8385
export function projectKeyFor(cwd: string): string {

0 commit comments

Comments
 (0)