mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
* feat(worktree): show checkout progress while a worktree is created A local create spends most of its time in the checkout `git worktree add` performs, and the card only said "Creating worktree…" for that whole time. Git already writes its "Updating files: N%" meter into our stderr pipe, so the add call reads it passively (same command line and environment) and the create card shows "Checking out files… N%" with a thin bar. All six create labels are translated so a create never switches language mid-flight. Rebuilt on main from the version stacked on the create-gating branch. On main a create can also claim a prepared checkout; that path never runs `git worktree add`, so it keeps today's "Creating worktree…" label with no bar, and a missed claim falls back to the plain add, which shows the bar. * test(worktree): pin that finalizing a prepared checkout reports no checkout progress Claiming a prepared checkout never runs `git worktree add`, so the create card keeps its label with no bar; this pins that at the finalize step, including the retarget `reset --hard`. * test(worktree): keep only the checkout-progress tests that pin a contract Drops unit tests that mirror the implementation; the real-git flow, the parser captures, the cross-version Git contract and the create card test remain. * test(worktree): pin the checkout meter's path from git to the create card * test(worktree): merge the narrow-terminal capture's pipe chunks
70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
/** One `Title: NN% (x/y)` record from git's stderr progress meter. */
|
|
export type GitProgressRecord = {
|
|
percent: number
|
|
completed: number
|
|
total: number
|
|
/** The final `, done.` record. */
|
|
done: boolean
|
|
}
|
|
|
|
const COUNTERS = /^\s*(\d+)% \((\d+)\/(\d+)\)(, done\.)?/
|
|
// Why: git redraws with a bare `\r` and only ends a line when done, so a stream
|
|
// without separators is never progress; keeping its tail bounds the buffer.
|
|
const MAX_PENDING_CHARS = 256
|
|
|
|
/**
|
|
* Reads git's progress meter for one title out of stderr chunks. Records may
|
|
* span chunks; everything else on stderr is ignored.
|
|
*/
|
|
export function createGitProgressRecordReader(
|
|
title: string,
|
|
onRecord: (record: GitProgressRecord) => void
|
|
): (chunk: string) => void {
|
|
const prefix = `${title}:`
|
|
let pending = ''
|
|
// Why: when the title and counters are wider than COLUMNS (80 by default in
|
|
// a pipe), git prints the title once on its own line and then bare counters.
|
|
let titleOnOwnLine = false
|
|
|
|
const readLine = (line: string): void => {
|
|
if (line === '') {
|
|
return
|
|
}
|
|
if (line.startsWith(prefix)) {
|
|
const counters = line.slice(prefix.length)
|
|
titleOnOwnLine = counters.trim() === ''
|
|
readCounters(counters)
|
|
return
|
|
}
|
|
if (titleOnOwnLine && !readCounters(line)) {
|
|
titleOnOwnLine = false
|
|
}
|
|
}
|
|
|
|
const readCounters = (text: string): boolean => {
|
|
const match = COUNTERS.exec(text)
|
|
if (!match) {
|
|
return false
|
|
}
|
|
const total = Number(match[3])
|
|
if (total <= 0) {
|
|
return false
|
|
}
|
|
onRecord({
|
|
percent: Math.min(100, Number(match[1])),
|
|
completed: Number(match[2]),
|
|
total,
|
|
done: match[4] !== undefined
|
|
})
|
|
return true
|
|
}
|
|
|
|
return (chunk) => {
|
|
const lines = (pending + chunk).split(/\r\n|\r|\n/)
|
|
pending = (lines.pop() ?? '').slice(-MAX_PENDING_CHARS)
|
|
for (const line of lines) {
|
|
readLine(line)
|
|
}
|
|
}
|
|
}
|