fix(server): checkout fork PRs into owner branches

This commit is contained in:
Mohamed Boudra
2026-04-22 17:53:53 +07:00
parent 274a7fd217
commit dd98ae9359
6 changed files with 481 additions and 43 deletions
@@ -185,6 +185,53 @@ const GitHubRepoViewSchema = z.object({
.optional(),
});
const PullRequestCheckoutTargetSchema = z.object({
data: z.object({
repository: z.object({
pullRequest: z
.object({
number: z.number(),
baseRefName: z.string().catch(""),
headRefName: z.string().catch(""),
isCrossRepository: z.boolean().catch(false),
headRepositoryOwner: z
.object({
login: z.string().catch(""),
})
.nullable()
.optional(),
headRepository: z
.object({
sshUrl: z.string().nullable().optional(),
url: z.string().nullable().optional(),
})
.nullable()
.optional(),
})
.nullable(),
}),
}),
});
const PULL_REQUEST_CHECKOUT_TARGET_QUERY = `
query PullRequestCheckoutTarget($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
number
baseRefName
headRefName
isCrossRepository
headRepositoryOwner {
login
}
headRepository {
sshUrl
url
}
}
}
}`;
const CURRENT_PR_STATUS_FIELDS =
"number,url,title,state,isDraft,baseRefName,headRefName,mergedAt,statusCheckRollup,reviewDecision,headRepositoryOwner";
@@ -266,6 +313,16 @@ export interface GitHubPullRequestSummary {
updatedAt: string;
}
export interface GitHubPullRequestCheckoutTarget {
number: number;
baseRefName: string;
headRefName: string;
headOwnerLogin: string | null;
headRepositorySshUrl: string | null;
headRepositoryUrl: string | null;
isCrossRepository: boolean;
}
export interface GitHubIssueSummary {
number: number;
title: string;
@@ -417,6 +474,9 @@ export interface GitHubService {
listIssues(options: ListGitHubIssuesOptions): Promise<GitHubIssueSummary[]>;
getPullRequest(options: GetGitHubPullRequestOptions): Promise<GitHubPullRequestSummary>;
getPullRequestHeadRef(options: GetGitHubPullRequestOptions): Promise<string>;
getPullRequestCheckoutTarget?(
options: GetGitHubPullRequestOptions,
): Promise<GitHubPullRequestCheckoutTarget>;
getCurrentPullRequestStatus(
options: {
cwd: string;
@@ -756,6 +816,40 @@ export function createGitHubService(options: CreateGitHubServiceOptions = {}): G
return pullRequest.headRefName;
},
getPullRequestCheckoutTarget(options) {
return cached({
cwd: options.cwd,
method: "getPullRequestCheckoutTarget",
args: { number: options.number },
readOptions: options,
load: async () => {
const repo = await getGitHubRepoView({ cwd: options.cwd, run });
const owner = repo?.owner?.login;
const name = repo?.name;
if (!owner || !name) {
throw new Error("Unable to resolve GitHub repository for pull request checkout");
}
const stdout = await run(
[
"api",
"graphql",
"-f",
`query=${PULL_REQUEST_CHECKOUT_TARGET_QUERY}`,
"-F",
`owner=${owner}`,
"-F",
`name=${name}`,
"-F",
`number=${options.number}`,
],
{ cwd: options.cwd },
);
return parsePullRequestCheckoutTarget(stdout);
},
});
},
getCurrentPullRequestStatus(options) {
return cached({
cwd: options.cwd,
@@ -1364,6 +1458,23 @@ function parsePullRequestSummary(stdout: string): GitHubPullRequestSummary {
return toPullRequestSummary(GitHubPullRequestSummarySchema.parse(JSON.parse(stdout || "{}")));
}
function parsePullRequestCheckoutTarget(stdout: string): GitHubPullRequestCheckoutTarget {
const parsed = PullRequestCheckoutTargetSchema.parse(JSON.parse(stdout || "{}"));
const pullRequest = parsed.data.repository.pullRequest;
if (!pullRequest) {
throw new Error("Pull request not found");
}
return {
number: pullRequest.number,
baseRefName: pullRequest.baseRefName,
headRefName: pullRequest.headRefName,
headOwnerLogin: pullRequest.headRepositoryOwner?.login || null,
headRepositorySshUrl: pullRequest.headRepository?.sshUrl || null,
headRepositoryUrl: pullRequest.headRepository?.url || null,
isCrossRepository: pullRequest.isCrossRepository,
};
}
function toPullRequestSummary(
item: z.infer<typeof GitHubPullRequestSummarySchema>,
): GitHubPullRequestSummary {