Files
orca/src/main/git/upstream.ts
T
Neil eb3f6838af perf: coalesce git upstream status reads (#11697)
* perf: coalesce git upstream status reads

* fix(git): repair upstream lease key imports and guard its field list

The read owner imported the shared/types barrel deleted by #14447, and its
push-target key hand-enumerated fields, so a new GitPushTarget field would
silently share a lease between two different targets. The destructure now
fails to compile if a field is added. Lease tests moved into their own file
after #14728 split ssh-git-provider.test.ts.

* test(git): enforce native upstream coalescing in CI

The 10-caller benchmark only runs under ORCA_GIT_UPSTREAM_COALESCING_BENCH_JSON,
so nothing in CI failed when the native/WSL lease was bypassed. Route status.ts
through invalidateGitUpstreamStatusReads so the export has a production caller.
2026-08-15 23:24:05 -07:00

96 lines
3.5 KiB
TypeScript

import type { GitUpstreamStatus } from '../../shared/git-status-types'
import type { GitPushTarget } from '../../shared/worktree/types'
import { upstreamOnlyCommitsArePatchEquivalent } from '../../shared/git-upstream-status'
import { isNoUpstreamError, normalizeGitErrorMessage } from '../../shared/git-remote-error'
import { getEffectiveGitUpstreamStatus } from '../../shared/git-effective-upstream'
import { getPublishTargetStatus } from '../../shared/git-publish-target-status'
import { gitExecFileAsync } from './runner'
import { validateGitPushTarget } from './push-target-validation'
import { nativeAndWslGitUpstreamStatusReadOwner } from './git-upstream-status-read-owner'
type GitExecOptions = {
wslDistro?: string
}
export function invalidateGitUpstreamStatusReads(): void {
nativeAndWslGitUpstreamStatusReadOwner.invalidate()
}
function gitExecOptions(
cwd: string,
options: GitExecOptions = {}
): { cwd: string; wslDistro?: string } {
return options.wslDistro ? { cwd, wslDistro: options.wslDistro } : { cwd }
}
async function getBehindCommitsArePatchEquivalent(
worktreePath: string,
upstreamName: string,
options: GitExecOptions = {}
): Promise<boolean> {
try {
const { stdout } = await gitExecFileAsync(
['log', '--oneline', '--cherry-mark', '--right-only', `HEAD...${upstreamName}`, '--'],
gitExecOptions(worktreePath, options)
)
return upstreamOnlyCommitsArePatchEquivalent(stdout)
} catch {
// Why: patch-equivalence is an optimization for the rebase case. If the
// probe fails, keep the conservative pull-first behavior.
return false
}
}
async function readUpstreamStatus(
worktreePath: string,
pushTarget?: GitPushTarget,
options: GitExecOptions = {}
): Promise<GitUpstreamStatus> {
try {
if (pushTarget) {
const target = await validateGitPushTarget(worktreePath, pushTarget, options)
return await getPublishTargetStatus(
(args) => gitExecFileAsync(args, gitExecOptions(worktreePath, options)),
target,
(upstreamName) => getBehindCommitsArePatchEquivalent(worktreePath, upstreamName, options)
)
}
return await getEffectiveGitUpstreamStatus(
(args) => gitExecFileAsync(args, gitExecOptions(worktreePath, options)),
(upstreamName) => getBehindCommitsArePatchEquivalent(worktreePath, upstreamName, options)
)
} catch (error) {
// Why: we only swallow clearly-no-upstream signals — that's an expected
// state, not a failure. Other errors (auth, corruption, "not a git
// repository", sparse-checkout) should surface to the user so they can
// act on them. The shared isNoUpstreamError helper intentionally omits
// broad phrases like "no such branch" to avoid masking real errors.
if (isNoUpstreamError(error)) {
return {
hasUpstream: false,
ahead: 0,
behind: 0
}
}
// Why: parity with gitPush/gitPull/gitFetch — normalize before crossing
// the IPC boundary so renderers don't see execFile stderr preambles or local paths.
throw new Error(normalizeGitErrorMessage(error, 'upstream'))
}
}
export function getUpstreamStatus(
worktreePath: string,
pushTarget?: GitPushTarget,
options: GitExecOptions = {}
): Promise<GitUpstreamStatus> {
const executionIdentity = options.wslDistro
? ({ kind: 'wsl', distro: options.wslDistro } as const)
: ({ kind: 'native' } as const)
return nativeAndWslGitUpstreamStatusReadOwner.read(
executionIdentity,
worktreePath,
pushTarget,
() => readUpstreamStatus(worktreePath, pushTarget, options)
)
}