perf(worktree): overlap base refresh with prepared checkout

This commit is contained in:
Neil
2026-09-05 19:39:39 -07:00
parent 986ebfe724
commit 64248b4893
6 changed files with 242 additions and 18 deletions
@@ -278,6 +278,58 @@ describe('prepared worktree creation with real Git', () => {
)
})
it.each(['before reset', 'after reset'])(
'finalizes refreshed content when the base moves %s',
async (when) => {
const { repoPath, root } = await createRepo()
const preparedPath = join(root, WORKTREE_CREATE_PREPARATION_DIRECTORY, 'fetch-overlap')
const finalPath = join(root, 'final-overlap')
await mkdir(join(root, WORKTREE_CREATE_PREPARATION_DIRECTORY), { recursive: true })
const original = git(repoPath, ['rev-parse', 'HEAD'])
await writeFile(join(repoPath, 'version.txt'), 'refreshed\n')
git(repoPath, ['commit', '-am', 'remote update'])
const refreshed = git(repoPath, ['rev-parse', 'HEAD'])
git(repoPath, ['update-ref', 'refs/remotes/origin/main', original])
const exec = gitRunner.gitExecFileAsync
let moved = false
const spy = vi
.spyOn(gitRunner, 'gitExecFileAsync')
.mockImplementation(async (args, options) => {
if (!moved && args.includes('reset') && when === 'before reset') {
git(repoPath, ['update-ref', 'refs/remotes/origin/main', refreshed])
moved = true
}
const result = await exec(args, options)
if (!moved && args.includes('reset') && when === 'after reset') {
git(repoPath, ['update-ref', 'refs/remotes/origin/main', refreshed])
moved = true
}
return result
})
try {
await prepareWorktreeCreateCheckout(
repoPath,
preparedPath,
'refs/remotes/origin/main',
createWorktreePreparationLockReason('fetch-overlap')
)
expect(moved).toBe(true)
await finalizePreparedWorktree(
repoPath,
preparedPath,
finalPath,
'feature/overlap',
'refs/remotes/origin/main'
)
expect(git(finalPath, ['rev-parse', 'HEAD'])).toBe(refreshed)
expect(await readFile(join(finalPath, 'version.txt'), 'utf8')).toBe('refreshed\n')
expect(git(finalPath, ['status', '--porcelain'])).toBe('')
} finally {
spy.mockRestore()
}
}
)
it('hides the preparation, retargets an advanced base, and attaches the final branch', async () => {
const { repoPath, root } = await createRepo()
const preparationRoot = join(root, WORKTREE_CREATE_PREPARATION_DIRECTORY)
@@ -15,15 +15,13 @@ export function registerWorktreePrefetchHandler(context: WorktreeIpcContext): vo
return
}
try {
const baseBranch = await prefetchWorktreeCreateBase({
await prefetchWorktreeCreateBase({
repo,
baseBranch: args.baseBranch,
runtime,
gitOptions: getWorktreeCreatePrefetchGitOptions(store, repo)
gitOptions: getWorktreeCreatePrefetchGitOptions(store, repo),
prepareCheckout: (base) => prepareWorktreeCreateForRepo(store, repo, base)
})
if (baseBranch) {
await prepareWorktreeCreateForRepo(store, repo, baseBranch)
}
} catch {
// Why: optimistic warm-up; the real create path awaits the same refresh and reports failures there.
}
@@ -107,7 +107,10 @@ describe('prefetchManagedWorktreeCreateBase (orca-runtime-get-worktree-terminal-
it('prepares the checkout the prefetch resolved', async () => {
_setWslCachesForTests({ available: true, distros: ['Ubuntu'] })
setPlatform('win32')
mocks.prefetchWorktreeCreateBase.mockResolvedValue('origin/main')
mocks.prefetchWorktreeCreateBase.mockImplementation(async ({ prepareCheckout }) => {
await prepareCheckout('origin/main')
return 'origin/main'
})
const runtime = new OrcaRuntimeService(makeStore() as never)
await runtime.prefetchManagedWorktreeCreateBase({ repoSelector: 'repo-1' })
@@ -50,18 +50,12 @@ export class OrcaRuntimeWithGetWorktreeTerminalProvisioningHost extends OrcaRunt
const repo = await this.resolveRepoSelector(args.repoSelector)
const store = this.requireStore()
const baseBranch = await prefetchWorktreeCreateBase({
await prefetchWorktreeCreateBase({
repo,
baseBranch: args.baseBranch,
runtime: this,
gitOptions: getWorktreeCreatePrefetchGitOptions(store, repo)
gitOptions: getWorktreeCreatePrefetchGitOptions(store, repo),
prepareCheckout: (base) => prepareWorktreeCreateForRepo(store, repo, base)
})
if (baseBranch) {
try {
await prepareWorktreeCreateForRepo(store, repo, baseBranch)
} catch {
// Why: speculative preparation is an optimistic warm-up; the real create path reports failures.
}
}
}
}
@@ -160,12 +160,14 @@ describe('prefetchWorktreeCreateBase local git routing', () => {
})
it('does not resolve a local base for SSH repos', async () => {
const prepareCheckout = vi.fn()
const provider = { exec: vi.fn() }
mocks.getSshGitProvider.mockReturnValue(provider)
await expect(
prefetchWorktreeCreateBase({
repo: { ...repo, connectionId: 'conn-1' },
prepareCheckout,
baseBranch: 'origin/main',
runtime: runtime(),
gitOptions: WSL
@@ -178,5 +180,145 @@ describe('prefetchWorktreeCreateBase local git routing', () => {
{ baseBranch: 'origin/main' }
)
expect(mocks.gitExecFileAsync).not.toHaveBeenCalled()
expect(prepareCheckout).not.toHaveBeenCalled()
})
})
describe('checkout and refresh overlap', () => {
it.each([{}, WSL])(
'starts one checkout before a blocked refresh finishes on %j',
async (gitOptions) => {
const base = {
remote: 'origin',
branch: 'main',
ref: 'refs/remotes/origin/main',
base: 'origin/main'
}
mocks.resolveRemoteTrackingBase.mockResolvedValue(base)
mocks.hasRemoteTrackingRef.mockResolvedValue(true)
let release!: () => void
mocks.getOrStartRemoteTrackingBaseRefresh.mockImplementation(
() =>
new Promise<void>((resolve) => {
release = resolve
})
)
const prepareCheckout = vi.fn().mockResolvedValue(undefined)
let settled = false
const result = prefetchWorktreeCreateBase({
repo,
baseBranch: 'origin/main',
runtime: runtime(),
gitOptions,
prepareCheckout
}).finally(() => {
settled = true
})
await vi.waitFor(() => expect(prepareCheckout).toHaveBeenCalledWith('origin/main'))
expect(settled).toBe(false)
release()
await expect(result).resolves.toBe('origin/main')
expect(prepareCheckout).toHaveBeenCalledTimes(1)
}
)
it('waits for refresh when the selected base is not local', async () => {
mocks.resolveRemoteTrackingBase.mockResolvedValue({
remote: 'origin',
branch: 'main',
ref: 'refs/remotes/origin/main',
base: 'origin/main'
})
let release!: () => void
mocks.getOrStartRemoteTrackingBaseRefresh.mockImplementation(
() =>
new Promise<void>((resolve) => {
release = resolve
})
)
const prepareCheckout = vi.fn().mockResolvedValue(undefined)
const result = prefetchWorktreeCreateBase({
repo,
baseBranch: 'origin/main',
runtime: runtime(),
gitOptions: {},
prepareCheckout
})
await vi.waitFor(() =>
expect(mocks.getOrStartRemoteTrackingBaseRefresh).toHaveBeenCalledTimes(1)
)
expect(prepareCheckout).not.toHaveBeenCalled()
release()
await result
expect(prepareCheckout).toHaveBeenCalledTimes(1)
})
it('does not fail a successful refresh because preparation fails', async () => {
mocks.resolveRemoteTrackingBase.mockResolvedValue({
remote: 'origin',
branch: 'main',
ref: 'refs/remotes/origin/main',
base: 'origin/main'
})
mocks.hasRemoteTrackingRef.mockResolvedValue(true)
const prepareCheckout = vi.fn().mockRejectedValue(new Error('checkout failed'))
await expect(
prefetchWorktreeCreateBase({
repo,
baseBranch: 'origin/main',
runtime: runtime(),
gitOptions: {},
prepareCheckout
})
).resolves.toBe('origin/main')
expect(prepareCheckout).toHaveBeenCalledTimes(1)
})
it('settles preparation before propagating refresh failure', async () => {
mocks.resolveRemoteTrackingBase.mockResolvedValue({
remote: 'origin',
branch: 'main',
ref: 'refs/remotes/origin/main',
base: 'origin/main'
})
mocks.hasRemoteTrackingRef.mockResolvedValue(true)
const error = new Error('refresh failed')
mocks.getOrStartRemoteTrackingBaseRefresh.mockRejectedValue(error)
let release!: () => void
const prepareCheckout = vi.fn(
() =>
new Promise<void>((resolve) => {
release = resolve
})
)
let settled = false
const result = prefetchWorktreeCreateBase({
repo,
baseBranch: 'origin/main',
runtime: runtime(),
gitOptions: {},
prepareCheckout
}).finally(() => {
settled = true
})
const assertion = expect(result).rejects.toBe(error)
await vi.waitFor(() => expect(prepareCheckout).toHaveBeenCalledTimes(1))
expect(settled).toBe(false)
release()
await assertion
})
})
it('does not prepare folder repositories', async () => {
const prepareCheckout = vi.fn()
await expect(
prefetchWorktreeCreateBase({
repo: { ...repo, kind: 'folder' },
runtime: runtime(),
gitOptions: {},
prepareCheckout
})
).resolves.toBeUndefined()
expect(prepareCheckout).not.toHaveBeenCalled()
expect(mocks.gitExecFileAsync).not.toHaveBeenCalled()
})
+38 -3
View File
@@ -45,7 +45,8 @@ async function prefetchLocalWorktreeCreateBase(
repo: Repo,
baseBranch: string | undefined,
runtime: WorktreeCreateBasePrefetchRuntime,
options: WorktreeCreateBaseGitOptions
options: WorktreeCreateBaseGitOptions,
prepareLocalCheckout: (base: string) => 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] : []
@@ -83,8 +84,17 @@ async function prefetchLocalWorktreeCreateBase(
...optionArgs
)
if (remoteTrackingBase) {
const hasTrackingRef = await runtime.hasRemoteTrackingRef(
repo.path,
remoteTrackingBase,
...optionArgs
)
if (hasTrackingRef) {
// Finalization revalidates the refreshed commit before exposing the checkout.
prepareLocalCheckout(resolvedBaseBranch)
}
if (
(await runtime.hasRemoteTrackingRef(repo.path, remoteTrackingBase, ...optionArgs)) ||
hasTrackingRef ||
!(await hasLocalWorktreeBaseRef(repo.path, resolvedBaseBranch, options))
) {
await runtime.getOrStartRemoteTrackingBaseRefresh(
@@ -114,6 +124,7 @@ export async function prefetchWorktreeCreateBase(args: {
/** 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) => Promise<void>
}): Promise<string | undefined> {
if (isFolderRepo(args.repo)) {
return undefined
@@ -126,5 +137,29 @@ export async function prefetchWorktreeCreateBase(args: {
await prefetchRemoteWorktreeCreateBase(provider, args.repo, { baseBranch: args.baseBranch })
return undefined
}
return prefetchLocalWorktreeCreateBase(args.repo, args.baseBranch, args.runtime, args.gitOptions)
const prepareCheckout = args.prepareCheckout
let preparation: Promise<void> | undefined
const prepare = (base: string): void => {
if (!preparation && prepareCheckout) {
preparation = Promise.resolve()
.then(() => prepareCheckout(base))
.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
}
}