mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 16:02:43 +00:00
fix(worktrees): retain missing-path recovery when git marker vanishes
This commit is contained in:
@@ -402,30 +402,39 @@ describe('registerWorktreeHandlers', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('retries stale Git registration cleanup after prior local filesystem recovery', async () => {
|
||||
setPlatform('win32')
|
||||
const missingWorktreePath = 'C:\\workspace\\already-removed'
|
||||
const worktreeId = `repo-1::${missingWorktreePath}`
|
||||
const registeredWorktrees = mockKnownFeatureWorktree(missingWorktreePath)
|
||||
listWorktreesMock.mockResolvedValueOnce(registeredWorktrees).mockResolvedValue([])
|
||||
store.getWorktreeMeta.mockReturnValue(makeWorktreeMeta())
|
||||
it.each([false, true])(
|
||||
'retries missing registration cleanup (prunable marker: %s)',
|
||||
async (prunableMarker) => {
|
||||
setPlatform('win32')
|
||||
const missingWorktreePath = prunableMarker
|
||||
? 'C:\\workspace\\already-removed\\.git'
|
||||
: 'C:\\workspace\\already-removed'
|
||||
const worktreeId = `repo-1::${missingWorktreePath}`
|
||||
const registeredWorktrees = mockKnownFeatureWorktree(missingWorktreePath).map((row) =>
|
||||
prunableMarker && row.path === missingWorktreePath
|
||||
? { ...row, branch: 'refs/heads/feature', prunable: true }
|
||||
: row
|
||||
)
|
||||
listWorktreesMock.mockResolvedValueOnce(registeredWorktrees).mockResolvedValue([])
|
||||
store.getWorktreeMeta.mockReturnValue(makeWorktreeMeta())
|
||||
|
||||
const result = await handlers['worktrees:remove'](null, {
|
||||
worktreeId,
|
||||
force: true
|
||||
})
|
||||
const result = await handlers['worktrees:remove'](null, {
|
||||
worktreeId,
|
||||
force: true
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
preservedBranch: { branchName: 'feature', head: 'feature' }
|
||||
})
|
||||
expect(runHookMock).not.toHaveBeenCalled()
|
||||
expect(killAllProcessesForWorktreeMock).not.toHaveBeenCalled()
|
||||
expect(removeWorktreeMock).not.toHaveBeenCalled()
|
||||
expect(gitExecFileAsyncMock).toHaveBeenCalledWith(['worktree', 'prune'], {
|
||||
cwd: '/workspace/repo'
|
||||
})
|
||||
expect(store.removeWorktreeMeta).toHaveBeenCalledWith(worktreeId, 'local')
|
||||
})
|
||||
expect(result).toEqual({
|
||||
preservedBranch: { branchName: 'feature', head: 'feature' }
|
||||
})
|
||||
expect(runHookMock).not.toHaveBeenCalled()
|
||||
expect(killAllProcessesForWorktreeMock).not.toHaveBeenCalled()
|
||||
expect(removeWorktreeMock).not.toHaveBeenCalled()
|
||||
expect(gitExecFileAsyncMock).toHaveBeenCalledWith(['worktree', 'prune'], {
|
||||
cwd: '/workspace/repo'
|
||||
})
|
||||
expect(store.removeWorktreeMeta).toHaveBeenCalledWith(worktreeId, 'local')
|
||||
}
|
||||
)
|
||||
|
||||
it('cleans a prunable Git-file row before archive or checkout teardown', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-prunable-ipc-'))
|
||||
|
||||
@@ -52,6 +52,10 @@ describe('prunable Git-file registration proof', () => {
|
||||
await expect(isPrunableGitFileWorktree(worktree)).resolves.toBe(false)
|
||||
}
|
||||
)
|
||||
it('leaves a vanished marker to existing missing-path recovery', async () => {
|
||||
statPath.mockRejectedValue(Object.assign(new Error('marker vanished'), { code: 'ENOENT' }))
|
||||
await expect(isPrunableGitFileWorktree(worktree)).resolves.toBe(false)
|
||||
})
|
||||
it('does not turn host failure into cleanup permission', async () => {
|
||||
statPath.mockRejectedValue(new Error('host unavailable'))
|
||||
await expect(isPrunableGitFileWorktree(worktree)).rejects.toThrow('host unavailable')
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isENOENT } from './ipc/filesystem-path-containment'
|
||||
import type { GitWorktreeInfo } from '../shared/worktree/types'
|
||||
import type { LocalWorktreeFilesystemOptions } from './local-worktree-filesystem'
|
||||
import { getLocalWorktreePathAccess, toLocalWorktreeRuntimePath } from './local-worktree-filesystem'
|
||||
@@ -20,7 +21,15 @@ export async function isPrunableGitFileWorktree(
|
||||
return false
|
||||
}
|
||||
const access = getLocalWorktreePathAccess(options)
|
||||
const entry = await access.statPath(toLocalWorktreeRuntimePath(worktree.path, options))
|
||||
const entry = await access
|
||||
.statPath(toLocalWorktreeRuntimePath(worktree.path, options))
|
||||
.catch((error: unknown) => {
|
||||
// A vanished marker leaves missing-path recovery to its existing stricter gate.
|
||||
if (isENOENT(error)) {
|
||||
return null
|
||||
}
|
||||
throw error
|
||||
})
|
||||
if (!entry || typeof entry !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user