mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 19:17:28 +00:00
* perf(worktrees): look up a branch's PR only after a name collision on the host's create The host's create (agent.launch, worktree.create, the phone and the CLI) called gh pr list for every create, a 1-3 s network round trip, while the desktop create probes only after a branch collision. A branch with an open PR exists on the remote, so the branch-conflict check already moves past it. * test(worktrees): pin first-name PR lookup skip on the host's create The runtime service specs still expected a PR lookup for a free first name. Update them to the desktop-parity behavior: no lookup without a branch collision, and a free push-target name is kept. Pin the exact suffixed name, and that a later name with a PR is skipped. * perf(worktrees): overlap the host create's base fetch with naming Keep the first-name PR lookup (restores main's naming) and take it off the critical path instead: start the base-ref refresh before naming and await it after, as the desktop create does, so the lookup and the fetch run concurrently. A refresh rejection while naming fails stays handled. * test(worktrees): pin the overlapped origin fetch on the host's create Cover the best-effort fetch origin path: it runs while the first-name PR lookup is pending, and its rejection while naming fails stays handled.
241 lines
8.8 KiB
TypeScript
241 lines
8.8 KiB
TypeScript
import type { LocalGitExecOptions } from '../git/repo-default-base-ref'
|
|
import { runWorktreeChangeInvalidators } from '../ipc/worktree-change-invalidators'
|
|
import type { GitPushTarget, GitWorktreeInfo } from '../../shared/worktree/types'
|
|
import type { Repo } from '../../shared/repo-types'
|
|
import { resolveCreatedWorktree } from '../ipc/created-worktree-reconciliation'
|
|
import { normalizeSparseDirectories } from '../ipc/sparse-checkout-directories'
|
|
import { configureCreatedWorktreePushTarget } from '../ipc/worktree-remote'
|
|
import {
|
|
addSparseWorktree,
|
|
addWorktree,
|
|
type AddWorktreeOptions,
|
|
type AddWorktreeResult
|
|
} from '../git/worktree'
|
|
import type { RuntimeStore } from './runtime-store-contract'
|
|
import type { RuntimeManagedWorktreeCreateArgs } from './runtime-managed-worktree-create-types'
|
|
import type { RemoteFetchResult, RemoteTrackingBase } from './runtime-remote-fetch-controller'
|
|
import type { RuntimeLocalWorktreeCreateBase } from './runtime-local-worktree-create-base'
|
|
import { isGeneratedWorktreeCreateName } from '../worktree-create-candidates'
|
|
import {
|
|
consumePreparedWorktreeCreate,
|
|
type PreparationRearmHolder
|
|
} from '../worktree-create-preparation'
|
|
import {
|
|
failedWorktreeCreationNeedsRetirement,
|
|
retireGeneratedWorktreeName
|
|
} from '../worktree-name-retirement'
|
|
import type { WorktreeCreateTimingRecorder } from '../worktree-create-timing'
|
|
|
|
export type RuntimeLocalBaseRefRefresh = {
|
|
/** Awaits the deferred refresh started before naming, if any. */
|
|
finish: () => Promise<void>
|
|
}
|
|
|
|
// Why: the deferred refresh can change neither the base nor the name, so start it before naming
|
|
// and await it before the add, as the desktop create does; naming's PR lookup overlaps it.
|
|
export function startRuntimeLocalBaseRefRefresh(args: {
|
|
repoPath: string
|
|
base: RuntimeLocalWorktreeCreateBase
|
|
localWorktreeGitOptions: LocalGitExecOptions
|
|
refreshRemoteTrackingBase: (
|
|
repoPath: string,
|
|
base: RemoteTrackingBase,
|
|
options?: LocalGitExecOptions
|
|
) => Promise<RemoteFetchResult>
|
|
fetchRemote: (repoPath: string, remote: string, options?: LocalGitExecOptions) => Promise<void>
|
|
timing: WorktreeCreateTimingRecorder
|
|
}): RuntimeLocalBaseRefRefresh {
|
|
const { remoteTrackingBase, deferredRefresh } = args.base
|
|
if (remoteTrackingBase && deferredRefresh === 'tracking_ref') {
|
|
const refresh = args.refreshRemoteTrackingBase(
|
|
args.repoPath,
|
|
remoteTrackingBase,
|
|
args.localWorktreeGitOptions
|
|
)
|
|
// Why: naming can throw before finish() awaits the refresh.
|
|
void refresh.catch(() => undefined)
|
|
return {
|
|
finish: async () => {
|
|
await args.timing.time('refresh_base_ref', () => refresh)
|
|
}
|
|
}
|
|
}
|
|
if (deferredRefresh === 'origin') {
|
|
const originFetch = args.fetchRemote(args.repoPath, 'origin', args.localWorktreeGitOptions)
|
|
void originFetch.catch(() => undefined)
|
|
return {
|
|
finish: async () => {
|
|
try {
|
|
await args.timing.time('refresh_base_ref', () => originFetch)
|
|
} catch {}
|
|
}
|
|
}
|
|
}
|
|
return { finish: async () => {} }
|
|
}
|
|
|
|
export async function createRuntimeLocalGitWorktree(args: {
|
|
request: RuntimeManagedWorktreeCreateArgs
|
|
repo: Repo
|
|
store: RuntimeStore
|
|
settings: {
|
|
workspaceDir: string
|
|
nestWorkspaces: boolean
|
|
refreshLocalBaseRefOnWorktreeCreate: boolean
|
|
localBaseRefSuggestionDismissed?: boolean
|
|
}
|
|
base: RuntimeLocalWorktreeCreateBase
|
|
workspaceRoot: string
|
|
branchName: string
|
|
worktreePath: string
|
|
effectiveSanitizedName?: string
|
|
checkoutExistingBranch: boolean
|
|
localWorktreeGitOptions: LocalGitExecOptions
|
|
baseRefRefresh: RuntimeLocalBaseRefRefresh
|
|
rearm: PreparationRearmHolder
|
|
timing: WorktreeCreateTimingRecorder
|
|
}): Promise<{
|
|
sparseDirectories: string[]
|
|
configuredPushTarget?: GitPushTarget
|
|
created: GitWorktreeInfo
|
|
addResult: AddWorktreeResult
|
|
}> {
|
|
const { baseBranch, remoteTrackingBase } = args.base
|
|
await args.baseRefRefresh.finish()
|
|
const sparseDirectories = args.request.sparseCheckout
|
|
? normalizeSparseDirectories(args.request.sparseCheckout.directories)
|
|
: []
|
|
if (args.request.sparseCheckout && sparseDirectories.length === 0) {
|
|
throw new Error('Sparse checkout requires at least one repo-relative directory.')
|
|
}
|
|
// Why: defer the remote add + fetch (fork case) or the redundant re-fetch
|
|
// (same-repo case, already fetched while resolving the PR start point) to
|
|
// first use -- push/pull/fetch/fast-forward materialize it on demand
|
|
// (#17828). Metadata is persisted untouched; only the git mutation defers.
|
|
const preparedPushTarget = args.request.pushTarget
|
|
const suggestLocalBaseRefUpdate =
|
|
!args.settings.refreshLocalBaseRefOnWorktreeCreate &&
|
|
!args.settings.localBaseRefSuggestionDismissed &&
|
|
Boolean(remoteTrackingBase)
|
|
const remoteOption = remoteTrackingBase ? { remoteTrackingBase } : undefined
|
|
const preparedWorktreeOptions: AddWorktreeOptions = {
|
|
...remoteOption,
|
|
...(suggestLocalBaseRefUpdate ? { suggestLocalBaseRefUpdate } : {}),
|
|
...args.localWorktreeGitOptions
|
|
}
|
|
const addOptions: AddWorktreeOptions = {
|
|
...preparedWorktreeOptions,
|
|
...(args.checkoutExistingBranch ? { checkoutExistingBranch: true } : {})
|
|
}
|
|
const shouldRetireGeneratedName =
|
|
args.request.nameWasGenerated === true &&
|
|
Boolean(args.effectiveSanitizedName) &&
|
|
isGeneratedWorktreeCreateName(args.effectiveSanitizedName!)
|
|
let addResult: AddWorktreeResult
|
|
try {
|
|
addResult = await args.timing.time('git_worktree_add', async () => {
|
|
if (sparseDirectories.length > 0 || args.checkoutExistingBranch) {
|
|
args.timing.recordPreparedCheckout({
|
|
status: 'miss',
|
|
reason: sparseDirectories.length > 0 ? 'sparse_checkout' : 'checkout_existing_branch'
|
|
})
|
|
} else {
|
|
const preparedAttempt = await consumePreparedWorktreeCreate({
|
|
repoPath: args.repo.path,
|
|
workspaceRoot: args.workspaceRoot,
|
|
worktreePath: args.worktreePath,
|
|
branch: args.branchName,
|
|
baseBranch,
|
|
refreshLocalBaseRef: args.settings.refreshLocalBaseRefOnWorktreeCreate,
|
|
options: preparedWorktreeOptions,
|
|
timing: args.timing
|
|
})
|
|
if (preparedAttempt.status === 'hit') {
|
|
// Deferred, not fired: re-arming is a full `reset --hard`, and the caller still has
|
|
// materialization probes and terminals ahead of it.
|
|
args.rearm.fire = preparedAttempt.rearm
|
|
return preparedAttempt.result
|
|
}
|
|
if (preparedAttempt.rearm) {
|
|
args.rearm.fire = preparedAttempt.rearm
|
|
}
|
|
}
|
|
if (sparseDirectories.length > 0) {
|
|
return (
|
|
(await addSparseWorktree(
|
|
args.repo.path,
|
|
args.worktreePath,
|
|
args.branchName,
|
|
sparseDirectories,
|
|
baseBranch,
|
|
args.settings.refreshLocalBaseRefOnWorktreeCreate,
|
|
addOptions
|
|
)) ?? {}
|
|
)
|
|
}
|
|
return (
|
|
(await addWorktree(
|
|
args.repo.path,
|
|
args.worktreePath,
|
|
args.branchName,
|
|
baseBranch,
|
|
args.settings.refreshLocalBaseRefOnWorktreeCreate,
|
|
false,
|
|
addOptions
|
|
)) ?? {}
|
|
)
|
|
})
|
|
} catch (error) {
|
|
if (shouldRetireGeneratedName && failedWorktreeCreationNeedsRetirement(error)) {
|
|
await retireGeneratedWorktreeName(
|
|
args.store as Parameters<typeof retireGeneratedWorktreeName>[0],
|
|
args.repo,
|
|
args.settings,
|
|
args.effectiveSanitizedName!
|
|
)
|
|
}
|
|
throw error
|
|
}
|
|
// Why: the worktree is listable from here on; scans that began before it appeared are stale.
|
|
runWorktreeChangeInvalidators(args.repo.id)
|
|
if (shouldRetireGeneratedName) {
|
|
await retireGeneratedWorktreeName(
|
|
args.store as Parameters<typeof retireGeneratedWorktreeName>[0],
|
|
args.repo,
|
|
args.settings,
|
|
args.effectiveSanitizedName!
|
|
)
|
|
}
|
|
// Why: `--set-upstream-to` requires the remote to already exist -- safe for a
|
|
// same-repo target (its remote, e.g. `origin`, always exists) but not for a
|
|
// deferred fork remote, which is materialized lazily at first push/pull/fetch.
|
|
const configuredPushTarget =
|
|
preparedPushTarget && !preparedPushTarget.remoteUrl
|
|
? await configureCreatedWorktreePushTarget(
|
|
args.worktreePath,
|
|
args.branchName,
|
|
preparedPushTarget,
|
|
args.localWorktreeGitOptions
|
|
)
|
|
: preparedPushTarget
|
|
const { created, worktrees, listingComplete } = await args.timing.time(
|
|
'list_created_worktree',
|
|
() =>
|
|
resolveCreatedWorktree(
|
|
args.repo.path,
|
|
args.worktreePath,
|
|
args.branchName,
|
|
args.localWorktreeGitOptions
|
|
)
|
|
)
|
|
if (listingComplete) {
|
|
args.timing.recordWorktreeCount(worktrees.length)
|
|
}
|
|
return {
|
|
sparseDirectories,
|
|
...(configuredPushTarget ? { configuredPushTarget } : {}),
|
|
created,
|
|
addResult
|
|
}
|
|
}
|