diff --git a/packages/app/src/git/policy.test.ts b/packages/app/src/git/policy.test.ts index efbd61efa6..e5ea1ee509 100644 --- a/packages/app/src/git/policy.test.ts +++ b/packages/app/src/git/policy.test.ts @@ -2,7 +2,12 @@ import { afterEach, describe, expect, it } from "vitest"; import { CheckoutPrStatusSchema } from "@getpaseo/protocol/messages"; import { i18n } from "@/i18n/i18next"; -import { actionsWithoutPrimary, buildGitActions, type BuildGitActionsInput } from "./policy"; +import { + actionsWithoutPrimary, + buildGitActions, + isCurrentBranchBase, + type BuildGitActionsInput, +} from "./policy"; import { deriveMergeCapability, type ForgeSpecificStatusFacts } from "./merge-capability"; type GithubMergeFactsFixture = ForgeSpecificStatusFacts & { @@ -208,6 +213,28 @@ describe("git-actions-policy", () => { ]); }); + it("distinguishes exact local and remote base refs and retains legacy names", () => { + expect(isCurrentBranchBase("release", "refs/heads/release")).toBe(true); + expect(isCurrentBranchBase("release", "refs/remotes/origin/release")).toBe(false); + expect(isCurrentBranchBase("release", "release")).toBe(true); + expect(isCurrentBranchBase("release", "origin/release")).toBe(true); + expect(isCurrentBranchBase(null, "refs/heads/release")).toBe(false); + }); + + it("offers update from the same-named remote base when it is ahead", () => { + const actions = buildGitActions( + createInput({ + isPaseoOwnedWorktree: true, + isOnBaseBranch: isCurrentBranchBase("release", "refs/remotes/origin/release"), + baseRefLabel: "release", + behindBaseCount: 1, + aheadOfOrigin: null, + behindOfOrigin: null, + }), + ); + expect(actions.primary?.id).toBe("merge-from-base"); + }); + it("drops a promoted merge action from the remaining menu actions", () => { const actions = buildGitActions( createInput({ diff --git a/packages/app/src/git/policy.ts b/packages/app/src/git/policy.ts index 71d63eae37..7dcd55d64e 100644 --- a/packages/app/src/git/policy.ts +++ b/packages/app/src/git/policy.ts @@ -50,6 +50,17 @@ interface GitActionRuntimeState { handler: () => void; } +export function isCurrentBranchBase( + currentBranch: string | null | undefined, + baseRef: string | null | undefined, +): boolean { + if (!currentBranch || !baseRef) return false; + if (baseRef.startsWith("refs/heads/")) return baseRef === `refs/heads/${currentBranch}`; + if (baseRef.startsWith("refs/")) return false; + // Older hosts send a display name, which cannot distinguish a local ref from its remote. + return currentBranch === baseRef.replace(/^origin\//, ""); +} + export interface BuildGitActionsInput { isGit: boolean; githubFeaturesEnabled: boolean; diff --git a/packages/app/src/git/use-actions.tsx b/packages/app/src/git/use-actions.tsx index 05e65a21a5..250581598a 100644 --- a/packages/app/src/git/use-actions.tsx +++ b/packages/app/src/git/use-actions.tsx @@ -12,6 +12,7 @@ import { type CheckoutStatusPayload, useCheckoutStatusQuery } from "@/git/use-st import { type CheckoutPrStatusPayload, useCheckoutPrStatusQuery } from "@/git/use-pr-status-query"; import { buildGitActions, + isCurrentBranchBase, narrowPullRequestState, type BuildGitActionsInput, type GitAction, @@ -116,7 +117,7 @@ interface DeriveGitActionsStateArgs { hasUncommittedChanges: boolean; postShipArchiveSuggested: boolean; isStatusLoading: boolean; - baseRefLabel: string; + baseRef: string | undefined; } interface DerivedGitActionsState { @@ -167,7 +168,7 @@ function deriveGitActionsState(args: DeriveGitActionsStateArgs): DerivedGitActio hasUncommittedChanges, postShipArchiveSuggested, isStatusLoading, - baseRefLabel, + baseRef, } = args; const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading; const isPaseoOwnedWorktree = gitStatus?.isPaseoOwnedWorktree ?? false; @@ -178,7 +179,7 @@ function deriveGitActionsState(args: DeriveGitActionsStateArgs): DerivedGitActio hasPullRequest: Boolean(prStatus?.url), hasRemote: gitStatus?.hasRemote ?? false, isPaseoOwnedWorktree, - isOnBaseBranch: gitStatus?.currentBranch === baseRefLabel, + isOnBaseBranch: isCurrentBranchBase(gitStatus?.currentBranch, baseRef), shouldPromoteArchive: computeShouldPromoteArchive({ hasUncommittedChanges, postShipArchiveSuggested, @@ -653,7 +654,7 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use hasUncommittedChanges, postShipArchiveSuggested, isStatusLoading, - baseRefLabel, + baseRef, }); const { actionsDisabled,