Files
orca/src/main/worktree-create-base-prefetch.ts
T
Neil 34ae0933e4 fix(worktrees): keep creation fast in large repositories (#24346)
* fix(worktrees): remove repeated scans and keep prepared checkouts fresh

* fix(worktrees): reclaim unlocked fallback preparations safely

* refactor(worktrees): simplify creation ownership and idle maintenance

* fix(git): keep ref maintenance armed after an index-only pass

An idle attempt that found the pack index due but refs still cooling down
returned without rescheduling, so loose refs from the arming fetch waited
for the next write instead of the ref cooldown.
2026-10-01 23:37:05 -07:00

170 lines
5.7 KiB
TypeScript

import { isFolderRepo } from '../shared/repo-kind'
import type { Repo } from '../shared/repo-types'
import { isFullGitObjectId } from './git/commit-object-ref'
import { hasLocalWorktreeBaseRef } from './git/worktree-base-ref-probe'
import { getBaseRefDefault } from './git/repo'
import { getSshGitProvider } from './providers/ssh-git-dispatch'
import { prefetchRemoteWorktreeCreateBase } from './ipc/worktree-remote'
import { resolveWorktreeCreateBase } from './worktree-create-base'
type WorktreeCreateBaseGitOptions = {
wslDistro?: string
}
type RemoteTrackingBaseForPrefetch = {
remote: string
branch: string
ref: string
base: string
}
type WorktreeCreateBasePrefetchRuntime = {
resolveRemoteTrackingBase: (
repoPath: string,
baseBranch: string,
options?: WorktreeCreateBaseGitOptions
) => Promise<RemoteTrackingBaseForPrefetch | null>
hasRemoteTrackingRef: (
repoPath: string,
base: RemoteTrackingBaseForPrefetch,
options?: WorktreeCreateBaseGitOptions
) => Promise<boolean>
getOrStartRemoteTrackingBaseRefresh: (
repoPath: string,
base: RemoteTrackingBaseForPrefetch,
options?: WorktreeCreateBaseGitOptions
) => Promise<{ ok: boolean }>
fetchRemoteWithCache: (
repoPath: string,
remote: string,
options?: WorktreeCreateBaseGitOptions
) => Promise<void>
}
async function prefetchLocalWorktreeCreateBase(
repo: Repo,
baseBranch: string | undefined,
runtime: WorktreeCreateBasePrefetchRuntime,
options: WorktreeCreateBaseGitOptions,
prepareLocalCheckout: (base: string, beforeMaterialization?: Promise<void>) => void
): Promise<string | undefined> {
// Keep host-routed calls at their original arity so they stay on the runtime's default options.
const optionArgs: [] | [WorktreeCreateBaseGitOptions] = options.wslDistro ? [options] : []
const resolvedBaseBranch = await resolveWorktreeCreateBase({
requestedBaseBranch: baseBranch,
repoWorktreeBaseRef: repo.worktreeBaseRef,
resolveDefaultBaseRef: () => getBaseRefDefault(repo.path, ...optionArgs),
isBaseUsable: async (baseBranchCandidate) => {
const remoteTrackingBase = await runtime.resolveRemoteTrackingBase(
repo.path,
baseBranchCandidate,
...optionArgs
)
if (remoteTrackingBase) {
if (await runtime.hasRemoteTrackingRef(repo.path, remoteTrackingBase, ...optionArgs)) {
return true
}
return hasLocalWorktreeBaseRef(repo.path, baseBranchCandidate, options)
}
return hasLocalWorktreeBaseRef(repo.path, baseBranchCandidate, options)
}
})
if (!resolvedBaseBranch) {
return undefined
}
if (
isFullGitObjectId(resolvedBaseBranch) &&
(await hasLocalWorktreeBaseRef(repo.path, resolvedBaseBranch, options))
) {
return resolvedBaseBranch
}
const remoteTrackingBase = await runtime.resolveRemoteTrackingBase(
repo.path,
resolvedBaseBranch,
...optionArgs
)
if (remoteTrackingBase) {
const hasTrackingRef = await runtime.hasRemoteTrackingRef(
repo.path,
remoteTrackingBase,
...optionArgs
)
if (
hasTrackingRef ||
!(await hasLocalWorktreeBaseRef(repo.path, resolvedBaseBranch, options))
) {
const refresh = runtime.getOrStartRemoteTrackingBaseRefresh(
repo.path,
remoteTrackingBase,
...optionArgs
)
if (hasTrackingRef) {
// Register during fetch, then materialize the settled base once; offline keeps its local tip.
const beforeMaterialization = refresh.then(
() => {},
() => {}
)
prepareLocalCheckout(resolvedBaseBranch, beforeMaterialization)
}
await refresh
return resolvedBaseBranch
}
}
if (await hasLocalWorktreeBaseRef(repo.path, resolvedBaseBranch, options)) {
// Why: hosted-review start points and local branch bases are already local; a broad remote fetch cannot make them fresher.
return resolvedBaseBranch
}
// Why: same best-effort fallback create takes, on create's own fetch key, so a
// create that lands here reuses this fetch instead of repeating it. A create
// that instead resolves an exact remote base still queues behind it.
await runtime.fetchRemoteWithCache(repo.path, 'origin', ...optionArgs)
return resolvedBaseBranch
}
export async function prefetchWorktreeCreateBase(args: {
repo: Repo
baseBranch?: string
runtime: WorktreeCreateBasePrefetchRuntime
/** Routing for the project's Git host; required so a caller cannot silently
* warm up the wrong ref store — pass `{}` for host Git. */
gitOptions: WorktreeCreateBaseGitOptions
prepareCheckout?: (base: string, beforeMaterialization?: Promise<void>) => Promise<void>
}): Promise<string | undefined> {
if (isFolderRepo(args.repo)) {
return undefined
}
if (args.repo.connectionId) {
const provider = getSshGitProvider(args.repo.connectionId)
if (!provider) {
return undefined
}
await prefetchRemoteWorktreeCreateBase(provider, args.repo, { baseBranch: args.baseBranch })
return undefined
}
const prepareCheckout = args.prepareCheckout
let preparation: Promise<void> | undefined
const prepare = (base: string, beforeMaterialization?: Promise<void>): void => {
if (!preparation && prepareCheckout) {
preparation = Promise.resolve().then(() => prepareCheckout(base, beforeMaterialization))
}
void preparation?.catch(() => {})
}
try {
const base = await prefetchLocalWorktreeCreateBase(
args.repo,
args.baseBranch,
args.runtime,
args.gitOptions,
prepare
)
if (base) {
prepare(base)
}
return base
} finally {
// Settle speculative work even if refresh fails; Create owns error reporting.
await preparation?.catch(() => {})
}
}