@@ -102,6 +102,10 @@ type prRepo struct {
102102 workOnRepoError error
103103}
104104
105+ type metadata struct {
106+ PRs []string `json:"prs"`
107+ }
108+
105109func (r prRepo ) String () string {
106110 return r .owner + "/" + r .repo + "@" + r .branch
107111}
@@ -335,12 +339,32 @@ func updateVersions(_ *cobra.Command, _ []string) error {
335339 workOnRepoErrors = append (workOnRepoErrors , err )
336340 log .Printf ("%s" , err )
337341 }
342+ if artifactsDir != "" {
343+ if err := saveMetadata (artifactsDir , metadata {PRs : prs }); err != nil {
344+ err = fmt .Errorf ("error saving metadata: %w" , err )
345+ workOnRepoErrors = append (workOnRepoErrors , err )
346+ log .Printf ("%s" , err )
347+ }
348+ }
338349 if len (workOnRepoErrors ) > 0 {
339350 return errors .Join (workOnRepoErrors ... )
340351 }
341352 return nil
342353}
343354
355+ func saveMetadata (dir string , meta metadata ) error {
356+ dest := path .Join (dir , "prs.json" )
357+ log .Printf ("saving metadata to %s" , dest )
358+ data , err := json .MarshalIndent (meta , "" , " " )
359+ if err != nil {
360+ return fmt .Errorf ("error marshaling PR metadata: %w" , err )
361+ }
362+ if err := os .WriteFile (dest , data , 0o644 ); err != nil {
363+ return fmt .Errorf ("error writing PR metadata file: %w" , err )
364+ }
365+ return nil
366+ }
367+
344368func sendPrReport (version * semver.Version , prs []string , smtpPassword string ) error {
345369 log .Println ("========================================================" )
346370 log .Println ("The following PRs are created:" )
@@ -436,12 +460,8 @@ func generateRepoList(
436460 log .Printf ("not bumping version on staging branch %s" , branch )
437461 continue
438462 }
439- ok , err := fileExistsInGit (branch , versionFile )
440- if err != nil {
441- return []prRepo {}, fmt .Errorf ("checking version file: %w" , err )
442- }
443- if ! ok {
444- log .Printf ("skipping version bump on the %s branch, because %s does not exist on that branch" , branch , versionFile )
463+ if branch == fmt .Sprintf ("release-%s-rc" , releasedVersion .String ()) {
464+ log .Printf ("not bumping version on the same branch %s" , branch )
445465 continue
446466 }
447467 curVersion , err := fileContent (remoteOrigin + "/" + branch , versionFile )
@@ -522,7 +542,7 @@ func generateRepoList(
522542 // 5. Merge baking branch back to the release branch.
523543 maybeBakingbranches := []string {
524544 fmt .Sprintf ("release-%s-rc" , releasedVersion .String ()), // e.g. release-23.1.17-rc
525- fmt .Sprintf ("staging-%s" , releasedVersion .Original ()), // e.g. staging-v23.1.17
545+ fmt .Sprintf ("staging-v %s" , releasedVersion .String ()), // e.g. staging-v23.1.17
526546 }
527547 var bakingBranches []string
528548 for _ , branch := range maybeBakingbranches {
@@ -535,8 +555,23 @@ func generateRepoList(
535555 if len (bakingBranches ) > 1 {
536556 return []prRepo {}, fmt .Errorf ("too many baking branches: %s" , strings .Join (maybeBakingbranches , ", " ))
537557 }
558+ // 6. Merge baking branch to the main release branch (e.g. release-25.1).
559+ // For pre-releases we may have no baking branches, thus we use `for` loop
560+ // to simplify the code.
538561 for _ , mergeBranch := range bakingBranches {
539562 baseBranch := fmt .Sprintf ("release-%d.%d" , releasedVersion .Major (), releasedVersion .Minor ())
563+ // Sometimes there are no changes on the baking/staging branches and a merge is not needed.
564+ alreadyOnBaseBranch , err := isAncestor (mergeBranch , baseBranch )
565+ if err != nil {
566+ return []prRepo {}, fmt .Errorf ("checking if %s is ancestor of %s: %w" , baseBranch , mergeBranch , err )
567+ }
568+ if alreadyOnBaseBranch {
569+ log .Printf ("skipping merge of %s to %s, because %s is already an ancestor of %s" , mergeBranch , baseBranch , mergeBranch , baseBranch )
570+ continue
571+ }
572+ // TODO: add a check to make sure the merge generates no unexpected
573+ // changes (we can ignore version.txt changes). The "ours" strategy
574+ // doesn't account for changes in the merge branch.
540575 repo := prRepo {
541576 owner : owner ,
542577 repo : prefix + "cockroach" ,
@@ -545,7 +580,7 @@ func generateRepoList(
545580 githubUsername : "cockroach-teamcity" ,
546581 commitMessage : generateCommitMessage (fmt .Sprintf ("merge %s to %s" , mergeBranch , baseBranch ), releasedVersion , nextVersion ),
547582 fn : func (gitDir string ) error {
548- cmd := exec .Command ("git" , "merge" , "-s" , "ours" , "--no-commit" , "origin /"+ mergeBranch )
583+ cmd := exec .Command ("git" , "merge" , "-s" , "ours" , "--no-commit" , remoteOrigin + " /"+ mergeBranch )
549584 cmd .Dir = gitDir
550585 out , err := cmd .CombinedOutput ()
551586 if err != nil {
@@ -557,6 +592,68 @@ func generateRepoList(
557592 }
558593 reposToWorkOn = append (reposToWorkOn , repo )
559594 }
595+ // 7. Merge staging branch to the next release RC branch if it is present.
596+ for _ , mergeBranch := range bakingBranches {
597+ // When we have extraordinary releases, we may have next release RC
598+ // branches created. Make sure we merge this branch to the RC branch
599+ // only if there are changes.
600+ if ! strings .HasPrefix (mergeBranch , "staging-" ) {
601+ log .Printf ("skipping merge of %s, because it's not a staging branch" , mergeBranch )
602+ continue
603+ }
604+ nextRCBranch := fmt .Sprintf ("release-%s-rc" , nextVersion .String ())
605+ maybeNextReleaseRCBranches , err := listRemoteBranches (nextRCBranch )
606+ if err != nil {
607+ return []prRepo {}, fmt .Errorf ("listing rc branch %s: %w" , nextRCBranch , err )
608+ }
609+ if len (maybeNextReleaseRCBranches ) < 1 {
610+ log .Printf ("no next release RC branches found, skipping merge to %s" , nextRCBranch )
611+ continue
612+ }
613+ alreadyOnRCBranch , err := isAncestor (mergeBranch , nextRCBranch )
614+ if err != nil {
615+ return []prRepo {}, fmt .Errorf ("checking if %s is ancestor of %s: %w" , mergeBranch , nextRCBranch , err )
616+ }
617+ if alreadyOnRCBranch {
618+ log .Printf ("skipping merge of %s to %s, because %s is already an ancestor of %s" , mergeBranch , nextRCBranch , mergeBranch , nextRCBranch )
619+ continue
620+ }
621+ // try to merge and see if anything is changed, ignore version.txt changes.
622+ createsMergeCommit , err := mergeCreatesContentChanges (mergeBranch , nextRCBranch , []string {versionFile })
623+ if err != nil {
624+ return []prRepo {}, fmt .Errorf ("checking if merge creates content changes: %w" , err )
625+ }
626+ if ! createsMergeCommit {
627+ log .Printf ("skipping merge of %s to %s, because the merge does not create content changes" , mergeBranch , nextRCBranch )
628+ continue
629+ }
630+ repo := prRepo {
631+ owner : owner ,
632+ repo : prefix + "cockroach" ,
633+ branch : nextRCBranch ,
634+ prBranch : fmt .Sprintf ("merge-%s-to-%s-%s" , mergeBranch , nextRCBranch , randomString (4 )),
635+ githubUsername : "cockroach-teamcity" ,
636+ commitMessage : generateCommitMessage (fmt .Sprintf ("merge %s to %s" , mergeBranch , nextRCBranch ), releasedVersion , nextVersion ),
637+ fn : func (gitDir string ) error {
638+ cmd := exec .Command ("git" , "merge" , "-X" , "ours" , "--strategy=recursive" , "--no-commit" , "--no-ff" , remoteOrigin + "/" + mergeBranch )
639+ cmd .Dir = gitDir
640+ out , err := cmd .CombinedOutput ()
641+ if err != nil {
642+ return fmt .Errorf ("failed running '%s' with message '%s': %w" , cmd .String (), string (out ), err )
643+ }
644+ log .Printf ("ran '%s': %s\n " , cmd .String (), string (out ))
645+ coCmd := exec .Command ("git" , "checkout" , remoteOrigin + "/" + nextRCBranch , "--" , versionFile )
646+ coCmd .Dir = gitDir
647+ out , err = coCmd .CombinedOutput ()
648+ if err != nil {
649+ return fmt .Errorf ("failed running '%s' with message '%s': %w" , coCmd .String (), string (out ), err )
650+ }
651+ log .Printf ("ran '%s': %s\n " , coCmd .String (), string (out ))
652+ return nil
653+ },
654+ }
655+ reposToWorkOn = append (reposToWorkOn , repo )
656+ }
560657 return reposToWorkOn , nil
561658}
562659
0 commit comments