Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/pi-extension/server/file-browser-watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ function ensureWatcher(target: WatchTarget): WatchEntry {
entry.gitWatcher = chokidar.watch(gitWatchPaths, {
ignoreInitial: true,
persistent: true,
// These are exact metadata files. Keep this non-recursive so a future
// target cannot make startup walk the repository's entire refs tree.
depth: 0,
awaitWriteFinish: {
stabilityThreshold: 80,
pollInterval: 30,
Expand Down
3 changes: 3 additions & 0 deletions packages/server/reference-watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ function ensureWatcher(target: WatchTarget): WatchEntry {
entry.gitWatcher = chokidar.watch(gitWatchPaths, {
ignoreInitial: true,
persistent: true,
// These are exact metadata files. Keep this non-recursive so a future
// target cannot make startup walk the repository's entire refs tree.
depth: 0,
awaitWriteFinish: {
stabilityThreshold: 80,
pollInterval: 30,
Expand Down
32 changes: 29 additions & 3 deletions packages/shared/workspace-status.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, test } from "bun:test";
import { chmodSync, existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
import { chmodSync, existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { delimiter, join } from "node:path";
import { spawnSync } from "node:child_process";
Expand Down Expand Up @@ -205,18 +205,44 @@ describe("workspace status", () => {
expect(status.totals.deletions).toBe(1);
});

test("resolves git metadata paths when watching a repository subdirectory", () => {
test("uses only bounded exact git metadata targets even when refs is large", () => {
const repo = tempRepo();
const subdir = join(repo, "docs", "sub");
mkdirSync(subdir, { recursive: true });
writeFileSync(join(subdir, "plan.md"), "# Plan\n");
git(repo, "add", "-A");
git(repo, "commit", "-m", "init");
for (let i = 0; i < 64; i++) {
const refDir = join(repo, ".git", "refs", "remotes", `remote-${i}`);
mkdirSync(refDir, { recursive: true });
writeFileSync(join(refDir, "topic"), `${"0".repeat(40)}\n`);
}

const paths = getGitMetadataWatchPaths(subdir);
const realRepo = realpathSync(repo);
const gitDir = join(realRepo, ".git");

expect(new Set(paths)).toEqual(new Set([
join(gitDir, "HEAD"),
join(gitDir, "index"),
join(gitDir, "logs", "HEAD"),
join(gitDir, "packed-refs"),
join(gitDir, "refs", "heads", "main"),
]));
expect(paths).not.toContain(join(gitDir, "refs"));
expect(paths.every((path) => !existsSync(path) || statSync(path).isFile())).toBe(true);
});

test("keeps the exact current branch target when its ref is packed", () => {
const repo = tempRepo();
writeFileSync(join(repo, "plan.md"), "# Plan\n");
git(repo, "add", "-A");
git(repo, "commit", "-m", "init");
git(repo, "pack-refs", "--all", "--prune");

expect(paths).toContain(join(realRepo, ".git", "refs"));
const currentRef = join(realpathSync(repo), ".git", "refs", "heads", "main");
expect(existsSync(currentRef)).toBe(false);
expect(getGitMetadataWatchPaths(repo)).toContain(currentRef);
});

test("counts staged and unstaged changes when the net diff against HEAD is empty", async () => {
Expand Down
29 changes: 22 additions & 7 deletions packages/shared/workspace-status.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { spawn, spawnSync } from "node:child_process";
import { existsSync, realpathSync } from "node:fs";
import { readFileSync, realpathSync, statSync } from "node:fs";
import { readFile, realpath, stat } from "node:fs/promises";
import { isAbsolute, relative, resolve } from "node:path";

Expand Down Expand Up @@ -445,15 +445,30 @@ export function filterWorkspaceStatusForDirectory(
export function getGitMetadataWatchPaths(cwd: string): string[] {
const repo = getGitRepositoryInfo(cwd);
if (!repo) return [];
let currentRefPath: string | null = null;
try {
const head = readFileSync(resolve(repo.gitDir, "HEAD"), "utf8").trim();
if (head.startsWith("ref: ")) {
const refsRoot = resolve(repo.gitCommonDir, "refs");
const candidate = resolve(repo.gitCommonDir, head.slice("ref: ".length).trim());
if (candidate !== refsRoot && isWithinPath(candidate, refsRoot)) currentRefPath = candidate;
}
} catch {
// A detached, missing, or concurrently replaced HEAD has no symbolic ref target.
}
const candidates = [
resolve(repo.gitDir, "HEAD"),
resolve(repo.gitDir, "index"),
resolve(repo.gitDir, "MERGE_HEAD"),
resolve(repo.gitDir, "rebase-merge"),
resolve(repo.gitDir, "rebase-apply"),
resolve(repo.gitCommonDir, "HEAD"),
resolve(repo.gitDir, "logs", "HEAD"),
resolve(repo.gitCommonDir, "packed-refs"),
resolve(repo.gitCommonDir, "refs"),
currentRefPath,
];
return [...new Set(candidates)].filter((path) => existsSync(path));
return [...new Set(candidates)].filter((path): path is string => {
if (path === null) return false;
try {
return !statSync(path).isDirectory();
} catch {
return true;
}
});
}