Files
orca/src/relay/git-handler-local-base-ref-refresh.ts
T
Neil 533992bdda fix(git): cache unsupported capabilities per host (#8109)
* fix(git): cache unsupported capabilities per host

Old Git worktree, ref-search, and merge-tree fallbacks retried unsupported flags on recurring operations, flooding subprocess traces. Centralize capability probing per native, WSL, and SSH execution host, coalesce concurrent probes, and retry periodically for in-place Git upgrades.

* fix(git): recognize real old-Git merge-tree rejection

* test(git): enforce real binary compatibility matrix

* fix(ci): preserve Git compatibility test ownership

* fix(git): retain supported capability state
2026-07-10 18:19:36 -07:00

89 lines
3.0 KiB
TypeScript

import type { GitExec } from './git-handler-ops'
import { areRelayWorktreePathsEqual, readRelayWorktreeList } from './git-handler-worktree-ops'
import type { GitCapabilityCache } from '../shared/git-capability-cache'
export async function refreshLocalBaseRefForWorktreeCreateOp(
git: GitExec,
params: Record<string, unknown>,
capabilities: GitCapabilityCache
): Promise<void> {
const repoPath = params.repoPath as string
const fullRef = params.fullRef as string
const remoteTrackingRef = params.remoteTrackingRef as string
const ownerWorktreePath = params.ownerWorktreePath as string | undefined
const checkOnly = params.checkOnly === true
if (
typeof repoPath !== 'string' ||
typeof fullRef !== 'string' ||
typeof remoteTrackingRef !== 'string' ||
(ownerWorktreePath !== undefined && typeof ownerWorktreePath !== 'string')
) {
throw new Error('Invalid local base ref refresh request.')
}
if (!fullRef.startsWith('refs/heads/') || !remoteTrackingRef.startsWith('refs/remotes/')) {
throw new Error('Invalid local base ref refresh refs.')
}
await git(['check-ref-format', fullRef], repoPath)
await git(['check-ref-format', remoteTrackingRef], repoPath)
const localOid = await revParseCommit(git, repoPath, fullRef, 'Local base ref is missing.')
const remoteOid = await revParseCommit(
git,
repoPath,
remoteTrackingRef,
'Remote-tracking base ref is missing.'
)
// Why: this RPC mutates refs/worktrees, so the relay repeats main-process
// safety checks at mutation time to close stale-preflight and direct-call gaps.
try {
await git(['merge-base', '--is-ancestor', localOid, remoteOid], repoPath)
} catch {
throw new Error('Local base ref is not a fast-forward update.')
}
const worktrees = await readRelayWorktreeList(git, repoPath, capabilities)
const ownerWorktree = worktrees.find((worktree) => worktree.branch === fullRef)
if (ownerWorktree) {
if (ownerWorktreePath && !areRelayWorktreePathsEqual(ownerWorktree.path, ownerWorktreePath)) {
throw new Error('Local base ref is checked out in a different worktree.')
}
const { stdout } = await git(
['status', '--porcelain', '--untracked-files=no'],
ownerWorktree.path
)
if (stdout.trim()) {
throw new Error('Local base ref worktree has tracked changes.')
}
if (checkOnly) {
return
}
await git(['reset', '--hard', remoteOid], ownerWorktree.path)
return
}
// Why: not checked out anywhere — fast-forward the bare ref. The
// expected-old-OID form is a no-op-safe compare-and-swap if the ref moved
// since the caller's evaluation snapshot.
if (checkOnly) {
return
}
await git(['update-ref', fullRef, remoteOid, localOid], repoPath)
}
async function revParseCommit(
git: GitExec,
repoPath: string,
ref: string,
missingMessage: string
): Promise<string> {
const { stdout } = await git(['rev-parse', '--verify', `${ref}^{commit}`], repoPath)
const oid = stdout.trim()
if (!oid) {
throw new Error(missingMessage)
}
return oid
}