authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-01 11:07:27-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-01 20:06:01-08:00
log0e2d50a62082aefb14249b85691cc500d4dd35dc
tree257790c4d428f883c4ed12ef78dad4dec48b8fc5
parentf3b3c548596af0a70fb8eddcc49118e5a5e154b2

Require ahead commits before offering Create PR on untracked branches

- Change quick action to `Push` (not `Push & create PR`) when branch has no upstream and is not ahead - Keep `Create PR` menu item disabled until the branch has commits ahead - Add/adjust logic tests for both no-ahead and ahead scenarios

2 files changed, 58 insertions(+), 5 deletions(-)

apps/web/src/components/GitActionsControl.logic.test.ts+54-4
......@@ -515,15 +515,15 @@ describe("when: HEAD is detached and there are no local changes", () => {
515515});
516516
517517describe("when: branch has no upstream configured", () => {
518 it("resolveQuickAction runs push and create PR when clean, no upstream, and no local commits are ahead", () => {
518 it("resolveQuickAction runs push when clean, no upstream, and no local commits are ahead", () => {
519519 const quick = resolveQuickAction(
520520 status({ hasUpstream: false, pr: null, aheadCount: 0 }),
521521 false,
522522 );
523523 assert.deepInclude(quick, {
524524 kind: "run_action",
525 action: "commit_push_pr",
526 label: "Push & create PR",
525 action: "commit_push",
526 label: "Push",
527527 disabled: false,
528528 });
529529 });
......@@ -576,11 +576,61 @@ describe("when: branch has no upstream configured", () => {
576576 });
577577 });
578578
579 it("buildMenuItems enables push and create PR to publish branch when no upstream exists", () => {
579 it("buildMenuItems enables push but keeps create PR disabled when no commits are ahead", () => {
580580 const items = buildMenuItems(
581581 status({ hasUpstream: false, pr: null, aheadCount: 0 }),
582582 false,
583583 );
584 assert.deepEqual(items, [
585 {
586 id: "commit",
587 label: "Commit",
588 disabled: true,
589 icon: "commit",
590 kind: "open_dialog",
591 dialogAction: "commit",
592 },
593 {
594 id: "push",
595 label: "Push",
596 disabled: false,
597 icon: "push",
598 kind: "open_dialog",
599 dialogAction: "push",
600 },
601 {
602 id: "pr",
603 label: "Create PR",
604 disabled: true,
605 icon: "pr",
606 kind: "open_dialog",
607 dialogAction: "create_pr",
608 },
609 ]);
610 });
611
612 it("resolveQuickAction runs push and create PR when no upstream and commits are ahead", () => {
613 const quick = resolveQuickAction(
614 status({
615 hasUpstream: false,
616 aheadCount: 2,
617 pr: null,
618 }),
619 false,
620 );
621 assert.deepInclude(quick, {
622 kind: "run_action",
623 action: "commit_push_pr",
624 label: "Push & create PR",
625 disabled: false,
626 });
627 });
628
629 it("buildMenuItems enables create PR when no upstream and commits are ahead", () => {
630 const items = buildMenuItems(
631 status({ hasUpstream: false, pr: null, aheadCount: 2 }),
632 false,
633 );
584634 assert.deepEqual(items, [
585635 {
586636 id: "commit",
apps/web/src/components/GitActionsControl.logic.ts+4-1
......@@ -124,7 +124,7 @@ export function buildMenuItems(
124124 hasBranch &&
125125 !hasChanges &&
126126 !hasOpenPr &&
127 (gitStatus.aheadCount > 0 || canPublishUntrackedBranch) &&
127 gitStatus.aheadCount > 0 &&
128128 !isBehind;
129129 const canOpenPr = !isBusy && hasOpenPr;
130130
......@@ -214,6 +214,9 @@ export function resolveQuickAction(
214214 if (hasOpenPr || isDefaultBranch) {
215215 return { label: "Push", disabled: false, kind: "run_action", action: "commit_push" };
216216 }
217 if (!isAhead) {
218 return { label: "Push", disabled: false, kind: "run_action", action: "commit_push" };
219 }
217220 return {
218221 label: "Push & create PR",
219222 disabled: false,