66 "fmt"
77 "slices"
88 "strings"
9+ "time"
910
1011 "github.com/compozy/releasepr/internal/domain"
1112 "github.com/compozy/releasepr/internal/logger"
@@ -372,7 +373,7 @@ func (o *PRReleaseOrchestrator) buildAndExecuteWorkflow(
372373 // Add all workflow steps
373374 o .addCheckChangesStep (saga , cfg , compensator , wctx )
374375 o .addCalculateVersionStep (saga , cfg , compensator , wctx )
375- o .addCreateBranchStep (saga , compensator , wctx , originalBranch )
376+ o .addCreateBranchStep (saga , cfg , compensator , wctx , originalBranch )
376377 o .addUpdatePackagesStep (saga , compensator , wctx )
377378 o .addGenerateChangelogStep (saga , compensator , wctx )
378379 o .addCommitChangesStep (saga , compensator , wctx )
@@ -461,6 +462,7 @@ func (o *PRReleaseOrchestrator) addCalculateVersionStep(
461462
462463func (o * PRReleaseOrchestrator ) addCreateBranchStep (
463464 saga * SagaExecutor ,
465+ cfg PRReleaseConfig ,
464466 compensator * CompensatingActions ,
465467 wctx * workflowContext ,
466468 originalBranch string ,
@@ -472,49 +474,39 @@ func (o *PRReleaseOrchestrator) addCreateBranchStep(
472474 if wctx .version == "" {
473475 return map [string ]any {"skip" : true }, nil
474476 }
475- wctx .branchName = fmt .Sprintf ("release/%s" , wctx .version )
476- o .logger (ctx ).Info ("Determined release branch" , zap .String ("branch" , wctx .branchName ))
477- if err := ValidateBranchName (wctx .branchName ); err != nil {
478- return nil , fmt .Errorf ("invalid branch name: %w" , err )
479- }
480- saga .SetBranchName (wctx .branchName )
481- branches , err := o .gitRepo .ListLocalBranches (ctx )
477+ branchName , err := o .prepareBranchName (ctx , saga , wctx )
482478 if err != nil {
483- return nil , fmt . Errorf ( "failed to list local branches: %w" , err )
479+ return nil , err
484480 }
485- branchExists := slices .Contains (branches , wctx .branchName )
486- remoteBranches , err := o .gitRepo .ListRemoteBranches (ctx )
481+ branchExists , remoteExists , err := o .checkBranchExistence (ctx , branchName )
487482 if err != nil {
488- return nil , fmt . Errorf ( "failed to list remote branches: %w" , err )
483+ return nil , err
489484 }
490- remoteExists := slices .Contains (remoteBranches , fmt .Sprintf ("origin/%s" , wctx .branchName ))
491- switch {
492- case branchExists && remoteExists :
493- o .logger (ctx ).Info (
494- "Reusing existing branch" ,
495- zap .String ("branch" , wctx .branchName ),
496- zap .Bool ("local" , true ),
497- zap .Bool ("remote" , true ),
485+ if cfg .ForceRelease {
486+ branchExists , remoteExists , err = o .forceDeleteBranch (
487+ ctx ,
488+ branchName ,
489+ branchExists ,
490+ remoteExists ,
491+ originalBranch ,
498492 )
499- case branchExists :
500- o .logger (ctx ).Info ("Checking out existing local branch" , zap .String ("branch" , wctx .branchName ))
501- case remoteExists :
502- o .logger (ctx ).Info ("Checking out remote branch" , zap .String ("branch" , wctx .branchName ))
503- default :
504- o .logger (ctx ).Info ("Creating new branch" , zap .String ("branch" , wctx .branchName ))
493+ if err != nil {
494+ return nil , err
495+ }
505496 }
497+ o .logBranchStatus (ctx , branchName , branchExists , remoteExists )
506498 wctx .createdInSession = ! branchExists
507499 if wctx .createdInSession {
508- if err := o .createReleaseBranch (ctx , wctx . branchName ); err != nil {
509- return nil , fmt .Errorf ("failed to create release branch %s: %w" , wctx . branchName , err )
500+ if err := o .createReleaseBranch (ctx , branchName ); err != nil {
501+ return nil , fmt .Errorf ("failed to create release branch %s: %w" , branchName , err )
510502 }
511503 }
512- if err := o .gitRepo .CheckoutBranch (ctx , wctx . branchName ); err != nil {
513- return nil , fmt .Errorf ("failed to checkout release branch %s: %w" , wctx . branchName , err )
504+ if err := o .gitRepo .CheckoutBranch (ctx , branchName ); err != nil {
505+ return nil , fmt .Errorf ("failed to checkout release branch %s: %w" , branchName , err )
514506 }
515- o .logger (ctx ).Info ("Checked out release branch" , zap .String ("branch" , wctx . branchName ))
507+ o .logger (ctx ).Info ("Checked out release branch" , zap .String ("branch" , branchName ))
516508 return map [string ]any {
517- "branch_name" : wctx . branchName ,
509+ "branch_name" : branchName ,
518510 "original_branch" : originalBranch ,
519511 "created_in_session" : wctx .createdInSession ,
520512 "remote_exists" : remoteExists ,
@@ -524,6 +516,98 @@ func (o *PRReleaseOrchestrator) addCreateBranchStep(
524516 })
525517}
526518
519+ func (o * PRReleaseOrchestrator ) prepareBranchName (
520+ ctx context.Context ,
521+ saga * SagaExecutor ,
522+ wctx * workflowContext ,
523+ ) (string , error ) {
524+ wctx .branchName = fmt .Sprintf ("release/%s" , wctx .version )
525+ o .logger (ctx ).Info ("Determined release branch" , zap .String ("branch" , wctx .branchName ))
526+ if err := ValidateBranchName (wctx .branchName ); err != nil {
527+ return "" , fmt .Errorf ("invalid branch name: %w" , err )
528+ }
529+ saga .SetBranchName (wctx .branchName )
530+ return wctx .branchName , nil
531+ }
532+
533+ func (o * PRReleaseOrchestrator ) checkBranchExistence (ctx context.Context , branchName string ) (bool , bool , error ) {
534+ branches , err := o .gitRepo .ListLocalBranches (ctx )
535+ if err != nil {
536+ return false , false , fmt .Errorf ("failed to list local branches: %w" , err )
537+ }
538+ branchExists := slices .Contains (branches , branchName )
539+ checkCtx , cancel := context .WithTimeout (ctx , 30 * time .Second )
540+ defer cancel ()
541+ remoteExists , err := o .gitRepo .RemoteBranchExists (checkCtx , branchName )
542+ if err != nil {
543+ o .logger (ctx ).Warn ("Failed to check remote branch, assuming it doesn't exist" ,
544+ zap .String ("branch" , branchName ),
545+ zap .Error (err ))
546+ remoteExists = false
547+ }
548+ return branchExists , remoteExists , nil
549+ }
550+
551+ func (o * PRReleaseOrchestrator ) forceDeleteBranch (
552+ ctx context.Context ,
553+ branchName string ,
554+ branchExists , remoteExists bool ,
555+ originalBranch string ,
556+ ) (bool , bool , error ) {
557+ if ! branchExists && ! remoteExists {
558+ return branchExists , remoteExists , nil
559+ }
560+ o .logger (ctx ).Info ("Force flag set, deleting existing branch" ,
561+ zap .String ("branch" , branchName ),
562+ zap .Bool ("local_exists" , branchExists ),
563+ zap .Bool ("remote_exists" , remoteExists ))
564+ if branchExists {
565+ if err := o .gitRepo .CheckoutBranch (ctx , originalBranch ); err != nil {
566+ return branchExists , remoteExists , fmt .Errorf (
567+ "failed to checkout original branch %s: %w" ,
568+ originalBranch ,
569+ err ,
570+ )
571+ }
572+ if err := o .gitRepo .DeleteBranch (ctx , branchName ); err != nil {
573+ return branchExists , remoteExists , fmt .Errorf ("failed to delete local branch %s: %w" , branchName , err )
574+ }
575+ o .logger (ctx ).Info ("Deleted local branch" , zap .String ("branch" , branchName ))
576+ }
577+ if remoteExists {
578+ if err := o .gitRepo .DeleteRemoteBranch (ctx , branchName ); err != nil {
579+ o .logger (ctx ).Warn ("Failed to delete remote branch, will force push" ,
580+ zap .String ("branch" , branchName ),
581+ zap .Error (err ))
582+ } else {
583+ o .logger (ctx ).Info ("Deleted remote branch" , zap .String ("branch" , branchName ))
584+ }
585+ }
586+ return false , false , nil
587+ }
588+
589+ func (o * PRReleaseOrchestrator ) logBranchStatus (
590+ ctx context.Context ,
591+ branchName string ,
592+ branchExists , remoteExists bool ,
593+ ) {
594+ switch {
595+ case branchExists && remoteExists :
596+ o .logger (ctx ).Info (
597+ "Reusing existing branch" ,
598+ zap .String ("branch" , branchName ),
599+ zap .Bool ("local" , true ),
600+ zap .Bool ("remote" , true ),
601+ )
602+ case branchExists :
603+ o .logger (ctx ).Info ("Checking out existing local branch" , zap .String ("branch" , branchName ))
604+ case remoteExists :
605+ o .logger (ctx ).Info ("Checking out remote branch" , zap .String ("branch" , branchName ))
606+ default :
607+ o .logger (ctx ).Info ("Creating new branch" , zap .String ("branch" , branchName ))
608+ }
609+ }
610+
527611func (o * PRReleaseOrchestrator ) addUpdatePackagesStep (
528612 saga * SagaExecutor ,
529613 compensator * CompensatingActions ,
0 commit comments