Reuse git facts for workspace observation

This commit is contained in:
Mohamed Boudra
2026-05-27 20:33:21 +07:00
parent 5d8dc800fc
commit fa1b3e88f0
4 changed files with 163 additions and 22 deletions
@@ -112,6 +112,8 @@ function createCheckoutSnapshotFacts(cwd: string): CheckoutSnapshotFacts {
worktreeRoot: cwd,
currentBranch: "main",
remoteUrl: "https://github.com/acme/repo.git",
absoluteGitDir: join(cwd, ".git"),
gitCommonDir: join(cwd, ".git"),
paseoWorktree: { isPaseoOwnedWorktree: false },
storedBaseRef: null,
resolvedBaseRef: "main",
@@ -427,10 +429,12 @@ describe("WorkspaceGitServiceImpl", () => {
test("multiple listeners on the same workspace share one observation setup", async () => {
const getPullRequestStatus = vi.fn(async () => createPullRequestStatusResult());
const getCheckoutSnapshotFacts = vi.fn(async (cwd: string) => createCheckoutSnapshotFacts(cwd));
const resolveAbsoluteGitDir = vi.fn(async () => join(REPO_CWD, ".git"));
let nowMs = Date.parse("2026-04-12T00:00:00.000Z");
const service = createService({
getCheckoutSnapshotFacts,
getPullRequestStatus,
resolveAbsoluteGitDir,
now: () => new Date(nowMs),
@@ -441,7 +445,8 @@ describe("WorkspaceGitServiceImpl", () => {
await flushPromises();
expect(getPullRequestStatus).toHaveBeenCalledTimes(0);
expect(resolveAbsoluteGitDir).toHaveBeenCalledTimes(1);
expect(getCheckoutSnapshotFacts).toHaveBeenCalledTimes(1);
expect(resolveAbsoluteGitDir).toHaveBeenCalledTimes(0);
first.unsubscribe();
second.unsubscribe();
@@ -473,7 +478,7 @@ describe("WorkspaceGitServiceImpl", () => {
);
expect(getPullRequestStatus).toHaveBeenCalledTimes(1);
expect(resolveAbsoluteGitDir).toHaveBeenCalledTimes(1);
expect(resolveAbsoluteGitDir).toHaveBeenCalledTimes(0);
subscription.unsubscribe();
service.dispose();
@@ -482,9 +487,15 @@ describe("WorkspaceGitServiceImpl", () => {
test("repo-level fetch intervals are shared for workspaces in the same repo", async () => {
const runGitFetch = vi.fn(async () => {});
const hasOriginRemote = vi.fn(async () => true);
const getCheckoutSnapshotFacts = vi.fn(async (cwd: string) => ({
...createCheckoutSnapshotFacts(cwd),
gitCommonDir: join(REPO_CWD, ".git"),
absoluteGitDir: join(REPO_CWD, ".git"),
}));
const resolveAbsoluteGitDir = vi.fn(async () => join(REPO_CWD, ".git"));
const service = createService({
getCheckoutSnapshotFacts,
resolveAbsoluteGitDir,
hasOriginRemote,
runGitFetch,
@@ -496,11 +507,12 @@ describe("WorkspaceGitServiceImpl", () => {
vi.fn(),
);
await vi.waitFor(() => {
expect(resolveAbsoluteGitDir).toHaveBeenCalledTimes(2);
expect(getCheckoutSnapshotFacts).toHaveBeenCalledTimes(2);
expect(runGitFetch).toHaveBeenCalledTimes(1);
});
expect(hasOriginRemote).toHaveBeenCalledTimes(1);
expect(resolveAbsoluteGitDir).toHaveBeenCalledTimes(0);
expect(hasOriginRemote).toHaveBeenCalledTimes(0);
expect(runGitFetch).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(180_000);
@@ -47,6 +47,7 @@ const WORKING_TREE_WATCH_FALLBACK_REFRESH_MS = 5_000;
const WORKSPACE_GIT_AUXILIARY_READ_TTL_MS = 15_000;
// Non-forced refresh triggers share this minimum gap to absorb watcher/self-heal bursts; force bypasses it.
const WORKSPACE_GIT_INTERNAL_MIN_GAP_MS = 2_000;
const WORKSPACE_GIT_FACTS_REUSE_TTL_MS = 1_000;
const LINUX_WATCH_MAX_DIRS = 5_000;
const LINUX_WATCH_REFRESH_COOLDOWN_MS = 2_000;
const LINUX_WATCH_IGNORE_TTL_MS = 5 * 60 * 1_000;
@@ -271,6 +272,9 @@ interface WorkspaceGitTarget {
latestGithubLoadedAtMs: number | null;
latestSnapshot: WorkspaceGitRuntimeSnapshot | null;
latestSnapshotLoadedAtMs: number | null;
latestFacts: CheckoutSnapshotFacts | null;
latestFactsLoadedAtMs: number | null;
factsPromise: Promise<CheckoutSnapshotFacts> | null;
latestFingerprint: string | null;
lastShellOutAtMs: number | null;
repoGitRoot: string | null;
@@ -787,6 +791,9 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
latestGithubLoadedAtMs: null,
latestSnapshot: null,
latestSnapshotLoadedAtMs: null,
latestFacts: null,
latestFactsLoadedAtMs: null,
factsPromise: null,
latestFingerprint: null,
lastShellOutAtMs: null,
repoGitRoot: null,
@@ -837,7 +844,8 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
}
private async setupWorkspaceObservation(target: WorkspaceGitTarget): Promise<void> {
const gitDir = await this.deps.resolveAbsoluteGitDir(target.cwd);
const facts = await this.getFactsForObservation(target);
const gitDir = facts?.isGit ? facts.absoluteGitDir : null;
if (!this.isActiveObservedWorkspaceTarget(target)) {
return;
}
@@ -846,7 +854,10 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
return;
}
const repoGitRoot = await this.resolveWorkspaceGitRefsRoot(gitDir);
const repoGitRoot =
facts?.isGit && facts.gitCommonDir
? facts.gitCommonDir
: await this.resolveWorkspaceGitRefsRoot(gitDir);
if (!this.isActiveObservedWorkspaceTarget(target)) {
return;
}
@@ -858,6 +869,48 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
}
}
private async getFactsForObservation(
target: WorkspaceGitTarget,
): Promise<CheckoutSnapshotFacts | null> {
return this.loadCheckoutFacts(target, {
paseoHome: this.paseoHome,
logger: this.logger,
allowRecent: true,
});
}
private loadCheckoutFacts(
target: WorkspaceGitTarget,
options: CheckoutContext & { allowRecent: boolean },
): Promise<CheckoutSnapshotFacts> {
if (options.allowRecent && target.latestFacts && target.latestFactsLoadedAtMs !== null) {
const ageMs = this.deps.now().getTime() - target.latestFactsLoadedAtMs;
if (ageMs < WORKSPACE_GIT_FACTS_REUSE_TTL_MS) {
return Promise.resolve(target.latestFacts);
}
}
if (target.factsPromise) {
return target.factsPromise;
}
const { allowRecent: _allowRecent, ...context } = options;
const promise = this.deps
.getCheckoutSnapshotFacts(target.cwd, context)
.then((facts) => {
target.latestFacts = facts;
target.latestFactsLoadedAtMs = this.deps.now().getTime();
return facts;
})
.finally(() => {
if (target.factsPromise === promise) {
target.factsPromise = null;
}
});
target.factsPromise = promise;
return promise;
}
private isActiveObservedWorkspaceTarget(target: WorkspaceGitTarget): boolean {
return (
!target.closed &&
@@ -996,7 +1049,11 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
return;
}
const hasOrigin = await this.deps.hasOriginRemote(workspaceTarget.cwd);
const facts = workspaceTarget.latestFacts;
const hasOrigin =
facts?.isGit === true
? facts.remoteUrl !== null
: await this.deps.hasOriginRemote(workspaceTarget.cwd);
if (!this.isActiveObservedWorkspaceTarget(workspaceTarget)) {
return;
}
@@ -1505,7 +1562,10 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
const cwd = target.cwd;
const previousGitHubPollKey = this.getGitHubPollKey(target);
const baseContext: CheckoutContext = { paseoHome: this.paseoHome, logger: this.logger };
const facts = await this.deps.getCheckoutSnapshotFacts(cwd, baseContext);
const facts = await this.loadCheckoutFacts(target, {
...baseContext,
allowRecent: !request.force,
});
const context: CheckoutContext = { ...baseContext, facts };
const checkoutStatus = await this.deps.getCheckoutStatus(cwd, context);
if (!checkoutStatus.isGit) {
+38 -7
View File
@@ -735,6 +735,8 @@ export type CheckoutSnapshotFacts =
worktreeRoot: string;
currentBranch: string | null;
remoteUrl: string | null;
absoluteGitDir: string | null;
gitCommonDir: string | null;
paseoWorktree: PaseoWorktreeForCwd;
storedBaseRef: string | null;
resolvedBaseRef: string | null;
@@ -817,7 +819,13 @@ export async function getMainRepoRoot(cwd: string): Promise<string> {
cwd,
envOverlay: READ_ONLY_GIT_ENV,
});
const commonDir = resolveGitRevParsePath(cwd, commonDirOut);
return getMainRepoRootFromCommonDir(cwd, resolveGitRevParsePath(cwd, commonDirOut));
}
async function getMainRepoRootFromCommonDir(
cwd: string,
commonDir: string | null,
): Promise<string> {
if (!commonDir) {
throw new Error("Not in a git repository");
}
@@ -1101,6 +1109,18 @@ export async function resolveAbsoluteGitDir(cwd: string): Promise<string | null>
}
}
async function resolveGitCommonDir(cwd: string): Promise<string | null> {
try {
const { stdout } = await runGitCommand(["rev-parse", "--git-common-dir"], {
cwd,
envOverlay: READ_ONLY_GIT_ENV,
});
return resolveGitRevParsePath(cwd, stdout);
} catch {
return null;
}
}
async function abortGitPullConflictState(cwd: string): Promise<void> {
const gitDir = await resolveAbsoluteGitDir(cwd);
if (!gitDir) {
@@ -1389,6 +1409,8 @@ interface CheckoutInspectionContext {
worktreeRoot: string;
currentBranch: string | null;
remoteUrl: string | null;
absoluteGitDir: string | null;
gitCommonDir: string | null;
paseoWorktree: PaseoWorktreeForCwd;
}
@@ -1402,16 +1424,21 @@ async function inspectCheckoutContext(
return null;
}
const [currentBranch, remoteUrl, paseoWorktree] = await Promise.all([
getCurrentBranch(cwd),
getOriginRemoteUrl(cwd),
getPaseoWorktreeForCwd(cwd, context, root),
]);
const [currentBranch, remoteUrl, absoluteGitDir, gitCommonDir, paseoWorktree] =
await Promise.all([
getCurrentBranch(cwd),
getOriginRemoteUrl(cwd),
resolveAbsoluteGitDir(cwd),
resolveGitCommonDir(cwd),
getPaseoWorktreeForCwd(cwd, context, root),
]);
return {
worktreeRoot: root,
currentBranch,
remoteUrl,
absoluteGitDir,
gitCommonDir,
paseoWorktree,
};
} catch (error) {
@@ -1464,7 +1491,9 @@ export async function getCheckoutSnapshotFacts(
? readPaseoWorktreeBaseRef(inspected.paseoWorktree.worktreeRoot)
: null;
const resolvedBaseRef = storedBaseRef ?? (await resolveBaseRef(cwd));
const mainRepoRoot = await getMainRepoRoot(cwd).catch(() => null);
const mainRepoRoot = await getMainRepoRootFromCommonDir(cwd, inspected.gitCommonDir).catch(
() => null,
);
let comparisonBaseRef: string | null = null;
if (
resolvedBaseRef &&
@@ -1512,6 +1541,8 @@ export async function getCheckoutSnapshotFacts(
worktreeRoot: inspected.worktreeRoot,
currentBranch: inspected.currentBranch,
remoteUrl: inspected.remoteUrl,
absoluteGitDir: inspected.absoluteGitDir,
gitCommonDir: inspected.gitCommonDir,
paseoWorktree: inspected.paseoWorktree,
storedBaseRef,
resolvedBaseRef,