Files
orca/src/shared/git-remote-url-index.ts
Neil 104f9655e4 perf(git): answer remote-URL questions from one subprocess, not one per remote (#18158)
Four copies of the same loop ran `git remote` and then a serial
`git remote get-url <name>` per remote to answer "which remote has this
URL". On a repo with 58 remotes that is 59 subprocesses -- measured at
1083 ms -- for one question, and worktree create asks it several times.
`git remote -v` answers for every remote from one child, reporting the
same insteadOf-expanded first fetch URL `get-url` prints.

The batched `cat-file --batch-check` branch-conflict probe decides from
stdout, but its WSL route was unfenced, so a login-shell fallback printed
the distro banner onto the stream it parses. That broke the
one-line-per-ref contract, made every batch undecided, and fell straight
back to one `show-ref` per remote -- the cost the batch exists to remove.

Measured at 58 remotes / 4346 branches, spawns and wall time:
  push-target remote scan      59 -> 1  (1083 ms -> 8 ms)
  branch-conflict probe        60 -> 3  (984 ms -> 43 ms)
  configured push target      123 -> 6  (2707 ms -> 157 ms)
2026-09-02 12:53:48 -07:00

65 lines
2.5 KiB
TypeScript

// Why: "which remote has this URL?" was answered with one `git remote get-url`
// subprocess per remote, awaited serially -- 58 spawns on a repo with 58 remotes,
// on every push-target resolution. `git remote -v` answers for every remote from
// one child.
//
// `remote -v` is the faithful one-command form, not `config --get-regexp '^remote\.'`:
// both `remote -v` and `remote get-url` print the URL *after* `url.<base>.insteadOf`
// expansion and pick the first of several `remote.<name>.url` values, while raw config
// reads return the unexpanded value and the last of the multiple values.
//
// `remote -v` also predates `remote get-url` (2.7), so this lowers rather than raises
// the Git floor and needs no capability gate.
import { iterateProcessOutputLines } from './process-output-field-scanner'
export type GitRemoteVerboseEntry = {
name: string
url: string
direction: 'fetch' | 'push'
}
// Greedy prefix so a URL containing spaces or parentheses keeps them.
const REMOTE_VERBOSE_URL_PATTERN = /^(.*) \((fetch|push)\)$/
/** Parse one `<name>\t<url> (fetch|push)` row. */
export function parseGitRemoteVerboseLine(line: string): GitRemoteVerboseEntry | null {
const tabIndex = line.indexOf('\t')
if (tabIndex === -1) {
return null
}
const name = line.slice(0, tabIndex)
const match = REMOTE_VERBOSE_URL_PATTERN.exec(line.slice(tabIndex + 1).trim())
return match ? { name, url: match[1], direction: match[2] as 'fetch' | 'push' } : null
}
/**
* Fetch URL per remote in `git remote` order -- the value `git remote get-url <name>`
* prints. A remote configured with only a `pushurl` has no fetch row and is absent
* here; `get-url` echoed the remote's own name for it, which no caller can match.
*/
export function parseGitRemoteFetchUrls(stdout: string): Map<string, string> {
const fetchUrls = new Map<string, string>()
for (const line of iterateProcessOutputLines(stdout)) {
const parsed = parseGitRemoteVerboseLine(line)
// First wins: `get-url` without `--all` prints the first `remote.<name>.url`.
if (parsed?.direction === 'fetch' && !fetchUrls.has(parsed.name)) {
fetchUrls.set(parsed.name, parsed.url)
}
}
return fetchUrls
}
/** First remote whose fetch URL matches, in the order the per-remote scan visited them. */
export function findGitRemoteNameByFetchUrl(
stdout: string,
matchesUrl: (url: string) => boolean
): string | null {
for (const [name, url] of parseGitRemoteFetchUrls(stdout)) {
if (matchesUrl(url)) {
return name
}
}
return null
}