diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts
index aab69de6a12..24d53cd4846 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.test.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts
@@ -153,6 +153,51 @@ it.effect("uses stable diagnostics for every parsed non-repository command", ()
}).pipe(Effect.provide(layer));
});
+it.effect("invalidates origin remote cache when a driver mutation adds origin", () =>
+ Effect.gen(function* () {
+ const driver = yield* GitVcsDriver.GitVcsDriver;
+ const cwd = yield* makeTmpDir();
+ const remote = yield* makeTmpDir("git-vcs-driver-remote-");
+ yield* initRepoWithCommit(cwd);
+ yield* git(remote, ["init", "--bare"]);
+
+ const before = yield* driver.statusDetailsLocal(cwd);
+ assert.equal(before.hasOriginRemote, false);
+
+ yield* driver.ensureRemote({ cwd, preferredName: "origin", url: remote });
+
+ const after = yield* driver.statusDetailsLocal(cwd);
+ assert.equal(after.hasOriginRemote, true);
+ }).pipe(Effect.provide(TestLayer)),
+);
+
+it.effect("re-reads origin remote status after cache TTL expiry and bypassed invalidation", () =>
+ Effect.gen(function* () {
+ const driver = yield* GitVcsDriver.GitVcsDriver;
+ const cwd = yield* makeTmpDir();
+ const remote = yield* makeTmpDir("git-vcs-driver-remote-");
+ yield* initRepoWithCommit(cwd);
+ yield* git(remote, ["init", "--bare"]);
+
+ // First call caches hasOriginRemote = false (5-min TTL)
+ assert.equal((yield* driver.statusDetailsLocal(cwd)).hasOriginRemote, false);
+
+ // Add origin via raw git (bypasses invalidation hook)
+ yield* git(cwd, ["remote", "add", "origin", remote]);
+
+ // Cache still has the stale false (TTL not yet expired)
+ const stillCached = yield* driver.statusDetailsLocal(cwd);
+ assert.equal(stillCached.hasOriginRemote, false);
+
+ // Advance past the 5-minute TTL so the cache entry expires
+ yield* TestClock.adjust("6 minutes");
+
+ // After expiry, the next call re-executes and picks up the remote
+ const afterExpiry = yield* driver.statusDetailsLocal(cwd);
+ assert.equal(afterExpiry.hasOriginRemote, true);
+ }).pipe(Effect.provide(TestLayer)),
+);
+
it.effect("coalesces concurrent ref pages into one repository snapshot", () =>
Effect.scoped(
Effect.gen(function* () {
diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts
index 3aa7575d590..e44dc048634 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.ts
@@ -61,6 +61,8 @@ const LIST_REFS_SNAPSHOT_CACHE_CAPACITY = 64;
const LIST_REFS_SNAPSHOT_CACHE_TTL = Duration.minutes(2);
const LIST_REFS_REFRESH_COALESCE_TTL = Duration.seconds(5);
const LIST_REFS_REFRESH_FAILURE_COOLDOWN = Duration.seconds(30);
+const STATUS_DEFAULT_BRANCH_CACHE_TTL = Duration.minutes(5);
+const STATUS_ORIGIN_EXISTS_CACHE_TTL = Duration.minutes(5);
const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({
GCM_INTERACTIVE: "never",
GIT_ASKPASS: "",
@@ -1119,6 +1121,63 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
return Cache.get(refresh ? repositoryPathsRefreshCache : repositoryPathsCache, cacheKey);
};
+ const defaultBranchCache = yield* Cache.makeWith(
+ (gitCommonDir: string) =>
+ Effect.gen(function* () {
+ const path = yield* Path.Path;
+ const fetchCwd =
+ path.basename(gitCommonDir) === ".git" ? path.dirname(gitCommonDir) : gitCommonDir;
+ return yield* executeGit(
+ "GitVcsDriver.statusDetails.defaultBranch",
+ fetchCwd,
+ ["--git-dir", gitCommonDir, "symbolic-ref", "refs/remotes/origin/HEAD"],
+ { allowNonZeroExit: true },
+ ).pipe(
+ Effect.map((result) => {
+ if (result.exitCode !== 0) return null;
+ return parseDefaultBranchFromRemoteHeadRef(result.stdout, "origin");
+ }),
+ );
+ }),
+ {
+ capacity: 2_048,
+ timeToLive: Exit.match({
+ onSuccess: () => STATUS_DEFAULT_BRANCH_CACHE_TTL,
+ onFailure: () => Duration.zero,
+ }),
+ },
+ );
+ const originExistsCache = yield* Cache.makeWith(
+ (gitCommonDir: string) =>
+ Effect.gen(function* () {
+ const path = yield* Path.Path;
+ const fetchCwd =
+ path.basename(gitCommonDir) === ".git" ? path.dirname(gitCommonDir) : gitCommonDir;
+ return yield* executeGit(
+ "GitVcsDriver.statusDetails.originExists",
+ fetchCwd,
+ ["--git-dir", gitCommonDir, "remote", "get-url", "origin"],
+ { allowNonZeroExit: true },
+ ).pipe(Effect.map((result) => result.exitCode === 0));
+ }),
+ {
+ capacity: 2_048,
+ timeToLive: Exit.match({
+ onSuccess: () => STATUS_ORIGIN_EXISTS_CACHE_TTL,
+ onFailure: () => Duration.zero,
+ }),
+ },
+ );
+ const invalidateStatusStaticCaches = (cwd: string) =>
+ Effect.gen(function* () {
+ const repositoryPaths = yield* resolveRepositoryPaths(cwd).pipe(
+ Effect.catchTags({ GitCommandError: () => Effect.succeed(null) }),
+ );
+ const cacheKey = repositoryPaths?.gitCommonDir ?? normalizeRepositoryPathsCacheKey(cwd);
+ yield* Cache.invalidate(defaultBranchCache, cacheKey);
+ yield* Cache.invalidate(originExistsCache, cacheKey);
+ });
+
const resolveGitCommonDir = Effect.fn("resolveGitCommonDir")(function* (cwd: string) {
const repositoryPaths = yield* resolveRepositoryPaths(cwd);
if (repositoryPaths !== null) {
@@ -1517,7 +1576,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
});
}
- const [numstatStdout, defaultRefResult, hasPrimaryRemote] = yield* Effect.all(
+ const repositoryPaths = yield* resolveRepositoryPaths(cwd).pipe(
+ Effect.catchTags({ GitCommandError: () => Effect.succeed(null) }),
+ );
+ const statusCacheKey = repositoryPaths?.gitCommonDir;
+ const [numstatStdout, defaultBranch, hasPrimaryRemote] = yield* Effect.all(
[
executeGitWithStableDiagnostics(
"GitVcsDriver.statusDetails.numstat",
@@ -1574,21 +1637,16 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
);
}),
),
- executeGit(
- "GitVcsDriver.statusDetails.defaultRef",
- cwd,
- ["symbolic-ref", "refs/remotes/origin/HEAD"],
- { allowNonZeroExit: true },
- ),
- originRemoteExists(cwd).pipe(Effect.orElseSucceed(() => false)),
+ statusCacheKey
+ ? Cache.get(defaultBranchCache, statusCacheKey).pipe(Effect.orElseSucceed(() => null))
+ : resolveDefaultBranchName(cwd, "origin").pipe(Effect.orElseSucceed(() => null)),
+ statusCacheKey
+ ? Cache.get(originExistsCache, statusCacheKey).pipe(Effect.orElseSucceed(() => false))
+ : originRemoteExists(cwd).pipe(Effect.orElseSucceed(() => false)),
],
{ concurrency: "unbounded" },
);
const statusStdout = statusResult.stdout;
- const defaultBranch =
- defaultRefResult.exitCode === 0
- ? defaultRefResult.stdout.trim().replace(/^refs\/remotes\/origin\//, "")
- : null;
let refName: string | null = null;
let upstreamRef: string | null = null;
@@ -2809,7 +2867,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
cwd: string,
effect: Effect.Effect,
): Effect.Effect =>
- effect.pipe(Effect.ensuring(invalidateListRefsSnapshot(cwd).pipe(Effect.ignore)));
+ effect.pipe(
+ Effect.ensuring(
+ Effect.all([
+ invalidateListRefsSnapshot(cwd).pipe(Effect.ignore),
+ invalidateStatusStaticCaches(cwd).pipe(Effect.ignore),
+ ]),
+ ),
+ );
const initRepoWithListRefsInvalidation: GitVcsDriver.GitVcsDriver["Service"]["initRepo"] = (
input,
) =>