From b0435c1ac596affad672ab0b6ffbe2253e253e83 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 08:33:10 -0700 Subject: [PATCH] perf: classify large bulk discard paths safely (#3743) --- src/main/git/status.test.ts | 20 ++++++++++++++++++++ src/main/git/status.ts | 8 +++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/git/status.test.ts b/src/main/git/status.test.ts index 252b23ff608..e8d1aac8249 100644 --- a/src/main/git/status.test.ts +++ b/src/main/git/status.test.ts @@ -210,6 +210,26 @@ describe('bulk git helpers', () => { expect(rmMock).not.toHaveBeenCalled() }) + it('handles large tracked path lists during bulk discard classification', async () => { + const trackedStdout = Array.from({ length: 150_000 }, (_, index) => `docs/file-${index}.ts`) + .join('\0') + .concat('\0') + gitExecFileAsyncMock.mockResolvedValueOnce({ stdout: trackedStdout }).mockResolvedValueOnce({ + stdout: '' + }) + + await bulkDiscardChanges('/repo', ['docs']) + + expect(gitExecFileAsyncMock).toHaveBeenNthCalledWith( + 2, + ['restore', '--worktree', '--source=HEAD', '--', ':(literal)docs'], + { + cwd: '/repo' + } + ) + expect(rmMock).not.toHaveBeenCalled() + }) + it('rejects bulk discard paths that traverse outside the worktree', async () => { await expect(bulkDiscardChanges('/repo', ['src/file.ts', '../outside.txt'])).rejects.toThrow( 'resolves outside the worktree' diff --git a/src/main/git/status.ts b/src/main/git/status.ts index 5ab2c757d83..a72c9141907 100644 --- a/src/main/git/status.ts +++ b/src/main/git/status.ts @@ -1158,7 +1158,13 @@ async function listTrackedPathSpecs( cwd: worktreePath } ) - trackedPaths.push(...stdout.split('\0').filter(Boolean)) + // Why: a tracked directory can contain enough paths for push(...split) + // to exceed the JavaScript argument limit before discard decisions run. + for (const trackedPath of stdout.split('\0')) { + if (trackedPath) { + trackedPaths.push(trackedPath) + } + } } return trackedPaths }