| author | |
| committer | |
| log | 61289a152d7c655a3239ff9c9cfef4ee3bd9f6c0 |
| tree | c330486d774d1461a9208be9474e7a5906833ec8 |
| parent | 906550f9af7d6fc37e1c9e4f21018dcb9ea03591 |
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) => { | ... | @@ -563,6 +563,14 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 563 | Effect.map((result) => result.stdout.trim()), | 563 | Effect.map((result) => result.stdout.trim()), |
| 564 | ), | 564 | ), |
| 565 | ).toBe("feature/implement-stacked-git-actions"); | 565 | ).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); | ||
| 566 | }), | 574 | }), |
| 567 | ); | 575 | ); |
| 568 | 576 | ||
| ... | @@ -598,6 +606,14 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -598,6 +606,14 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 598 | expect(result.commit.status).toBe("created"); | 606 | expect(result.commit.status).toBe("created"); |
| 599 | expect(result.commit.subject).toBe("feat: custom summary line"); | 607 | expect(result.commit.subject).toBe("feat: custom summary line"); |
| 600 | expect(generatedCount).toBe(0); | 608 | 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); | ||
| 601 | }), | 617 | }), |
| 602 | ); | 618 | ); |
| 603 | 619 |
apps/web/src/components/GitActionsControl.tsx+2-1| ... | @@ -254,7 +254,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions | ... | @@ -254,7 +254,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 254 | !skipDefaultBranchPrompt && | 254 | !skipDefaultBranchPrompt && |
| 255 | !featureBranch && | 255 | !featureBranch && |
| 256 | requiresDefaultBranchConfirmation(action, actionIsDefaultBranch) && | 256 | requiresDefaultBranchConfirmation(action, actionIsDefaultBranch) && |
| 257 | actionBranch | 257 | actionBranch && |
| 258 | !!actionStatus?.hasWorkingTreeChanges | ||
| 258 | ) { | 259 | ) { |
| 259 | if (action !== "commit_push" && action !== "commit_push_pr") { | 260 | if (action !== "commit_push" && action !== "commit_push_pr") { |
| 260 | return; | 261 | return; |
packages/shared/src/git.ts+1-6| ... | @@ -43,14 +43,9 @@ export function resolveAutoFeatureBranchName( | ... | @@ -43,14 +43,9 @@ export function resolveAutoFeatureBranchName( |
| 43 | preferredBranch?: string, | 43 | preferredBranch?: string, |
| 44 | ): string { | 44 | ): string { |
| 45 | const preferred = preferredBranch?.trim(); | 45 | const preferred = preferredBranch?.trim(); |
| 46 | const normalized = sanitizeBranchFragment( | 46 | const resolvedBase = sanitizeFeatureBranchName( |
| 47 | preferred && preferred.length > 0 ? preferred : AUTO_FEATURE_BRANCH_FALLBACK, | 47 | preferred && preferred.length > 0 ? preferred : AUTO_FEATURE_BRANCH_FALLBACK, |
| 48 | ); | 48 | ); |
| 49 | const resolvedBase = normalized.includes("/") | ||
| 50 | ? normalized.startsWith("feature/") | ||
| 51 | ? normalized | ||
| 52 | : `feature/${normalized}` | ||
| 53 | : `feature/${normalized}`; | ||
| 54 | const existingNames = new Set(existingBranchNames); | 49 | const existingNames = new Set(existingBranchNames); |
| 55 | 50 | ||
| 56 | if (!existingNames.has(resolvedBase)) { | 51 | if (!existingNames.has(resolvedBase)) { |