mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
* fix(git): cache unsupported capabilities per host Old Git worktree, ref-search, and merge-tree fallbacks retried unsupported flags on recurring operations, flooding subprocess traces. Centralize capability probing per native, WSL, and SSH execution host, coalesce concurrent probes, and retry periodically for in-place Git upgrades. * fix(git): recognize real old-Git merge-tree rejection * test(git): enforce real binary compatibility matrix * fix(ci): preserve Git compatibility test ownership * fix(git): retain supported capability state
139 lines
4.6 KiB
TypeScript
139 lines
4.6 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { gitExecFileAsyncMock } = vi.hoisted(() => ({
|
|
gitExecFileAsyncMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('./runner', () => ({
|
|
gitExecFileAsync: gitExecFileAsyncMock,
|
|
gitExecFileSync: vi.fn(),
|
|
translateWslOutputPaths: (output: string) => output
|
|
}))
|
|
|
|
import { clearGitCapabilityStateForTests } from './git-capability-state'
|
|
import { listWorktrees } from './worktree'
|
|
|
|
const WORKTREE_LIST_OUTPUT = `worktree /repo
|
|
HEAD abc123
|
|
branch refs/heads/main
|
|
`
|
|
|
|
describe('worktree Git capabilities', () => {
|
|
beforeEach(() => {
|
|
clearGitCapabilityStateForTests()
|
|
gitExecFileAsyncMock.mockReset()
|
|
})
|
|
|
|
it('does not repeat a known-unsupported -z probe on later scans', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args.includes('-z')) {
|
|
return Promise.reject(
|
|
Object.assign(new Error('git usage error'), {
|
|
code: 129,
|
|
stderr: 'usage: git worktree list [<options>]\n'
|
|
})
|
|
)
|
|
}
|
|
return Promise.resolve({ stdout: WORKTREE_LIST_OUTPUT })
|
|
})
|
|
|
|
await listWorktrees('/repo')
|
|
await listWorktrees('/repo')
|
|
|
|
expect(gitExecFileAsyncMock.mock.calls.map(([args]) => args)).toEqual([
|
|
['worktree', 'list', '--porcelain', '-z'],
|
|
['worktree', 'list', '--porcelain'],
|
|
['worktree', 'list', '--porcelain']
|
|
])
|
|
})
|
|
|
|
it('keeps native and WSL Git capability results separate', async () => {
|
|
gitExecFileAsyncMock.mockImplementation(
|
|
(args: string[], options: { cwd: string; wslDistro?: string }) => {
|
|
if (args.includes('-z') && !options.wslDistro) {
|
|
return Promise.reject(
|
|
Object.assign(new Error('git usage error'), {
|
|
code: 129,
|
|
stderr: 'usage: git worktree list [<options>]\n'
|
|
})
|
|
)
|
|
}
|
|
return Promise.resolve({
|
|
stdout: `worktree ${options.cwd}\nHEAD abc123\nbranch refs/heads/main\n`
|
|
})
|
|
}
|
|
)
|
|
|
|
await listWorktrees('/native-repo')
|
|
await listWorktrees('/wsl-repo', { wslDistro: 'Ubuntu' })
|
|
|
|
expect(
|
|
gitExecFileAsyncMock.mock.calls.map(([args, options]) => ({
|
|
args,
|
|
wslDistro: options.wslDistro
|
|
}))
|
|
).toEqual([
|
|
{ args: ['worktree', 'list', '--porcelain', '-z'], wslDistro: undefined },
|
|
{ args: ['worktree', 'list', '--porcelain'], wslDistro: undefined },
|
|
{ args: ['worktree', 'list', '--porcelain', '-z'], wslDistro: 'Ubuntu' }
|
|
])
|
|
})
|
|
|
|
it('does not repeat a known-unsupported rev-parse --path-format probe', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'worktree') {
|
|
return Promise.resolve({
|
|
stdout: 'worktree /git-store/project.git\nHEAD abc123\nbranch refs/heads/main\n'
|
|
})
|
|
}
|
|
if (args.includes('--path-format=absolute')) {
|
|
return Promise.reject(
|
|
Object.assign(new Error('unknown option: --path-format=absolute'), {
|
|
stderr: 'error: unknown option `path-format=absolute`\n'
|
|
})
|
|
)
|
|
}
|
|
return Promise.resolve({ stdout: '/repo\n/git-store/project.git\n' })
|
|
})
|
|
|
|
await listWorktrees('/repo')
|
|
await listWorktrees('/repo')
|
|
|
|
const revParseCalls = gitExecFileAsyncMock.mock.calls.filter(
|
|
([args]) => (args as string[])[0] === 'rev-parse'
|
|
)
|
|
expect(revParseCalls.map(([args]) => args)).toEqual([
|
|
['rev-parse', '--path-format=absolute', '--show-toplevel', '--git-common-dir'],
|
|
['rev-parse', '--show-toplevel', '--git-common-dir'],
|
|
['rev-parse', '--show-toplevel', '--git-common-dir']
|
|
])
|
|
})
|
|
|
|
it('remembers old Git that echoes --path-format while exiting successfully', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'worktree') {
|
|
return Promise.resolve({
|
|
stdout: 'worktree /git-store/project.git\nHEAD abc123\nbranch refs/heads/main\n'
|
|
})
|
|
}
|
|
if (args.includes('--path-format=absolute')) {
|
|
return Promise.resolve({
|
|
stdout: '--path-format=absolute\n/repo\n/git-store/project.git\n'
|
|
})
|
|
}
|
|
return Promise.resolve({ stdout: '/repo\n/git-store/project.git\n' })
|
|
})
|
|
|
|
await listWorktrees('/repo')
|
|
await listWorktrees('/repo')
|
|
|
|
const revParseCalls = gitExecFileAsyncMock.mock.calls.filter(
|
|
([args]) => (args as string[])[0] === 'rev-parse'
|
|
)
|
|
expect(revParseCalls.map(([args]) => args)).toEqual([
|
|
['rev-parse', '--path-format=absolute', '--show-toplevel', '--git-common-dir'],
|
|
['rev-parse', '--show-toplevel', '--git-common-dir']
|
|
])
|
|
})
|
|
})
|