Files
orca/src/relay/git-handler-worktree-list.ts
T
Neil a40e5012f5 perf(git): reuse queries and stop canceled catalog scans (#24923)
* perf(git): reuse queries and stop canceled catalog scans

* refactor(git): check relay filesystem error codes
2026-10-03 02:54:20 -07:00

103 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}