@@ -141,6 +141,8 @@ interface GitActionFailureDialogState {
141141 retryInput : RetryableGitActionInput ;
142142}
143143
144+ type GitDialogAction = GitStackedAction ;
145+
144146const isGitActionFailure = Schema . is ( GitActionFailureSchema ) ;
145147
146148function toRetryableGitActionInput ( input : RunGitActionWithToastInput ) : RetryableGitActionInput {
@@ -262,9 +264,62 @@ function getMenuActionDisabledReason({
262264 return "Create PR is currently unavailable." ;
263265}
264266
265- const COMMIT_DIALOG_TITLE = "Commit changes" ;
266- const COMMIT_DIALOG_DESCRIPTION =
267- "Review and confirm your commit. Leave the message blank to auto-generate one." ;
267+ function dialogIncludesCommit ( action : GitDialogAction | null , gitStatus : GitStatusResult | null ) : boolean {
268+ if ( ! action ) return false ;
269+ return action === "commit" || ! ! gitStatus ?. hasWorkingTreeChanges ;
270+ }
271+
272+ function resolveDialogCopy ( input : {
273+ action : GitDialogAction | null ;
274+ includesCommit : boolean ;
275+ } ) : {
276+ title : string ;
277+ description : string ;
278+ confirmLabel : string ;
279+ newBranchLabel : string ;
280+ } {
281+ if ( input . action === "commit_push_pr" ) {
282+ if ( input . includesCommit ) {
283+ return {
284+ title : "Commit, push, and create PR" ,
285+ description :
286+ "Review the commit details, then continue through the full publish flow with PR creation." ,
287+ confirmLabel : "Commit, push & create PR" ,
288+ newBranchLabel : "Use new branch instead" ,
289+ } ;
290+ }
291+ return {
292+ title : "Create pull request" ,
293+ description : "Push local commits if needed, then create a pull request for this branch." ,
294+ confirmLabel : "Push & create PR" ,
295+ newBranchLabel : "Use new branch instead" ,
296+ } ;
297+ }
298+
299+ if ( input . action === "commit_push" ) {
300+ if ( input . includesCommit ) {
301+ return {
302+ title : "Commit and push changes" ,
303+ description : "Review the commit details, then publish this branch." ,
304+ confirmLabel : "Commit & push" ,
305+ newBranchLabel : "Use new branch instead" ,
306+ } ;
307+ }
308+ return {
309+ title : "Push branch" ,
310+ description : "Push local commits on this branch." ,
311+ confirmLabel : "Push" ,
312+ newBranchLabel : "Use new branch instead" ,
313+ } ;
314+ }
315+
316+ return {
317+ title : "Commit changes" ,
318+ description : "Review and confirm your commit. Leave the message blank to auto-generate one." ,
319+ confirmLabel : "Commit" ,
320+ newBranchLabel : "Commit on new branch" ,
321+ } ;
322+ }
268323
269324function GitActionItemIcon ( { icon } : { icon : GitActionIconName } ) {
270325 if ( icon === "commit" ) return < GitCommitIcon /> ;
@@ -299,7 +354,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
299354 [ activeThreadId ] ,
300355 ) ;
301356 const queryClient = useQueryClient ( ) ;
302- const [ isCommitDialogOpen , setIsCommitDialogOpen ] = useState ( false ) ;
357+ const [ activeDialogAction , setActiveDialogAction ] = useState < GitDialogAction | null > ( null ) ;
303358 const [ dialogCommitMessage , setDialogCommitMessage ] = useState ( "" ) ;
304359 const [ excludedFiles , setExcludedFiles ] = useState < ReadonlySet < string > > ( new Set ( ) ) ;
305360 const [ isEditingFiles , setIsEditingFiles ] = useState ( false ) ;
@@ -345,6 +400,11 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
345400 const selectedFiles = allFiles . filter ( ( f ) => ! excludedFiles . has ( f . path ) ) ;
346401 const allSelected = excludedFiles . size === 0 ;
347402 const noneSelected = selectedFiles . length === 0 ;
403+ const activeDialogIncludesCommit = dialogIncludesCommit ( activeDialogAction , gitStatusForActions ) ;
404+ const activeDialogCopy = resolveDialogCopy ( {
405+ action : activeDialogAction ,
406+ includesCommit : activeDialogIncludesCommit ,
407+ } ) ;
348408
349409 const initMutation = useMutation ( gitInitMutationOptions ( { cwd : gitCwd , queryClient } ) ) ;
350410
@@ -824,22 +884,28 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
824884 } , [ pendingDefaultBranchAction ] ) ;
825885
826886 const runDialogActionOnNewBranch = useCallback ( ( ) => {
827- if ( ! isCommitDialogOpen ) return ;
887+ if ( ! activeDialogAction || ! activeDialogIncludesCommit ) return ;
828888 const commitMessage = dialogCommitMessage . trim ( ) ;
829889
830- setIsCommitDialogOpen ( false ) ;
890+ setActiveDialogAction ( null ) ;
831891 setDialogCommitMessage ( "" ) ;
832892 setExcludedFiles ( new Set ( ) ) ;
833893 setIsEditingFiles ( false ) ;
834894
835895 void runGitActionWithToast ( {
836- action : "commit" ,
896+ action : activeDialogAction ,
837897 ...( commitMessage ? { commitMessage } : { } ) ,
838898 ...( ! allSelected ? { filePaths : selectedFiles . map ( ( f ) => f . path ) } : { } ) ,
839899 featureBranch : true ,
840900 skipDefaultBranchPrompt : true ,
841901 } ) ;
842- } , [ allSelected , isCommitDialogOpen , dialogCommitMessage , selectedFiles ] ) ;
902+ } , [
903+ activeDialogAction ,
904+ activeDialogIncludesCommit ,
905+ allSelected ,
906+ dialogCommitMessage ,
907+ selectedFiles ,
908+ ] ) ;
843909
844910 const conflictedFiles = useMemo (
845911 ( ) => gitStatusForActions ?. conflictedFiles ?? [ ] ,
@@ -1002,9 +1068,18 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
10021068 return ;
10031069 }
10041070 if ( quickAction . action ) {
1005- void runGitActionWithToast ( { action : quickAction . action } ) ;
1071+ setDialogCommitMessage ( "" ) ;
1072+ setExcludedFiles ( new Set ( ) ) ;
1073+ setIsEditingFiles ( false ) ;
1074+ setActiveDialogAction ( quickAction . action ) ;
10061075 }
1007- } , [ openConflictedFilesInEditor , openExistingPr , quickAction , runPullWithToast , threadToastData ] ) ;
1076+ } , [
1077+ openConflictedFilesInEditor ,
1078+ openExistingPr ,
1079+ quickAction ,
1080+ runPullWithToast ,
1081+ threadToastData ,
1082+ ] ) ;
10081083
10091084 const openDialogForMenuItem = useCallback (
10101085 ( item : GitActionMenuItem ) => {
@@ -1013,40 +1088,48 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
10131088 void openExistingPr ( ) ;
10141089 return ;
10151090 }
1091+ setDialogCommitMessage ( "" ) ;
1092+ setExcludedFiles ( new Set ( ) ) ;
1093+ setIsEditingFiles ( false ) ;
10161094 if ( item . dialogAction === "push" ) {
1017- void runGitActionWithToast ( { action : "commit_push" , forcePushOnlyProgress : true } ) ;
1095+ setActiveDialogAction ( "commit_push" ) ;
10181096 return ;
10191097 }
10201098 if ( item . dialogAction === "create_pr" ) {
1021- void runGitActionWithToast ( { action : "commit_push_pr" } ) ;
1099+ setActiveDialogAction ( "commit_push_pr" ) ;
10221100 return ;
10231101 }
1024- setExcludedFiles ( new Set ( ) ) ;
1025- setIsEditingFiles ( false ) ;
1026- setIsCommitDialogOpen ( true ) ;
1102+ setActiveDialogAction ( "commit" ) ;
10271103 } ,
1028- [ openExistingPr , setIsCommitDialogOpen ] ,
1104+ [ openExistingPr ] ,
10291105 ) ;
10301106
10311107 const runDialogAction = useCallback ( ( ) => {
1032- if ( ! isCommitDialogOpen ) return ;
1108+ if ( ! activeDialogAction ) return ;
10331109 const commitMessage = dialogCommitMessage . trim ( ) ;
1034- setIsCommitDialogOpen ( false ) ;
1110+ const includesCommit = dialogIncludesCommit ( activeDialogAction , gitStatusForActions ) ;
1111+ setActiveDialogAction ( null ) ;
10351112 setDialogCommitMessage ( "" ) ;
10361113 setExcludedFiles ( new Set ( ) ) ;
10371114 setIsEditingFiles ( false ) ;
10381115 void runGitActionWithToast ( {
1039- action : "commit" ,
1040- ...( commitMessage ? { commitMessage } : { } ) ,
1041- ...( ! allSelected ? { filePaths : selectedFiles . map ( ( f ) => f . path ) } : { } ) ,
1116+ action : activeDialogAction ,
1117+ ...( includesCommit && commitMessage ? { commitMessage } : { } ) ,
1118+ ...( includesCommit
1119+ ? ! allSelected
1120+ ? { filePaths : selectedFiles . map ( ( f ) => f . path ) }
1121+ : { }
1122+ : activeDialogAction !== "commit"
1123+ ? { forcePushOnlyProgress : true }
1124+ : { } ) ,
10421125 } ) ;
10431126 } , [
1127+ activeDialogAction ,
10441128 allSelected ,
10451129 dialogCommitMessage ,
1046- isCommitDialogOpen ,
1130+ gitStatusForActions ,
10471131 selectedFiles ,
10481132 setDialogCommitMessage ,
1049- setIsCommitDialogOpen ,
10501133 ] ) ;
10511134
10521135 const openChangedFileInEditor = useCallback (
@@ -1309,10 +1392,10 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
13091392 ) }
13101393
13111394 < Dialog
1312- open = { isCommitDialogOpen }
1395+ open = { activeDialogAction !== null }
13131396 onOpenChange = { ( open : boolean ) => {
13141397 if ( ! open ) {
1315- setIsCommitDialogOpen ( false ) ;
1398+ setActiveDialogAction ( null ) ;
13161399 setDialogCommitMessage ( "" ) ;
13171400 setExcludedFiles ( new Set ( ) ) ;
13181401 setIsEditingFiles ( false ) ;
@@ -1321,8 +1404,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
13211404 >
13221405 < DialogPopup >
13231406 < DialogHeader >
1324- < DialogTitle > { COMMIT_DIALOG_TITLE } </ DialogTitle >
1325- < DialogDescription > { COMMIT_DIALOG_DESCRIPTION } </ DialogDescription >
1407+ < DialogTitle > { activeDialogCopy . title } </ DialogTitle >
1408+ < DialogDescription > { activeDialogCopy . description } </ DialogDescription >
13261409 </ DialogHeader >
13271410 < DialogPanel className = "space-y-4" >
13281411 < div className = "space-y-3 rounded-lg border border-input bg-muted/40 p-3 text-xs" >
@@ -1340,7 +1423,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
13401423 < div className = "space-y-1" >
13411424 < div className = "flex items-center justify-between" >
13421425 < div className = "flex items-center gap-2" >
1343- { isEditingFiles && allFiles . length > 0 && (
1426+ { activeDialogIncludesCommit && isEditingFiles && allFiles . length > 0 && (
13441427 < Checkbox
13451428 checked = { allSelected }
13461429 indeterminate = { ! allSelected && ! noneSelected }
@@ -1352,13 +1435,13 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
13521435 />
13531436 ) }
13541437 < span className = "text-muted-foreground" > Files</ span >
1355- { ! allSelected && ! isEditingFiles && (
1438+ { activeDialogIncludesCommit && ! allSelected && ! isEditingFiles && (
13561439 < span className = "text-muted-foreground" >
13571440 ({ selectedFiles . length } of { allFiles . length } )
13581441 </ span >
13591442 ) }
13601443 </ div >
1361- { allFiles . length > 0 && (
1444+ { activeDialogIncludesCommit && allFiles . length > 0 && (
13621445 < Button
13631446 variant = "ghost"
13641447 size = "xs"
@@ -1381,7 +1464,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
13811464 key = { file . path }
13821465 className = "flex w-full items-center gap-2 rounded-md px-2 py-1 font-mono text-xs transition-colors hover:bg-accent/50"
13831466 >
1384- { isEditingFiles && (
1467+ { activeDialogIncludesCommit && isEditingFiles && (
13851468 < Checkbox
13861469 checked = { ! excludedFiles . has ( file . path ) }
13871470 onCheckedChange = { ( ) => {
@@ -1437,39 +1520,47 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
14371520 ) }
14381521 </ div >
14391522 </ div >
1440- < div className = "space-y-1" >
1441- < p className = "text-xs font-medium" > Commit message (optional)</ p >
1442- < Textarea
1443- value = { dialogCommitMessage }
1444- onChange = { ( event ) => setDialogCommitMessage ( event . target . value ) }
1445- placeholder = "Leave empty to auto-generate"
1446- size = "sm"
1447- />
1448- </ div >
1523+ { activeDialogIncludesCommit ? (
1524+ < div className = "space-y-1" >
1525+ < p className = "text-xs font-medium" > Commit message (optional)</ p >
1526+ < Textarea
1527+ value = { dialogCommitMessage }
1528+ onChange = { ( event ) => setDialogCommitMessage ( event . target . value ) }
1529+ placeholder = "Leave empty to auto-generate"
1530+ size = "sm"
1531+ />
1532+ </ div >
1533+ ) : null }
14491534 </ DialogPanel >
14501535 < DialogFooter >
14511536 < Button
14521537 variant = "outline"
14531538 size = "sm"
14541539 onClick = { ( ) => {
1455- setIsCommitDialogOpen ( false ) ;
1540+ setActiveDialogAction ( null ) ;
14561541 setDialogCommitMessage ( "" ) ;
14571542 setExcludedFiles ( new Set ( ) ) ;
14581543 setIsEditingFiles ( false ) ;
14591544 } }
14601545 >
14611546 Cancel
14621547 </ Button >
1548+ { activeDialogIncludesCommit ? (
1549+ < Button
1550+ variant = "outline"
1551+ size = "sm"
1552+ disabled = { noneSelected }
1553+ onClick = { runDialogActionOnNewBranch }
1554+ >
1555+ { activeDialogCopy . newBranchLabel }
1556+ </ Button >
1557+ ) : null }
14631558 < Button
1464- variant = "outline"
14651559 size = "sm"
1466- disabled = { noneSelected }
1467- onClick = { runDialogActionOnNewBranch }
1560+ disabled = { activeDialogIncludesCommit && noneSelected }
1561+ onClick = { runDialogAction }
14681562 >
1469- Commit on new branch
1470- </ Button >
1471- < Button size = "sm" disabled = { noneSelected } onClick = { runDialogAction } >
1472- Commit
1563+ { activeDialogCopy . confirmLabel }
14731564 </ Button >
14741565 </ DialogFooter >
14751566 </ DialogPopup >
0 commit comments