Files
orca/src/shared/git-change-list.ts
T
Neil 62451920ed fix(git): preserve SSH review context and worktree ownership (#24945)
* fix(git): preserve SSH arguments and guard background writes

* test(terminal): settle fish fixture startup readiness

* fix(git): preserve bare UNC SSH paths

* fix(git): unescape shell operators in Windows SSH paths

* test(runtime): settle removal writes before fixture cleanup

* test(git): skip optional OpenSSH probe when unavailable

* perf(git): skip equal-tip reads and bound relay discovery

* test(processes): ratchet the removed relay Git spawn

* test(shells): wait for initial zsh output before sending input

* Fix SSH review context and recover Git maintenance cleanup safely

* Keep relay Git compatibility fixtures outside shared client projects

* Fence superseded maintenance and preserve mixed-version session search

* test: model Git child termination and search catalogs

* test: retain catalog authority over history flags
2026-10-03 05:24:35 -07:00

64 lines
2.1 KiB
TypeScript

import type { GitBranchChangeEntry } from './git-diff-compare-types'
import type { GitBranchChangeStatus } from './git-status-types'
import { parseNumstat } from './git-uncommitted-line-stats'
const CHANGE_STATUS: Record<string, GitBranchChangeStatus> = {
A: 'added',
D: 'deleted',
R: 'renamed',
C: 'copied'
}
export function gitChangeListArgs(fromOid: string | null, toOid: string): string[] {
const format = ['--raw', '--numstat', '-z', '-M', '-C']
return fromOid
? ['diff', ...format, fromOid, toOid, '--']
: ['diff-tree', '--root', '--no-commit-id', '-r', ...format, toOid, '--']
}
export function parseGitChangeList(
stdout: string,
format: 'raw' | 'name-status' = 'raw'
): GitBranchChangeEntry[] {
const entries: GitBranchChangeEntry[] = []
let offset = 0
function readField(): string {
const end = stdout.indexOf('\0', offset)
if (end === -1) {
throw new Error('Incomplete Git change record')
}
const value = stdout.slice(offset, end)
offset = end + 1
return value
}
// Raw records precede numstat records; filenames are separate NUL-delimited fields.
while (offset < stdout.length && (format === 'name-status' || stdout[offset] === ':')) {
const header = readField()
const match =
format === 'name-status'
? /^([A-Z])\d*$/.exec(header)
: /^:[0-7]{6} [0-7]{6} [0-9a-f]+ [0-9a-f]+ ([A-Z])\d*$/.exec(header)
if (!match) {
throw new Error('Invalid Git change record')
}
const code = match[1] ?? ''
const firstPath = readField()
const oldPath = code === 'R' || code === 'C' ? firstPath : undefined
const path = oldPath === undefined ? firstPath : readField()
if (!path || oldPath === '') {
throw new Error('Missing Git change path')
}
entries.push({
path,
status: CHANGE_STATUS[code] ?? 'modified',
...(oldPath === undefined ? {} : { oldPath })
})
}
if (offset === stdout.length) {
return entries
}
const statsByPath = parseNumstat(stdout.slice(offset))
return entries.map((entry) => ({ ...entry, ...statsByPath.get(entry.path) }))
}