mirror of
https://github.com/stablyai/orca.git
synced 2026-09-27 08:02:35 +00:00
* perf(worktrees): delete worktree directories after the removal returns `git worktree remove` deleted the whole checkout inline, so the remove IPC held the watcher/PTY gate for the entire recursive delete (prod traces: worktree.remove.git_remove p50 8-14s, p90 29s, max 34.7s). Local removals now rename the checkout into a hidden sibling trash root, clear Git's registration for the missing path, and delete the moved tree in the background. Renames that cannot run (WSL, other volume, Windows open handles) fall back to the previous in-place removal unchanged. * test(worktrees): keep no empty trash root when the rename cannot run * fix(worktrees): harden deferred trash cleanup * fix(worktrees): keep WSL trash on its owning host
86 lines
3.6 KiB
TypeScript
86 lines
3.6 KiB
TypeScript
// Manual benchmark for the `worktree.remove.git_remove` stage on a large checkout.
|
|
// Opt in (it builds ~100k files): ORCA_WORKTREE_REMOVAL_BENCH=1 pnpm exec vitest run \
|
|
// --config config/vitest.config.ts src/main/git/worktree-removal-large-tree.bench.test.ts
|
|
import { execFile } from 'node:child_process'
|
|
import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'
|
|
import { existsSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { promisify } from 'node:util'
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
import { removeWorktree } from './worktree'
|
|
import { getWorktreeTrashRoot, whenWorktreeTrashDeletionsSettled } from '../worktree-trash'
|
|
|
|
const execFileAsync = promisify(execFile)
|
|
const describeBench = process.env.ORCA_WORKTREE_REMOVAL_BENCH ? describe : describe.skip
|
|
|
|
const FIXTURE_DIRECTORIES = 200
|
|
const FIXTURE_FILES_PER_DIRECTORY = 500
|
|
|
|
describeBench('worktree removal on a large checkout', () => {
|
|
let scratchDir = ''
|
|
let repoPath = ''
|
|
let worktreePath = ''
|
|
|
|
async function git(args: string[], cwd: string): Promise<void> {
|
|
await execFileAsync('git', args, { cwd, maxBuffer: 64 * 1024 * 1024 })
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
scratchDir = await mkdtemp(join(tmpdir(), 'orca-worktree-removal-bench-'))
|
|
repoPath = join(scratchDir, 'repo')
|
|
worktreePath = join(scratchDir, 'workspaces', 'repo', 'bench')
|
|
await mkdir(repoPath, { recursive: true })
|
|
await git(['init', '-q', '-b', 'main'], repoPath)
|
|
await git(['config', 'user.email', 'bench@example.invalid'], repoPath)
|
|
await git(['config', 'user.name', 'Bench'], repoPath)
|
|
await writeFile(join(repoPath, 'seed.txt'), 'seed\n')
|
|
await git(['add', 'seed.txt'], repoPath)
|
|
await git(['commit', '-qm', 'seed'], repoPath)
|
|
await mkdir(join(scratchDir, 'workspaces', 'repo'), { recursive: true })
|
|
await git(['worktree', 'add', '-q', worktreePath, '-b', 'bench'], repoPath)
|
|
|
|
// A committed synthetic node_modules-shaped tree: the removal must be clean-checked and deleted.
|
|
for (let dirIndex = 0; dirIndex < FIXTURE_DIRECTORIES; dirIndex += 1) {
|
|
const dir = join(worktreePath, 'node_modules', `pkg-${dirIndex}`)
|
|
await mkdir(dir, { recursive: true })
|
|
await Promise.all(
|
|
Array.from({ length: FIXTURE_FILES_PER_DIRECTORY }, (_unused, fileIndex) =>
|
|
writeFile(join(dir, `file-${fileIndex}.js`), `module.exports = ${fileIndex}\n`)
|
|
)
|
|
)
|
|
}
|
|
await git(['add', '-A'], worktreePath)
|
|
await git(['commit', '-qm', 'large tree'], worktreePath)
|
|
}, 900_000)
|
|
|
|
afterAll(async () => {
|
|
await whenWorktreeTrashDeletionsSettled()
|
|
if (scratchDir) {
|
|
await rm(scratchDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('returns from removeWorktree without waiting for the recursive delete', async () => {
|
|
const startedAt = Date.now()
|
|
await removeWorktree(repoPath, worktreePath, false, { deleteBranch: false })
|
|
const userVisibleMs = Date.now() - startedAt
|
|
|
|
const trashRoot = getWorktreeTrashRoot(worktreePath)
|
|
console.log(
|
|
`[bench] files=${FIXTURE_DIRECTORIES * FIXTURE_FILES_PER_DIRECTORY} removeWorktree=${userVisibleMs}ms`
|
|
)
|
|
expect(existsSync(worktreePath)).toBe(false)
|
|
const { stdout } = await execFileAsync('git', ['worktree', 'list'], { cwd: repoPath })
|
|
expect(stdout).not.toContain(worktreePath)
|
|
|
|
const backgroundStartedAt = Date.now()
|
|
await whenWorktreeTrashDeletionsSettled()
|
|
console.log(
|
|
`[bench] background deletion=${Date.now() - backgroundStartedAt}ms (trash root ${trashRoot})`
|
|
)
|
|
expect(existsSync(trashRoot)).toBe(true)
|
|
expect(await readdir(trashRoot)).toEqual([])
|
|
}, 900_000)
|
|
})
|