diff --git a/src/main/git/runner-wsl-direct-read.test.ts b/src/main/git/runner-wsl-direct-read.test.ts index 5206f742732..efe98075686 100644 --- a/src/main/git/runner-wsl-direct-read.test.ts +++ b/src/main/git/runner-wsl-direct-read.test.ts @@ -270,6 +270,46 @@ describe('WSL direct Git reads', () => { }) }) + it('keeps the fast path when direct and login Git both report an expected failure', async () => { + await withPlatform('win32', async () => { + seedWslGitReadEnvironmentForTests(DISTRO, LOGIN_ENVIRONMENT) + execFileMock + .mockImplementationOnce((_command, _args, _options, callback) => { + const child = createMockChild() + queueMicrotask(() => + callback?.(Object.assign(new Error('exit 1'), { code: 1 }), '', 'missing ref') + ) + return child + }) + .mockImplementationOnce((_command, _args, _options, callback) => { + const child = createMockChild() + queueMicrotask(() => + callback?.(Object.assign(new Error('exit 1'), { code: 1 }), '', 'missing ref') + ) + return child + }) + .mockImplementationOnce((_command, _args, _options, callback) => { + const child = createMockChild() + queueMicrotask(() => callback?.(null, 'ok', '')) + return child + }) + const options = { + cwd: String.raw`C:\repo`, + preferWslDirectGit: true as const, + wslDistro: DISTRO + } + + await expect(gitExecFileAsync(['rev-parse', '--verify', 'missing'], options)).rejects.toThrow( + 'exit 1' + ) + await gitExecFileAsync(['status', '--short'], options) + + expect(execFileMock.mock.calls[0]?.[1]).toContain('--exec') + expect(execFileMock.mock.calls[1]?.[1]?.slice(3, 5)).toEqual(['sh', '-lc']) + expect(execFileMock.mock.calls[2]?.[1]).toContain('--exec') + }) + }) + it('streams opted-in reads directly', async () => { await withPlatform('win32', async () => { seedWslGitReadEnvironmentForTests(DISTRO, LOGIN_ENVIRONMENT) diff --git a/src/main/git/runner.ts b/src/main/git/runner.ts index 6751bb9b264..0a31d742b13 100644 --- a/src/main/git/runner.ts +++ b/src/main/git/runner.ts @@ -387,13 +387,19 @@ function directWslGitExitCode(error: unknown, resolved: ResolvedCommand): number return typeof code === 'number' ? code : null } -function disableOrInvalidateDirectWslGit(error: unknown, resolved: ResolvedCommand): void { - if (!resolved.wsl) { - return - } - if (isDirectWslGitNotFound(error, resolved)) { +function invalidateMissingDirectWslGit(error: unknown, resolved: ResolvedCommand): boolean { + const isMissing = isDirectWslGitNotFound(error, resolved) + if (isMissing && resolved.wsl) { invalidateWslGitReadEnvironment(resolved.wsl.distro) - } else { + } + return isMissing +} + +function disableDirectWslGitAfterSuccessfulFallback( + wasMissing: boolean, + resolved: ResolvedCommand +): void { + if (!wasMissing && resolved.wsl) { disableWslGitReadEnvironment(resolved.wsl.distro) } } @@ -979,8 +985,10 @@ export async function gitExecFileAsync( result = await capture(resolved) } catch (error) { if (directWslGitExitCode(error, resolved) !== null && !options.signal?.aborted) { - disableOrInvalidateDirectWslGit(error, resolved) + const wasMissing = invalidateMissingDirectWslGit(error, resolved) result = await capture(resolveGitCommand(args, options, true)) + // Why: matching failures can be normal Git control flow; only a successful login retry proves the direct environment was insufficient. + disableDirectWslGitAfterSuccessfulFallback(wasMissing, resolved) const { stdout, stderr } = result return { stdout: stdout as string, stderr: stderr as string } } @@ -1232,9 +1240,11 @@ export async function gitStreamStdout( directWslGitExitCode(error, resolved) !== null && !options.signal?.aborted ) { - disableOrInvalidateDirectWslGit(error, resolved) + const wasMissing = invalidateMissingDirectWslGit(error, resolved) resolved = resolveGitCommandWithoutProbe(args, gitOptions) - return stream(resolved) + const result = await stream(resolved) + disableDirectWslGitAfterSuccessfulFallback(wasMissing, resolved) + return result } throw error }