Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 43df26f

Browse files
feature/release: update sg release cut to automate stitch graph gen and release branch creation (#63794)
<!-- PR description tips: https://www.notion.so/sourcegraph/Write-a-good-pull-request-description-610a7fd3e613496eb76f450db5a49b6e --> Resolve: [https://linear.app/sourcegraph/issue/REL-253/bug-automate-the-stitched-migration-graph-bazel-archive-generation](https://linear.app/sourcegraph/issue/REL-253/bug-automate-the-stitched-migration-graph-bazel-archive-generation) ## Test plan Tested by running `go run ./dev/sg release cut --version “5.7.0"` <!-- REQUIRED; info at https://docs-legacy.sourcegraph.com/dev/background-information/testing_principles --> ## Changelog <!-- OPTIONAL; info at https://www.notion.so/sourcegraph/Writing-a-changelog-entry-dd997f411d524caabf0d8d38a24a878c --> --------- Co-authored-by: Anish Lakhwara <anish+git@lakhwara.com> Co-authored-by: Anish Lakhwara <anish+github@lakhwara.com>
1 parent 0bf40cd commit 43df26f

File tree

3 files changed

+134
-56
lines changed

3 files changed

+134
-56
lines changed

dev/sg/internal/release/cut.go

Lines changed: 123 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package release
22

33
import (
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

1822
func 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+
}

dev/sg/internal/release/release.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ var Command = &cli.Command{
199199
},
200200
{
201201
Name: "cut",
202-
Usage: "Cut a release",
202+
Usage: "Cut a minor release branch",
203203
Category: category.Util,
204204
UsageText: "sg release cut",
205205
Action: cutReleaseBranch,

dev/sg/internal/repo/git_repo.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ func (g *GitRepo) IsDirty(ctx context.Context) (bool, error) {
110110
return len(files) > 0, nil
111111
}
112112

113+
// Add a file to staged changes
114+
func (g *GitRepo) Add(ctx context.Context, file string) (string, error) {
115+
return run.Cmd(ctx, "git", "add", file).Run().String()
116+
}
117+
118+
// Commit commits the staged changes with the given message
119+
func (g *GitRepo) Commit(ctx context.Context, message string) (string, error) {
120+
return run.Cmd(ctx, "git", "commit", "-m", message).Run().String()
121+
}
122+
113123
// Push pushes the current branch to origin using the specific push options
114124
func (g *GitRepo) Push(ctx context.Context, opts ...pushOpt) (string, error) {
115125
cmd := []string{"git", "push", "origin", g.Branch}

0 commit comments

Comments
 (0)