mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 10:42:45 +00:00
fix(gitea): surface hasHeadBranch on the pr checkout target
resolveForkCheckoutFacts already knew whether a pr had a real head branch (agit flow, or head.ref == the pull ref both mean no). now that signal reaches buildPrLocalBranchName so worktree names and local branches stop being named after the raw pull ref. covers same-repo, fork with owner known, and deleted fork all with no real head branch. added an end to end gitea-service test plus resolver cases since the worktree slug follows localBranchName too.
This commit is contained in:
committed by
paseo-bot[bot]
parent
1dbb838af1
commit
1be237d353
@@ -333,6 +333,106 @@ describe("resolveWorktreeCreationIntent", () => {
|
||||
expect(deps.headRefLookups).toEqual([]);
|
||||
});
|
||||
|
||||
test("names the local branch pr-<number> for a gitea pull-ref-only head", async () => {
|
||||
const deps = createResolverHarness({
|
||||
forge: "gitea",
|
||||
forgeService: {
|
||||
defaultCheckoutRefs: ({ changeRequestNumber }) => [
|
||||
{ remoteName: "origin", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
{ remoteName: "upstream", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
],
|
||||
buildPrLocalBranchName: ({ headRef, checkoutTarget }) =>
|
||||
buildForkLocalBranchName({ headRef, ...checkoutTarget }),
|
||||
getPullRequestCheckoutTarget: async ({ number }) => ({
|
||||
number,
|
||||
baseRefName: "main",
|
||||
headRefName: `refs/pull/${number}/head`,
|
||||
headOwnerLogin: null,
|
||||
headRepositorySshUrl: null,
|
||||
headRepositoryUrl: null,
|
||||
isCrossRepository: false,
|
||||
hasOriginHeadBranch: false,
|
||||
hasHeadBranch: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const intent = await resolveWorktreeCreationIntent(
|
||||
{ action: "checkout", checkoutSource: { kind: "change_request", number: 3 } },
|
||||
repoRoot,
|
||||
deps,
|
||||
);
|
||||
|
||||
expect(intent).toMatchObject({ localBranchName: "pr-3" });
|
||||
expect(intent).not.toHaveProperty("trackOriginHead");
|
||||
});
|
||||
|
||||
test("names the local branch owner/pr-<number> for a gitea fork pull-ref-only head", async () => {
|
||||
const deps = createResolverHarness({
|
||||
forge: "gitea",
|
||||
forgeService: {
|
||||
defaultCheckoutRefs: ({ changeRequestNumber }) => [
|
||||
{ remoteName: "origin", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
{ remoteName: "upstream", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
],
|
||||
buildPrLocalBranchName: ({ headRef, checkoutTarget }) =>
|
||||
buildForkLocalBranchName({ headRef, ...checkoutTarget }),
|
||||
supportsCrossRepoCheckoutWithoutRefs: true,
|
||||
getPullRequestCheckoutTarget: async ({ number }) => ({
|
||||
number,
|
||||
baseRefName: "main",
|
||||
headRefName: `refs/pull/${number}/head`,
|
||||
headOwnerLogin: "contributor",
|
||||
headRepositorySshUrl: "git@gitea.test:contributor/repo.git",
|
||||
headRepositoryUrl: "https://gitea.test/contributor/repo",
|
||||
isCrossRepository: true,
|
||||
hasHeadBranch: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const intent = await resolveWorktreeCreationIntent(
|
||||
{ action: "checkout", checkoutSource: { kind: "change_request", number: 3 } },
|
||||
repoRoot,
|
||||
deps,
|
||||
);
|
||||
|
||||
expect(intent).toMatchObject({ localBranchName: "contributor/pr-3" });
|
||||
});
|
||||
|
||||
test("does not double the pr number for a gitea deleted fork's pull-ref-only head", async () => {
|
||||
const deps = createResolverHarness({
|
||||
forge: "gitea",
|
||||
forgeService: {
|
||||
defaultCheckoutRefs: ({ changeRequestNumber }) => [
|
||||
{ remoteName: "origin", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
{ remoteName: "upstream", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
],
|
||||
buildPrLocalBranchName: ({ headRef, checkoutTarget }) =>
|
||||
buildForkLocalBranchName({ headRef, ...checkoutTarget }),
|
||||
supportsCrossRepoCheckoutWithoutRefs: true,
|
||||
getPullRequestCheckoutTarget: async ({ number }) => ({
|
||||
number,
|
||||
baseRefName: "main",
|
||||
headRefName: `refs/pull/${number}/head`,
|
||||
headOwnerLogin: null,
|
||||
headRepositorySshUrl: null,
|
||||
headRepositoryUrl: null,
|
||||
isCrossRepository: true,
|
||||
hasHeadBranch: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const intent = await resolveWorktreeCreationIntent(
|
||||
{ action: "checkout", checkoutSource: { kind: "change_request", number: 3 } },
|
||||
repoRoot,
|
||||
deps,
|
||||
);
|
||||
|
||||
expect(intent).toMatchObject({ localBranchName: "pr-3" });
|
||||
});
|
||||
|
||||
test("reports unsupported cross-repository checkout targets without a checkout ref", async () => {
|
||||
const deps = createResolverHarness({
|
||||
forge: "gitea",
|
||||
|
||||
@@ -41,6 +41,8 @@ export interface PullRequestCheckoutTarget {
|
||||
isCrossRepository: boolean;
|
||||
/** false when origin has no head branch to track */
|
||||
hasOriginHeadBranch?: boolean;
|
||||
/** false when headRefName is a synthetic pull ref, not a real branch name (agit PRs, deleted branches) */
|
||||
hasHeadBranch?: boolean;
|
||||
}
|
||||
|
||||
export interface PullRequestCheckoutRef {
|
||||
|
||||
@@ -2304,6 +2304,7 @@ describe("createGiteaService", () => {
|
||||
headRepositoryUrl: null,
|
||||
isCrossRepository: false,
|
||||
hasOriginHeadBranch: true,
|
||||
hasHeadBranch: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2341,6 +2342,7 @@ describe("createGiteaService", () => {
|
||||
headRepositoryUrl: "https://gitea.com/contributor/sample-repo",
|
||||
isCrossRepository: true,
|
||||
hasOriginHeadBranch: false,
|
||||
hasHeadBranch: true,
|
||||
});
|
||||
|
||||
expect(calls).toContainEqual(["api", "repos/example-user/sample-repo/pulls/5"]);
|
||||
@@ -2368,9 +2370,34 @@ describe("createGiteaService", () => {
|
||||
],
|
||||
isCrossRepository: false,
|
||||
hasOriginHeadBranch: false,
|
||||
hasHeadBranch: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("names the local branch pr-<number> end to end for an agit pull request", async () => {
|
||||
// the tea CLI reports head as the raw pull ref too when there's no real
|
||||
// branch, same as the Forgejo API's head.ref
|
||||
const { service } = makeService((args) =>
|
||||
args[0] === "api"
|
||||
? ok(
|
||||
JSON.stringify({
|
||||
flow: 1,
|
||||
head: { ref: "refs/pull/5/head", repo: { id: 1 } },
|
||||
base: { repo: { id: 1 } },
|
||||
}),
|
||||
)
|
||||
: ok(JSON.stringify({ ...STATUS_PR_VIEW, head: "refs/pull/5/head" })),
|
||||
);
|
||||
|
||||
const target = await service.getPullRequestCheckoutTarget({ cwd: "/repo", number: 5 });
|
||||
const localBranchName = service.buildPrLocalBranchName?.({
|
||||
headRef: target.headRefName,
|
||||
checkoutTarget: target,
|
||||
});
|
||||
|
||||
expect(localBranchName).toBe("pr-5");
|
||||
});
|
||||
|
||||
it("skips the origin branch fallback when the head branch was deleted", async () => {
|
||||
const { service } = makeService((args) =>
|
||||
args[0] === "api"
|
||||
@@ -2392,6 +2419,7 @@ describe("createGiteaService", () => {
|
||||
],
|
||||
isCrossRepository: false,
|
||||
hasOriginHeadBranch: false,
|
||||
hasHeadBranch: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2412,6 +2440,7 @@ describe("createGiteaService", () => {
|
||||
headOwnerLogin: null,
|
||||
isCrossRepository: true,
|
||||
hasOriginHeadBranch: false,
|
||||
hasHeadBranch: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2450,6 +2479,66 @@ describe("createGiteaService", () => {
|
||||
expect(localBranchName).toBe("contributor/patch-1");
|
||||
});
|
||||
|
||||
it("names the local branch pr-<number> for a same-repo pull-ref-only head", () => {
|
||||
const { service } = makeService(() => ok(""));
|
||||
|
||||
const localBranchName = service.buildPrLocalBranchName?.({
|
||||
headRef: "refs/pull/5/head",
|
||||
checkoutTarget: {
|
||||
number: 5,
|
||||
baseRefName: "main",
|
||||
headRefName: "refs/pull/5/head",
|
||||
headOwnerLogin: null,
|
||||
headRepositorySshUrl: null,
|
||||
headRepositoryUrl: null,
|
||||
isCrossRepository: false,
|
||||
hasHeadBranch: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(localBranchName).toBe("pr-5");
|
||||
});
|
||||
|
||||
it("prefixes pr-<number> with the fork owner for a pull-ref-only head", () => {
|
||||
const { service } = makeService(() => ok(""));
|
||||
|
||||
const localBranchName = service.buildPrLocalBranchName?.({
|
||||
headRef: "refs/pull/5/head",
|
||||
checkoutTarget: {
|
||||
number: 5,
|
||||
baseRefName: "main",
|
||||
headRefName: "refs/pull/5/head",
|
||||
headOwnerLogin: "contributor",
|
||||
headRepositorySshUrl: null,
|
||||
headRepositoryUrl: null,
|
||||
isCrossRepository: true,
|
||||
hasHeadBranch: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(localBranchName).toBe("contributor/pr-5");
|
||||
});
|
||||
|
||||
it("does not double the pr number for a deleted fork's pull-ref-only head", () => {
|
||||
const { service } = makeService(() => ok(""));
|
||||
|
||||
const localBranchName = service.buildPrLocalBranchName?.({
|
||||
headRef: "refs/pull/5/head",
|
||||
checkoutTarget: {
|
||||
number: 5,
|
||||
baseRefName: "main",
|
||||
headRefName: "refs/pull/5/head",
|
||||
headOwnerLogin: null,
|
||||
headRepositorySshUrl: null,
|
||||
headRepositoryUrl: null,
|
||||
isCrossRepository: true,
|
||||
hasHeadBranch: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(localBranchName).toBe("pr-5");
|
||||
});
|
||||
|
||||
it("returns the pull head ref for default checkout refs", () => {
|
||||
const { service } = makeService(() => ok(""));
|
||||
|
||||
|
||||
@@ -2049,6 +2049,7 @@ export function createGiteaService(options: CreateGiteaServiceOptions = {}): For
|
||||
headRepositoryUrl: fork.headRepositoryUrl,
|
||||
isCrossRepository: fork.isCrossRepository,
|
||||
hasOriginHeadBranch: headBranchOnOrigin,
|
||||
hasHeadBranch: fork.hasHeadBranch,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user