mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 18:17:36 +00:00
* fix(windows): stop recursive deletes from retry-storming when a file is held open Node's async recursive rm retries every nested entry separately and orphans sibling walkers when it rejects, so deleting a workspace holding a live browser profile multiplied filesystem work per directory level until the main process hung. Delete Windows host trees with a walker that retries whole passes from one shared budget and leaves nothing running on failure. Also bound and log detached worktree removal, and fail a file transaction whose proper-lockfile lock is compromised instead of throwing from its timer. * fix(windows): retry a locked delete as whole passes instead of a custom walker Replace the hand-rolled tree walker with Node's own recursive rm and no per-entry retries. Async rm gives each nested entry its own retry counter and never stops siblings, which is what multiplied the work; with retries off each pass is one bounded walk, and a transient Windows lock repeats the whole pass from the existing 250/500/1000/2000 ms budget, checking the job's signal between passes. A failed delete now names the file another program holds open instead of Node's EBUSY/EPERM text. Drop the 30-minute background deadline. The lost-lock error no longer embeds the library's message and says the write may or may not have been saved. * test: cover bounded deletion failure and recovery contracts * test: prune unchanged cleanup coverage * fix(worktrees): give truthful failed-delete cleanup advice * test: document fake-clock retry adapter * fix(windows): preserve checkout ownership until files are removed * fix: preserve worktree ownership checks across metadata aliases * test: make checkout retry clock completion explicit * Keep failed Windows workspace deletions retryable * test: correct removal fixture imports and path formatting * fix: retain failed removal until a retry is accepted * test: identify the mocked backoff clock * test: preserve failed removal records and recreate marker fixtures * test(worktrees): preserve exact checkout bytes in refusal contract * test(worktrees): exercise current native deletion contracts * test(windows): prove long-path deletion through native checkout removal * test(windows): preserve real Git runner exports in removal fixture --------- Co-authored-by: Orca Worker <orca-worker@localhost>
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
import { readFile, stat } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { waitForPromiseWithSignal } from './abort-signal-reason'
|
|
import { resolveGitMetadataPath, type GitMetadataPathOptions } from './git-metadata-path'
|
|
import { parseGitdirMarkerPayload } from './gitdir-marker-payload'
|
|
|
|
export type GitAdminReadOptions = GitMetadataPathOptions & { signal?: AbortSignal }
|
|
|
|
type GitLayoutStat = { isDirectory(): boolean; isFile(): boolean }
|
|
export type GitCommonDirectoryReader = {
|
|
statPath(path: string): Promise<GitLayoutStat>
|
|
readPath(path: string): Promise<string | null>
|
|
join(...paths: string[]): string
|
|
resolvePath(base: string, pointer: string): string | null
|
|
parseMarker(content: string): string | null
|
|
isMissing(error: unknown): boolean
|
|
}
|
|
|
|
export function isMissingGitAdminEntry(error: unknown): boolean {
|
|
return (
|
|
error instanceof Error &&
|
|
'code' in error &&
|
|
(error.code === 'ENOENT' || error.code === 'ENOTDIR')
|
|
)
|
|
}
|
|
|
|
export async function readGitAdminFile(
|
|
filePath: string,
|
|
signal?: AbortSignal
|
|
): Promise<string | null> {
|
|
signal?.throwIfAborted()
|
|
try {
|
|
return await readFile(filePath, { encoding: 'utf8', signal })
|
|
} catch (error) {
|
|
if (isMissingGitAdminEntry(error)) {
|
|
return null
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/** Read the owning host's Git layout without starting a subprocess. */
|
|
export async function resolveGitCommonDirectory(
|
|
repoPath: string,
|
|
options: GitAdminReadOptions = {},
|
|
reader: GitCommonDirectoryReader = {
|
|
statPath: (file) => waitForPromiseWithSignal(stat(file), options.signal),
|
|
readPath: (file) => readGitAdminFile(file, options.signal),
|
|
join: path.join,
|
|
resolvePath: (base, pointer) => resolveGitMetadataPath(base, pointer, options),
|
|
parseMarker: parseGitdirMarkerPayload,
|
|
isMissing: isMissingGitAdminEntry
|
|
}
|
|
): Promise<string | null> {
|
|
const dotGit = reader.join(repoPath, '.git')
|
|
let metadata: GitLayoutStat | undefined
|
|
let gitDir: string | null = null
|
|
try {
|
|
metadata = await reader.statPath(dotGit)
|
|
} catch (error) {
|
|
if (!reader.isMissing(error)) {
|
|
throw error
|
|
}
|
|
if ((await reader.readPath(reader.join(repoPath, 'HEAD'))) !== null) {
|
|
gitDir = repoPath
|
|
}
|
|
}
|
|
if (metadata?.isDirectory()) {
|
|
gitDir = dotGit
|
|
} else if (metadata?.isFile()) {
|
|
const marker = reader.parseMarker((await reader.readPath(dotGit)) ?? '')
|
|
if (marker) {
|
|
gitDir = reader.resolvePath(repoPath, marker)
|
|
}
|
|
}
|
|
if (!gitDir) {
|
|
return null
|
|
}
|
|
const commonDir = (await reader.readPath(reader.join(gitDir, 'commondir')))?.trim()
|
|
return commonDir ? reader.resolvePath(gitDir, commonDir) : gitDir
|
|
}
|