fix: stop stale checkout diff subscriptions (#2317)

* Fix pending checkout diff subscriptions

* Reduce checkout status Git spawns

* fix(checkout): own pending diff cancellation

Checkout sessions could invalidate a subscription before its shared watch had finished opening. Register targets synchronously and let the diff manager honor cancellation so pending and concurrent subscriptions share one teardown path.

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
nikuscs
2026-07-22 16:49:44 +02:00
committed by GitHub
co-authored by Mohamed Boudra
parent bd937850b3
commit 9952615c33
7 changed files with 356 additions and 133 deletions
+37 -50
View File
@@ -1052,10 +1052,15 @@ type PaseoWorktreeForCwd =
| { isPaseoOwnedWorktree: false }
| { isPaseoOwnedWorktree: true; worktreeRoot: string };
interface PaseoWorktreeLookupOptions {
context?: CheckoutContext;
knownWorktreeRoot?: string | null;
knownGitCommonDir?: string | null;
}
async function getPaseoWorktreeForCwd(
cwd: string,
context?: CheckoutContext,
knownWorktreeRoot?: string | null,
options: PaseoWorktreeLookupOptions = {},
): Promise<PaseoWorktreeForCwd> {
// Fast-path reject: non-worktree paths do not need expensive ownership checks.
if (!/[\\/]worktrees[\\/]/.test(cwd)) {
@@ -1063,8 +1068,9 @@ async function getPaseoWorktreeForCwd(
}
const ownership = await isPaseoOwnedWorktreeCwd(cwd, {
paseoHome: context?.paseoHome,
worktreesRoot: context?.worktreesRoot,
paseoHome: options.context?.paseoHome,
worktreesRoot: options.context?.worktreesRoot,
knownGitCommonDir: options.knownGitCommonDir,
});
if (!ownership.allowed) {
return { isPaseoOwnedWorktree: false };
@@ -1072,7 +1078,7 @@ async function getPaseoWorktreeForCwd(
return {
isPaseoOwnedWorktree: true,
worktreeRoot: knownWorktreeRoot ?? (await getWorktreeRoot(cwd, context)) ?? cwd,
worktreeRoot: options.knownWorktreeRoot ?? (await getWorktreeRoot(cwd, options.context)) ?? cwd,
};
}
@@ -1087,7 +1093,7 @@ async function getStoredBaseRefForCwd(
if (context?.facts?.isGit) {
return context.facts.storedBaseRef;
}
const paseoWorktree = await getPaseoWorktreeForCwd(cwd, context);
const paseoWorktree = await getPaseoWorktreeForCwd(cwd, { context });
if (!paseoWorktree.isPaseoOwnedWorktree) {
return null;
}
@@ -1492,30 +1498,6 @@ async function getAheadBehind(
return { ahead, behind };
}
async function getAheadOfOrigin(
cwd: string,
currentBranch: string,
context?: CheckoutContext,
): Promise<number | null> {
if (!currentBranch) {
return null;
}
const upstreamRef = await getConfiguredUpstreamRef(cwd, currentBranch, context);
if (!upstreamRef) {
return null;
}
try {
const { stdout } = await runGitCommand(
["rev-list", "--count", `${upstreamRef}..${currentBranch}`],
{ cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger },
);
const count = Number.parseInt(stdout.trim(), 10);
return Number.isNaN(count) ? null : count;
} catch {
return null;
}
}
async function getConfiguredUpstreamRef(
cwd: string,
currentBranch: string,
@@ -1537,11 +1519,11 @@ async function getConfiguredUpstreamRef(
return upstreamBranch ? `${remoteName}/${upstreamBranch}` : null;
}
async function getBehindOfOrigin(
async function getOriginAheadBehind(
cwd: string,
currentBranch: string,
context?: CheckoutContext,
): Promise<number | null> {
): Promise<AheadBehind | null> {
if (!currentBranch) {
return null;
}
@@ -1551,11 +1533,13 @@ async function getBehindOfOrigin(
}
try {
const { stdout } = await runGitCommand(
["rev-list", "--count", `${currentBranch}..${upstreamRef}`],
["rev-list", "--left-right", "--count", `${currentBranch}...${upstreamRef}`],
{ cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger },
);
const count = Number.parseInt(stdout.trim(), 10);
return Number.isNaN(count) ? null : count;
const [aheadRaw, behindRaw] = stdout.trim().split(/\s+/);
const ahead = Number.parseInt(aheadRaw ?? "", 10);
const behind = Number.parseInt(behindRaw ?? "", 10);
return Number.isNaN(ahead) || Number.isNaN(behind) ? null : { ahead, behind };
} catch {
return null;
}
@@ -1579,15 +1563,17 @@ async function inspectCheckoutContext(
return null;
}
const [currentBranch, remoteUrl, absoluteGitDir, gitCommonDir, paseoWorktree] = await Promise.all(
[
getCurrentBranch(cwd),
getOriginRemoteUrl(cwd),
resolveAbsoluteGitDir(cwd),
resolveGitCommonDir(cwd),
getPaseoWorktreeForCwd(cwd, context, root),
],
);
const [currentBranch, remoteUrl, absoluteGitDir, gitCommonDir] = await Promise.all([
getCurrentBranch(cwd),
getOriginRemoteUrl(cwd),
resolveAbsoluteGitDir(cwd),
resolveGitCommonDir(cwd),
]);
const paseoWorktree = await getPaseoWorktreeForCwd(cwd, {
context,
knownWorktreeRoot: root,
knownGitCommonDir: gitCommonDir,
});
return {
worktreeRoot: root,
@@ -1776,7 +1762,9 @@ export async function getCheckoutSnapshotFacts(
if (branchRemoteName) {
[branchMergeRef, branchRemoteUrl] = await Promise.all([
getGitConfigValue(cwd, `branch.${inspected.currentBranch}.merge`, context),
getGitConfigValue(cwd, `remote.${branchRemoteName}.url`, context),
branchRemoteName === "origin"
? inspected.remoteUrl
: getGitConfigValue(cwd, `remote.${branchRemoteName}.url`, context),
]);
}
}
@@ -1955,17 +1943,16 @@ export async function getCheckoutStatus(
const baseRef = facts.resolvedBaseRef;
const mainRepoRoot = facts.mainRepoRoot;
const factsContext = { ...context, facts };
const [aheadBehind, aheadOfOrigin, behindOfOrigin] = await Promise.all([
const [aheadBehind, originAheadBehind] = await Promise.all([
baseRef && currentBranch
? getAheadBehind(cwd, baseRef, currentBranch, factsContext)
: Promise.resolve(null),
hasRemote && currentBranch
? getAheadOfOrigin(cwd, currentBranch, factsContext)
: Promise.resolve(null),
hasRemote && currentBranch
? getBehindOfOrigin(cwd, currentBranch, factsContext)
? getOriginAheadBehind(cwd, currentBranch, factsContext)
: Promise.resolve(null),
]);
const aheadOfOrigin = originAheadBehind?.ahead ?? null;
const behindOfOrigin = originAheadBehind?.behind ?? null;
if (paseoWorktree.isPaseoOwnedWorktree && baseRef) {
return {