Files
orca/src/main/git/command-runner/command-exec-file.ts
T
Jinjing 775a932651 fix(git): distinguish binary absence from missing cwd on spawn ENOENT (#20798)
* fix(repos): preserve unknown Git availability

* fix(git): distinguish binary absence from missing cwd on spawn ENOENT

Node reports ENOENT for both a missing git binary and a missing working directory
during spawn. The fix checks specifically for spawn syscall, then verifies the cwd
exists to disambiguate. This prevents reporting "no Git" when the error is actually
a missing working directory. Centralizes probe logic in a reusable function; other
failures cause rejection so callers preserve the unknown status instead of collapsing
to false.
2026-09-15 00:22:15 -07:00

64 lines
2.1 KiB
TypeScript

import { isWindowsBatchScript, resolveWindowsCommand } from '../../win32-utils'
import { isMissingCommandBinaryError } from '../exec-error'
import { resolveCommand, type ResolvedCommand } from './wsl-command-resolution'
import { execFileCapture } from './exec-file-capture'
import { spawnCommandCapture, type CommandExecOptions } from './spawn-command-capture'
function hasPathSeparator(command: string): boolean {
return command.includes('/') || command.includes('\\')
}
function shouldRetryWindowsCommandShim(error: unknown, resolved: ResolvedCommand): boolean {
return (
process.platform === 'win32' &&
resolved.wsl === null &&
isMissingCommandBinaryError(error) &&
!hasPathSeparator(resolved.binary) &&
!/\.[A-Za-z0-9]+$/.test(resolved.binary)
)
}
/**
* Async command execution with the same WSL cwd translation as repo-scoped git.
* Keep this for fixed binary+argv call sites; never pass shell fragments.
*/
export async function commandExecFileAsync(
command: string,
args: string[],
options: CommandExecOptions = {}
): Promise<{ stdout: string; stderr: string }> {
const { wslDistro, ...execOptions } = options
const resolved = resolveCommand(command, args, options.cwd, wslDistro)
const binary =
resolved.wsl === null ? resolveWindowsCommand(resolved.binary, options.env) : resolved.binary
if (isWindowsBatchScript(binary)) {
return spawnCommandCapture(binary, resolved.args, {
...execOptions,
cwd: resolved.cwd
})
}
try {
const { stdout, stderr } = await execFileCapture(binary, resolved.args, {
cwd: resolved.cwd,
encoding: execOptions.encoding ?? 'utf-8',
maxBuffer: execOptions.maxBuffer,
timeout: execOptions.timeout,
env: execOptions.env,
signal: execOptions.signal
})
return { stdout: stdout as string, stderr: stderr as string }
} catch (error) {
if (shouldRetryWindowsCommandShim(error, resolved)) {
return spawnCommandCapture(
resolveWindowsCommand(`${resolved.binary}.cmd`, options.env),
resolved.args,
{
...execOptions,
cwd: resolved.cwd
}
)
}
throw error
}
}