diff --git a/apps/web/src/components/GitActionsControl.logic.test.ts b/apps/web/src/components/GitActionsControl.logic.test.ts index d0e243cda25c4..f6073e957ad59 100644 --- a/apps/web/src/components/GitActionsControl.logic.test.ts +++ b/apps/web/src/components/GitActionsControl.logic.test.ts @@ -4,10 +4,12 @@ import { resolveAutoFeatureBranchName } from "@t3tools/shared/git"; import { assert, describe, it } from "vite-plus/test"; import { buildMenuItems, + type DefaultBranchConfirmableAction, hasUsableGitMenuContent, requiresDefaultBranchConfirmation, resolveChangesTotals, resolveDefaultBranchActionDialogCopy, + resolveFeatureBranchUnavailableReason, resolveLiveThreadBranchUpdate, resolveQuickAction, } from "./GitActionsControl.logic"; @@ -117,6 +119,16 @@ describe("when: on default ref without open PR", () => { disabled: false, }); }); + + it("offers a feature branch only to actions that commit the changes", () => { + const reason = (action: DefaultBranchConfirmableAction, hasWorkingTreeChanges: boolean) => + resolveFeatureBranchUnavailableReason({ action, branchName: "main", hasWorkingTreeChanges }); + assert.isNull(reason("commit_push", true)); + assert.isNull(reason("commit_push_pr", true)); + assert.isNotNull(reason("commit_push", false)); + assert.isNotNull(reason("push", true)); + assert.isNotNull(reason("create_pr", true)); + }); }); describe("when: local changes are behind upstream", () => { diff --git a/apps/web/src/components/GitActionsControl.logic.ts b/apps/web/src/components/GitActionsControl.logic.ts index 399e013068364..11369ea8aa4c5 100644 --- a/apps/web/src/components/GitActionsControl.logic.ts +++ b/apps/web/src/components/GitActionsControl.logic.ts @@ -379,13 +379,19 @@ export function resolveDefaultBranchActionDialogCopy(input: { }; } -/** Clean trees cannot create feature branches because the server derives names from uncommitted changes. */ +/** The server names a feature branch from the commit it makes, so the action must commit changes. */ export function resolveFeatureBranchUnavailableReason(input: { + action: DefaultBranchConfirmableAction; branchName: string; hasWorkingTreeChanges: boolean; }): string | null { - if (input.hasWorkingTreeChanges) return null; - return `There are no changes to commit, so a feature branch can't be named. Create a branch yourself or continue on ${input.branchName}.`; + if (!input.hasWorkingTreeChanges) { + return `There are no changes to commit, so a feature branch can't be named. Create a branch yourself or continue on ${input.branchName}.`; + } + if (input.action === "push" || input.action === "create_pr") { + return `This action doesn't commit your changes, so a feature branch can't be named. Create a branch yourself or continue on ${input.branchName}.`; + } + return null; } export function resolveThreadBranchUpdate( diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 3fe8374d0e12f..339b3fd874314 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -1245,6 +1245,7 @@ export default function GitActionsControl({ // Keep enabled until live status loads; the server rejects stale requests. const featureBranchUnavailableReason = pendingDefaultBranchAction ? resolveFeatureBranchUnavailableReason({ + action: pendingDefaultBranchAction.action, branchName: pendingDefaultBranchAction.branchName, hasWorkingTreeChanges: gitStatus?.hasWorkingTreeChanges ?? true, })