11package release
22
33import (
4+ "context"
45 "fmt"
56 "os/exec"
7+ "strings"
68
7- "github.com/Masterminds/semver "
9+ "github.com/grafana/regexp "
810 "github.com/urfave/cli/v2"
9- "strings"
1011
11- "github.com/sourcegraph/sourcegraph/dev/sg/internal/execute"
12- "github.com/sourcegraph/sourcegraph/dev/sg/internal/repo"
1312 "github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
1413 "github.com/sourcegraph/sourcegraph/lib/errors"
1514 "github.com/sourcegraph/sourcegraph/lib/output"
15+
16+ "github.com/sourcegraph/run"
17+
18+ "github.com/sourcegraph/sourcegraph/dev/sg/internal/execute"
19+ "github.com/sourcegraph/sourcegraph/dev/sg/internal/repo"
1620)
1721
1822func cutReleaseBranch (cctx * cli.Context ) error {
@@ -24,48 +28,57 @@ func cutReleaseBranch(cctx *cli.Context) error {
2428 }
2529 p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Using GitHub CLI at %q" , ghPath ))
2630
27- var version string
28- if cctx .String ("version" ) == "auto" {
29- var err error
30- version , err = determineNextReleaseVersion (cctx .Context )
31- if err != nil {
32- return err
33- }
34- } else {
35- // Normalize the version string, to prevent issues where this was given with the wrong convention
36- // which requires a full rebuild.
37- version = fmt .Sprintf ("v%s" , strings .TrimPrefix (cctx .String ("version" ), "v" ))
38- }
39- v , err := semver .NewVersion (version )
40- if err != nil {
41- return errors .Newf ("invalid version %q, must be semver" , version )
42- }
43-
44- releaseBranch := v .String ()
31+ // Check that main has been pulled to the local branch
4532 defaultBranch := cctx .String ("branch" )
46-
47- ctx := cctx .Context
48- releaseGitRepoBranch := repo .NewGitRepo (releaseBranch , releaseBranch )
4933 defaultGitRepoBranch := repo .NewGitRepo (defaultBranch , defaultBranch )
50-
51- if ok , err := defaultGitRepoBranch .IsDirty (ctx ); err != nil {
52- return errors .Wrap (err , "check if current branch is dirty" )
34+ p = std .Out .Pending (output .Styled (output .StylePending , "Checking if the default branch exists locally..." ))
35+ if err := defaultGitRepoBranch .Checkout (cctx .Context ); err != nil {
36+ p .Destroy ()
37+ return errors .Wrapf (err , "checking out %q" , defaultBranch )
38+ }
39+ p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Default branch exists locally" ))
40+ p = std .Out .Pending (output .Styled (output .StylePending , "Checking if the default branch is up to date with remote ..." ))
41+ if _ , err := defaultGitRepoBranch .FetchOrigin (cctx .Context ); err != nil {
42+ p .Destroy ()
43+ return errors .Wrapf (err , "fetching origin for %q" , defaultBranch )
44+ }
45+ if ok , err := defaultGitRepoBranch .IsOutOfSync (cctx .Context ); err != nil {
46+ p .Destroy ()
47+ return errors .Wrapf (err , "checking if %q branch is up to date with remote" , defaultBranch )
5348 } else if ok {
54- return errors .Newf ("current branch is dirty. please commit your unstaged changes" )
49+ p .Destroy ()
50+ return errors .Newf ("local branch %q is not up to date with remote" , defaultBranch )
5551 }
52+ p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Local branch is up to date with remote" ))
5653
54+ // Normalize the version string, to prevent issues where this was given with the wrong convention
55+ // which requires a full rebuild.
56+ version := fmt .Sprintf ("v%s" , strings .TrimPrefix (cctx .String ("version" ), "v" ))
57+ if ! regexp .MustCompile ("\\ d+\\ .\\ d+\\ .0$" ).MatchString (version ) {
58+ return errors .Newf ("invalid version input %q, must be of the form X.Y.0" , version )
59+ }
60+ releaseBranch := strings .TrimPrefix (strings .Replace (version , ".0" , ".x" , 1 ), "v" )
61+
62+ // Ensure release branch conforms to release process policy
63+ if ! regexp .MustCompile ("\\ d+\\ .\\ d+\\ .x$" ).MatchString (releaseBranch ) {
64+ return errors .Newf ("invalid branch name %q, must be of the form X.Y.x" , releaseBranch )
65+ }
66+
67+ // Check that the branch doesn't exist locally
68+ releaseGitRepoBranch := repo .NewGitRepo (releaseBranch , releaseBranch )
5769 p = std .Out .Pending (output .Styled (output .StylePending , "Checking if the release branch exists locally ..." ))
58- if ok , err := releaseGitRepoBranch .HasLocalBranch (ctx ); err != nil {
70+ if ok , err := releaseGitRepoBranch .HasLocalBranch (cctx . Context ); err != nil {
5971 p .Destroy ()
60- return errors .Wrapf (err , "checking if %q branch exists localy " , releaseBranch )
72+ return errors .Wrapf (err , "checking if %q branch exists locally " , releaseBranch )
6173 } else if ok {
6274 p .Destroy ()
6375 return errors .Newf ("branch %q exists locally" , releaseBranch )
6476 }
6577 p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Release branch %q does not exist locally" , releaseBranch ))
6678
79+ // Check the remote doesn't have the new release branch already
6780 p = std .Out .Pending (output .Styled (output .StylePending , "Checking if the release branch exists in remote ..." ))
68- if ok , err := releaseGitRepoBranch .HasRemoteBranch (ctx ); err != nil {
81+ if ok , err := releaseGitRepoBranch .HasRemoteBranch (cctx . Context ); err != nil {
6982 p .Destroy ()
7083 return errors .Wrapf (err , "checking if %q branch exists in remote repo" , releaseBranch )
7184 } else if ok {
@@ -74,43 +87,61 @@ func cutReleaseBranch(cctx *cli.Context) error {
7487 }
7588 p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Release branch %q does not exist in remote" , releaseBranch ))
7689
77- p = std .Out .Pending (output .Styled (output .StylePending , "Checking if the default branch is up to date with remote ..." ))
78- if _ , err := defaultGitRepoBranch .FetchOrigin (ctx ); err != nil {
79- p .Destroy ()
80- return errors .Wrapf (err , "fetching origin for %q" , defaultBranch )
90+ // Checkout and push release branch
91+ err = releaseGitRepoBranch .CheckoutNewBranch (cctx .Context )
92+ if err != nil {
93+ return errors .Wrapf (err , "could not checkout release branch %q" , releaseBranch )
94+ }
95+ _ , err = releaseGitRepoBranch .Push (cctx .Context )
96+ if err != nil {
97+ return errors .Wrapf (err , "could not push release branch %q" , releaseBranch )
8198 }
8299
83- if err := defaultGitRepoBranch .Checkout (ctx ); err != nil {
100+ // generate max string const and migration graph
101+ p = std .Out .Pending (output .Styled (output .StylePending , "Increasing max version number const" ))
102+ err = replaceMaxVersion (cctx .Context , version )
103+ if err != nil {
84104 p .Destroy ()
85- return errors . Wrapf ( err , "checking out %q" , defaultBranch )
105+ return err
86106 }
107+ p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Max version number increased" ))
87108
88- if ok , err := defaultGitRepoBranch .IsOutOfSync (ctx ); err != nil {
89- p .Destroy ()
90- return errors .Wrapf (err , "checking if %q branch is up to date with remote" , defaultBranch )
91- } else if ok {
109+ p = std .Out .Pending (output .Styled (output .StylePending , "Generating stitched migration graph" ))
110+ err = genStitchMigrationGraph (cctx .Context )
111+ if err != nil {
92112 p .Destroy ()
93- return errors . Newf ( "local branch %q is not up to date with remote" , defaultBranch )
113+ return err
94114 }
95- p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Local branch is up to date with remote " ))
115+ p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Stitched migration graph generated " ))
96116
97- p = std .Out .Pending (output .Styled (output .StylePending , "Creating release branch..." ))
98- if err := releaseGitRepoBranch .CheckoutNewBranch (ctx ); err != nil {
99- p .Destroy ()
100- return errors .Wrap (err , "failed to create release branch" )
117+ // commit changes
118+ _ , err = releaseGitRepoBranch .Add (cctx .Context , "internal/database/migration/shared/data/cmd/generator/consts.go" )
119+ if err != nil {
120+ return errors .Wrap (err , "could not add consts.go to staged changes" )
121+ }
122+ _ , err = releaseGitRepoBranch .Add (cctx .Context , "internal/database/migration/shared/data/stitched-migration-graph.json" )
123+ if err != nil {
124+ return errors .Wrap (err , "could not add stitched-migration-graph.json to staged changes" )
125+ }
126+ _ , err = releaseGitRepoBranch .Commit (cctx .Context , "chore: update max version" )
127+ if err != nil {
128+ return errors .Wrap (err , "could not commit staged changes" )
129+ }
130+ _ , err = releaseGitRepoBranch .Push (cctx .Context )
131+ if err != nil {
132+ return errors .Wrap (err , "could not push staged changes" )
101133 }
102- p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Release branch %q created" , releaseBranch ))
103134
104- p = std . Out . Pending ( output . Styled ( output . StylePending , "Pushing release branch..." ))
105- if _ , err := releaseGitRepoBranch . Push ( ctx ); err != nil {
106- p . Destroy ()
107- return errors . Wrap ( err , "failed to push release branch" )
135+ // generate new stitched migration archive
136+ err = genStitchMigrationArchive ( cctx . Context , version )
137+ if err != nil {
138+ return err
108139 }
109- p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Release branch %q pushed" , releaseBranch ))
110140
141+ // Create backport label
111142 p = std .Out .Pending (output .Styled (output .StylePending , "Creating backport label..." ))
112143 if _ , err := execute .GH (
113- ctx ,
144+ cctx . Context ,
114145 "label" ,
115146 "create" ,
116147 fmt .Sprintf ("backport %s" , releaseBranch ),
@@ -124,3 +155,40 @@ func cutReleaseBranch(cctx *cli.Context) error {
124155
125156 return nil
126157}
158+
159+ func replaceMaxVersion (ctx context.Context , newVersion string ) error {
160+ p := std .Out .Pending (output .Styled (output .StylePending , "Checking for comby CLI..." ))
161+ combyPath , err := exec .LookPath ("comby" )
162+ if err != nil {
163+ p .Destroy ()
164+ return errors .Wrap (err , "Comby (https://comby.dev/) is required for installation" )
165+ }
166+ p .Complete (output .Linef (output .EmojiSuccess , output .StyleSuccess , "Using Comby at %q" , combyPath ))
167+ noVNewVersion := strings .TrimPrefix (newVersion , "v" )
168+ err = run .Cmd (ctx , "comby" , "-in-place" , "\" const maxVersionString = :[1]\" " , fmt .Sprintf ("\" const maxVersionString = \\ \" %s\\ \" \" " , noVNewVersion ), "internal/database/migration/shared/data/cmd/generator/consts.go" ).Run ().Wait ()
169+ if err != nil {
170+ return errors .Wrap (err , "Could not run comby to change maxVersionString" )
171+ }
172+ return nil
173+ }
174+
175+ func genStitchMigrationGraph (ctx context.Context ) error {
176+ err := run .Cmd (ctx , "sg" , "bazel" , "run" , "//internal/database/migration/shared:write_stitched_migration_graph" ).Run ().Wait ()
177+ if err != nil {
178+ return errors .Wrap (err , "Could not run stitch migration generator" )
179+ }
180+ return nil
181+ }
182+
183+ // TODO set timestamp during archive generation for cache
184+ func genStitchMigrationArchive (ctx context.Context , newVersion string ) error {
185+ err := run .Cmd (ctx , "git" , "archive" , "--format=tar.gz" , "HEAD" , "migrations" , "-o" , fmt .Sprintf ("migrations-%s.tar.gz" , newVersion )).Run ().Wait ()
186+ if err != nil {
187+ return errors .Wrap (err , "Could not create git archive" )
188+ }
189+ err = run .Cmd (ctx , "CLOUDSDK_CORE_PROJECT=\" sourcegraph-ci\" " , "gsutil" , "cp" , fmt .Sprintf ("migrations-%s" , newVersion ), "gs://schemas-migrations/migrations/" ).Run ().Wait ()
190+ if err != nil {
191+ return errors .Wrap (err , "Could not push git archive to GCS" )
192+ }
193+ return nil
194+ }
0 commit comments