mirror of
https://github.com/stablyai/orca.git
synced 2026-10-28 08:02:54 +00:00
* fix(git): run WSL git reads without a shell WSL-routed git ran through the distro user's interactive login shell for one reason: to inherit their PATH. That shell also runs the distro's rc/motd and writes it to the stdout callers parse, which is why #10917 reports a shell banner breaking GitHub source detection. A shell-free route already existed (`--exec /usr/bin/env PATH=... git`, added for status reads in #13207) but it was opt-in, and only gitStatusReadOptionsForWorktree opted in. Every other read -- remote get-url, config --get, log, show, rev-parse -- took the login shell on every call, so the reported parse never benefited. Classify reads at the resolver instead of at each caller. A read needs nothing the login shell provides, so it takes the direct route without the caller asking. Writes and network operations stay on the login shell: they can depend on credential helpers and ssh-agent that the user's profile sets up. Subcommands that both read and write (config, remote, branch, submodule) require an explicit read flag before they qualify, so `config --get` goes direct while `config user.email x` does not. `git show` blob reads stop forcing the login shell and go direct too. The fence stays on that path: the login shell is still the fallback when the environment probe is cold or rejected, and a banner there would become file content. Behavior note: the first WSL read per distro now also warms the environment probe in the background, so a cold read spawns one extra wsl.exe. Subsequent reads start no shell at all. * fix(git): keep queried WSL remote reads on login shell * style: format WSL runner test * fix(git): match WSL read markers positionally The conditional read markers were matched anywhere after the subcommand, so a positional argument that happened to share a marker's name routed a write shell-free: `worktree remove list` read as a listing, and `submodule foreach status` as a status query -- the latter can run arbitrary commands, including network ones. Split the two kinds of marker apart. `config`/`branch` are flag-marked and still match anywhere; `remote`/`worktree`/`submodule` are action-marked and must match the first non-flag argument. The queried `remote show` rule and the `symbolic-ref` arity rule are unchanged. Neither case is reachable today -- Orca issues no `submodule foreach`, and the worktree paths are its own CLI, not git argv -- but the loose match was the shape of the defect, not those two instances. * test(git): pin read routing behind global options Writes hidden behind -c/-C/--git-dir must not reach the shell-free route, and an unparsed global form must fall back to the login shell rather than guess at the subcommand. Both directions fail safe. --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
/**
|
|
* Decide whether a git invocation is a plain read that can run without a shell.
|
|
*
|
|
* Why: WSL-routed git otherwise goes through the distro user's interactive login
|
|
* shell, purely to inherit their PATH. That shell also runs the distro's rc/motd
|
|
* and writes it to the stdout callers parse. Reads need none of it -- the direct
|
|
* route supplies PATH and HOME explicitly and starts no shell at all.
|
|
*
|
|
* Writes and network operations stay on the login shell: they can depend on
|
|
* credential helpers, ssh-agent and other environment only the user's profile
|
|
* sets up.
|
|
*/
|
|
|
|
// Subcommands that only ever read. `status` is here for completeness; its
|
|
// callers already opted in explicitly.
|
|
const ALWAYS_READ_SUBCOMMANDS = new Set([
|
|
'blame',
|
|
'cat-file',
|
|
'check-ignore',
|
|
'describe',
|
|
'diff',
|
|
'for-each-ref',
|
|
'log',
|
|
'ls-files',
|
|
'ls-tree',
|
|
'merge-base',
|
|
'name-rev',
|
|
'rev-list',
|
|
'rev-parse',
|
|
'show',
|
|
'show-ref',
|
|
'status',
|
|
'var'
|
|
])
|
|
|
|
// Read markers that appear as a flag anywhere after the subcommand.
|
|
const READ_FLAG_SUBCOMMANDS: Record<string, ReadonlySet<string>> = {
|
|
branch: new Set(['--list', '-l', '--show-current', '--contains', '--points-at']),
|
|
config: new Set(['--get', '--get-all', '--get-regexp', '--get-urlmatch', '--list', '-l'])
|
|
}
|
|
|
|
// Read markers that must be the *first non-flag* argument, i.e. the action.
|
|
// Position matters here: matching them anywhere would read `worktree remove list`
|
|
// as a listing, because a worktree may legitimately be named "list".
|
|
const READ_ACTION_SUBCOMMANDS: Record<string, ReadonlySet<string>> = {
|
|
remote: new Set(['get-url']),
|
|
submodule: new Set(['status']),
|
|
worktree: new Set(['list'])
|
|
}
|
|
|
|
// Subcommands whose action-less form only lists (`git remote`, `git submodule`).
|
|
const BARE_FORM_IS_READ = new Set(['remote', 'submodule'])
|
|
|
|
/** Leading `-c key=value` / `--git-dir=...` style options precede the subcommand. */
|
|
function findSubcommandIndex(args: readonly string[]): number {
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const arg = args[index]
|
|
if (arg === '-c' || arg === '-C') {
|
|
index += 1
|
|
continue
|
|
}
|
|
if (arg.startsWith('-')) {
|
|
continue
|
|
}
|
|
return index
|
|
}
|
|
return -1
|
|
}
|
|
|
|
export function isWslDirectGitReadCommand(args: readonly string[]): boolean {
|
|
const subcommandIndex = findSubcommandIndex(args)
|
|
if (subcommandIndex === -1) {
|
|
return false
|
|
}
|
|
const subcommand = args[subcommandIndex]
|
|
if (ALWAYS_READ_SUBCOMMANDS.has(subcommand)) {
|
|
return true
|
|
}
|
|
const rest = args.slice(subcommandIndex + 1)
|
|
|
|
if (subcommand === 'symbolic-ref') {
|
|
if (rest.some((arg) => arg === '-d' || arg === '--delete' || arg === '-m')) {
|
|
return false
|
|
}
|
|
// Reading takes one ref; a second positional is the value being written.
|
|
return rest.filter((arg) => arg !== '--' && !arg.startsWith('-')).length <= 1
|
|
}
|
|
|
|
const readActions = READ_ACTION_SUBCOMMANDS[subcommand]
|
|
if (readActions) {
|
|
const action = rest.find((arg) => !arg.startsWith('-'))
|
|
if (!action) {
|
|
return BARE_FORM_IS_READ.has(subcommand)
|
|
}
|
|
// `remote show` queries the transport unless -n is given, so the queried
|
|
// form has to keep the profile's SSH and credential setup.
|
|
if (subcommand === 'remote' && action === 'show') {
|
|
return rest.includes('-n')
|
|
}
|
|
return readActions.has(action)
|
|
}
|
|
|
|
const readFlags = READ_FLAG_SUBCOMMANDS[subcommand]
|
|
return Boolean(readFlags && rest.some((arg) => readFlags.has(arg.split('=')[0])))
|
|
}
|