Files
orca/src/main/worktree-create-base-prefetch.test.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

365 lines
12 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
getBaseRefDefault: vi.fn(),
gitExecFileAsync: vi.fn(),
getSshGitProvider: vi.fn(),
prefetchRemoteWorktreeCreateBase: vi.fn(),
resolveRemoteTrackingBase: vi.fn(),
hasRemoteTrackingRef: vi.fn(),
getOrStartRemoteTrackingBaseRefresh: vi.fn(),
fetchRemoteWithCache: vi.fn()
}))
vi.mock('./git/repo', () => ({ getBaseRefDefault: mocks.getBaseRefDefault }))
vi.mock('./git/runner', () => ({ gitExecFileAsync: mocks.gitExecFileAsync }))
vi.mock('./providers/ssh-git-dispatch', () => ({ getSshGitProvider: mocks.getSshGitProvider }))
vi.mock('./ipc/worktree-remote', () => ({
prefetchRemoteWorktreeCreateBase: mocks.prefetchRemoteWorktreeCreateBase
}))
import { prefetchWorktreeCreateBase } from './worktree-create-base-prefetch'
const repo = {
id: 'repo-1',
path: String.raw`C:\workspace\repo`,
displayName: 'repo',
badgeColor: '#000000',
addedAt: 0
}
const WSL = { wslDistro: 'Ubuntu' }
function runtime() {
return {
resolveRemoteTrackingBase: mocks.resolveRemoteTrackingBase,
hasRemoteTrackingRef: mocks.hasRemoteTrackingRef,
getOrStartRemoteTrackingBaseRefresh: mocks.getOrStartRemoteTrackingBaseRefresh,
fetchRemoteWithCache: mocks.fetchRemoteWithCache
}
}
/** Resolve only the named refs/objects; every other rev-parse answers "absent". */
function resolveOnly(present: string[]): void {
mocks.gitExecFileAsync.mockImplementation(async (args: string[]) => {
const rev = args.at(-1)?.replace('^{commit}', '') ?? ''
return present.includes(rev)
? { stdout: `${'f'.repeat(40)}\n`, stderr: '' }
: { stdout: '', stderr: '' }
})
}
function revParse(ref: string): string[] {
return ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]
}
beforeEach(() => {
for (const mock of Object.values(mocks)) {
mock.mockReset()
}
mocks.getBaseRefDefault.mockResolvedValue('origin/main')
resolveOnly([])
mocks.resolveRemoteTrackingBase.mockResolvedValue(null)
mocks.hasRemoteTrackingRef.mockResolvedValue(false)
mocks.getOrStartRemoteTrackingBaseRefresh.mockResolvedValue({ ok: true })
mocks.fetchRemoteWithCache.mockResolvedValue(undefined)
})
describe('prefetchWorktreeCreateBase local git routing', () => {
it('resolves the default base and its ref probes inside the selected WSL distro', async () => {
resolveOnly(['refs/remotes/origin/main'])
await expect(
prefetchWorktreeCreateBase({ repo, runtime: runtime(), gitOptions: WSL })
).resolves.toBe('origin/main')
expect(mocks.getBaseRefDefault).toHaveBeenCalledWith(repo.path, WSL)
expect(mocks.resolveRemoteTrackingBase).toHaveBeenCalledWith(repo.path, 'origin/main', WSL)
expect(mocks.gitExecFileAsync).toHaveBeenCalledWith(revParse('refs/remotes/origin/main'), {
cwd: repo.path,
...WSL
})
// A base that is already local needs no fetch.
expect(mocks.fetchRemoteWithCache).not.toHaveBeenCalled()
})
it('probes a full commit object inside the selected WSL distro', async () => {
const sha = 'a'.repeat(40)
resolveOnly([sha])
await expect(
prefetchWorktreeCreateBase({ repo, baseBranch: sha, runtime: runtime(), gitOptions: WSL })
).resolves.toBe(sha)
expect(mocks.gitExecFileAsync).toHaveBeenCalledWith(revParse(sha), {
cwd: repo.path,
...WSL
})
expect(mocks.fetchRemoteWithCache).not.toHaveBeenCalled()
})
it('routes the exact remote-base refresh through the selected WSL distro', async () => {
const remoteTrackingBase = {
remote: 'origin',
branch: 'main',
ref: 'refs/remotes/origin/main',
base: 'origin/main'
}
mocks.resolveRemoteTrackingBase.mockResolvedValue(remoteTrackingBase)
mocks.hasRemoteTrackingRef.mockResolvedValue(true)
await expect(
prefetchWorktreeCreateBase({
repo,
baseBranch: 'origin/main',
runtime: runtime(),
gitOptions: WSL
})
).resolves.toBe('origin/main')
expect(mocks.hasRemoteTrackingRef).toHaveBeenCalledWith(repo.path, remoteTrackingBase, WSL)
expect(mocks.getOrStartRemoteTrackingBaseRefresh).toHaveBeenCalledWith(
repo.path,
remoteTrackingBase,
WSL
)
})
it('routes the broad remote-fetch fallback through the selected WSL distro', async () => {
await expect(
prefetchWorktreeCreateBase({
repo,
baseBranch: 'feature/topic',
runtime: runtime(),
gitOptions: WSL
})
).resolves.toBe('feature/topic')
expect(mocks.fetchRemoteWithCache).toHaveBeenCalledWith(repo.path, 'origin', WSL)
})
it('leaves host-routed probes on the git host they used before routing existed', async () => {
await expect(
prefetchWorktreeCreateBase({
repo,
baseBranch: 'feature/topic',
runtime: runtime(),
gitOptions: {}
})
).resolves.toBe('feature/topic')
expect(mocks.gitExecFileAsync).toHaveBeenCalledWith(revParse('refs/remotes/feature/topic'), {
cwd: repo.path
})
for (const call of mocks.gitExecFileAsync.mock.calls) {
expect(call[1]).toEqual({ cwd: repo.path })
}
// Runtime calls keep their original arity so host repos stay on the runtime's own defaults.
expect(mocks.resolveRemoteTrackingBase).toHaveBeenCalledWith(repo.path, 'feature/topic')
expect(mocks.fetchRemoteWithCache).toHaveBeenCalledWith(repo.path, 'origin')
})
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
})
).resolves.toBeUndefined()
expect(mocks.prefetchRemoteWorktreeCreateBase).toHaveBeenCalledWith(
provider,
expect.objectContaining({ connectionId: 'conn-1' }),
{ baseBranch: 'origin/main' }
)
expect(mocks.gitExecFileAsync).not.toHaveBeenCalled()
expect(prepareCheckout).not.toHaveBeenCalled()
})
})
describe('checkout and refresh overlap', () => {
it.each([{}, WSL])(
'starts one checkout with a shared refresh barrier before fetch settles 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<{ ok: boolean }>((resolve) => {
release = () => resolve({ ok: true })
})
)
let materialized = false
const prepareCheckout = vi.fn(
async (_base: string, beforeMaterialization?: Promise<void>) => {
await beforeMaterialization
materialized = true
}
)
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.any(Promise))
)
expect(settled).toBe(false)
expect(materialized).toBe(false)
expect(mocks.getOrStartRemoteTrackingBaseRefresh).toHaveBeenCalledOnce()
release()
await expect(result).resolves.toBe('origin/main')
expect(materialized).toBe(true)
expect(prepareCheckout).toHaveBeenCalledOnce()
}
)
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<{ ok: boolean }>((resolve) => {
release = () => resolve({ ok: true })
})
)
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('settles the materialization barrier on fetch failure without hiding the fetch error', async () => {
mocks.resolveRemoteTrackingBase.mockResolvedValue({
remote: 'origin',
branch: 'main',
ref: 'refs/remotes/origin/main',
base: 'origin/main'
})
mocks.hasRemoteTrackingRef.mockResolvedValue(true)
const error = new Error('offline')
mocks.getOrStartRemoteTrackingBaseRefresh.mockRejectedValue(error)
let materialized = false
const prepareCheckout = vi.fn(async (_base: string, beforeMaterialization?: Promise<void>) => {
await beforeMaterialization
materialized = true
})
await expect(
prefetchWorktreeCreateBase({
repo,
baseBranch: 'origin/main',
runtime: runtime(),
gitOptions: {},
prepareCheckout
})
).rejects.toBe(error)
expect(materialized).toBe(true)
expect(prepareCheckout).toHaveBeenCalledOnce()
expect(mocks.getOrStartRemoteTrackingBaseRefresh).toHaveBeenCalledOnce()
})
})
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()
})