fix: preserve large relay bulk discard matches

This commit is contained in:
Neil
2026-05-30 06:11:43 -07:00
parent 84fb0875d1
commit ee8b566942
2 changed files with 36 additions and 3 deletions
+29 -2
View File
@@ -17,17 +17,18 @@ import {
type MockDispatcher,
type RelayDispatcher
} from './git-handler-test-setup'
import type { GitExec } from './git-handler-ops'
describe('GitHandler', () => {
let dispatcher: MockDispatcher
let tmpDir: string
let handler: GitHandler
beforeEach(() => {
tmpDir = mkdtempSync(path.join(tmpdir(), 'relay-git-'))
dispatcher = createMockDispatcher()
const ctx = new RelayContext()
// eslint-disable-next-line no-new
new GitHandler(dispatcher as unknown as RelayDispatcher, ctx)
handler = new GitHandler(dispatcher as unknown as RelayDispatcher, ctx)
})
afterEach(async () => {
@@ -498,6 +499,32 @@ describe('GitHandler', () => {
await expect(fs.access(path.join(tmpDir, 'new.txt'))).rejects.toThrow()
})
it('bulk discards a tracked directory with a very large tracked child list', async () => {
const trackedOutput = `${Array.from(
{ length: 130_000 },
(_, index) => `src/generated/file-${index}.ts`
).join('\0')}\0`
const git = vi.fn<GitExec>(async (args) => {
if (args[0] === 'ls-files') {
return { stdout: trackedOutput, stderr: '' }
}
return { stdout: '', stderr: '' }
})
;(handler as unknown as { git: GitExec }).git = git
await expect(
dispatcher.callRequest('git.bulkDiscard', {
worktreePath: tmpDir,
filePaths: ['src']
})
).resolves.toBeUndefined()
expect(git).toHaveBeenCalledWith(
['restore', '--worktree', '--source=HEAD', '--', ':(literal)src'],
tmpDir
)
})
it('rejects path traversal', async () => {
gitInit(tmpDir)
await expect(
+7 -1
View File
@@ -276,7 +276,13 @@ export class GitHandler {
['ls-files', '-z', '--', ...chunk.map((p) => this.literalPathspec(p))],
worktreePath
)
trackedPathSpecs.push(...stdout.split('\0').filter(Boolean))
// Why: selecting a tracked directory can expand to more child paths than
// V8 allows as function arguments.
for (const trackedPath of stdout.split('\0')) {
if (trackedPath) {
trackedPathSpecs.push(trackedPath)
}
}
}
const trackedPaths = filePaths.filter((filePath) =>