mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 16:02:42 +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>
92 lines
3.5 KiB
TypeScript
92 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { isWslDirectGitReadCommand } from './wsl-direct-git-read-commands'
|
|
|
|
describe('isWslDirectGitReadCommand', () => {
|
|
it.each([
|
|
[['status', '--porcelain=v2']],
|
|
[['log', '--oneline', '-n', '10']],
|
|
[['show', ':src/file.ts']],
|
|
[['show', '--end-of-options', 'HEAD:src/file.ts']],
|
|
[['rev-parse', '--show-toplevel']],
|
|
[['ls-files', '-z', '--', 'src']],
|
|
[['check-ignore', '-z', '--stdin']],
|
|
[['config', '--get', 'core.sshCommand']],
|
|
[['config', '--get-regexp', String.raw`^remote\.`]],
|
|
[['remote', 'get-url', 'origin']],
|
|
[['remote']],
|
|
[['remote', '-v']],
|
|
[['remote', 'show', '-n', 'origin']],
|
|
[['symbolic-ref', '--quiet', '--short', 'HEAD']],
|
|
[['worktree', 'list', '--porcelain']],
|
|
[['branch', '--list']],
|
|
[['branch', '--show-current']],
|
|
// Global options precede the subcommand.
|
|
[['-c', 'core.quotepath=off', 'log', '--oneline']],
|
|
[['-C', '/repo', 'rev-parse', 'HEAD']],
|
|
[['--git-dir=/repo/.git', 'ls-tree', 'HEAD']]
|
|
])('routes %j without a shell', (args) => {
|
|
expect(isWslDirectGitReadCommand(args)).toBe(true)
|
|
})
|
|
|
|
// Why these matter most: a write misrouted to the shell-free path loses the
|
|
// credential helpers and ssh-agent the user's login shell sets up.
|
|
it.each([
|
|
[['commit', '-m', 'msg']],
|
|
[['push', 'origin', 'main']],
|
|
[['fetch', '--all']],
|
|
[['pull', '--rebase']],
|
|
[['clone', 'https://example.com/r.git']],
|
|
[['checkout', '-b', 'feature']],
|
|
[['add', '--', 'file.ts']],
|
|
[['worktree', 'add', '/tmp/wt']],
|
|
[['submodule', 'update', '--init']],
|
|
// Same subcommands as above, in their writing form.
|
|
[['config', 'user.email', 'me@example.com']],
|
|
[['config', '--unset', 'core.sshCommand']],
|
|
[['remote', 'add', 'origin', 'https://example.com/r.git']],
|
|
[['remote', 'remove', 'origin']],
|
|
[['remote', 'show', 'origin']],
|
|
[['symbolic-ref', 'HEAD', 'refs/heads/main']],
|
|
[['worktree', 'add', '/tmp/wt']],
|
|
[['branch', '-D', 'feature']],
|
|
[['branch', 'newbranch']],
|
|
[[]]
|
|
])('keeps %j on the login shell', (args) => {
|
|
expect(isWslDirectGitReadCommand(args)).toBe(false)
|
|
})
|
|
|
|
// Why positional: these read markers are matched as the action, not anywhere in
|
|
// argv. Matching loosely routed a write shell-free whenever a positional
|
|
// happened to share a read marker's name.
|
|
it.each([[['worktree', 'remove', 'list']], [['submodule', 'foreach', 'status']]])(
|
|
'keeps %j on the login shell despite a read-shaped positional',
|
|
(args) => {
|
|
expect(isWslDirectGitReadCommand(args)).toBe(false)
|
|
}
|
|
)
|
|
|
|
it.each([[['submodule', 'status']], [['submodule']], [['remote']]])(
|
|
'routes the listing form %j without a shell',
|
|
(args) => {
|
|
expect(isWslDirectGitReadCommand(args)).toBe(true)
|
|
}
|
|
)
|
|
|
|
// Why: a write hidden behind global options must never reach the shell-free
|
|
// route, and an unparsed global form must fall back to the login shell rather
|
|
// than guess. Both directions fail safe.
|
|
it.each([
|
|
[['-c', 'k=v', 'push', 'origin', 'main']],
|
|
[['-C', '/repo', 'commit', '-m', 'x']],
|
|
[['-c', 'k=v', 'fetch', '--all']],
|
|
[['--git-dir=/x', 'push']],
|
|
// Space-separated global values are not parsed; the value reads as the
|
|
// subcommand, which is in no read set, so it stays on the login shell.
|
|
[['--git-dir', '/x', 'log']],
|
|
[['--work-tree', '/x', 'status']],
|
|
[['-c', 'k=v']]
|
|
])('keeps %j on the login shell behind global options', (args) => {
|
|
expect(isWslDirectGitReadCommand(args)).toBe(false)
|
|
})
|
|
})
|