Fix PR worktree status lookup

This commit is contained in:
Mohamed Boudra
2026-04-23 20:10:48 +07:00
parent dfc8223bcc
commit 3edf812578
4 changed files with 299 additions and 17 deletions
@@ -1220,6 +1220,78 @@ describe("GitHubService", () => {
]);
});
it("selects the requested fork owner when resolving a scoped PR worktree branch", async () => {
const runner = createScriptedRunner([
{ error: noPullRequestError() },
JSON.stringify([
{
number: 77,
url: "https://github.com/repoOwner/repo/pull/77",
title: "Unrelated fork main branch",
state: "OPEN",
isDraft: false,
baseRefName: "main",
headRefName: "main",
mergedAt: null,
statusCheckRollup: [],
reviewDecision: "REVIEW_REQUIRED",
headRepositoryOwner: { login: "otherForkOwner" },
},
{
number: 345,
url: "https://github.com/repoOwner/repo/pull/345",
title: "Requested fork main branch",
state: "OPEN",
isDraft: false,
baseRefName: "main",
headRefName: "main",
mergedAt: null,
statusCheckRollup: [],
reviewDecision: "REVIEW_REQUIRED",
headRepositoryOwner: { login: "chethanuk" },
},
]),
]);
const service = createGitHubService({
runner: runner.runner,
resolveGhPath: async () => "/usr/bin/gh",
now: () => 100,
});
const status = await service.getCurrentPullRequestStatus({
cwd: "/repo",
headRef: "main",
headRepositoryOwner: "chethanuk",
});
expect(status).toMatchObject({
number: 345,
repoOwner: "repoOwner",
repoName: "repo",
headRefName: "main",
});
expect(runner.calls.map((call) => call.args)).toEqual([
[
"pr",
"view",
"--json",
"number,url,title,state,isDraft,baseRefName,headRefName,mergedAt,statusCheckRollup,reviewDecision,headRepositoryOwner",
],
[
"pr",
"list",
"--state",
"all",
"--head",
"main",
"--json",
"number,url,title,state,isDraft,baseRefName,headRefName,mergedAt,statusCheckRollup,reviewDecision,headRepositoryOwner",
"--limit",
"10",
],
]);
});
it("finds a fork PR in the parent repo when the direct current branch view is unavailable", async () => {
const runner = createScriptedRunner([
{ error: noPullRequestError() },
+39 -16
View File
@@ -481,6 +481,7 @@ export interface GitHubService {
options: {
cwd: string;
headRef: string;
headRepositoryOwner?: string;
} & GitHubReadOptions,
): Promise<GitHubCurrentPullRequestStatus | null>;
getPullRequestTimeline(
@@ -854,12 +855,16 @@ export function createGitHubService(options: CreateGitHubServiceOptions = {}): G
return cached({
cwd: options.cwd,
method: "getCurrentPullRequestStatus",
args: { headRef: options.headRef },
args: {
headRef: options.headRef,
headRepositoryOwner: options.headRepositoryOwner,
},
readOptions: options,
load: async () => {
return resolveCurrentPullRequestView({
cwd: options.cwd,
headRef: options.headRef,
headRepositoryOwner: options.headRepositoryOwner,
run,
});
},
@@ -1281,33 +1286,51 @@ function isNoPullRequestFoundError(error: unknown): boolean {
async function resolveCurrentPullRequestView(options: {
cwd: string;
headRef: string;
headRepositoryOwner?: string;
run: (args: string[], options: GitHubCommandRunnerOptions) => Promise<string>;
}): Promise<GitHubCurrentPullRequestStatus | null> {
const viewCandidate = await tryCurrentPullRequestView(options);
if (viewCandidate && isCandidateForHeadRef(viewCandidate, options.headRef)) {
return viewCandidate.status;
const viewMatch = viewCandidate
? pickPullRequestCandidate({
candidates: [viewCandidate],
headRef: options.headRef,
headRepositoryOwner: options.headRepositoryOwner,
})
: null;
if (viewMatch) {
return viewMatch.status;
}
const repo = await getGitHubRepoView(options);
const forkOwner = repo?.owner?.login;
const parentOwner = repo?.parent?.owner?.login;
const parentName = repo?.parent?.name;
if (!forkOwner || !parentOwner || !parentName) {
return null;
let listHeadRef = options.headRef;
let listRepo: string | undefined;
let headRepositoryOwner = options.headRepositoryOwner;
if (!headRepositoryOwner) {
const repo = await getGitHubRepoView(options);
const forkOwner = repo?.owner?.login;
const parentOwner = repo?.parent?.owner?.login;
const parentName = repo?.parent?.name;
if (!forkOwner || !parentOwner || !parentName) {
return null;
}
listHeadRef = `${forkOwner}:${options.headRef}`;
listRepo = `${parentOwner}/${parentName}`;
headRepositoryOwner = forkOwner;
}
const parentCandidates = await listCurrentPullRequestCandidates({
const candidates = await listCurrentPullRequestCandidates({
cwd: options.cwd,
headRef: `${forkOwner}:${options.headRef}`,
headRef: listHeadRef,
run: options.run,
repo: `${parentOwner}/${parentName}`,
repo: listRepo,
});
const parentMatch = pickPullRequestCandidate({
candidates: parentCandidates,
const match = pickPullRequestCandidate({
candidates,
headRef: options.headRef,
headRepositoryOwner: forkOwner,
headRepositoryOwner,
});
return parentMatch?.status ?? null;
return match?.status ?? null;
}
async function tryCurrentPullRequestView(options: {