refactor(relay): delete the SSH relay server, its build assets and tests (#27215)

* wip: delete the SSH relay server and its build, assets and tests

* wip: drop main/relay plugin conformance; WSL reader lookup test

* fix: drop the emptied relay bench; prune baselines

* test: keep the native halves of tests that also covered the relay

* chore(ci): drop shard timings for the deleted relay server tests

* fix: keep the directory listing budget that darwin terminal names now import

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
OrcaWin
2026-10-10 22:36:42 -07:00
committed by GitHub
co-authored by m4air
parent c2c63e7f9a
commit 2b4b60a87e
512 changed files with 359 additions and 85654 deletions
@@ -4,7 +4,6 @@ import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { runProcess } from '@orca/process-host'
import { quotePosixShell } from '../../shared/wsl-login-shell-command'
import { createGitHandlerRelay } from '../../relay/git-handler-test-harness'
import { gitExecFileAsync, gitSpawnAfterWindowsEnvironmentReady } from './runner'
let root = ''
@@ -73,25 +72,6 @@ async function expectIdentity(identity: string): Promise<void> {
}
describe('network SSH configuration with real Git', () => {
it.each(['native', 'relay'] as const)(
'%s honors repository SSH wrappers on ordinary fetches',
async (host) => {
await git(['config', 'core.sshCommand', sshCommand('configured identity')])
if (host === 'native') {
await expect(gitExecFileAsync(['fetch', 'origin'], { cwd: repo, env })).rejects.toThrow()
} else {
const { dispatcher, handler } = createGitHandlerRelay()
try {
await expect(
dispatcher.callRequest('git.fetch', { worktreePath: repo })
).rejects.toThrow()
} finally {
handler.dispose()
}
}
await expectIdentity('configured identity')
}
)
it('preserves command-line SSH configuration ahead of repository configuration', async () => {
await git(['config', 'core.sshCommand', sshCommand('repository')])
@@ -104,28 +84,6 @@ describe('network SSH configuration with real Git', () => {
await expectIdentity('command-line')
})
it.each(['native', 'relay'] as const)('%s preserves an explicit SSH command', async (host) => {
await git(['config', 'core.sshCommand', sshCommand('repository')])
const command = sshCommand('environment')
if (host === 'native') {
await expect(
gitExecFileAsync(['fetch', 'origin'], {
cwd: repo,
env: { ...env, GIT_SSH_COMMAND: command }
})
).rejects.toThrow()
} else {
vi.stubEnv('GIT_SSH_COMMAND', command)
const { dispatcher, handler } = createGitHandlerRelay()
try {
await expect(dispatcher.callRequest('git.fetch', { worktreePath: repo })).rejects.toThrow()
} finally {
handler.dispose()
}
}
await expectIdentity('environment')
})
it('honors configured global SSH wrappers for streaming clones from a folder', async () => {
await git(['config', '--global', 'core.sshCommand', sshCommand('clone identity')])
const child = await gitSpawnAfterWindowsEnvironmentReady(
@@ -1,36 +1,39 @@
import { readFile, unlink, utimes, writeFile } from 'node:fs/promises'
import { readFile, rm, unlink, utimes, writeFile } from 'node:fs/promises'
import { execFileSync } from 'node:child_process'
import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import * as path from 'node:path'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { runProcess } from '@orca/process-host'
import { gitCommit, gitInit, type MockDispatcher } from '../../../relay/git-handler-test-setup'
import {
createGitHandlerRelay,
createGitTempDir,
removeGitTempDir
} from '../../../relay/git-handler-test-harness'
import type { GitHandler } from '../../../relay/git-handler'
import { isMissingGitBlobPath } from '../../../shared/git-blob-absence'
import { readGitBlobAtIndexPath } from './git-blob-read'
import { getDiff } from './file-diff'
import { invalidateGitReadCaches, settledDiffCache } from './git-read-cache-invalidation'
import { readWorktreeDiffStamp } from './worktree-diff-stamp'
function gitInit(dir: string): void {
execFileSync('git', ['init'], { cwd: dir, stdio: 'pipe' })
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir, stdio: 'pipe' })
execFileSync('git', ['config', 'user.name', 'Test User'], { cwd: dir, stdio: 'pipe' })
}
function gitCommit(dir: string, message: string): void {
execFileSync('git', ['add', '.'], { cwd: dir, stdio: 'pipe' })
execFileSync('git', ['commit', '-m', message, '--allow-empty'], { cwd: dir, stdio: 'pipe' })
}
describe('real Git missing paths and failed blob reads', () => {
let repo: string
let dispatcher: MockDispatcher
let handler: GitHandler
beforeEach(() => {
repo = createGitTempDir()
;({ dispatcher, handler } = createGitHandlerRelay())
repo = mkdtempSync(path.join(tmpdir(), 'orca-real-git-'))
gitInit(repo)
invalidateGitReadCaches()
})
afterEach(async () => {
handler.dispose()
invalidateGitReadCaches()
await removeGitTempDir(repo)
await rm(repo, { recursive: true, force: true })
})
async function git(args: string[], input?: string): Promise<string> {
@@ -39,75 +42,60 @@ describe('real Git missing paths and failed blob reads', () => {
return result.stdout
}
function diff(host: 'native' | 'relay', staged = false): Promise<unknown> {
return host === 'native'
? getDiff(repo, 'file.txt', staged)
: dispatcher.callRequest('git.diff', { worktreePath: repo, filePath: 'file.txt', staged })
function diff(staged = false): Promise<unknown> {
return getDiff(repo, 'file.txt', staged)
}
it.each(['native', 'relay'] as const)(
'does not substitute HEAD for a corrupt index blob on %s',
async (host) => {
await writeFile(path.join(repo, 'file.txt'), 'committed\n')
gitCommit(repo, 'initial')
await writeFile(path.join(repo, 'file.txt'), 'staged\n')
await git(['add', 'file.txt'])
const oid = (await git(['rev-parse', ':file.txt'])).trim()
await writeFile(path.join(repo, 'file.txt'), 'working\n')
const old = new Date(Date.now() - 10_000)
await Promise.all(
['.git/index', 'file.txt'].map((file) => utimes(path.join(repo, file), old, old))
)
const before = await readWorktreeDiffStamp(repo, 'file.txt', true)
await unlink(path.join(repo, '.git', 'objects', oid.slice(0, 2), oid.slice(2)))
expect(await readGitBlobAtIndexPath(repo, 'file.txt')).toMatchObject({
exists: false,
failed: true
})
it('does not substitute HEAD for a corrupt index blob', async () => {
await writeFile(path.join(repo, 'file.txt'), 'committed\n')
gitCommit(repo, 'initial')
await writeFile(path.join(repo, 'file.txt'), 'staged\n')
await git(['add', 'file.txt'])
const oid = (await git(['rev-parse', ':file.txt'])).trim()
await writeFile(path.join(repo, 'file.txt'), 'working\n')
const old = new Date(Date.now() - 10_000)
await Promise.all(
['.git/index', 'file.txt'].map((file) => utimes(path.join(repo, file), old, old))
)
const before = await readWorktreeDiffStamp(repo, 'file.txt', true)
await unlink(path.join(repo, '.git', 'objects', oid.slice(0, 2), oid.slice(2)))
expect(await readGitBlobAtIndexPath(repo, 'file.txt')).toMatchObject({
exists: false,
failed: true
})
expect(await diff(host)).toMatchObject({ originalContent: '', modifiedContent: 'working\n' })
if (host === 'native') {
expect(settledDiffCache.stats().entries).toBe(0)
}
await git(['hash-object', '-w', '--stdin'], 'staged\n')
const after = await readWorktreeDiffStamp(repo, 'file.txt', true)
expect(after?.value).toBe(before?.value)
expect(await diff()).toMatchObject({ originalContent: '', modifiedContent: 'working\n' })
expect(settledDiffCache.stats().entries).toBe(0)
await git(['hash-object', '-w', '--stdin'], 'staged\n')
const after = await readWorktreeDiffStamp(repo, 'file.txt', true)
expect(after?.value).toBe(before?.value)
expect(await diff(host)).toMatchObject({
originalContent: 'staged\n',
modifiedContent: 'working\n'
})
if (host === 'native') {
expect(settledDiffCache.stats().entries).toBe(1)
}
}
)
expect(await diff()).toMatchObject({
originalContent: 'staged\n',
modifiedContent: 'working\n'
})
expect(settledDiffCache.stats().entries).toBe(1)
})
it.each(['native', 'relay'] as const)(
'retains added paths on an unborn branch on %s',
async (host) => {
await writeFile(path.join(repo, 'file.txt'), 'new file\n')
await git(['add', 'file.txt'])
expect(await diff(host, true)).toMatchObject({
originalContent: '',
modifiedContent: 'new file\n'
})
}
)
it('retains added paths on an unborn branch', async () => {
await writeFile(path.join(repo, 'file.txt'), 'new file\n')
await git(['add', 'file.txt'])
expect(await diff(true)).toMatchObject({
originalContent: '',
modifiedContent: 'new file\n'
})
})
it.each(['native', 'relay'] as const)(
'retains additions absent from a valid HEAD tree on %s',
async (host) => {
await writeFile(path.join(repo, 'seed.txt'), 'seed\n')
gitCommit(repo, 'initial')
await writeFile(path.join(repo, 'file.txt'), 'new file\n')
await git(['add', 'file.txt'])
expect(await diff(host, true)).toMatchObject({
originalContent: '',
modifiedContent: 'new file\n'
})
}
)
it('retains additions absent from a valid HEAD tree', async () => {
await writeFile(path.join(repo, 'seed.txt'), 'seed\n')
gitCommit(repo, 'initial')
await writeFile(path.join(repo, 'file.txt'), 'new file\n')
await git(['add', 'file.txt'])
expect(await diff(true)).toMatchObject({
originalContent: '',
modifiedContent: 'new file\n'
})
})
it('recognizes actual index and tree absence without accepting corrupt objects', async () => {
await writeFile(path.join(repo, 'seed.txt'), 'seed\n')
@@ -1,103 +0,0 @@
import { readFile, writeFile } from 'node:fs/promises'
import * as path from 'node:path'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { runProcess } from '@orca/process-host'
import { gitCommit, gitInit, type MockDispatcher } from '../../../relay/git-handler-test-setup'
import {
createGitHandlerRelay,
createGitTempDir,
removeGitTempDir
} from '../../../relay/git-handler-test-harness'
import type { GitHandler } from '../../../relay/git-handler'
import { bulkStageFiles, bulkUnstageFiles } from './staging'
import { bulkDiscardChanges } from './discard-changes'
describe('bulk pathspec stdin on execution hosts', () => {
let repo: string
let dispatcher: MockDispatcher
let handler: GitHandler
beforeEach(() => {
repo = createGitTempDir()
;({ dispatcher, handler } = createGitHandlerRelay())
gitInit(repo)
})
afterEach(async () => {
handler.dispose()
await removeGitTempDir(repo)
})
async function changedFiles(staged: boolean): Promise<string[]> {
const result = await runProcess({
program: 'git',
args: ['diff', ...(staged ? ['--cached'] : []), '--name-only', '-z'],
cwd: repo
})
expect(result.code).toBe(0)
return result.stdout.split('\0').filter(Boolean).sort()
}
function mutate(
host: 'native' | 'relay',
action: 'stage' | 'unstage' | 'discard',
filePaths: string[]
): Promise<unknown> {
if (host === 'relay') {
const methods = {
stage: 'git.bulkStage',
unstage: 'git.bulkUnstage',
discard: 'git.bulkDiscard'
}
return dispatcher.callRequest(methods[action], { worktreePath: repo, filePaths })
}
const operations = {
stage: bulkStageFiles,
unstage: bulkUnstageFiles,
discard: bulkDiscardChanges
}
return operations[action](repo, filePaths)
}
it.each(['native', 'relay'] as const)(
'stages, unstages and discards over 100 literal paths on %s',
async (host) => {
const selected = Array.from({ length: 105 }, (_, index) => `file-${index}.txt`)
selected.push('[k]eep.log', 'space name.txt', '-option.txt')
if (process.platform !== 'win32') {
selected.push('line\nname.txt', ':(magic).txt', 'back\\slash.txt')
}
const allPaths = [...selected, 'keep.log']
await Promise.all(
allPaths.map((filePath) => writeFile(path.join(repo, filePath), 'original\n'))
)
gitCommit(repo, 'initial')
await Promise.all(
allPaths.map((filePath) => writeFile(path.join(repo, filePath), 'modified\n'))
)
await mutate(host, 'stage', [])
expect(await changedFiles(true)).toEqual([])
await mutate(host, 'stage', selected)
expect(await changedFiles(true)).toEqual([...selected].sort())
await mutate(host, 'unstage', [])
expect(await changedFiles(true)).toEqual([...selected].sort())
await mutate(host, 'unstage', selected)
expect(await changedFiles(true)).toEqual([])
await mutate(host, 'discard', [])
expect(await changedFiles(false)).toEqual([...allPaths].sort())
await mutate(host, 'discard', selected)
expect(await changedFiles(false)).toEqual(['keep.log'])
expect(await readFile(path.join(repo, 'keep.log'), 'utf8')).toBe('modified\n')
}
)
it.each(['native', 'relay'] as const)('rejects NUL path injection on %s', async (host) => {
await writeFile(path.join(repo, 'one.txt'), 'original\n')
gitCommit(repo, 'initial')
await writeFile(path.join(repo, 'one.txt'), 'modified\n')
await expect(mutate(host, 'stage', ['one.txt\0:(top)**'])).rejects.toThrow('NUL')
expect(await changedFiles(true)).toEqual([])
})
})
@@ -1,38 +1,36 @@
import { readFile, writeFile } from 'node:fs/promises'
import { readFile, rm, writeFile } from 'node:fs/promises'
import { execFileSync } from 'node:child_process'
import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import * as path from 'node:path'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { runProcess } from '@orca/process-host'
import { gitCommit, gitInit, type MockDispatcher } from '../../../relay/git-handler-test-setup'
import {
createGitHandlerRelay,
createGitTempDir,
removeGitTempDir
} from '../../../relay/git-handler-test-harness'
import type { GitHandler } from '../../../relay/git-handler'
import { bulkUnstageFiles, unstageFile } from './staging'
import { bulkDiscardChanges, discardChanges } from './discard-changes'
const modes = [
{ host: 'native', bulk: false },
{ host: 'native', bulk: true },
{ host: 'relay', bulk: false },
{ host: 'relay', bulk: true }
] as const
function gitInit(dir: string): void {
execFileSync('git', ['init'], { cwd: dir, stdio: 'pipe' })
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir, stdio: 'pipe' })
execFileSync('git', ['config', 'user.name', 'Test User'], { cwd: dir, stdio: 'pipe' })
}
function gitCommit(dir: string, message: string): void {
execFileSync('git', ['add', '.'], { cwd: dir, stdio: 'pipe' })
execFileSync('git', ['commit', '-m', message, '--allow-empty'], { cwd: dir, stdio: 'pipe' })
}
const modes = [{ bulk: false }, { bulk: true }] as const
describe('staging and discard preserve index authority', () => {
let repo: string
let dispatcher: MockDispatcher
let handler: GitHandler
beforeEach(() => {
repo = createGitTempDir()
;({ dispatcher, handler } = createGitHandlerRelay())
repo = mkdtempSync(path.join(tmpdir(), 'orca-real-git-'))
gitInit(repo)
})
afterEach(async () => {
handler.dispose()
await removeGitTempDir(repo)
await rm(repo, { recursive: true, force: true })
})
async function git(args: string[], input?: string): Promise<string> {
@@ -46,19 +44,13 @@ describe('staging and discard preserve index authority', () => {
action: 'unstage' | 'discard',
filePaths: string[]
): Promise<unknown> {
if (mode.host === 'relay') {
return dispatcher.callRequest(
`git.${mode.bulk ? (action === 'unstage' ? 'bulkUnstage' : 'bulkDiscard') : action}`,
{ worktreePath: repo, ...(mode.bulk ? { filePaths } : { filePath: filePaths[0] }) }
)
}
if (mode.bulk) {
return (action === 'unstage' ? bulkUnstageFiles : bulkDiscardChanges)(repo, filePaths)
}
return (action === 'unstage' ? unstageFile : discardChanges)(repo, filePaths[0])
}
it.each(modes)('preserves staged MM content during $host bulk=$bulk discard', async (mode) => {
it.each(modes)('preserves staged MM content during bulk=$bulk discard', async (mode) => {
await writeFile(path.join(repo, 'file.txt'), 'committed\n')
gitCommit(repo, 'initial')
await writeFile(path.join(repo, 'file.txt'), 'staged\n')
@@ -73,7 +65,7 @@ describe('staging and discard preserve index authority', () => {
expect(await git(['diff', '--cached', '--name-only'])).toBe('file.txt\n')
})
it.each(modes)('restores staged additions during $host bulk=$bulk AM discard', async (mode) => {
it.each(modes)('restores staged additions during bulk=$bulk AM discard', async (mode) => {
await writeFile(path.join(repo, 'initial.txt'), 'committed\n')
gitCommit(repo, 'initial')
await writeFile(path.join(repo, 'new.txt'), 'staged addition\n')
@@ -87,7 +79,7 @@ describe('staging and discard preserve index authority', () => {
expect(await git(['diff', '--name-only'])).toBe('')
})
it.each(modes)('unstages selected unborn paths on $host bulk=$bulk', async (mode) => {
it.each(modes)('unstages selected unborn paths on bulk=$bulk', async (mode) => {
await writeFile(path.join(repo, 'selected.txt'), 'selected\n')
await writeFile(path.join(repo, 'keep.txt'), 'keep\n')
await git(['add', '.'])
@@ -98,7 +90,7 @@ describe('staging and discard preserve index authority', () => {
expect(await readFile(path.join(repo, 'selected.txt'), 'utf8')).toBe('selected\n')
})
it.each(['native', 'relay'] as const)('unstages both rename paths on %s', async (host) => {
it('unstages both rename paths', async () => {
await writeFile(path.join(repo, 'old.txt'), 'rename content\n')
await writeFile(path.join(repo, 'keep.txt'), 'keep\n')
gitCommit(repo, 'initial')
@@ -106,32 +98,29 @@ describe('staging and discard preserve index authority', () => {
await writeFile(path.join(repo, 'keep.txt'), 'keep staged\n')
await git(['add', 'keep.txt'])
await mutate({ host, bulk: true }, 'unstage', ['new.txt', 'old.txt'])
await mutate({ bulk: true }, 'unstage', ['new.txt', 'old.txt'])
expect(await git(['diff', '--cached', '--name-only'])).toBe('keep.txt\n')
expect(await git(['ls-files', '-z'])).toBe('keep.txt\0old.txt\0')
expect(await readFile(path.join(repo, 'new.txt'), 'utf8')).toBe('rename content\n')
})
it.each(['native', 'relay'] as const)(
'discards a staged rename after unstaging both paths on %s',
async (host) => {
await writeFile(path.join(repo, 'old.txt'), 'rename content\n')
gitCommit(repo, 'initial')
await git(['mv', 'old.txt', 'new.txt'])
it('discards a staged rename after unstaging both paths', async () => {
await writeFile(path.join(repo, 'old.txt'), 'rename content\n')
gitCommit(repo, 'initial')
await git(['mv', 'old.txt', 'new.txt'])
await mutate({ host, bulk: true }, 'unstage', ['new.txt', 'old.txt'])
await mutate({ host, bulk: true }, 'discard', ['new.txt', 'old.txt'])
await mutate({ bulk: true }, 'unstage', ['new.txt', 'old.txt'])
await mutate({ bulk: true }, 'discard', ['new.txt', 'old.txt'])
expect(await git(['status', '--porcelain'])).toBe('')
expect(await readFile(path.join(repo, 'old.txt'), 'utf8')).toBe('rename content\n')
await expect(readFile(path.join(repo, 'new.txt'), 'utf8')).rejects.toMatchObject({
code: 'ENOENT'
})
}
)
expect(await git(['status', '--porcelain'])).toBe('')
expect(await readFile(path.join(repo, 'old.txt'), 'utf8')).toBe('rename content\n')
await expect(readFile(path.join(repo, 'new.txt'), 'utf8')).rejects.toMatchObject({
code: 'ENOENT'
})
})
it.each(modes)('rejects an unresolved conflict on $host bulk=$bulk', async (mode) => {
it.each(modes)('rejects an unresolved conflict on bulk=$bulk', async (mode) => {
await writeFile(path.join(repo, 'file.txt'), 'base\n')
gitCommit(repo, 'initial')
const blobs = await Promise.all(
@@ -4,8 +4,6 @@ import { tmpdir } from 'node:os'
import * as path from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { getBranchCompare } from './status'
import { branchCompare } from '../../relay/git-handler-ops'
import { gitChangeListArgs, parseGitChangeList } from '../../shared/git-change-list'
const tempRoots: string[] = []
@@ -17,22 +15,12 @@ function git(repo: string, args: string[]): string {
}).trim()
}
function relayCompare(repo: string, baseRef: string) {
return branchCompare(
async (args, cwd) => ({ stdout: git(cwd, args), stderr: '' }),
repo,
baseRef,
async (mergeBase, headOid) =>
parseGitChangeList(git(repo, gitChangeListArgs(mergeBase, headOid)))
)
}
afterEach(async () => {
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
})
describe('getBranchCompare real refs', () => {
it('reports equal commits as empty on native and relay despite staged and unstaged changes', async () => {
it('reports equal commits as empty despite staged and unstaged changes', async () => {
const repo = await mkdtemp(path.join(tmpdir(), 'orca-equal-branch-compare-'))
tempRoots.push(repo)
git(repo, ['init', '-q'])
@@ -46,37 +34,18 @@ describe('getBranchCompare real refs', () => {
git(repo, ['add', 'changes.txt'])
await writeFile(path.join(repo, 'changes.txt'), 'unstaged\n')
for (const result of await Promise.all([
getBranchCompare(repo, 'base'),
relayCompare(repo, 'base')
])) {
expect(result).toEqual({
summary: {
baseRef: 'base',
baseOid: oid,
compareRef: 'feature',
headOid: oid,
mergeBase: oid,
changedFiles: 0,
commitsAhead: 0,
commitsBehind: 0,
status: 'ready'
},
entries: []
})
}
})
it('keeps the merge-base failure for identical blob tips on the relay', async () => {
const repo = await mkdtemp(path.join(tmpdir(), 'orca-blob-branch-compare-'))
tempRoots.push(repo)
git(repo, ['init', '-q'])
await writeFile(path.join(repo, 'blob.txt'), 'not a commit\n')
const oid = git(repo, ['hash-object', '-w', 'blob.txt'])
await writeFile(path.join(repo, '.git', 'HEAD'), `${oid}\n`)
await expect(relayCompare(repo, oid)).resolves.toMatchObject({
summary: { headOid: oid, baseOid: oid, status: 'no-merge-base', mergeBase: null },
expect(await getBranchCompare(repo, 'base')).toEqual({
summary: {
baseRef: 'base',
baseOid: oid,
compareRef: 'feature',
headOid: oid,
mergeBase: oid,
changedFiles: 0,
commitsAhead: 0,
commitsBehind: 0,
status: 'ready'
},
entries: []
})
})
@@ -109,19 +78,14 @@ describe('getBranchCompare real refs', () => {
])
expect(rawOid).not.toBe(peeledOid)
for (const result of await Promise.all([
getBranchCompare(client, 'origin/tagbase'),
relayCompare(client, 'origin/tagbase')
])) {
expect(result.summary).toMatchObject({
baseOid: rawOid,
headOid: peeledOid,
mergeBase: peeledOid,
changedFiles: 0,
commitsAhead: 0,
commitsBehind: 0,
status: 'ready'
})
}
expect((await getBranchCompare(client, 'origin/tagbase')).summary).toMatchObject({
baseOid: rawOid,
headOid: peeledOid,
mergeBase: peeledOid,
changedFiles: 0,
commitsAhead: 0,
commitsBehind: 0,
status: 'ready'
})
})
})
@@ -1,178 +0,0 @@
/**
* Main and relay must publish the same `branchLineTotal` for the same fixture
* repo. Both call sites
* share `src/shared/git-branch-line-total.ts`; this is the test that catches one
* of them wiring it up differently — different flags, a different untracked
* source, a different completeness gate.
*/
import { execFile, execFileSync } from 'node:child_process'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import * as path from 'node:path'
import { promisify } from 'node:util'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { getStatus } from './status'
import type { GitExec } from '../../relay/git-handler-ops'
import { getStatusOp } from '../../relay/git-handler-status-ops'
import type { RelayGitStreamExec } from '../../relay/git-stdout-stream'
import { invalidateGitBranchLineTotalInFlight } from '../../shared/git-branch-line-total'
import { clearGitStatusLineStatsCache } from '../../shared/git-status-line-stats-cache'
const execFileAsync = promisify(execFile)
// Fork point → working tree for the fixture below:
// tracked.txt +2 (branch commit only)
// partial.txt +1 (staged +2 then one of those lines removed unstaged)
// flip.txt 0 (added in the branch commit, removed again in the worktree)
// moved.txt 0 (pure rename)
// fresh.txt +3 (untracked)
// No fixture path here looks like test or generated code, so it is all source.
const EXPECTED_TOTAL = {
added: 6,
removed: 0,
test: { added: 0, removed: 0 },
generated: { added: 0, removed: 0 }
}
// Summing the per-area status rows instead would give this — the wrong answer
// the shared module exists to avoid.
const AREA_ROW_SUM = { added: 5, removed: 2 }
const relayGit: GitExec = async (args, cwd, opts) => {
const { stdout, stderr } = await execFileAsync('git', args, {
cwd,
encoding: 'utf8',
...(opts?.signal ? { signal: opts.signal } : {}),
...(opts?.timeout ? { timeout: opts.timeout } : {})
})
return { stdout, stderr }
}
const relayStreamGit: RelayGitStreamExec = async (args, cwd, options) => {
const { stdout } = await relayGit(args, cwd, {
disableOptionalLocks: options.disableOptionalLocks,
signal: options.signal
})
return { stoppedEarly: options.onStdout(stdout) === true }
}
function runFixtureGit(repo: string, args: string[]): string {
return execFileSync(
'git',
[
'-c',
'user.email=test@test.com',
'-c',
'user.name=Test',
'-c',
'commit.gpgSign=false',
...args
],
{ cwd: repo, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }
).trim()
}
/** Returns the merge-base OID the chip is measured against. */
async function seedParityFixture(repo: string): Promise<string> {
execFileSync('git', ['init', '-q', repo], { stdio: 'pipe' })
await writeFile(path.join(repo, 'tracked.txt'), 'a\nb\n')
await writeFile(path.join(repo, 'partial.txt'), 'one\ntwo\nthree\n')
await writeFile(path.join(repo, 'renamed.txt'), 'stable\n')
await writeFile(path.join(repo, 'flip.txt'), 'p\n')
runFixtureGit(repo, ['add', '.'])
runFixtureGit(repo, ['commit', '-m', 'base'])
const mergeBase = runFixtureGit(repo, ['rev-parse', 'HEAD'])
runFixtureGit(repo, ['checkout', '-q', '-b', 'feature'])
await writeFile(path.join(repo, 'tracked.txt'), 'a\nb\nc\nd\n')
await writeFile(path.join(repo, 'flip.txt'), 'p\nq\n')
runFixtureGit(repo, ['add', '-A'])
runFixtureGit(repo, ['commit', '-m', 'branch commit'])
runFixtureGit(repo, ['mv', 'renamed.txt', 'moved.txt'])
// Staged and unstaged hunks land on the same added lines, so an area sum
// double-counts them.
await writeFile(path.join(repo, 'partial.txt'), 'one\ntwo\nthree\nfoo\nbaz\n')
runFixtureGit(repo, ['add', 'partial.txt'])
await writeFile(path.join(repo, 'partial.txt'), 'one\ntwo\nthree\nfoo\n')
// The branch commit's line, taken back out in the worktree: net zero.
await writeFile(path.join(repo, 'flip.txt'), 'p\n')
await writeFile(path.join(repo, 'fresh.txt'), 'n1\nn2\nn3\n')
return mergeBase
}
function sumAreaRows(entries: readonly { added?: number; removed?: number }[]): {
added: number
removed: number
} {
let added = 0
let removed = 0
for (const entry of entries) {
added += entry.added ?? 0
removed += entry.removed ?? 0
}
return { added, removed }
}
describe('branch line total parity between main and relay', () => {
let repo: string
beforeEach(async () => {
clearGitStatusLineStatsCache()
invalidateGitBranchLineTotalInFlight()
repo = await mkdtemp(path.join(tmpdir(), 'branch-line-total-parity-'))
})
afterEach(async () => {
clearGitStatusLineStatsCache()
invalidateGitBranchLineTotalInFlight()
await rm(repo, { recursive: true, force: true })
})
it('produces identical totals for the same fixture repo', async () => {
const mergeBase = await seedParityFixture(repo)
const mainStatus = await getStatus(repo, { branchLineTotalMergeBase: mergeBase })
clearGitStatusLineStatsCache()
const relayStatus = await getStatusOp(relayGit, relayStreamGit, {
worktreePath: repo,
branchLineTotalMergeBase: mergeBase
})
expect(mainStatus.branchLineTotal).toEqual({ ...EXPECTED_TOTAL, mergeBase })
expect(relayStatus.branchLineTotal).toEqual(mainStatus.branchLineTotal)
// Both sides model the same worktree, so a parity pass on a mismatched
// entry list would be meaningless.
expect(relayStatus.entries.map((entry) => entry.path).sort()).toEqual(
mainStatus.entries.map((entry) => entry.path).sort()
)
})
it('agrees on a number no per-area row sum could produce', async () => {
const mergeBase = await seedParityFixture(repo)
const mainStatus = await getStatus(repo, { branchLineTotalMergeBase: mergeBase })
clearGitStatusLineStatsCache()
const relayStatus = await getStatusOp(relayGit, relayStreamGit, {
worktreePath: repo,
branchLineTotalMergeBase: mergeBase
})
expect(sumAreaRows(mainStatus.entries)).toEqual(AREA_ROW_SUM)
expect(sumAreaRows(relayStatus.entries as { added?: number; removed?: number }[])).toEqual(
AREA_ROW_SUM
)
expect(mainStatus.branchLineTotal).not.toMatchObject(AREA_ROW_SUM)
expect(relayStatus.branchLineTotal).toEqual(mainStatus.branchLineTotal)
})
it('omits the total on both sides when no merge base is requested', async () => {
await seedParityFixture(repo)
const mainStatus = await getStatus(repo)
clearGitStatusLineStatsCache()
const relayStatus = await getStatusOp(relayGit, relayStreamGit, { worktreePath: repo })
expect(Object.hasOwn(mainStatus, 'branchLineTotal')).toBe(false)
expect(Object.hasOwn(relayStatus, 'branchLineTotal')).toBe(false)
})
})
@@ -1,250 +0,0 @@
import { performance } from 'node:perf_hooks'
import { join } from 'node:path'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type * as NodeFsPromises from 'node:fs/promises'
import type * as GitRunner from './runner'
import type { GitExec } from '../../relay/git-handler-ops'
import type { RelayGitStreamExec } from '../../relay/git-stdout-stream'
const { gitStreamStdoutMock, readFileMock } = vi.hoisted(() => ({
gitStreamStdoutMock: vi.fn(),
readFileMock: vi.fn()
}))
vi.mock('node:fs/promises', async (importOriginal) => {
const actual = await importOriginal<typeof NodeFsPromises>()
return { ...actual, readFile: readFileMock }
})
vi.mock('./runner', async (importOriginal) => {
const actual = await importOriginal<typeof GitRunner>()
return { ...actual, gitStreamStdout: gitStreamStdoutMock }
})
import { getStatus } from './status'
import { getStatusOp } from '../../relay/git-handler-status-ops'
const BENCH_DELAY_MS = 25
const BENCH_SAMPLES = 31
const BENCH_WARMUPS = 5
const describeBench = process.env.ORCA_GIT_STATUS_OVERLAP_BENCH === '1' ? describe : describe.skip
type Deferred<T> = {
promise: Promise<T>
resolve: (value: T) => void
}
type BenchmarkResult = {
path: string
medianMs: number
p95Ms: number
minMs: number
maxMs: number
}
function deferred<T>(): Deferred<T> {
let resolve!: (value: T) => void
const promise = new Promise<T>((innerResolve) => {
resolve = innerResolve
})
return { promise, resolve }
}
function wait(delayMs: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, delayMs))
}
function percentile(samples: number[], fraction: number): number {
const sorted = [...samples].sort((a, b) => a - b)
return sorted[Math.ceil(sorted.length * fraction) - 1]!
}
describe('git status conflict-read overlap', () => {
const relayGit: GitExec = async () => ({ stdout: '', stderr: '' })
beforeEach(() => {
readFileMock.mockReset()
gitStreamStdoutMock.mockReset()
readFileMock.mockResolvedValue('gitdir: /repo/.git/worktrees/feature\n')
gitStreamStdoutMock.mockResolvedValue({ stoppedEarly: false })
})
it('starts native status before conflict-marker I/O settles', async () => {
const markerRead = deferred<string>()
const statusStarted = deferred<void>()
readFileMock.mockReturnValue(markerRead.promise)
gitStreamStdoutMock.mockImplementation(async () => {
statusStarted.resolve()
return { stoppedEarly: false }
})
const resultPromise = getStatus('/repo')
await statusStarted.promise
expect(readFileMock).toHaveBeenCalledWith(join('/repo', '.git'), 'utf-8')
markerRead.resolve('gitdir: /repo/.git/worktrees/feature\n')
await expect(resultPromise).resolves.toMatchObject({
entries: [],
conflictOperation: 'unknown'
})
})
it('starts relay status before conflict-marker I/O settles', async () => {
const markerRead = deferred<string>()
const statusStarted = deferred<void>()
readFileMock.mockReturnValue(markerRead.promise)
const relayStreamGit: RelayGitStreamExec = async () => {
statusStarted.resolve()
return { stoppedEarly: false }
}
const resultPromise = getStatusOp(relayGit, relayStreamGit, { worktreePath: '/repo' })
await statusStarted.promise
expect(readFileMock).toHaveBeenCalledWith(join('/repo', '.git'), 'utf-8')
markerRead.resolve('gitdir: /repo/.git/worktrees/feature\n')
await expect(resultPromise).resolves.toMatchObject({
entries: [],
conflictOperation: 'unknown'
})
})
it('observes an early native status rejection while marker I/O remains pending', async () => {
const markerRead = deferred<string>()
readFileMock.mockReturnValue(markerRead.promise)
gitStreamStdoutMock.mockRejectedValue(new Error('status failed first'))
let settled = false
const resultPromise = getStatus('/repo').finally(() => {
settled = true
})
await Promise.resolve()
await Promise.resolve()
expect(settled).toBe(false)
markerRead.resolve('gitdir: /repo/.git/worktrees/feature\n')
await expect(resultPromise).resolves.toMatchObject({
entries: [],
conflictOperation: 'unknown'
})
})
it('keeps a synchronous native status failure fail-soft', async () => {
const statusError = new Error('status threw')
gitStreamStdoutMock.mockImplementation(() => {
throw statusError
})
await expect(getStatus('/repo')).resolves.toMatchObject({
entries: [],
conflictOperation: 'unknown'
})
})
it.each(['throw', 'reject'] as const)('keeps relay %s failures fail-soft', async (mode) => {
const statusError = new Error(`status ${mode}`)
const relayStreamGit = (() => {
if (mode === 'throw') {
throw statusError
}
return Promise.reject(statusError)
}) as RelayGitStreamExec
await expect(
getStatusOp(relayGit, relayStreamGit, { worktreePath: '/repo' })
).resolves.toMatchObject({ entries: [], conflictOperation: 'unknown' })
})
it('rethrows the original relay status failure after cancellation', async () => {
const controller = new AbortController()
const statusError = new Error('cancelled status')
const relayStreamGit: RelayGitStreamExec = async () => {
controller.abort(statusError)
throw statusError
}
await expect(
getStatusOp(
relayGit,
relayStreamGit,
{ worktreePath: '/repo' },
{ signal: controller.signal }
)
).rejects.toBe(statusError)
})
it('surfaces detector errors without waiting for a hung status read', async () => {
const statusStarted = deferred<void>()
const relayStreamGit: RelayGitStreamExec = () => {
statusStarted.resolve()
return new Promise(() => {})
}
const invalidPath = { toString: () => '/repo' }
const resultPromise = getStatusOp(relayGit, relayStreamGit, {
worktreePath: invalidPath
})
const rejection = expect(resultPromise).rejects.toThrow(TypeError)
await statusStarted.promise
await rejection
})
it('keeps detector errors ahead of concurrent status failures', async () => {
const statusError = new Error('status failed too')
const relayStreamGit = (() => {
throw statusError
}) as RelayGitStreamExec
const invalidPath = { toString: () => '/repo' }
const result = getStatusOp(relayGit, relayStreamGit, { worktreePath: invalidPath })
await expect(result).rejects.toBeInstanceOf(TypeError)
})
})
describeBench('git status conflict-read overlap benchmark', () => {
it('measures native and relay orchestration with matched independent latency', async () => {
readFileMock.mockImplementation(async () => {
await wait(BENCH_DELAY_MS)
return 'gitdir: /repo/.git/worktrees/feature\n'
})
gitStreamStdoutMock.mockImplementation(async () => {
await wait(BENCH_DELAY_MS)
return { stoppedEarly: false }
})
const relayGit: GitExec = async () => ({ stdout: '', stderr: '' })
const relayStreamGit: RelayGitStreamExec = async () => {
await wait(BENCH_DELAY_MS)
return { stoppedEarly: false }
}
const cases = [
{ name: 'native', run: () => getStatus('/repo') },
{
name: 'relay',
run: () => getStatusOp(relayGit, relayStreamGit, { worktreePath: '/repo' })
}
]
const results: BenchmarkResult[] = []
for (const benchmarkCase of cases) {
for (let index = 0; index < BENCH_WARMUPS; index += 1) {
await benchmarkCase.run()
}
const samples: number[] = []
for (let index = 0; index < BENCH_SAMPLES; index += 1) {
const startedAt = performance.now()
await benchmarkCase.run()
samples.push(performance.now() - startedAt)
}
results.push({
path: benchmarkCase.name,
medianMs: Number(percentile(samples, 0.5).toFixed(3)),
p95Ms: Number(percentile(samples, 0.95).toFixed(3)),
minMs: Number(Math.min(...samples).toFixed(3)),
maxMs: Number(Math.max(...samples).toFixed(3))
})
}
console.table(results)
expect(results).toHaveLength(2)
}, 10_000)
})
-1
View File
@@ -253,7 +253,6 @@ async function performAddWorktree(
await persistWorktreeCreationBase(worktreePath, branch, effectiveBase, options)
}
// SSH parity: relay's addWorktreeOp (src/relay/git-handler-worktree-ops.ts) mirrors this — change both in lockstep.
// Why: --no-track leaves no upstream until first push; push.autoSetupRemote=true lets a plain
// `git push` create+set origin/<branch> (git >=2.37; older clients ignore it). `--local` on a
// linked worktree writes the shared common-dir config (whole repo) — intentional and idempotent,
@@ -10,7 +10,6 @@ vi.mock('./local-repo-ref-maintenance', () => ({
}))
import { forceDeleteLocalBranch } from './worktree-branch-removal'
import { forceDeletePreservedRelayBranch } from '../../relay/git-handler-branch-cleanup'
const image = process.env.ORCA_GIT_COMPAT_IMAGE
const binary = process.env.ORCA_GIT_COMPAT_BINARY ?? 'git'
@@ -82,10 +81,10 @@ afterEach(async () => {
})
describe('rebase branch reservations with real Git', () => {
it.for(['native', 'relay'] as const)(
it.for(['native'] as const)(
'%s retains auxiliary branches reserved by a paused real rebase --update-refs',
{ timeout: 120_000 },
async (host, context) => {
async (_host, context) => {
await git(['worktree', 'add', '-q', '-b', 'feature', checkout])
await writeFile(join(checkout, 'first'), 'first\n')
await git(['add', 'first'], checkout)
@@ -117,10 +116,7 @@ describe('rebase branch reservations with real Git', () => {
/checked out|used by worktree/
)
const cleanupGit = vi.fn((args: string[], cwd: string) => git(args, cwd))
const remove = () =>
host === 'native'
? forceDeleteLocalBranch(repo, 'auxiliary', expected, cleanupGit)
: forceDeletePreservedRelayBranch(cleanupGit, repo, 'auxiliary', expected)
const remove = () => forceDeleteLocalBranch(repo, 'auxiliary', expected, cleanupGit)
await expect(remove()).rejects.toThrow('checked out in another worktree')
expect(cleanupGit.mock.calls.map(([args]) => args)).toEqual([
['worktree', 'list', '--porcelain']
+1 -164
View File
@@ -1,14 +1,9 @@
import { execFile } from 'node:child_process'
import { mkdir, mkdtemp, readFile, realpath, rm, writeFile } from 'node:fs/promises'
import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { promisify } from 'node:util'
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { GitCapabilityCache } from '../../shared/git-capability-cache'
import { isWorktreeCreatePreparation } from '../../shared/worktree/create-preparation'
import { parseWorktreeList } from '../../shared/git-worktree-porcelain-parser'
import { annotateWorktreeLocksFromAdmin } from '../../shared/git-worktree-admin'
import type { GitExec } from '../../relay/git-handler-ops'
const { runner } = vi.hoisted(() => ({ runner: vi.fn() }))
vi.mock('./runner', () => ({
@@ -18,14 +13,8 @@ vi.mock('./runner', () => ({
}))
vi.mock('./status', () => ({ runWithGitReadCacheInvalidation: (run: () => unknown) => run() }))
import { removeWorktree } from './worktree-removal'
import { forceDeleteLocalBranch } from './worktree-branch-removal'
import { readWorktreeList } from './worktree-list-reader'
import { getBranchConflictKind, getBranchConflictKindViaExec } from './repo-branch-conflict'
import { clearGitCapabilityStateForTests } from './git-capability-state'
import { removeWorktreeOp } from '../../relay/git-handler-worktree-remove'
import { forceDeletePreservedRelayBranch } from '../../relay/git-handler-branch-cleanup'
import { readRelayWorktreeList } from '../../relay/git-handler-worktree-list'
const execFileAsync = promisify(execFile)
const image = process.env.ORCA_GIT_COMPAT_IMAGE
@@ -37,7 +26,6 @@ const dockerUser =
: []
let root = ''
let repo = ''
let checkout = ''
async function git(args: string[], cwd = repo): Promise<{ stdout: string; stderr: string }> {
return image
@@ -66,8 +54,6 @@ async function git(args: string[], cwd = repo): Promise<{ stdout: string; stderr
})
}
const relay: GitExec = (args, cwd) => git(args, cwd)
async function initializeRepo(cwd: string): Promise<void> {
await mkdir(cwd, { recursive: true })
await git(['init', '-q', '-b', 'main'], cwd).catch(() => git(['init', '-q'], cwd))
@@ -92,7 +78,6 @@ beforeAll(async () => {
beforeEach(async () => {
root = await realpath(await mkdtemp(join(tmpdir(), 'orca-worktree-safety-')))
repo = join(root, 'repo')
checkout = join(root, 'checkout')
await initializeRepo(repo)
clearGitCapabilityStateForTests()
runner.mockImplementation((args: string[], options: { cwd?: string }) => git(args, options.cwd))
@@ -103,99 +88,6 @@ afterEach(async () => {
})
describe('worktree safety with the real Git binary', () => {
it.each(['native', 'relay'] as const)(
'%s preserves unpublished submodule objects without implicit force',
async (host) => {
const origin = join(root, 'sub-origin')
await initializeRepo(origin)
await git(['-c', 'protocol.file.allow=always', 'submodule', 'add', origin, 'sub'])
await git(['commit', '-qam', 'submodule'])
await git(['worktree', 'add', '-q', '-b', 'feature', checkout])
await git(['-c', 'protocol.file.allow=always', 'submodule', 'update', '--init'], checkout)
const sub = join(checkout, 'sub')
await git(['checkout', '-qb', 'unpublished'], sub)
await writeFile(join(sub, 'local'), 'unpublished work\n')
await git(['add', 'local'], sub)
await git(
[
'-c',
'user.name=Safety',
'-c',
'user.email=safety@example.invalid',
'-c',
'commit.gpgSign=false',
'commit',
'-qm',
'local'
],
sub
)
const local = (await git(['rev-parse', 'HEAD'], sub)).stdout.trim()
await git(['checkout', '--detach', 'HEAD~1'], sub)
expect(
(await git(['status', '--porcelain', '--ignore-submodules=none'], checkout)).stdout
).toBe('')
const remove = (force: boolean) =>
host === 'native'
? removeWorktree(repo, checkout, force, {
deleteBranch: false,
knownRemovedWorktree: { branch: 'refs/heads/feature', head: '', locked: false }
})
: removeWorktreeOp(
relay,
{ worktreePath: checkout, force, deleteBranch: false },
new GitCapabilityCache()
)
await expect(remove(false)).rejects.toThrow(/submodules cannot be moved or removed/)
expect((await git(['cat-file', '-t', local], sub)).stdout.trim()).toBe('commit')
expect((await git(['worktree', 'list', '--porcelain'])).stdout).toContain(checkout)
await expect(remove(true)).resolves.toEqual({})
await expect(readFile(join(checkout, '.git'))).rejects.toThrow()
},
120_000
)
it.each(['native', 'relay'] as const)(
'%s reads preparation ownership from admin metadata on old porcelain',
async (host) => {
await git(['worktree', 'add', '-q', '--detach', checkout])
const reason = 'orca-create-preparation:v1:12345:retained'
await git(['worktree', 'lock', '--reason', reason, checkout])
const oldList = (await git(['worktree', 'list', '--porcelain'])).stdout
.split('\n')
.filter((line) => !line.startsWith('locked'))
.join('\n')
const capabilities = new GitCapabilityCache()
capabilities.rememberUnsupported('worktree-list-z')
runner.mockImplementation(async (args: string[], options: { cwd?: string }) => {
if (args[0] === 'worktree' && args[1] === 'list') {
if (args.includes('-z')) {
throw Object.assign(new Error("unknown switch `z'"), {
code: 129,
stderr: "error: unknown switch `z'"
})
}
return { stdout: oldList, stderr: '' }
}
return git(args, options.cwd)
})
const entries =
host === 'native'
? await readWorktreeList(repo)
: await readRelayWorktreeList((args, cwd) => runner(args, { cwd }), repo, capabilities)
const prepared = entries.find((entry) => entry.path === checkout)
expect(prepared).toMatchObject({ locked: true, lockReason: reason })
expect(prepared && isWorktreeCreatePreparation(prepared)).toBe(true)
await rm(checkout, { recursive: true })
const annotated = await annotateWorktreeLocksFromAdmin(repo, parseWorktreeList(oldList))
expect(annotated.find((entry) => entry.path === checkout)).toMatchObject({
locked: true,
lockReason: reason
})
},
90_000
)
it.each([
['feature', 'Feature'],
@@ -226,59 +118,4 @@ describe('worktree safety with the real Git binary', () => {
90_000
)
it.each(['native', 'relay'] as const)(
'%s retains detached branches reserved by rebase or bisect',
async (host) => {
await git(['worktree', 'add', '-q', '-b', 'feature', checkout])
const expected = (await git(['rev-parse', 'refs/heads/feature'])).stdout.trim()
await git(['checkout', '--detach'], checkout)
const gitDir = (await git(['rev-parse', '--absolute-git-dir'], checkout)).stdout.trim()
const remove = () =>
host === 'native'
? forceDeleteLocalBranch(repo, 'feature', expected, git)
: forceDeletePreservedRelayBranch(relay, repo, 'feature', expected)
for (const marker of ['rebase-merge/head-name', 'rebase-apply/head-name', 'BISECT_START']) {
const file = join(gitDir, ...marker.split('/'))
await mkdir(join(file, '..'), { recursive: true })
await writeFile(file, marker === 'BISECT_START' ? 'feature\n' : 'refs/heads/feature\n')
if (marker === 'BISECT_START') {
await writeFile(join(gitDir, 'BISECT_LOG'), '')
}
await expect(git(['branch', '-D', 'feature'])).rejects.toThrow(
/checked out|used by worktree/
)
await expect(remove()).rejects.toThrow('checked out in another worktree')
expect((await git(['rev-parse', 'refs/heads/feature'])).stdout.trim()).toBe(expected)
await rm(file)
if (marker === 'BISECT_START') {
await rm(join(gitDir, 'BISECT_LOG'))
}
}
await expect(remove()).resolves.toBeUndefined()
},
120_000
)
it.each(['native', 'relay'] as const)(
'%s restores a deleted ref when detached branch use starts during deletion',
async (host) => {
await git(['worktree', 'add', '-q', '-b', 'feature', checkout])
const expected = (await git(['rev-parse', 'refs/heads/feature'])).stdout.trim()
await git(['checkout', '--detach'], checkout)
const gitDir = (await git(['rev-parse', '--absolute-git-dir'], checkout)).stdout.trim()
const racingGit = async (args: string[], cwd: string) => {
const result = await git(args, cwd)
if (args[0] === 'update-ref' && args[1] === '-d') {
await writeFile(join(gitDir, 'BISECT_START'), 'feature\n')
}
return result
}
const remove = () =>
host === 'native'
? forceDeleteLocalBranch(repo, 'feature', expected, racingGit)
: forceDeletePreservedRelayBranch(racingGit, repo, 'feature', expected)
await expect(remove()).rejects.toThrow('checked out in another worktree')
expect((await git(['rev-parse', 'refs/heads/feature'])).stdout.trim()).toBe(expected)
},
90_000
)
})