| author | |
| committer | |
| log | f11764c517732b1c02057f28ff1a10f03f8157da |
| tree | c75cf72a7089bf042f345cdf584dacef408b85d7 |
| parent | 77c81b7468003e7b7d56fd4dd123c499fdf3e073 |
Co-authored-by: codex <codex@users.noreply.github.com>3 files changed, 53 insertions(+), 10 deletions(-)
apps/web/src/components/GitActionsControl.logic.test.ts+21-4| ... | ... | @@ -250,15 +250,20 @@ describe("when: branch is clean, ahead, and has no open PR", () => { |
| 250 | 250 | }); |
| 251 | 251 | |
| 252 | 252 | describe("when: branch is clean, up to date, and has no open PR", () => { |
| 253 | it("resolveQuickAction returns disabled no-action state", () => { | |
| 253 | it("resolveQuickAction offers create PR", () => { | |
| 254 | 254 | const quick = resolveQuickAction( |
| 255 | 255 | status({ aheadCount: 0, behindCount: 0, hasWorkingTreeChanges: false, pr: null }), |
| 256 | 256 | false, |
| 257 | 257 | ); |
| 258 | assert.deepInclude(quick, { kind: "show_hint", label: "Commit", disabled: true }); | |
| 258 | assert.deepInclude(quick, { | |
| 259 | kind: "run_action", | |
| 260 | action: "commit_push_pr", | |
| 261 | label: "Create PR", | |
| 262 | disabled: false, | |
| 263 | }); | |
| 259 | 264 | }); |
| 260 | 265 | |
| 261 | it("buildMenuItems disables commit, push, and create PR", () => { | |
| 266 | it("buildMenuItems enables create PR and keeps commit/push disabled", () => { | |
| 262 | 267 | const items = buildMenuItems(status({ aheadCount: 0, behindCount: 0, pr: null }), false); |
| 263 | 268 | assert.deepEqual(items, [ |
| 264 | 269 | { |
| ... | ... | @@ -280,13 +285,25 @@ describe("when: branch is clean, up to date, and has no open PR", () => { |
| 280 | 285 | { |
| 281 | 286 | id: "pr", |
| 282 | 287 | label: "Create PR", |
| 283 | disabled: true, | |
| 288 | disabled: false, | |
| 284 | 289 | icon: "pr", |
| 285 | 290 | kind: "open_dialog", |
| 286 | 291 | dialogAction: "create_pr", |
| 287 | 292 | }, |
| 288 | 293 | ]); |
| 289 | 294 | }); |
| 295 | ||
| 296 | it("buildMenuItems keeps create PR disabled on default branch", () => { | |
| 297 | const items = buildMenuItems(status({ branch: "main", aheadCount: 0, behindCount: 0 }), false, true); | |
| 298 | assert.deepEqual(items[2], { | |
| 299 | id: "pr", | |
| 300 | label: "Create PR", | |
| 301 | disabled: true, | |
| 302 | icon: "pr", | |
| 303 | kind: "open_dialog", | |
| 304 | dialogAction: "create_pr", | |
| 305 | }); | |
| 306 | }); | |
| 290 | 307 | }); |
| 291 | 308 | |
| 292 | 309 | describe("when: branch is behind upstream", () => { |
apps/web/src/components/GitActionsControl.logic.ts+13-2| ... | ... | @@ -113,6 +113,7 @@ export function summarizeGitResult(result: GitRunStackedActionResult): { |
| 113 | 113 | export function buildMenuItems( |
| 114 | 114 | gitStatus: GitStatusResult | null, |
| 115 | 115 | isBusy: boolean, |
| 116 | isDefaultBranch = false, | |
| 116 | 117 | ): GitActionMenuItem[] { |
| 117 | 118 | if (!gitStatus) return []; |
| 118 | 119 | |
| ... | ... | @@ -127,8 +128,9 @@ export function buildMenuItems( |
| 127 | 128 | hasBranch && |
| 128 | 129 | !hasChanges && |
| 129 | 130 | !hasOpenPr && |
| 130 | gitStatus.aheadCount > 0 && | |
| 131 | !isBehind; | |
| 131 | !isBehind && | |
| 132 | !isDefaultBranch && | |
| 133 | (gitStatus.aheadCount > 0 || gitStatus.hasUpstream); | |
| 132 | 134 | const canOpenPr = !isBusy && hasOpenPr; |
| 133 | 135 | |
| 134 | 136 | return [ |
| ... | ... | @@ -265,6 +267,15 @@ export function resolveQuickAction( |
| 265 | 267 | }; |
| 266 | 268 | } |
| 267 | 269 | |
| 270 | if (!hasOpenPr && !isDefaultBranch && gitStatus.hasUpstream) { | |
| 271 | return { | |
| 272 | label: "Create PR", | |
| 273 | disabled: false, | |
| 274 | kind: "run_action", | |
| 275 | action: "commit_push_pr", | |
| 276 | }; | |
| 277 | } | |
| 278 | ||
| 268 | 279 | if (hasOpenPr && gitStatus.hasUpstream) { |
| 269 | 280 | return { label: "Open PR", disabled: false, kind: "open_pr" }; |
| 270 | 281 | } |
apps/web/src/components/GitActionsControl.tsx+19-4| ... | ... | @@ -180,10 +180,19 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 180 | 180 | const current = branchList?.branches.find((branch) => branch.name === branchName); |
| 181 | 181 | return current?.isDefault ?? (branchName === "main" || branchName === "master"); |
| 182 | 182 | }, [branchList?.branches, gitStatusForActions?.branch]); |
| 183 | const isDefaultBranchName = useCallback( | |
| 184 | (branchName: string | null | undefined) => { | |
| 185 | if (!branchName) return false; | |
| 186 | const defaultBranchName = branchList?.branches.find((branch) => branch.isDefault)?.name; | |
| 187 | if (defaultBranchName) return branchName === defaultBranchName; | |
| 188 | return branchName === "main" || branchName === "master"; | |
| 189 | }, | |
| 190 | [branchList?.branches], | |
| 191 | ); | |
| 183 | 192 | |
| 184 | 193 | const gitActionMenuItems = useMemo( |
| 185 | () => buildMenuItems(gitStatusForActions, isGitActionRunning), | |
| 186 | [gitStatusForActions, isGitActionRunning], | |
| 194 | () => buildMenuItems(gitStatusForActions, isGitActionRunning, isDefaultBranch), | |
| 195 | [gitStatusForActions, isGitActionRunning, isDefaultBranch], | |
| 187 | 196 | ); |
| 188 | 197 | const quickAction = useMemo( |
| 189 | 198 | () => resolveQuickAction(gitStatusForActions, isGitActionRunning, isDefaultBranch), |
| ... | ... | @@ -237,6 +246,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 237 | 246 | onConfirmed, |
| 238 | 247 | skipDefaultBranchPrompt = false, |
| 239 | 248 | featureBranch = false, |
| 249 | isDefaultBranchOverride, | |
| 240 | 250 | }: { |
| 241 | 251 | action: GitStackedAction; |
| 242 | 252 | commitMessage?: string; |
| ... | ... | @@ -244,10 +254,12 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 244 | 254 | onConfirmed?: () => void; |
| 245 | 255 | skipDefaultBranchPrompt?: boolean; |
| 246 | 256 | featureBranch?: boolean; |
| 257 | isDefaultBranchOverride?: boolean; | |
| 247 | 258 | }) => { |
| 248 | 259 | const actionStatus = gitStatusForActions; |
| 249 | const actionIsDefaultBranch = featureBranch ? false : isDefaultBranch; | |
| 250 | 260 | const actionBranch = actionStatus?.branch ?? null; |
| 261 | const actionIsDefaultBranch = | |
| 262 | isDefaultBranchOverride ?? (featureBranch ? false : isDefaultBranchName(actionBranch)); | |
| 251 | 263 | const includesCommit = |
| 252 | 264 | !forcePushOnlyProgress && (action === "commit" || !!actionStatus?.hasWorkingTreeChanges); |
| 253 | 265 | if ( |
| ... | ... | @@ -348,6 +360,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 348 | 360 | forcePushOnlyProgress: true, |
| 349 | 361 | onConfirmed: closeResultToast, |
| 350 | 362 | ...(featureBranch ? { skipDefaultBranchPrompt: true } : {}), |
| 363 | ...(featureBranch ? { isDefaultBranchOverride: false } : {}), | |
| 351 | 364 | }); |
| 352 | 365 | }, |
| 353 | 366 | }, |
| ... | ... | @@ -374,6 +387,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 374 | 387 | action: "commit_push_pr", |
| 375 | 388 | forcePushOnlyProgress: true, |
| 376 | 389 | ...(featureBranch ? { skipDefaultBranchPrompt: true } : {}), |
| 390 | ...(featureBranch ? { isDefaultBranchOverride: false } : {}), | |
| 377 | 391 | }); |
| 378 | 392 | }, |
| 379 | 393 | }, |
| ... | ... | @@ -392,7 +406,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 392 | 406 | }, |
| 393 | 407 | |
| 394 | 408 | [ |
| 395 | isDefaultBranch, | |
| 409 | isDefaultBranchName, | |
| 396 | 410 | runImmediateGitActionMutation, |
| 397 | 411 | setPendingDefaultBranchAction, |
| 398 | 412 | threadToastData, |
| ... | ... | @@ -410,6 +424,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 410 | 424 | forcePushOnlyProgress, |
| 411 | 425 | ...(onConfirmed ? { onConfirmed } : {}), |
| 412 | 426 | skipDefaultBranchPrompt: true, |
| 427 | isDefaultBranchOverride: true, | |
| 413 | 428 | }); |
| 414 | 429 | }, [pendingDefaultBranchAction, runGitActionWithToast]); |
| 415 | 430 |