mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 00:02:56 +00:00
92 lines
3.8 KiB
TypeScript
92 lines
3.8 KiB
TypeScript
import {
|
|
commandLineLength,
|
|
MAX_COMMAND_LINE_CHARS
|
|
} from '../../../shared/windows-command-line-budget'
|
|
import { resolveGitCommandWithoutProbe } from '../command-runner/git-command-resolution'
|
|
import type { GitRuntimeOptions } from '../git-runtime-options'
|
|
|
|
/** Ceiling on argv entries per invocation; under WSL the byte budget bites first. */
|
|
const BULK_CHUNK_SIZE = 100
|
|
|
|
/**
|
|
* POSIX hosts have no CreateProcess cap: ARG_MAX is 256KB on macOS and 2MB on
|
|
* Linux, shared with the environment block. Half the macOS floor keeps a native
|
|
* or SSH-host invocation clear of E2BIG without charging it the WSL wrapper's
|
|
* quoting overhead, which is a different transport's problem.
|
|
*/
|
|
const POSIX_COMMAND_LINE_BUDGET = 128_000
|
|
|
|
export function literalPathspec(filePath: string, options: GitRuntimeOptions): string {
|
|
// Why: Git inside WSL needs POSIX paths, but host paths must stay literal, so convert backslashes only for WSL.
|
|
const runtimePath = options.wslDistro ? filePath.replace(/\\/g, '/') : filePath
|
|
return `:(literal)${runtimePath}`
|
|
}
|
|
|
|
/**
|
|
* Length of the line the OS will actually be handed, wrapper included.
|
|
*
|
|
* Why resolve rather than estimate: a WSL-routed write goes through the login
|
|
* shell, which shell-quotes every pathspec, quotes the resulting command line
|
|
* again, and embeds that three times (one branch per guest shell). The finished
|
|
* line runs ~3.4x the raw pathspec bytes, and nothing about the path list says
|
|
* so. Writes never take the direct-git lane, so this is the exact shape they
|
|
* get; a read that does take it resolves shorter, so the estimate stays safe.
|
|
*/
|
|
function finishedCommandLineLength(
|
|
args: readonly string[],
|
|
worktreePath: string,
|
|
options: GitRuntimeOptions
|
|
): number {
|
|
const resolved = resolveGitCommandWithoutProbe([...args], {
|
|
cwd: worktreePath,
|
|
...(options.wslDistro ? { wslDistro: options.wslDistro } : {})
|
|
})
|
|
return commandLineLength([resolved.binary, ...resolved.args])
|
|
}
|
|
|
|
/**
|
|
* Split a bulk pathspec operation into invocations the host can actually spawn.
|
|
*
|
|
* Why a byte budget and not a path count: 100 was chosen against a raw argv, but
|
|
* a WSL-routed `git add` is folded into one login-shell command line, so 100
|
|
* ordinary paths reached ~43,000 characters -- past the 32,767 CreateProcess cap
|
|
* -- and the bulk stage failed with nothing staged. Cost is measured per
|
|
* pathspec through the real resolver so the wrapper's quoting rules live in one
|
|
* place.
|
|
*
|
|
* Chunks split only between whole pathspecs, and a pathspec that alone exceeds
|
|
* the budget still ships alone rather than being dropped or truncated. If an
|
|
* invocation fails partway through, the earlier chunks stay applied: every
|
|
* operation here is idempotent and per-path, so `git status` shows the true
|
|
* state and re-running converges.
|
|
*/
|
|
export function bulkPathspecCommands(
|
|
leadingArgs: readonly string[],
|
|
filePaths: readonly string[],
|
|
worktreePath: string,
|
|
options: GitRuntimeOptions
|
|
): string[][] {
|
|
// Budget belongs to the host that spawns; the overhead measured above belongs to the transport.
|
|
const budget = process.platform === 'win32' ? MAX_COMMAND_LINE_CHARS : POSIX_COMMAND_LINE_BUDGET
|
|
const baseLength = finishedCommandLineLength(leadingArgs, worktreePath, options)
|
|
const commands: string[][] = []
|
|
let pathspecs: string[] = []
|
|
let length = baseLength
|
|
for (const filePath of filePaths) {
|
|
const pathspec = literalPathspec(filePath, options)
|
|
const cost =
|
|
finishedCommandLineLength([...leadingArgs, pathspec], worktreePath, options) - baseLength
|
|
if (pathspecs.length > 0 && (pathspecs.length >= BULK_CHUNK_SIZE || length + cost > budget)) {
|
|
commands.push([...leadingArgs, ...pathspecs])
|
|
pathspecs = []
|
|
length = baseLength
|
|
}
|
|
pathspecs.push(pathspec)
|
|
length += cost
|
|
}
|
|
if (pathspecs.length > 0) {
|
|
commands.push([...leadingArgs, ...pathspecs])
|
|
}
|
|
return commands
|
|
}
|