@@ -5,6 +5,22 @@ import { spawn } from 'node:child_process'
|
||||
|
||||
const delay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
/** Map `fn` over `items` with at most `limit` running at once, preserving order.
|
||||
* Keeps the per-file git/disk reads overlapping without spawning hundreds of
|
||||
* subprocesses at once (macOS has a low default open-file limit). */
|
||||
async function mapLimit<T, R>(items: T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]> {
|
||||
const out = new Array<R>(items.length)
|
||||
let next = 0
|
||||
const worker = async (): Promise<void> => {
|
||||
while (next < items.length) {
|
||||
const i = next++
|
||||
out[i] = await fn(items[i], i)
|
||||
}
|
||||
}
|
||||
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker))
|
||||
return out
|
||||
}
|
||||
|
||||
export type GitStatusLetter = 'A' | 'M' | 'D' | 'R' | 'U'
|
||||
|
||||
export interface GitChange {
|
||||
@@ -193,15 +209,19 @@ async function doLoad(root: string): Promise<GitLoad | null> {
|
||||
if (raw == null) return null
|
||||
|
||||
const { branch, files } = parseStatus(raw)
|
||||
const changes: GitChange[] = []
|
||||
for (const f of files) {
|
||||
// Each changed file needs its HEAD blob (a `git show` spawn) + its disk text.
|
||||
// Done serially this is O(files) subprocess spawns in a row — staging one file
|
||||
// re-reads ALL of them, which is the dominant cost of a reload. Run them with
|
||||
// bounded concurrency instead so the spawns overlap (cap keeps us well under
|
||||
// macOS's low default FD limit). Order is preserved by index.
|
||||
const changes = await mapLimit(files, 12, async (f) => {
|
||||
const { letter, staged } = classify(f.index, f.working)
|
||||
const isNew = f.index === '?' || f.index === 'A'
|
||||
const isDeleted = letter === 'D'
|
||||
const original = isNew ? '' : await headText(root, f.path)
|
||||
const updated = isDeleted ? '' : await diskText(root, f.path)
|
||||
changes.push({ path: f.path, status: letter, staged, original, updated })
|
||||
}
|
||||
return { path: f.path, status: letter, staged, original, updated } as GitChange
|
||||
})
|
||||
|
||||
return { branch, changes }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user