Reduce workspace git refresh polling (#1102)

* Reduce workspace git refresh polling

* Keep workspace git snapshot reads passive

* Update workspace git refresh tests
This commit is contained in:
Mohamed Boudra
2026-05-19 23:54:52 +08:00
committed by GitHub
parent b7a8567092
commit 0e1c590a3b
3 changed files with 273 additions and 105 deletions
@@ -269,7 +269,7 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
vi.useRealTimers();
});
test("getSnapshot returns cached warm value without shelling out within consumer TTL", async () => {
test("getSnapshot returns the current snapshot without shelling out", async () => {
let nowMs = Date.parse("2026-04-12T00:00:00.000Z");
const getCheckoutStatus = vi.fn(async (cwd: string) => createCheckoutStatus(cwd));
const service = createService({
@@ -411,7 +411,7 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
service.dispose();
});
test("warm non-forced getSnapshot calls attach to an in-flight refresh instead of returning stale cache", async () => {
test("non-forced getSnapshot returns the current snapshot during an in-flight refresh", async () => {
let nowMs = Date.parse("2026-04-12T00:00:00.000Z");
const refreshStatus = createDeferred<CheckoutStatusGit>();
const getCheckoutStatus = vi
@@ -434,24 +434,34 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
}),
);
nowMs += 16_000;
const first = service.getSnapshot(REPO_CWD);
await flushPromises();
const second = service.getSnapshot(REPO_CWD);
const initialSnapshot = createSnapshot(REPO_CWD, {
git: { diffStat: { additions: 4, deletions: 2 } },
});
nowMs += 3_000;
const refresh = service.refresh(REPO_CWD);
await flushPromises();
const directRead = service.getSnapshot(REPO_CWD);
expect(getCheckoutStatus).toHaveBeenCalledTimes(2);
expect(getCheckoutShortstat).toHaveBeenCalledTimes(1);
await expect(directRead).resolves.toEqual(initialSnapshot);
refreshStatus.resolve(createCheckoutStatus(REPO_CWD, { currentBranch: "feature" }));
const freshSnapshot = createSnapshot(REPO_CWD, {
git: {
currentBranch: "feature",
diffStat: { additions: 4, deletions: 2 },
},
});
await expect(Promise.all([first, second])).resolves.toEqual([freshSnapshot, freshSnapshot]);
await refresh;
expect(service.peekSnapshot(REPO_CWD)).toEqual(
createSnapshot(REPO_CWD, {
git: {
currentBranch: "feature",
diffStat: { additions: 4, deletions: 2 },
},
github: {
featuresEnabled: false,
pullRequest: null,
error: null,
},
}),
);
expect(getCheckoutStatus).toHaveBeenCalledTimes(2);
expect(getCheckoutShortstat).toHaveBeenCalledTimes(2);
@@ -543,10 +553,9 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
.fn<() => Promise<CheckoutStatusGit>>()
.mockImplementationOnce(async () => forcedGitRefresh.promise)
.mockImplementation(async () => createCheckoutStatus(REPO_CWD));
const getPullRequestStatus = vi
.fn()
.mockResolvedValueOnce(createPullRequestStatusResult("Stale cached PR"))
.mockResolvedValueOnce(createPullRequestStatusResult("Fresh validation PR"));
const getPullRequestStatus = vi.fn(async () =>
createPullRequestStatusResult("Fresh validation PR"),
);
const service = createService({ getCheckoutStatus, getPullRequestStatus });
const gitRefresh = service.getSnapshot(REPO_CWD, {
@@ -584,7 +593,8 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
await gitRefresh;
expect(getCheckoutStatus).toHaveBeenCalledTimes(2);
expect(getPullRequestStatus).toHaveBeenNthCalledWith(2, REPO_CWD, expect.anything(), {
expect(getPullRequestStatus).toHaveBeenCalledTimes(1);
expect(getPullRequestStatus).toHaveBeenCalledWith(REPO_CWD, expect.anything(), {
force: true,
reason: "merge-pr-validation",
});
@@ -634,7 +644,7 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
service.dispose();
});
test("consumer TTL triggers cold-load after expiry on getSnapshot", async () => {
test("non-forced getSnapshot keeps returning the current snapshot after time passes", async () => {
let nowMs = 0;
const getCheckoutStatus = vi.fn(async (cwd: string) => createCheckoutStatus(cwd));
const service = createService({
@@ -646,12 +656,12 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
nowMs = 16_000;
await service.getSnapshot(REPO_CWD);
expect(getCheckoutStatus).toHaveBeenCalledTimes(2);
expect(getCheckoutStatus).toHaveBeenCalledTimes(1);
service.dispose();
});
test("self-heal timer fires at the interval and calls getSnapshot without force", async () => {
test("self-heal timer refreshes git without refreshing GitHub", async () => {
let nowMs = 0;
const getCheckoutStatus = vi.fn(async (cwd: string) => createCheckoutStatus(cwd));
const getPullRequestStatus = vi.fn(async () => createPullRequestStatusResult());
@@ -668,9 +678,10 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
await flushPromises();
expect(getCheckoutStatus).toHaveBeenCalledTimes(2);
expect(getPullRequestStatus).toHaveBeenLastCalledWith(REPO_CWD, expect.anything(), {
expect(getPullRequestStatus).toHaveBeenCalledTimes(1);
expect(getPullRequestStatus).toHaveBeenCalledWith(REPO_CWD, expect.anything(), {
force: false,
reason: "self-heal-git",
reason: "initial",
});
subscription.unsubscribe();
@@ -803,8 +814,12 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
github,
now: () => new Date(nowMs),
});
const subscription = service.registerWorkspace({ cwd: REPO_CWD }, vi.fn());
const listener = vi.fn();
const subscription = service.registerWorkspace({ cwd: REPO_CWD }, listener);
await flushPromises();
await vi.advanceTimersByTimeAsync(0);
await flushPromises();
const gitReadsAfterInitialSnapshot = getCheckoutStatus.mock.calls.length;
nowMs = 20_000;
await vi.advanceTimersByTimeAsync(20_000);
@@ -814,6 +829,73 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
reason: "self-heal-github",
tickMs: 20_000,
});
expect(getCheckoutStatus).toHaveBeenCalledTimes(gitReadsAfterInitialSnapshot);
expect(listener).toHaveBeenCalledWith(
expect.objectContaining({
github: expect.objectContaining({
pullRequest: expect.objectContaining({
checksStatus: "pending",
}),
}),
}),
);
subscription.unsubscribe();
service.dispose();
github.dispose?.();
});
test("settled GitHub self-heal reads stay on the slow poll window without refreshing git", async () => {
let nowMs = 0;
const githubReadCalls: Array<{ reason: string | undefined; tickMs: number }> = [];
const github = createGitHubService({
ttlMs: 0,
runner: vi.fn(async () => ({
stdout: currentPullRequestJson(),
stderr: "",
})),
resolveGhPath: async () => "/usr/bin/gh",
now: () => nowMs,
});
const getCurrentPullRequestStatus = github.getCurrentPullRequestStatus.bind(github);
github.getCurrentPullRequestStatus = vi.fn(
async (options): Promise<GitHubCurrentPullRequestStatus | null> => {
githubReadCalls.push({ reason: options.reason, tickMs: nowMs });
return getCurrentPullRequestStatus(options);
},
);
const getCheckoutStatus = vi.fn(async (cwd: string) =>
createCheckoutStatus(cwd, { currentBranch: "feature" }),
);
const service = createService({
getCheckoutStatus,
github,
now: () => new Date(nowMs),
});
const subscription = service.registerWorkspace({ cwd: REPO_CWD }, vi.fn());
await flushPromises();
await vi.advanceTimersByTimeAsync(0);
await flushPromises();
const gitReadsAfterInitialSnapshot = getCheckoutStatus.mock.calls.length;
nowMs = 20_000;
await vi.advanceTimersByTimeAsync(20_000);
await flushPromises();
expect(githubReadCalls).not.toContainEqual({
reason: "self-heal-github",
tickMs: 20_000,
});
expect(getCheckoutStatus).toHaveBeenCalledTimes(gitReadsAfterInitialSnapshot);
nowMs = 120_000;
await vi.advanceTimersByTimeAsync(100_000);
await flushPromises();
expect(githubReadCalls).toContainEqual({
reason: "self-heal-github",
tickMs: 120_000,
});
subscription.unsubscribe();
service.dispose();
@@ -929,7 +1011,7 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
expect(getCheckoutStatus).toHaveBeenCalledTimes(0);
});
test("self-heal poll coalesces with a concurrent direct getSnapshot call", async () => {
test("direct getSnapshot returns current snapshot during a self-heal refresh", async () => {
let nowMs = 0;
const selfHealRefresh = createDeferred<CheckoutStatusGit>();
const getCheckoutStatus = vi
@@ -950,9 +1032,10 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
await flushPromises();
expect(getCheckoutStatus).toHaveBeenCalledTimes(2);
await expect(directRead).resolves.toEqual(createSnapshot(REPO_CWD));
selfHealRefresh.resolve(createCheckoutStatus(REPO_CWD));
await directRead;
await flushPromises();
expect(getCheckoutStatus).toHaveBeenCalledTimes(2);
@@ -341,7 +341,7 @@ describe("WorkspaceGitServiceImpl", () => {
);
});
test("non-forced GitHub refresh does not emit when pull request state is unchanged", async () => {
test("non-forced workspace refresh does not reload GitHub or emit when state is unchanged", async () => {
let nowMs = Date.parse("2026-04-12T00:00:00.000Z");
const getPullRequestStatus = vi.fn(async () => createPullRequestStatusResult());
const service = createService({
@@ -355,7 +355,7 @@ describe("WorkspaceGitServiceImpl", () => {
nowMs += 3_000;
await service.refresh(REPO_CWD);
expect(getPullRequestStatus).toHaveBeenCalledTimes(2);
expect(getPullRequestStatus).toHaveBeenCalledTimes(1);
expect(listener).not.toHaveBeenCalled();
subscription.unsubscribe();
@@ -401,7 +401,7 @@ describe("WorkspaceGitServiceImpl", () => {
service.dispose();
});
test("multiple listeners on the same workspace share one GitHub pull request lookup", async () => {
test("multiple listeners on the same workspace share one observation setup", async () => {
const getPullRequestStatus = vi.fn(async () => createPullRequestStatusResult());
const resolveAbsoluteGitDir = vi.fn(async () => join(REPO_CWD, ".git"));
@@ -416,7 +416,7 @@ describe("WorkspaceGitServiceImpl", () => {
const second = service.registerWorkspace({ cwd: REPO_CWD }, vi.fn());
await flushPromises();
expect(getPullRequestStatus).toHaveBeenCalledTimes(1);
expect(getPullRequestStatus).toHaveBeenCalledTimes(0);
expect(resolveAbsoluteGitDir).toHaveBeenCalledTimes(1);
first.unsubscribe();
@@ -448,7 +448,7 @@ describe("WorkspaceGitServiceImpl", () => {
createSnapshot(REPO_CWD),
);
expect(getPullRequestStatus).toHaveBeenCalledTimes(2);
expect(getPullRequestStatus).toHaveBeenCalledTimes(1);
expect(resolveAbsoluteGitDir).toHaveBeenCalledTimes(1);
subscription.unsubscribe();
@@ -489,7 +489,7 @@ describe("WorkspaceGitServiceImpl", () => {
service.dispose();
});
test("explicit refresh recomputes github state and notifies listeners", async () => {
test("explicit forced snapshot refresh recomputes github state and notifies listeners", async () => {
const getPullRequestStatus = vi
.fn<() => Promise<PullRequestStatusResult>>()
.mockResolvedValueOnce(
@@ -529,7 +529,10 @@ describe("WorkspaceGitServiceImpl", () => {
expect(initialSnapshot.github.pullRequest?.title).toBe("Before refresh");
await service.refresh(REPO_CWD);
await service.getSnapshot(REPO_CWD, {
force: true,
reason: "test-force-github-refresh",
});
await flushPromises();
expect(getPullRequestStatus).toHaveBeenCalledTimes(2);
@@ -41,8 +41,8 @@ const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
const BACKGROUND_GIT_FETCH_INTERVAL_MS = 180_000;
export const WORKSPACE_GIT_SELF_HEAL_INTERVAL_MS = 60_000;
const WORKING_TREE_WATCH_FALLBACK_REFRESH_MS = 5_000;
// Consumer reads may reuse cached values within this window; older peeks cold-load through the service.
const WORKSPACE_GIT_CONSUMER_TTL_MS = 15_000;
// Auxiliary reads may reuse cached values within this window; snapshots do not expire on read.
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 LINUX_WATCH_MAX_DIRS = 5_000;
@@ -262,6 +262,10 @@ interface WorkspaceGitTarget {
githubPollSubscription: { unsubscribe: () => void } | null;
githubPollHeadRef: string | null;
refreshState: WorkspaceGitRefreshState;
latestGit: WorkspaceGitRuntimeSnapshot["git"] | null;
latestGitLoadedAtMs: number | null;
latestGithub: WorkspaceGitRuntimeSnapshot["github"] | null;
latestGithubLoadedAtMs: number | null;
latestSnapshot: WorkspaceGitRuntimeSnapshot | null;
latestSnapshotLoadedAtMs: number | null;
latestFingerprint: string | null;
@@ -409,7 +413,7 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
cwd = normalizeWorkspaceId(cwd);
const request = this.normalizeRefreshRequest(options, "getSnapshot", true);
const target = this.ensureWorkspaceTarget(cwd);
if (!request.force && this.isSnapshotWarm(target)) {
if (!request.force && target.latestSnapshot) {
return target.latestSnapshot;
}
@@ -698,7 +702,7 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
const nowMs = this.deps.now().getTime();
if (!options?.force && entry.value !== null && entry.loadedAtMs !== null) {
const ageMs = nowMs - entry.loadedAtMs;
if (ageMs <= WORKSPACE_GIT_CONSUMER_TTL_MS) {
if (ageMs <= WORKSPACE_GIT_AUXILIARY_READ_TTL_MS) {
return Promise.resolve(entry.value);
}
if (
@@ -773,6 +777,10 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
githubPollSubscription: null,
githubPollHeadRef: null,
refreshState: { status: "idle" },
latestGit: null,
latestGitLoadedAtMs: null,
latestGithub: null,
latestGithubLoadedAtMs: null,
latestSnapshot: null,
latestSnapshotLoadedAtMs: null,
latestFingerprint: null,
@@ -1045,7 +1053,12 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
if (!target.selfHealTimer) {
target.selfHealTimer = setInterval(() => {
this.scheduleWorkspaceObservationSetup(target);
this.getSnapshot(target.cwd, { reason: "self-heal-git" }).catch((error) => {
this.refreshWorkspaceTarget(target, {
force: false,
includeGitHub: false,
reason: "self-heal-git",
notify: true,
}).catch((error) => {
this.logger.warn(
{ err: error, cwd: target.cwd, reason: "self-heal-git" },
"Failed to run workspace git self-heal refresh",
@@ -1063,15 +1076,15 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
return;
}
const snapshot = target.latestSnapshot;
if (!snapshot || !this.deps.github.retainCurrentPullRequestStatusPoll) {
const git = target.latestGit;
if (!git || !this.deps.github.retainCurrentPullRequestStatusPoll) {
this.stopGitHubPollForTarget(target);
return;
}
const headRef = snapshot.git.currentBranch;
const headRef = git.currentBranch;
const hasGitHubRemote =
target.cachedGitHubRemote?.remoteUrl === snapshot.git.remoteUrl &&
target.cachedGitHubRemote?.remoteUrl === git.remoteUrl &&
target.cachedGitHubRemote.identity !== null;
if (!headRef || !hasGitHubRemote) {
this.stopGitHubPollForTarget(target);
@@ -1086,14 +1099,11 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
target.githubPollSubscription = this.deps.github.retainCurrentPullRequestStatusPoll({
cwd: target.cwd,
headRef,
onStatus: () => {
onStatus: (status) => {
if (!this.isActiveObservedWorkspaceTarget(target)) {
return;
}
void this.refreshWorkspaceTarget(target, {
force: false,
includeGitHub: true,
reason: "self-heal-github",
this.rememberGitHubSnapshot(target, buildGitHubSnapshotFromStatus(status), {
notify: true,
});
},
@@ -1381,25 +1391,12 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
const force = options?.force === true;
return {
force,
includeGitHub: options?.includeGitHub ?? force,
includeGitHub: options?.includeGitHub ?? true,
reason: options?.reason ?? defaultReason,
notify,
};
}
private isSnapshotWarm(target: WorkspaceGitTarget): target is WorkspaceGitTarget & {
latestSnapshot: WorkspaceGitRuntimeSnapshot;
latestSnapshotLoadedAtMs: number;
} {
if (!target.latestSnapshot || target.latestSnapshotLoadedAtMs === null) {
return false;
}
return (
this.deps.now().getTime() - target.latestSnapshotLoadedAtMs <= WORKSPACE_GIT_CONSUMER_TTL_MS
);
}
private async resolveGitHubRemoteForTarget(
target: WorkspaceGitTarget,
remoteUrl: string | null,
@@ -1484,57 +1481,128 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
target: WorkspaceGitTarget,
request: WorkspaceGitRefreshRequest,
): Promise<WorkspaceGitRuntimeSnapshot> {
await this.refreshGitSnapshot(target, request);
if (request.includeGitHub) {
await this.refreshGitHubSnapshot(target, request);
}
const snapshot = this.combineSnapshot(target);
target.latestSnapshotLoadedAtMs = this.deps.now().getTime();
return snapshot;
}
private async refreshGitSnapshot(
target: WorkspaceGitTarget,
request: WorkspaceGitRefreshRequest,
): Promise<void> {
const now = this.deps.now();
target.lastShellOutAtMs = now.getTime();
const cwd = target.cwd;
const previousGitHubPollKey = this.getGitHubPollKey(target);
const context: CheckoutContext = { paseoHome: this.paseoHome, logger: this.logger };
const checkoutStatus = await this.deps.getCheckoutStatus(cwd, context);
if (!checkoutStatus.isGit) {
target.latestGit = buildNotGitSnapshot(cwd).git;
target.latestGitLoadedAtMs = this.deps.now().getTime();
target.cachedGitHubRemote = null;
target.latestGithub = buildGitHubUnavailableSnapshot();
target.latestGithubLoadedAtMs = target.latestGitLoadedAtMs;
return;
}
await this.resolveGitHubRemoteForTarget(target, checkoutStatus.remoteUrl);
const diffStat = await this.deps
.getCheckoutShortstat(cwd, context, { force: request.force })
.catch(() => null);
target.latestGit = {
isGit: true,
repoRoot: checkoutStatus.repoRoot,
mainRepoRoot: checkoutStatus.mainRepoRoot,
currentBranch: checkoutStatus.currentBranch,
remoteUrl: checkoutStatus.remoteUrl,
isPaseoOwnedWorktree: checkoutStatus.isPaseoOwnedWorktree,
isDirty: checkoutStatus.isDirty,
baseRef: checkoutStatus.baseRef,
aheadBehind: checkoutStatus.aheadBehind,
aheadOfOrigin: checkoutStatus.aheadOfOrigin,
behindOfOrigin: checkoutStatus.behindOfOrigin,
hasRemote: checkoutStatus.hasRemote,
diffStat,
};
target.latestGitLoadedAtMs = this.deps.now().getTime();
if (previousGitHubPollKey !== this.getGitHubPollKey(target)) {
target.latestGithub = buildGitHubUnavailableSnapshot();
target.latestGithubLoadedAtMs = target.latestGitLoadedAtMs;
}
}
private async refreshGitHubSnapshot(
target: WorkspaceGitTarget,
request: WorkspaceGitRefreshRequest,
): Promise<void> {
const githubRemote = target.cachedGitHubRemote?.identity ?? null;
const forceGitHub = request.force && request.includeGitHub;
if (forceGitHub) {
this.deps.github.invalidate({ cwd: target.cwd });
}
const cwd = target.cwd;
const context: CheckoutContext = { paseoHome: this.paseoHome, logger: this.logger };
const checkoutStatus = await this.deps.getCheckoutStatus(cwd, context);
if (!checkoutStatus.isGit) {
target.latestSnapshotLoadedAtMs = now.getTime();
return buildNotGitSnapshot(cwd);
target.latestGithub = await loadGitHubSnapshot({
cwd: target.cwd,
githubRemote,
now: this.deps.now(),
deps: this.deps,
force: forceGitHub,
reason: request.reason,
});
target.latestGithubLoadedAtMs = this.deps.now().getTime();
}
private combineSnapshot(target: WorkspaceGitTarget): WorkspaceGitRuntimeSnapshot {
if (!target.latestGit) {
return target.latestSnapshot ?? buildNotGitSnapshot(target.cwd);
}
const githubRemote = await this.resolveGitHubRemoteForTarget(target, checkoutStatus.remoteUrl);
const [diffStat, github] = await Promise.all([
this.deps.getCheckoutShortstat(cwd, context, { force: request.force }).catch(() => null),
loadGitHubSnapshot({
cwd,
githubRemote,
now,
deps: this.deps,
force: forceGitHub,
reason: request.reason,
}),
]);
target.latestSnapshotLoadedAtMs = now.getTime();
return {
cwd,
git: {
isGit: true,
repoRoot: checkoutStatus.repoRoot,
mainRepoRoot: checkoutStatus.mainRepoRoot,
currentBranch: checkoutStatus.currentBranch,
remoteUrl: checkoutStatus.remoteUrl,
isPaseoOwnedWorktree: checkoutStatus.isPaseoOwnedWorktree,
isDirty: checkoutStatus.isDirty,
baseRef: checkoutStatus.baseRef,
aheadBehind: checkoutStatus.aheadBehind,
aheadOfOrigin: checkoutStatus.aheadOfOrigin,
behindOfOrigin: checkoutStatus.behindOfOrigin,
hasRemote: checkoutStatus.hasRemote,
diffStat,
},
github,
cwd: target.cwd,
git: target.latestGit,
github: target.latestGithub ?? buildGitHubUnavailableSnapshot(),
};
}
private getGitHubPollKey(target: WorkspaceGitTarget): string | null {
const git = target.latestGit;
if (!git?.currentBranch || !git.remoteUrl) {
return null;
}
const githubRemote = target.cachedGitHubRemote;
if (!githubRemote || githubRemote.remoteUrl !== git.remoteUrl || !githubRemote.identity) {
return null;
}
return JSON.stringify([git.remoteUrl, git.currentBranch]);
}
private rememberGitHubSnapshot(
target: WorkspaceGitTarget,
github: WorkspaceGitRuntimeSnapshot["github"],
options?: { notify?: boolean },
): void {
if (target.closed || this.workspaceTargets.get(target.cwd) !== target) {
return;
}
target.latestGithub = github;
target.latestGithubLoadedAtMs = this.deps.now().getTime();
this.rememberSnapshot(target, this.combineSnapshot(target), {
notify: options?.notify,
forceEmit: false,
});
}
private rememberSnapshot(
target: WorkspaceGitTarget,
snapshot: WorkspaceGitRuntimeSnapshot,
@@ -1794,11 +1862,25 @@ function buildNotGitSnapshot(cwd: string): WorkspaceGitRuntimeSnapshot {
hasRemote: false,
diffStat: null,
},
github: {
featuresEnabled: false,
pullRequest: null,
error: null,
},
github: buildGitHubUnavailableSnapshot(),
};
}
function buildGitHubUnavailableSnapshot(): WorkspaceGitRuntimeSnapshot["github"] {
return {
featuresEnabled: false,
pullRequest: null,
error: null,
};
}
function buildGitHubSnapshotFromStatus(
status: WorkspaceGitRuntimeSnapshot["github"]["pullRequest"],
): WorkspaceGitRuntimeSnapshot["github"] {
return {
featuresEnabled: true,
pullRequest: status,
error: null,
};
}