Skip to content

Commit d303dee

Browse files
committed
perf(session): memoize project root lookups
1 parent f5e3cd2 commit d303dee

2 files changed

Lines changed: 78 additions & 9 deletions

File tree

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

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { afterEach, beforeEach, expect, test } from "bun:test";
2-
import { mkdir, rm, writeFile } from "node:fs/promises";
2+
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
33
import { realpathSync } from "node:fs";
4-
import { join } from "node:path";
4+
import { delimiter, join } from "node:path";
55
import { tmpdir } from "node:os";
66
import { execFileSync } from "node:child_process";
77

@@ -27,6 +27,10 @@ afterEach(async () => {
2727
await rm(root, { recursive: true, force: true });
2828
});
2929

30+
function shQuote(value: string): string {
31+
return `'${value.replaceAll("'", "'\\''")}'`;
32+
}
33+
3034
async function commitReadme(dir: string): Promise<void> {
3135
await writeFile(join(dir, "README"), "x");
3236
execFileSync("git", ["add", "README"], { cwd: dir, stdio: "ignore" });
@@ -51,6 +55,49 @@ test("projectKeyFor shares nested dirs under the same git toplevel", async () =>
5155
expect(projectKeyFor(nested)).toBe(projectKeyFor(root));
5256
});
5357

58+
test("projectRootFor spawns git rev-parse once for the same cwd", async () => {
59+
initTemporaryGitRepo(root);
60+
await commitReadme(root);
61+
62+
const countFile = join(root, "rev-parse-spawns");
63+
await writeFile(countFile, "0\n");
64+
const realGit = execFileSync("/usr/bin/which", ["git"], {
65+
encoding: "utf8",
66+
}).trim();
67+
const bin = join(root, "git-bin");
68+
await mkdir(bin);
69+
const wrapper = join(bin, "git");
70+
const resolved = realpathSync(root);
71+
await writeFile(
72+
wrapper,
73+
`#!/bin/sh
74+
if [ "$1" = "rev-parse" ] && [ "$(pwd)" = ${shQuote(resolved)} ]; then
75+
n=$(cat ${shQuote(countFile)})
76+
echo $((n + 1)) > ${shQuote(countFile)}
77+
fi
78+
exec ${shQuote(realGit)} "$@"
79+
`,
80+
);
81+
await chmod(wrapper, 0o755);
82+
83+
const previousPath = process.env.PATH;
84+
process.env.PATH = `${bin}${delimiter}${previousPath ?? ""}`;
85+
try {
86+
const first = projectRootFor(root);
87+
const second = projectRootFor(root);
88+
expect(second).toBe(first);
89+
expect(first).toBe(realpathSync(root));
90+
const spawns = Number((await readFile(countFile, "utf8")).trim());
91+
expect(spawns).toBe(1);
92+
} finally {
93+
if (previousPath === undefined) {
94+
delete process.env.PATH;
95+
} else {
96+
process.env.PATH = previousPath;
97+
}
98+
}
99+
});
100+
54101
test("linked worktrees have distinct project roots and keys from main and each other", async () => {
55102
initTemporaryGitRepo(root);
56103
await commitReadme(root);

‎src/session/project-key.ts‎

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

32-
/**
33-
* Checkout project root for session identity.
34-
*
35-
* Nested cwd under a git toplevel shares that root (and therefore the same
36-
* project key). Linked worktrees each have their own `--show-toplevel`.
37-
*/
38-
export function projectRootFor(cwd: string): string {
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.
35+
const projectRootByResolvedCwd = new Map<string, string>();
36+
37+
function resolvedCwd(cwd: string): string {
38+
try {
39+
return realpathSync(cwd);
40+
} catch {
41+
return cwd;
42+
}
43+
}
44+
45+
function projectRootUncached(cwd: string): string {
3946
try {
4047
const toplevelRaw = execFileSync(
4148
"git",
@@ -58,6 +65,21 @@ export function projectRootFor(cwd: string): string {
5865
return realpathOr(cwd);
5966
}
6067

68+
/**
69+
* Checkout project root for session identity.
70+
*
71+
* Nested cwd under a git toplevel shares that root (and therefore the same
72+
* project key). Linked worktrees each have their own `--show-toplevel`.
73+
*/
74+
export function projectRootFor(cwd: string): string {
75+
const key = resolvedCwd(cwd);
76+
const cached = projectRootByResolvedCwd.get(key);
77+
if (cached !== undefined) return cached;
78+
const root = projectRootUncached(cwd);
79+
projectRootByResolvedCwd.set(key, root);
80+
return root;
81+
}
82+
6183
export function projectKeyFor(cwd: string): string {
6284
const root = projectRootFor(cwd);
6385
const base = slugSegment(basename(root));

0 commit comments

Comments
 (0)