authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-02 14:35:36-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-02 20:46:05-08:00
log61289a152d7c655a3239ff9c9cfef4ee3bd9f6c0
treec330486d774d1461a9208be9474e7a5906833ec8
parent906550f9af7d6fc37e1c9e4f21018dcb9ea03591

Fix duplicated branch logic and feature-branch dialog on push-only

Deduplicate feature/ prefix logic in resolveAutoFeatureBranchName by delegating to sanitizeFeatureBranchName. Skip the default-branch confirmation dialog when there are no uncommitted changes (push-only), since runFeatureBranchStep requires dirty worktree. Add merge-base assertions to feature branch tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

3 files changed, 19 insertions(+), 7 deletions(-)

apps/server/src/git/Layers/GitManager.test.ts+16
......@@ -563,6 +563,14 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
563563 Effect.map((result) => result.stdout.trim()),
564564 ),
565565 ).toBe("feature/implement-stacked-git-actions");
566
567 const mainSha = yield* runGit(repoDir, ["rev-parse", "main"]).pipe(
568 Effect.map((r) => r.stdout.trim()),
569 );
570 const mergeBase = yield* runGit(repoDir, ["merge-base", "main", "HEAD"]).pipe(
571 Effect.map((r) => r.stdout.trim()),
572 );
573 expect(mergeBase).toBe(mainSha);
566574 }),
567575 );
568576
......@@ -598,6 +606,14 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
598606 expect(result.commit.status).toBe("created");
599607 expect(result.commit.subject).toBe("feat: custom summary line");
600608 expect(generatedCount).toBe(0);
609
610 const mainSha = yield* runGit(repoDir, ["rev-parse", "main"]).pipe(
611 Effect.map((r) => r.stdout.trim()),
612 );
613 const mergeBase = yield* runGit(repoDir, ["merge-base", "main", result.branch.name!]).pipe(
614 Effect.map((r) => r.stdout.trim()),
615 );
616 expect(mergeBase).toBe(mainSha);
601617 }),
602618 );
603619
apps/web/src/components/GitActionsControl.tsx+2-1
......@@ -254,7 +254,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
254254 !skipDefaultBranchPrompt &&
255255 !featureBranch &&
256256 requiresDefaultBranchConfirmation(action, actionIsDefaultBranch) &&
257 actionBranch
257 actionBranch &&
258 !!actionStatus?.hasWorkingTreeChanges
258259 ) {
259260 if (action !== "commit_push" && action !== "commit_push_pr") {
260261 return;
packages/shared/src/git.ts+1-6
......@@ -43,14 +43,9 @@ export function resolveAutoFeatureBranchName(
4343 preferredBranch?: string,
4444): string {
4545 const preferred = preferredBranch?.trim();
46 const normalized = sanitizeBranchFragment(
46 const resolvedBase = sanitizeFeatureBranchName(
4747 preferred && preferred.length > 0 ? preferred : AUTO_FEATURE_BRANCH_FALLBACK,
4848 );
49 const resolvedBase = normalized.includes("/")
50 ? normalized.startsWith("feature/")
51 ? normalized
52 : `feature/${normalized}`
53 : `feature/${normalized}`;
5449 const existingNames = new Set(existingBranchNames);
5550
5651 if (!existingNames.has(resolvedBase)) {