mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
The relay carried its own copies of the worktree-list and unmerged-entry porcelain parsers, and both had drifted from the desktop originals: the relay copy had no `sparse` branch, so SSH sparse checkouts were never marked, and it never C-quote-decoded a conflict path, so a conflicted file with a space or non-ASCII byte was published under its raw quoted name and probed as missing. Move both parsers into src/shared and delete the relay copies, so there is one implementation each. Type the relay's worktree-list plumbing on GitWorktreeInfo instead of Record<string, unknown> so a field-copying step can no longer silently drop a newly parsed field. `isSparse` is a new optional field on the git.listWorktrees result (remote-wire-compatibility Rule 1); Git <2.28 omits the porcelain line and the field stays absent. No new git subcommand or option. Closes #18280
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
import { mkdtemp, rm } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import * as path from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { annotatePrunableWorktreesByExistence } from './git-handler-worktree-list'
|
|
import type { GitWorktreeInfo } from '../shared/worktree/types'
|
|
|
|
function listedWorktree(fields: Partial<GitWorktreeInfo> & { path: string }): GitWorktreeInfo {
|
|
return {
|
|
head: 'abc123',
|
|
branch: 'refs/heads/main',
|
|
isBare: false,
|
|
isMainWorktree: false,
|
|
...fields
|
|
}
|
|
}
|
|
|
|
const tempRoots: string[] = []
|
|
|
|
async function createTempDir(): Promise<string> {
|
|
const root = await mkdtemp(path.join(tmpdir(), 'orca-relay-prunable-'))
|
|
tempRoots.push(root)
|
|
return root
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
|
|
})
|
|
|
|
describe('annotatePrunableWorktreesByExistence', () => {
|
|
it('marks linked worktrees with missing directories as prunable', async () => {
|
|
const liveDir = await createTempDir()
|
|
const missingDir = path.join(liveDir, 'deleted-worktree')
|
|
|
|
const annotated = await annotatePrunableWorktreesByExistence([
|
|
listedWorktree({ path: liveDir, isMainWorktree: true }),
|
|
listedWorktree({ path: path.join(liveDir, 'also-missing-main'), isMainWorktree: true }),
|
|
listedWorktree({ path: liveDir }),
|
|
listedWorktree({ path: missingDir })
|
|
])
|
|
|
|
expect(annotated[0]?.prunable).toBeUndefined()
|
|
// Git never marks the main worktree prunable; repo-level handling owns it.
|
|
expect(annotated[1]?.prunable).toBeUndefined()
|
|
expect(annotated[2]?.prunable).toBeUndefined()
|
|
expect(annotated[3]).toMatchObject({ path: missingDir, prunable: true })
|
|
})
|
|
|
|
it('shields locked registrations, mirroring git prunable semantics', async () => {
|
|
const liveDir = await createTempDir()
|
|
const missingDir = path.join(liveDir, 'deleted-locked-worktree')
|
|
|
|
const annotated = await annotatePrunableWorktreesByExistence([
|
|
listedWorktree({ path: liveDir, isMainWorktree: true }),
|
|
listedWorktree({ path: missingDir, locked: true, lockReason: 'agent session' })
|
|
])
|
|
|
|
expect(annotated[1]?.prunable).toBeUndefined()
|
|
})
|
|
})
|