perf(forge): compare from the recorded base while confirming the head

Status snapshots carry the base commit the host recorded with the head
(baseSha). ForgeService.diff passes it to the provider when the latest
status names the requested head, and forge-github starts the compare read
from it while its GraphQL read confirms the head; a stale head or another
base cancels and joins the early read.
This commit is contained in:
Yichen Jiang committed 2026-11-07 16:22:24 +08:00
1 parent d8d6081f44
commit 9b456b4e93
18 files changed
+152 -39

No files matched your search

@@ -32,7 +32,7 @@ const THREAD_FIELDS = 'pageInfo { hasNextPage endCursor } nodes { isResolved }'
function pullRequestFields(threads: number): string {
return `id number url title state isDraft updatedAt closedAt
author { login }
headRefName headRefOid baseRefName additions deletions mergeable mergeStateStatus reviewDecision
headRefName headRefOid baseRefName baseRefOid additions deletions mergeable mergeStateStatus reviewDecision
reviewThreads(first: ${threads}) { ${THREAD_FIELDS} }
commits(last: 1) { nodes { commit { statusCheckRollup { id state contexts(first: ${PAGE_SIZE}) {
${CHECK_FIELDS}
@@ -132,6 +132,7 @@ const pullRequestSchema = z.object({
headRefName: z.string(),
headRefOid: z.string(),
baseRefName: z.string(),
baseRefOid: z.string().refine(isCommit),
additions: z.number().int().nonnegative(),
deletions: z.number().int().nonnegative(),
mergeable: z.string(),
@@ -466,6 +467,7 @@ function toSnapshot(
headBranch: node.headRefName,
headSha: node.headRefOid,
baseBranch: node.baseRefName,
baseSha: node.baseRefOid,
additions: node.additions,
deletions: node.deletions,
mergeability: mergeability(node.mergeable),
@@ -159,24 +159,44 @@ export class GitHubProvider implements ForgeProvider {
* current head and the base commit GitHub recorded with it; a head other
* than `headSha` returns `stale`. Otherwise the compare endpoint reads the
* patch from the merge base of that base commit and `headSha`, as the pull
* request's own diff does, with both ends fixed.
* request's own diff does, with both ends fixed. Given the `base` the latest
* status recorded with `headSha`, the comparison from it runs while the
* GraphQL read confirms the head; a stale head or another base commit
* cancels that comparison and waits for it to end before the read returns.
*/
async diff(ref: ChangeRequestRef, headSha: string, signal: AbortSignal): Promise<string | ForgeFailure> {
async diff(ref: ChangeRequestRef, headSha: string, signal: AbortSignal, base?: string): Promise<string | ForgeFailure> {
const { host, owner, name } = checkedRepository(ref)
const failure = this.hostFailure(host)
if (failure !== undefined) return failure
if (!isCommit(headSha)) throw new Error(`forge-github: invalid head commit ${JSON.stringify(headSha)}`)
const output = await this.read([
'api', 'graphql', '--hostname', host,
'-f', `query=${PULL_REQUEST_COMMITS_QUERY}`, '-f', `owner=${owner}`, '-f', `name=${name}`, '-F', `number=${ref.number}`,
], signal)
if (typeof output !== 'string') return output
const commits = mapPullRequestCommitsResponse(parseJson(output))
if (commits === undefined) return { code: 'unavailable', message: 'unexpected GitHub response to the pull request commit read' }
if (commits.head.toLowerCase() !== headSha.toLowerCase()) {
return { code: 'stale', message: `the pull request head moved from ${headSha} to ${commits.head}` }
if (base !== undefined && !isCommit(base)) throw new Error(`forge-github: invalid base commit ${JSON.stringify(base)}`)
const compare = async (from: string, operation: AbortSignal): Promise<string | ForgeFailure> =>
await readComparison(async (args, inner) => await this.read(args, inner), ref.repository, from, headSha, operation)
const speculation = new AbortController()
const early = base === undefined ? undefined : { base, patch: compare(base, AbortSignal.any([signal, speculation.signal])) }
// The early comparison is observed here, so a cancelled or unused one never rejects unhandled.
const settled = early?.patch.then(() => undefined, () => undefined)
const discard = async (): Promise<void> => {
speculation.abort()
await settled
}
try {
const output = await this.read([
'api', 'graphql', '--hostname', host,
'-f', `query=${PULL_REQUEST_COMMITS_QUERY}`, '-f', `owner=${owner}`, '-f', `name=${name}`, '-F', `number=${ref.number}`,
], signal)
if (typeof output !== 'string') return output
const commits = mapPullRequestCommitsResponse(parseJson(output))
if (commits === undefined) return { code: 'unavailable', message: 'unexpected GitHub response to the pull request commit read' }
if (commits.head.toLowerCase() !== headSha.toLowerCase()) {
return { code: 'stale', message: `the pull request head moved from ${headSha} to ${commits.head}` }
}
if (early?.base.toLowerCase() === commits.base.toLowerCase()) return await early.patch
await discard()
return await compare(commits.base, signal)
} finally {
await discard()
}
return await readComparison(async (args, operation) => await this.read(args, operation), ref.repository, commits.base, headSha, signal)
}
/** Read one file at a commit through the contents API in its raw form. */