mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
* [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>
187 lines
5.2 KiB
TypeScript
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
|
|
})
|
|
})
|
|
})
|