mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
* fix(worktrees): create from the usable local base when there is no tracking ref, and say so, on the host's create The host's create (agent.launch, worktree.create, the phone and the CLI) asked for a remote base like origin/main with no tracking ref yet fetched it, and threw offline, even when the local branch existed; it also never reported a base it fell back to. Like the desktop create, it now uses the local branch without fetching and returns baseFallback, which the desktop window already turns into a notice. * fix(worktrees): fetch the remote base first and fall back to the local branch only when the fetch fails The host's create skipped the fetch whenever a local branch matched a remote base with no tracking ref, so online phone/CLI/agent.launch creates silently built on a possibly stale local branch, every time. Fetch first, as main does; only when that fetch fails use the local branch the remote names and report it with baseFallback. With no local branch the existing error is unchanged. Decide the base before naming so branch reuse and conflict checks, the add and the persisted baseRef all see the base actually used, and return baseFallback as its own field of the create result instead of riding on the git add result. * fix(worktrees): report the fallback for a local ref of the requested name, as the desktop create does The tracking ref is still missing there; only the fetch is skipped, as before. * test(worktrees): pin that the host create names only after the base fetch settles Also pins the existing not-found-after-fetching error, now owned by the base decision. * fix(worktrees): fall back to the local base only for creates a person asked for; keep the exact-name ref silent Scheduled automations, orchestration workers and federation keep main's "Check your network" error offline: nobody is watching to be told the workspace was built on a possibly stale local branch. The fallback is a host-side create option (allowLocalBaseFallback, default off, not on the wire) that the worktree.create RPC and agent.launch set unless the request carries automation provenance. A local ref of the exact requested name goes back to silent, as on main's host: reporting it showed "may not include the latest remote changes" online for plain local branches. * test(worktrees): pin the local-base opt-in at agent.launch and its absence for an automation's worktree.create
89 lines
3.8 KiB
TypeScript
89 lines
3.8 KiB
TypeScript
import type { LocalGitExecOptions } from '../git/repo-default-base-ref'
|
|
import type { WorktreeCreateBaseFallback } from '../../shared/worktree/create-types'
|
|
import { hasLocalWorktreeBaseRef } from '../git/worktree-base-ref-probe'
|
|
import type { WorktreeCreateTimingRecorder } from '../worktree-create-timing'
|
|
import type { RemoteFetchResult, RemoteTrackingBase } from './runtime-remote-fetch-controller'
|
|
|
|
export type RuntimeLocalWorktreeCreateBase = {
|
|
/** The base the add uses: the request's, or the local branch it fell back to. */
|
|
baseBranch: string
|
|
remoteTrackingBase: RemoteTrackingBase | null
|
|
/**
|
|
* Best-effort refresh the git create runs after naming, since it cannot change the base: the
|
|
* existing tracking ref, or `fetch origin` for a plain base that is not local yet.
|
|
*/
|
|
deferredRefresh: 'tracking_ref' | 'origin' | null
|
|
baseFallback?: WorktreeCreateBaseFallback
|
|
}
|
|
|
|
/**
|
|
* Decides the base before naming, because branch reuse and conflict checks must see the base the
|
|
* add will use, including the local fallback.
|
|
*/
|
|
export async function resolveRuntimeLocalWorktreeCreateBase(args: {
|
|
repoPath: string
|
|
baseBranch: string
|
|
localWorktreeGitOptions: LocalGitExecOptions
|
|
allowLocalBaseFallback: boolean
|
|
resolveRemoteTrackingBase: (
|
|
repoPath: string,
|
|
baseBranch: string,
|
|
options?: LocalGitExecOptions
|
|
) => Promise<RemoteTrackingBase | null>
|
|
hasRemoteTrackingRef: (
|
|
repoPath: string,
|
|
base: RemoteTrackingBase,
|
|
options?: LocalGitExecOptions
|
|
) => Promise<boolean>
|
|
refreshRemoteTrackingBase: (
|
|
repoPath: string,
|
|
base: RemoteTrackingBase,
|
|
options?: LocalGitExecOptions
|
|
) => Promise<RemoteFetchResult>
|
|
timing: WorktreeCreateTimingRecorder
|
|
}): Promise<RuntimeLocalWorktreeCreateBase> {
|
|
const { repoPath, baseBranch, localWorktreeGitOptions: options } = args
|
|
const remoteTrackingBase = await args.resolveRemoteTrackingBase(repoPath, baseBranch, options)
|
|
if (!remoteTrackingBase) {
|
|
const hasBase = await hasLocalWorktreeBaseRef(repoPath, baseBranch, options)
|
|
return { baseBranch, remoteTrackingBase: null, deferredRefresh: hasBase ? null : 'origin' }
|
|
}
|
|
const [hadRemoteRef, hasNamedLocalBaseRef] = await Promise.all([
|
|
args.hasRemoteTrackingRef(repoPath, remoteTrackingBase, options),
|
|
hasLocalWorktreeBaseRef(repoPath, baseBranch, options)
|
|
])
|
|
if (hadRemoteRef) {
|
|
return { baseBranch, remoteTrackingBase, deferredRefresh: 'tracking_ref' }
|
|
}
|
|
if (hasNamedLocalBaseRef) {
|
|
// Why: a local ref of the requested name (often a plain local branch like `team/feature`) is
|
|
// the base asked for, so there is nothing to fetch and no fallback to report.
|
|
return { baseBranch, remoteTrackingBase: null, deferredRefresh: null }
|
|
}
|
|
const refresh = await args.timing.time('refresh_base_ref', () =>
|
|
args.refreshRemoteTrackingBase(repoPath, remoteTrackingBase, options)
|
|
)
|
|
if (!refresh.ok) {
|
|
// Why: fetch first so an online create still gets the fresh remote base; only when that fails
|
|
// (offline) does the local branch the remote names beat failing the create, and the result says so.
|
|
if (
|
|
args.allowLocalBaseFallback &&
|
|
(await hasLocalWorktreeBaseRef(repoPath, remoteTrackingBase.branch, options))
|
|
) {
|
|
return {
|
|
baseBranch: remoteTrackingBase.branch,
|
|
remoteTrackingBase: null,
|
|
deferredRefresh: null,
|
|
baseFallback: { requestedRef: remoteTrackingBase.base, localRef: remoteTrackingBase.branch }
|
|
}
|
|
}
|
|
throw new Error(
|
|
`Could not refresh base ref "${baseBranch}" from "${remoteTrackingBase.remote}". Check your network and try again.`
|
|
)
|
|
}
|
|
if (!(await args.hasRemoteTrackingRef(repoPath, remoteTrackingBase, options))) {
|
|
throw new Error(`Base ref "${baseBranch}" was not found after fetching.`)
|
|
}
|
|
return { baseBranch, remoteTrackingBase, deferredRefresh: null }
|
|
}
|