| ... | ... | @@ -13,7 +13,6 @@ import { |
| 13 | 13 | resolveQuickAction, |
| 14 | 14 | summarizeGitResult, |
| 15 | 15 | } from "./GitActionsControl.logic"; |
| 16 | | import { Badge } from "~/components/ui/badge"; |
| 17 | 16 | import { Button } from "~/components/ui/button"; |
| 18 | 17 | import { |
| 19 | 18 | Dialog, |
| ... | ... | @@ -27,6 +26,7 @@ import { |
| 27 | 26 | import { Group, GroupSeparator } from "~/components/ui/group"; |
| 28 | 27 | import { Menu, MenuItem, MenuPopup, MenuTrigger } from "~/components/ui/menu"; |
| 29 | 28 | import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; |
| 29 | import { ScrollArea } from "~/components/ui/scroll-area"; |
| 30 | 30 | import { Textarea } from "~/components/ui/textarea"; |
| 31 | 31 | import { toastManager } from "~/components/ui/toast"; |
| 32 | 32 | import { |
| ... | ... | @@ -103,20 +103,6 @@ function getMenuActionDisabledReason( |
| 103 | 103 | return "Create PR is currently unavailable."; |
| 104 | 104 | } |
| 105 | 105 | |
| 106 | | function formatUpstreamStatus(input: { |
| 107 | | hasUpstream: boolean; |
| 108 | | aheadCount: number; |
| 109 | | behindCount: number; |
| 110 | | }): string { |
| 111 | | if (!input.hasUpstream) return "No upstream configured"; |
| 112 | | if (input.aheadCount === 0 && input.behindCount === 0) return "Up to date"; |
| 113 | | if (input.aheadCount > 0 && input.behindCount > 0) { |
| 114 | | return `Diverged (+${input.aheadCount} / -${input.behindCount})`; |
| 115 | | } |
| 116 | | if (input.aheadCount > 0) return `Ahead by ${input.aheadCount}`; |
| 117 | | return `Behind by ${input.behindCount}`; |
| 118 | | } |
| 119 | | |
| 120 | 106 | function GitActionItemIcon({ icon }: { icon: GitActionIconName }) { |
| 121 | 107 | if (icon === "commit") return <GitCommitIcon />; |
| 122 | 108 | if (icon === "push") return <CloudUploadIcon />; |
| ... | ... | @@ -207,7 +193,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 207 | 193 | return true; |
| 208 | 194 | } |
| 209 | 195 | return api.dialogs.confirm( |
| 210 | | `Push to default branch "${gitStatus.branch}"? This will update the shared base branch.`, |
| 196 | `You're about to push to the default branch "${gitStatus.branch}". Continue?`, |
| 211 | 197 | ); |
| 212 | 198 | }, |
| 213 | 199 | [api, gitStatus?.branch, isDefaultBranch], |
| ... | ... | @@ -290,7 +276,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 290 | 276 | ? { |
| 291 | 277 | actionProps: { |
| 292 | 278 | children: "Push", |
| 293 | | onClick: () => setActiveDialogAction("push"), |
| 279 | onClick: () => void runGitActionWithToast({ action: "commit_push" }), |
| 294 | 280 | }, |
| 295 | 281 | } |
| 296 | 282 | : shouldOfferOpenPrCta |
| ... | ... | @@ -371,11 +357,15 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 371 | 357 | void openExistingPr(); |
| 372 | 358 | return; |
| 373 | 359 | } |
| 360 | if (item.dialogAction === "push") { |
| 361 | void runGitActionWithToast({ action: "commit_push" }); |
| 362 | return; |
| 363 | } |
| 374 | 364 | if (item.dialogAction) { |
| 375 | 365 | setActiveDialogAction(item.dialogAction); |
| 376 | 366 | } |
| 377 | 367 | }, |
| 378 | | [openExistingPr], |
| 368 | [openExistingPr, runGitActionWithToast], |
| 379 | 369 | ); |
| 380 | 370 | |
| 381 | 371 | const runDialogAction = useCallback(() => { |
| ... | ... | @@ -425,10 +415,8 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 425 | 415 | activeDialogAction === "commit" |
| 426 | 416 | ? "Review and confirm your commit. Leave the message blank to auto-generate one." |
| 427 | 417 | : activeDialogAction === "push" |
| 428 | | ? "Push this branch now. If this is the default branch, you'll be asked to confirm." |
| 418 | ? "Push this branch now." |
| 429 | 419 | : "Create a pull request using generated title/body content."; |
| 430 | | const showCompactStatusCard = |
| 431 | | !!gitStatus && gitStatus.hasWorkingTreeChanges && !gitStatus.hasUpstream; |
| 432 | 420 | |
| 433 | 421 | if (!gitCwd) return null; |
| 434 | 422 | |
| ... | ... | @@ -578,17 +566,19 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 578 | 566 | <div className="grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-1"> |
| 579 | 567 | <span className="text-muted-foreground">Branch</span> |
| 580 | 568 | <span className="font-medium">{gitStatus?.branch ?? "(detached HEAD)"}</span> |
| 581 | | {!showCompactStatusCard && ( |
| 569 | {activeDialogAction !== "commit" && ( |
| 582 | 570 | <> |
| 583 | 571 | <span className="text-muted-foreground">Upstream</span> |
| 584 | 572 | <span className="font-medium"> |
| 585 | | {gitStatus |
| 586 | | ? formatUpstreamStatus({ |
| 587 | | hasUpstream: gitStatus.hasUpstream, |
| 588 | | aheadCount: gitStatus.aheadCount, |
| 589 | | behindCount: gitStatus.behindCount, |
| 590 | | }) |
| 591 | | : "unknown"} |
| 573 | {!gitStatus || !gitStatus.hasUpstream |
| 574 | ? "No upstream configured" |
| 575 | : gitStatus.aheadCount === 0 && gitStatus.behindCount === 0 |
| 576 | ? "Up to date" |
| 577 | : gitStatus.aheadCount > 0 && gitStatus.behindCount > 0 |
| 578 | ? `Diverged (+${gitStatus.aheadCount} / -${gitStatus.behindCount})` |
| 579 | : gitStatus.aheadCount > 0 |
| 580 | ? `Ahead by ${gitStatus.aheadCount}` |
| 581 | : `Behind by ${gitStatus.behindCount}`} |
| 592 | 582 | </span> |
| 593 | 583 | <span className="text-muted-foreground">Working tree</span> |
| 594 | 584 | <span className="font-medium"> |
| ... | ... | @@ -618,27 +608,31 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 618 | 608 | {!gitStatus || gitStatus.workingTree.files.length === 0 ? ( |
| 619 | 609 | <p className="font-medium">none</p> |
| 620 | 610 | ) : ( |
| 621 | | <div className="space-y-1"> |
| 622 | | {gitStatus.workingTree.files.slice(0, 6).map((file) => ( |
| 623 | | <button |
| 624 | | type="button" |
| 625 | | key={file.path} |
| 626 | | className="flex w-full items-center justify-between gap-3 rounded-md border border-input bg-background px-2 py-1 font-mono text-left transition-colors hover:bg-accent/50" |
| 627 | | onClick={() => openChangedFileInEditor(file.path)} |
| 628 | | > |
| 629 | | <span className="truncate">{file.path}</span> |
| 630 | | <span className="shrink-0"> |
| 631 | | <span className="text-success">+{file.insertions}</span> |
| 632 | | <span className="text-muted-foreground"> / </span> |
| 633 | | <span className="text-destructive">-{file.deletions}</span> |
| 634 | | </span> |
| 635 | | </button> |
| 636 | | ))} |
| 637 | | {gitStatus.workingTree.files.length > 6 && ( |
| 638 | | <Badge variant="outline" size="sm"> |
| 639 | | +{gitStatus.workingTree.files.length - 6} more |
| 640 | | </Badge> |
| 641 | | )} |
| 611 | <div className="space-y-2"> |
| 612 | <ScrollArea className="h-44 rounded-md border border-input bg-background"> |
| 613 | <div className="space-y-1 p-1"> |
| 614 | {gitStatus.workingTree.files.map((file) => ( |
| 615 | <button |
| 616 | type="button" |
| 617 | key={file.path} |
| 618 | className="flex w-full items-center justify-between gap-3 rounded-md px-2 py-1 font-mono text-left transition-colors hover:bg-accent/50" |
| 619 | onClick={() => openChangedFileInEditor(file.path)} |
| 620 | > |
| 621 | <span className="truncate">{file.path}</span> |
| 622 | <span className="shrink-0"> |
| 623 | <span className="text-success">+{file.insertions}</span> |
| 624 | <span className="text-muted-foreground"> / </span> |
| 625 | <span className="text-destructive">-{file.deletions}</span> |
| 626 | </span> |
| 627 | </button> |
| 628 | ))} |
| 629 | </div> |
| 630 | </ScrollArea> |
| 631 | <div className="flex justify-end font-mono"> |
| 632 | <span className="text-success">+{gitStatus.workingTree.insertions}</span> |
| 633 | <span className="text-muted-foreground"> / </span> |
| 634 | <span className="text-destructive">-{gitStatus.workingTree.deletions}</span> |
| 635 | </div> |
| 642 | 636 | </div> |
| 643 | 637 | )} |
| 644 | 638 | </div> |