mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 00:02:39 +00:00
* perf(git): reuse queries and stop canceled catalog scans * refactor(git): check relay filesystem error codes
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import { throwIfSignalAborted, waitForPromiseWithSignal } from '../shared/abort-signal-reason'
|
||
import { annotateWorktreeLocksFromAdmin } from '../shared/git-worktree-admin'
|
||
import { expandTilde } from './context'
|
||
import { stat } from 'node:fs/promises'
|
||
import type { GitCapabilityCache } from '../shared/git-capability-cache'
|
||
import type { GitExec } from './git-handler-ops'
|
||
import { isUnsupportedWorktreeListZError } from './git-handler-utils'
|
||
import { parseWorktreeList } from '../shared/git-worktree-porcelain-parser'
|
||
import type { GitWorktreeInfo } from '../shared/worktree/types'
|
||
|
||
export type RelayWorktreeInfo = {
|
||
path: string
|
||
branch?: string
|
||
head?: string
|
||
locked?: boolean
|
||
lockReason?: string
|
||
}
|
||
|
||
export async function readRelayWorktreeList(
|
||
git: GitExec,
|
||
repoPath: string,
|
||
capabilities: GitCapabilityCache
|
||
): Promise<RelayWorktreeInfo[]> {
|
||
return capabilities.runWithFallback(
|
||
'worktree-list-z',
|
||
async () => {
|
||
const { stdout } = await git(['worktree', 'list', '--porcelain', '-z'], repoPath)
|
||
return normalizeRelayWorktrees(parseWorktreeList(stdout, { nulDelimited: true }))
|
||
},
|
||
async () => {
|
||
// Why: `-z` preserves newlines; fallback keeps Git <2.36 compatible.
|
||
const { stdout } = await git(['worktree', 'list', '--porcelain'], repoPath)
|
||
return normalizeRelayWorktrees(
|
||
await annotateWorktreeLocksFromAdmin(expandTilde(repoPath), parseWorktreeList(stdout))
|
||
)
|
||
},
|
||
isUnsupportedWorktreeListZError
|
||
)
|
||
}
|
||
|
||
const PRUNABLE_EXISTENCE_PROBE_CONCURRENCY = 8
|
||
|
||
/** Why: Git <2.31 does not emit the `prunable` porcelain annotation, so probe
|
||
* each linked worktree path directly instead of treating a stale registration
|
||
* as a live workspace (issue #8389). Runs on the `-z`-unsupported fallback
|
||
* (Git <2.36); on Git 2.31–2.35 the annotation is already parsed, so this is a
|
||
* harmless backstop. The relay owns the filesystem, so a plain stat is
|
||
* authoritative. */
|
||
export async function annotatePrunableWorktreesByExistence(
|
||
worktrees: GitWorktreeInfo[],
|
||
signal?: AbortSignal
|
||
): Promise<GitWorktreeInfo[]> {
|
||
const annotated = [...worktrees]
|
||
let nextIndex = 0
|
||
|
||
async function probeNext(): Promise<void> {
|
||
while (nextIndex < worktrees.length) {
|
||
throwIfSignalAborted(signal)
|
||
const index = nextIndex
|
||
nextIndex += 1
|
||
const worktree = worktrees[index]
|
||
const worktreePath = worktree?.path ?? ''
|
||
// Locks protect missing linked worktrees; repository failures own the main row.
|
||
if (
|
||
!worktreePath ||
|
||
worktree.isMainWorktree === true ||
|
||
worktree.isBare === true ||
|
||
worktree.locked === true ||
|
||
worktree.prunable === true
|
||
) {
|
||
continue
|
||
}
|
||
try {
|
||
await stat(worktreePath)
|
||
} catch (err) {
|
||
if (typeof err === 'object' && err !== null && 'code' in err && err.code === 'ENOENT') {
|
||
annotated[index] = { ...worktree, prunable: true }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const workerCount = Math.min(PRUNABLE_EXISTENCE_PROBE_CONCURRENCY, worktrees.length)
|
||
await waitForPromiseWithSignal(
|
||
Promise.all(Array.from({ length: workerCount }, () => probeNext())),
|
||
signal
|
||
)
|
||
throwIfSignalAborted(signal)
|
||
return annotated
|
||
}
|
||
|
||
function normalizeRelayWorktrees(worktrees: GitWorktreeInfo[]): RelayWorktreeInfo[] {
|
||
return worktrees
|
||
.map((worktree) => ({
|
||
path: worktree.path,
|
||
head: worktree.head,
|
||
branch: worktree.branch,
|
||
locked: worktree.locked === true ? true : undefined,
|
||
lockReason: worktree.lockReason
|
||
}))
|
||
.filter((worktree) => worktree.path.length > 0)
|
||
}
|