+
{icon}
{label}
diff --git a/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts b/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts
index 5e75851f08ad..ac0c9d01d17e 100644
--- a/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts
+++ b/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts
@@ -7,7 +7,9 @@ import {
type PullRequestComment,
type PullRequestDetail,
type PullRequestDetailView,
+ type PullRequestRef,
type PullRequestReviewThread,
+ type RepositoryIdentity,
type ThreadPullRequestLink,
} from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";
@@ -27,6 +29,7 @@ import {
stripPullRequestHandoffReferences,
isPullRequestVerdictStale,
isStackedPullRequestBase,
+ loadingPullRequestCheckoutCommand,
pullRequestPanelContext,
latestPullRequestReviewOutcomes,
newestPullRequestCommitAt,
@@ -94,6 +97,49 @@ describe("pull request checkout commands", () => {
"git fetch 'https://forgejo.local/maria/repo'\\''$(echo nope)' refs/pull/42/head && git checkout -B pulls/42 FETCH_HEAD",
);
});
+
+ const reference = (host?: string): PullRequestRef => ({
+ projectId: ProjectId.make("project-1"),
+ ...(host === undefined ? {} : { host }),
+ repository: "acme/web",
+ number: 42,
+ });
+ const identity = (provider: string, canonicalKey: string): RepositoryIdentity => ({
+ canonicalKey,
+ locator: {
+ source: "git-remote",
+ remoteName: "origin",
+ remoteUrl: "git@github.com:acme/web.git",
+ },
+ provider,
+ });
+
+ it("uses a public host when no repository identity is available", () => {
+ expect(loadingPullRequestCheckoutCommand(reference("github.com"), undefined)).toBe(
+ "gh pr checkout 42",
+ );
+ expect(loadingPullRequestCheckoutCommand(reference("gitlab.com"), null)).toBe(
+ "glab mr checkout 42",
+ );
+ });
+
+ it("uses a matching enterprise identity and rejects an explicit host mismatch", () => {
+ const enterprise = identity("github", "github.example.test/acme/web");
+ expect(loadingPullRequestCheckoutCommand(reference("github.example.test"), enterprise)).toBe(
+ "gh pr checkout 42",
+ );
+ expect(loadingPullRequestCheckoutCommand(reference("github.com"), enterprise)).toBeNull();
+ });
+
+ it("does not infer a number-only command without a trusted provider", () => {
+ expect(loadingPullRequestCheckoutCommand(reference(), undefined)).toBeNull();
+ expect(
+ loadingPullRequestCheckoutCommand(
+ reference("github.com"),
+ identity("gitlab", "gitlab.com/acme/web"),
+ ),
+ ).toBeNull();
+ });
});
const TIMELINE_SOURCE: Pick<
diff --git a/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts b/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts
index 733e314b770f..2ec4863072dc 100644
--- a/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts
+++ b/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts
@@ -140,6 +140,22 @@ export function pullRequestCheckoutCommand(
}
}
+/** Build a checkout command from identity metadata while the detail request is still pending. */
+export function loadingPullRequestCheckoutCommand(
+ reference: PullRequestRef,
+ identity: RepositoryIdentity | null | undefined,
+): string | null {
+ const host = reference.host?.trim().toLowerCase();
+ const provider =
+ identity?.provider ??
+ (host === "github.com" ? "github" : host === "gitlab.com" ? "gitlab" : null);
+ if (provider !== "github" && provider !== "gitlab" && provider !== "azure-devops") return null;
+ if (identity?.provider !== undefined && host && pullRequestHostOf(identity, provider) !== host) {
+ return null;
+ }
+ return pullRequestCheckoutCommand(provider, reference.number, "");
+}
+
/** Activity changes only when the same host resource reports a newer revision. */
export function shouldRefreshPullRequestActivity(
previous: { readonly key: string; readonly updatedAt: string } | null,