Hide local merge for unsupported remote bases

This commit is contained in:
Mohamed Boudra
2026-11-03 08:02:04 +01:00
parent 40c44ba796
commit a9b2a7b6ed
3 changed files with 39 additions and 24 deletions
+25 -13
View File
@@ -6,7 +6,7 @@ import {
actionsWithoutPrimary,
buildGitActions,
isCurrentBranchBase,
isCurrentBranchLocalMergeTarget,
canMergeIntoLocalBase,
type BuildGitActionsInput,
} from "./policy";
import { deriveMergeCapability, type ForgeSpecificStatusFacts } from "./merge-capability";
@@ -78,7 +78,7 @@ function createInput(
hasRemote: false,
isPaseoOwnedWorktree: false,
isOnBaseBranch: true,
isOnLocalMergeTarget: false,
canMergeLocally: true,
hasUncommittedChanges: false,
baseRefAvailable: true,
baseRefLabel: "main",
@@ -221,9 +221,9 @@ describe("git-actions-policy", () => {
expect(isCurrentBranchBase("release", "release")).toBe(true);
expect(isCurrentBranchBase("release", "origin/release")).toBe(true);
expect(isCurrentBranchBase(null, "refs/heads/release")).toBe(false);
expect(isCurrentBranchLocalMergeTarget("release", "refs/heads/release")).toBe(true);
expect(isCurrentBranchLocalMergeTarget("feature", "refs/remotes/origin/release")).toBe(false);
expect(isCurrentBranchLocalMergeTarget("release", "refs/remotes/upstream/release")).toBe(false);
expect(canMergeIntoLocalBase("release", "refs/heads/release")).toBe(false);
expect(canMergeIntoLocalBase("feature", "refs/remotes/origin/release")).toBe(true);
expect(canMergeIntoLocalBase("release", "refs/remotes/upstream/release")).toBe(false);
});
it("offers local merge when the selected local base starts with origin slash", () => {
@@ -231,10 +231,7 @@ describe("git-actions-policy", () => {
createInput({
isPaseoOwnedWorktree: true,
isOnBaseBranch: isCurrentBranchBase("release", "refs/heads/origin/release"),
isOnLocalMergeTarget: isCurrentBranchLocalMergeTarget(
"release",
"refs/heads/origin/release",
),
canMergeLocally: canMergeIntoLocalBase("release", "refs/heads/origin/release"),
baseRefLabel: "origin/release",
aheadCount: 1,
shipDefault: "merge",
@@ -243,15 +240,30 @@ describe("git-actions-policy", () => {
expect(actions.primary?.id).toBe("merge-branch");
});
it("offers update but not local merge for a non-origin remote base", () => {
const actions = buildGitActions(
createInput({
isPaseoOwnedWorktree: true,
isOnBaseBranch: false,
canMergeLocally: canMergeIntoLocalBase("feature", "refs/remotes/upstream/release"),
baseRefLabel: "upstream/release",
aheadCount: 1,
behindBaseCount: 1,
aheadOfOrigin: null,
behindOfOrigin: null,
shipDefault: "merge",
}),
);
expect(actions.primary?.id).toBe("merge-from-base");
expect(actionsWithoutPrimary(actions).map((action) => action.id)).not.toContain("merge-branch");
});
it("does not offer merging into the current local branch when the remote base shares its name", () => {
const actions = buildGitActions(
createInput({
isPaseoOwnedWorktree: true,
isOnBaseBranch: false,
isOnLocalMergeTarget: isCurrentBranchLocalMergeTarget(
"release",
"refs/remotes/origin/release",
),
canMergeLocally: canMergeIntoLocalBase("release", "refs/remotes/origin/release"),
baseRefLabel: "release",
aheadCount: 1,
behindBaseCount: 1,
+8 -5
View File
@@ -61,13 +61,16 @@ export function isCurrentBranchBase(
return currentBranch === baseRef.replace(/^origin\//, "");
}
export function isCurrentBranchLocalMergeTarget(
export function canMergeIntoLocalBase(
currentBranch: string | null | undefined,
baseRef: string | null | undefined,
): boolean {
if (!currentBranch || !baseRef) return false;
if (baseRef.startsWith("refs/remotes/") && !baseRef.startsWith("refs/remotes/origin/")) {
return false;
}
const localTarget = baseRef.replace(/^(?:refs\/heads\/|refs\/remotes\/origin\/|origin\/)/, "");
return currentBranch === localTarget;
return currentBranch !== localTarget;
}
export interface BuildGitActionsInput {
@@ -88,7 +91,7 @@ export interface BuildGitActionsInput {
hasRemote: boolean;
isPaseoOwnedWorktree: boolean;
isOnBaseBranch: boolean;
isOnLocalMergeTarget: boolean;
canMergeLocally: boolean;
hasUncommittedChanges: boolean;
baseRefAvailable: boolean;
baseRefLabel: string;
@@ -370,7 +373,7 @@ function getPrimaryActionId(input: BuildGitActionsInput): GitActionId | null {
if (input.shipDefault === "pr" && canUsePullRequestActionAsShipDefault(input)) {
return "pr";
}
if (!input.isOnBaseBranch && !input.isOnLocalMergeTarget && input.aheadCount > 0) {
if (!input.isOnBaseBranch && input.canMergeLocally && input.aheadCount > 0) {
return "merge-branch";
}
if (!input.isOnBaseBranch && canMergeFromBase(input)) {
@@ -401,7 +404,7 @@ function getPullRequestActionIds(filter: {
function getFeatureActionIds(input: BuildGitActionsInput): GitActionId[] {
return [
"merge-from-base",
...(!input.isOnLocalMergeTarget ? (["merge-branch"] as const) : []),
...(input.canMergeLocally ? (["merge-branch"] as const) : []),
...getPullRequestActionIds({ roles: ["status", "direct", "auto"], input }),
];
}
+6 -6
View File
@@ -13,7 +13,7 @@ import { type CheckoutPrStatusPayload, useCheckoutPrStatusQuery } from "@/git/us
import {
buildGitActions,
isCurrentBranchBase,
isCurrentBranchLocalMergeTarget,
canMergeIntoLocalBase,
narrowPullRequestState,
type BuildGitActionsInput,
type GitAction,
@@ -131,7 +131,7 @@ interface DerivedGitActionsState {
hasRemote: boolean;
isPaseoOwnedWorktree: boolean;
isOnBaseBranch: boolean;
isOnLocalMergeTarget: boolean;
canMergeLocally: boolean;
shouldPromoteArchive: boolean;
}
@@ -182,7 +182,7 @@ function deriveGitActionsState(args: DeriveGitActionsStateArgs): DerivedGitActio
hasRemote: gitStatus?.hasRemote ?? false,
isPaseoOwnedWorktree,
isOnBaseBranch: isCurrentBranchBase(gitStatus?.currentBranch, baseRef),
isOnLocalMergeTarget: isCurrentBranchLocalMergeTarget(gitStatus?.currentBranch, baseRef),
canMergeLocally: canMergeIntoLocalBase(gitStatus?.currentBranch, baseRef),
shouldPromoteArchive: computeShouldPromoteArchive({
hasUncommittedChanges,
postShipArchiveSuggested,
@@ -669,7 +669,7 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use
hasRemote,
isPaseoOwnedWorktree,
isOnBaseBranch,
isOnLocalMergeTarget,
canMergeLocally,
shouldPromoteArchive,
} = derived;
@@ -700,7 +700,7 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use
hasRemote,
isPaseoOwnedWorktree,
isOnBaseBranch,
isOnLocalMergeTarget,
canMergeLocally,
hasUncommittedChanges,
baseRefAvailable: Boolean(baseRef),
baseRefLabel,
@@ -818,7 +818,7 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use
behindBaseCount,
isPaseoOwnedWorktree,
isOnBaseBranch,
isOnLocalMergeTarget,
canMergeLocally,
githubFeaturesEnabled,
forge,
githubAutoMergeActionsEnabled,