Files
orca/src/main/git/worktree-graph-listing.test.ts
T
OrcaWinandOrcaWin 4687e90a16 [comment audit] Continue main-process comment cleanup (#32946)
* [comment audit] Shorten daemon, hook, and emulator comments [skip ci]

* [comment audit] Shorten VM, conversation, SQLite, and theme-import comments [skip ci]

* [comment audit] Tighten Git execution and source-control comment contracts [skip ci]

* [comment audit] Clarify repository probes and maintenance comments [skip ci]

* [comment audit] Shorten status, diff-cache, and checkout comments [skip ci]

* [comment audit] Clarify worktree lifecycle and WSL routing comments [skip ci]

* [comment audit] Shorten forge identity and review-refresh comments [skip ci]

* [comment audit] Tighten hosted-review client comment contracts [skip ci]

* [comment audit] Clarify GitLab compatibility and review comments [skip ci]

* [comment audit] Tighten hook ownership and watchdog rationale [skip ci]

* [comment audit] Condense host and integration compatibility comments [skip ci]

* [comment audit] Clarify IPC authority and remote-routing rationale [skip ci]

* [comment audit] Finalize file-import and containment comment cleanup

* [comment audit] Fix stale Jira type imports blocking full CI

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-23 16:27:37 -07:00

187 lines
5.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const { gitExecFileAsyncMock, gitExecFileSyncMock, translateWslOutputPathsMock } = vi.hoisted(
() => ({
gitExecFileAsyncMock: vi.fn(),
gitExecFileSyncMock: vi.fn(),
translateWslOutputPathsMock: vi.fn((output: string) => output)
})
)
vi.mock('../../shared/git-worktree-admin', () => ({
annotateWorktreeLocksFromAdmin: async (_repoPath: string, rows: unknown[]) => rows
}))
vi.mock('./runner', () => ({
gitExecFileAsync: gitExecFileAsyncMock,
gitExecFileSync: gitExecFileSyncMock,
translateWslOutputPaths: translateWslOutputPathsMock
}))
import { listWorktreeGraph, WORKTREE_LIST_TIMEOUT_MS } from './worktree'
import { registerWorktreeSuiteHooks } from './worktree-test-harness'
registerWorktreeSuiteHooks()
describe('listWorktreeGraph', () => {
beforeEach(() => {
gitExecFileAsyncMock.mockReset()
gitExecFileSyncMock.mockReset()
translateWslOutputPathsMock.mockClear()
})
it('returns the worktree graph without sparse-checkout annotation probes', async () => {
gitExecFileAsyncMock.mockResolvedValueOnce({
stdout: `worktree /repo
HEAD abc123
branch refs/heads/main
worktree /repo-feature
HEAD def456
branch refs/heads/feature/test
`
})
await expect(listWorktreeGraph('/repo')).resolves.toEqual([
{
path: '/repo',
head: 'abc123',
branch: 'refs/heads/main',
isBare: false,
isMainWorktree: true
},
{
path: '/repo-feature',
head: 'def456',
branch: 'refs/heads/feature/test',
isBare: false,
isMainWorktree: false
}
])
expect(gitExecFileAsyncMock).toHaveBeenCalledTimes(1)
expect(gitExecFileAsyncMock).toHaveBeenCalledWith(['worktree', 'list', '--porcelain', '-z'], {
cwd: '/repo',
timeout: WORKTREE_LIST_TIMEOUT_MS
})
})
it('falls back when older Git rejects worktree list -z with usage exit code', async () => {
gitExecFileAsyncMock
.mockRejectedValueOnce(
Object.assign(new Error('git usage error'), {
code: 129,
stderr: 'usage: git worktree list [<options>]\n'
})
)
.mockResolvedValueOnce({
stdout: `worktree /repo
HEAD abc123
branch refs/heads/main
worktree /repo-main-2
HEAD def456
branch refs/heads/main-2
`
})
await expect(listWorktreeGraph('/repo')).resolves.toEqual([
{
path: '/repo',
head: 'abc123',
branch: 'refs/heads/main',
isBare: false,
isMainWorktree: true
},
{
path: '/repo-main-2',
head: 'def456',
branch: 'refs/heads/main-2',
isBare: false,
isMainWorktree: false,
// The legacy listing probes existence; this mocked linked path is absent.
prunable: true
}
])
expect(gitExecFileAsyncMock.mock.calls).toEqual([
[
['worktree', 'list', '--porcelain', '-z'],
{ cwd: '/repo', timeout: WORKTREE_LIST_TIMEOUT_MS }
],
[['worktree', 'list', '--porcelain'], { cwd: '/repo', timeout: WORKTREE_LIST_TIMEOUT_MS }]
])
})
it('falls back on a localized (non-English) older-Git usage error via exit code 129', async () => {
gitExecFileAsyncMock
.mockRejectedValueOnce(
Object.assign(new Error('git worktree list --porcelain -z'), {
code: 129,
stderr: 'Fehler: Unbekannter Schalter »z«\nAufruf: git worktree list [<Optionen>]\n'
})
)
.mockResolvedValueOnce({
stdout: `worktree /repo
HEAD abc123
branch refs/heads/main
`
})
await expect(listWorktreeGraph('/repo')).resolves.toEqual([
{
path: '/repo',
head: 'abc123',
branch: 'refs/heads/main',
isBare: false,
isMainWorktree: true
}
])
expect(gitExecFileAsyncMock.mock.calls).toEqual([
[
['worktree', 'list', '--porcelain', '-z'],
{ cwd: '/repo', timeout: WORKTREE_LIST_TIMEOUT_MS }
],
[['worktree', 'list', '--porcelain'], { cwd: '/repo', timeout: WORKTREE_LIST_TIMEOUT_MS }]
])
})
it('does not retry without -z for non-usage Git failures', async () => {
gitExecFileAsyncMock.mockRejectedValueOnce(
Object.assign(new Error('git worktree list --porcelain -z'), {
code: 128,
stderr: 'fatal: unable to read tree\n'
})
)
await expect(listWorktreeGraph('/repo')).resolves.toEqual([])
expect(gitExecFileAsyncMock.mock.calls).toEqual([
[
['worktree', 'list', '--porcelain', '-z'],
{ cwd: '/repo', timeout: WORKTREE_LIST_TIMEOUT_MS }
]
])
})
it('returns an empty graph for paths Git reports as non-repositories', async () => {
gitExecFileAsyncMock.mockRejectedValueOnce(new Error('fatal: not a git repository'))
await expect(listWorktreeGraph('/not-a-repo')).resolves.toEqual([])
})
it('lets callers override the default worktree list timeout', async () => {
gitExecFileAsyncMock.mockResolvedValueOnce({
stdout: 'worktree /repo\nHEAD abc123\nbranch refs/heads/main\n'
})
await listWorktreeGraph('/repo', { timeout: 5_000 })
expect(gitExecFileAsyncMock).toHaveBeenCalledWith(['worktree', 'list', '--porcelain', '-z'], {
cwd: '/repo',
timeout: 5_000
})
})
})