From 7643c8e19d68daf6557e38387678149231c86907 Mon Sep 17 00:00:00 2001 From: "tuti." Date: Fri, 20 Dec 2024 05:56:45 -0800 Subject: [PATCH] [release-tool] refactor update branch command and manager (#9616) * update branching and versioning * address review feedback --- release/cmd/branch.go | 10 +--- release/cmd/flags.go | 14 ++++++ release/internal/version/version.go | 68 ++++++++++++++++++++++----- release/pkg/manager/branch/manager.go | 34 +++++++------- 4 files changed, 90 insertions(+), 36 deletions(-) diff --git a/release/cmd/branch.go b/release/cmd/branch.go index bad0bb05f88..08e50146d72 100644 --- a/release/cmd/branch.go +++ b/release/cmd/branch.go @@ -18,7 +18,6 @@ import ( "fmt" "path/filepath" - "github.com/sirupsen/logrus" cli "github.com/urfave/cli/v2" "github.com/projectcalico/calico/release/internal/utils" @@ -53,7 +52,7 @@ func branchSubCommands(cfg *Config) []*cli.Command { skipValidationFlag, }, Action: func(c *cli.Context) error { - configureLogging("cut-branch.log") + configureLogging("branch-cut.log") m := branch.NewManager( branch.WithRepoRoot(cfg.RepoRootDir), @@ -79,12 +78,7 @@ func branchSubCommands(cfg *Config) []*cli.Command { skipValidationFlag, ), Action: func(c *cli.Context) error { - configureLogging("cut-operator-branch.log") - - // Warn if the new branch is not the default base branch - if c.String(newBranchFlag.Name) != newBranchFlag.Value { - logrus.Warnf("The new branch will be created from %s which is not the default branch %s", c.String(newBranchFlag.Name), newBranchFlag.Value) - } + configureLogging("branch-cut-operator.log") // Clone the operator repository operatorDir := filepath.Join(cfg.TmpDir, operator.DefaultRepoName) diff --git a/release/cmd/flags.go b/release/cmd/flags.go index 62133afd228..33d44ee1a83 100644 --- a/release/cmd/flags.go +++ b/release/cmd/flags.go @@ -98,6 +98,13 @@ var ( Name: "base-branch", Usage: "The base branch to cut the release branch from", EnvVars: []string{"RELEASE_BRANCH_BASE"}, + Value: utils.DefaultBranch, + Action: func(c *cli.Context, str string) error { + if str != utils.DefaultBranch { + logrus.Warnf("The new branch will be created from %s which is not the default branch %s", str, utils.DefaultBranch) + } + return nil + }, } ) @@ -216,6 +223,13 @@ var ( Name: operatorBranchFlag.Name, Usage: "The base branch to cut the Tigera operator release branch from", EnvVars: []string{"OPERATOR_BRANCH_BASE"}, + Value: operator.DefaultBranchName, + Action: func(c *cli.Context, str string) error { + if str != operator.DefaultBranchName { + logrus.Warnf("The new branch will be created from %s which is not the default branch %s", str, operator.DefaultBranchName) + } + return nil + }, } // Container image flags diff --git a/release/internal/version/version.go b/release/internal/version/version.go index f5aa333e32e..7565ba0320d 100644 --- a/release/internal/version/version.go +++ b/release/internal/version/version.go @@ -100,10 +100,21 @@ func (v *Version) Milestone() string { return fmt.Sprintf("%s v%d.%d.%d", utils.DisplayProductName(), ver.Major(), ver.Minor(), ver.Patch()) } -// Stream returns the "release stream" of the version, i.e., the major and minor version without the patch version. +// Stream returns the "release stream" of the version. +// Typically it is the major and minor version without the patch version. +// For example, for version "v3.15.0", the stream is "v3.15". +// +// Early preview versions are handled differently. +// For example, for version "v3.15.0-1.0", the stream is "v3.15-1". +// For version "v3.15.0-2.0", the stream is "v3.15" (same as v3.15.1+). func (v *Version) Stream() string { - ver := semver.MustParse(string(*v)) - return fmt.Sprintf("v%d.%d", ver.Major(), ver.Minor()) + ver := v.Semver() + ep, epVer := IsEarlyPreviewVersion(ver) + stream := fmt.Sprintf("v%d.%d", ver.Major(), ver.Minor()) + if ep && epVer == 1 { + return fmt.Sprintf("%s-1", v.String()) + } + return stream } func (v *Version) Semver() *semver.Version { @@ -111,6 +122,33 @@ func (v *Version) Semver() *semver.Version { return ver } +// NextBranchVersion returns version of the next branch. +// If the version is a EP1 version, then return EP2. +// Otherwise, increment the minor version. +func (v *Version) NextBranchVersion() Version { + ver := v.Semver() + ep, epVer := IsEarlyPreviewVersion(ver) + if ep && epVer == 1 { + return New(fmt.Sprintf("v%d.%d.0-2.0", ver.Major(), ver.Minor())) + } + return New(ver.IncMinor().String()) +} + +// IsEarlyPreviewVersion handles the logic for determining if a version is an early preview (EP) version. +// +// An early preview version is a version that has a prerelease tag starting with "1." or "2.". +// The function returns true if it is an EP and EP major version as EP 1 is treated differently from EP 2. +func IsEarlyPreviewVersion(v *semver.Version) (bool, int) { + if v.Prerelease() != "" { + if strings.HasPrefix(v.Prerelease(), "1.") { + return true, 1 + } else if strings.HasPrefix(v.Prerelease(), "2.") { + return true, 2 + } + } + return false, -1 +} + // GitVersion returns the current git version of the directory as a Version object. func GitVersion() Version { // First, determine the git revision. @@ -123,30 +161,38 @@ func GitVersion() Version { } // HasDevTag returns true if the version has the given dev tag suffix. -// The dev tag suffix is expected to be in the format "vX.Y.Z--N-gCOMMIT" or "vX.Y.Z--N-gCOMMIT-dirty". +// The dev tag suffix is expected to be in one of the following formats: +// - vX.Y.Z--N-gCOMMIT +// - vX.Y.Z--N-gCOMMIT-dirty +// - vX.Y.Z-A.B--N-gCOMMIT +// - vX.Y.Z-A.B--N-gCOMMIT-dirty +// +// where vX.Y.Z is the semver version, is the dev tag suffix, N is the number of commits since the tag, +// A.B is the EP version, and COMMIT is the git commit hash abbreviated to 12 characters (e.g., 1a2b3c4d5e67). +// The "dirty" suffix indicates that the working directory is dirty. func HasDevTag(v Version, devTagSuffix string) bool { devTagSuffix = strings.TrimPrefix(devTagSuffix, "-") - re := regexp.MustCompile(fmt.Sprintf(`^v\d+\.\d+\.\d+-%s-\d+-g[0-9a-f]{12}(-dirty)?$`, devTagSuffix)) + re := regexp.MustCompile(fmt.Sprintf(`^v\d+\.\d+\.\d+(-\d+\.\d+)?-%s-\d+-g[0-9a-f]{12}(-dirty)?$`, devTagSuffix)) return re.MatchString(string(v)) } // DetermineReleaseVersion uses historical clues to figure out the next semver // release number to use for this release based on the current git revision. -// -// OSS Calico uses the following rules: -// - If the current git revision is a "vX.Y.Z-0.dev-N-gCOMMIT" tag, then the next release version is simply vX.Y.Z. -// - If the current git revision is a patch release (e.g., vX.Y.Z-N-gCOMMIT), then the next release version is vX.Y.Z+1. +// - If the current git revision is a "vX.Y.Z--N-gCOMMIT" tag, then the next release version is simply vX.Y.Z. +// - If the current git revision is a patch release with no dev tag (e.g., vX.Y.Z-N-gCOMMIT), then the next release version is vX.Y.Z+1. +// - If the current git revision is a patch release with a dev tag (e.g., vX.Y.Z--N-gCOMMIT), then the next release version is vX.Y.Z. func DetermineReleaseVersion(v Version, devTagSuffix string) (Version, error) { gitVersion := v.FormattedString() // There are two types of tag that this might be - either it was a previous patch release, - // or it was a "vX.Y.Z-0.dev" tag produced when cutting the release branch. + // or it was a "vX.Y.Z-" tag produced when cutting the release branch. if HasDevTag(v, devTagSuffix) { // This is the first release from this branch - we can simply extract the version from // the dev tag. if !strings.HasPrefix(devTagSuffix, "-") { // The dev tag marker should start with a hyphen. - // For example in "v3.15.0-0.dev-1-g1234567", we want to split on the "-0.dev" part. + // For example in "v3.15.0-0.dev-1-g1a2b3c4d5e67" with devTagSuffix "0.dev", + // we want to split on the "-0.dev" part and return "v3.15.0". devTagSuffix = "-" + devTagSuffix } return New(strings.Split(gitVersion, devTagSuffix)[0]), nil diff --git a/release/pkg/manager/branch/manager.go b/release/pkg/manager/branch/manager.go index 64690cbd108..0347288eb9f 100644 --- a/release/pkg/manager/branch/manager.go +++ b/release/pkg/manager/branch/manager.go @@ -88,13 +88,15 @@ func NewManager(opts ...Option) *BranchManager { return b } -func (b *BranchManager) CutVersionedBranch(version string) error { +// CutVersionedBranch creates a new release branch from the main branch +// with the given stream. New branch name will be - +func (b *BranchManager) CutVersionedBranch(stream string) error { if b.validate { if err := b.PreBranchCutValidation(); err != nil { return fmt.Errorf("pre-branch cut validation failed: %s", err) } } - newBranchName := fmt.Sprintf("%s-%s", b.releaseBranchPrefix, version) + newBranchName := fmt.Sprintf("%s-%s", b.releaseBranchPrefix, stream) logrus.WithField("branch", newBranchName).Info("Creating new release branch") if _, err := b.git("checkout", "-b", newBranchName); err != nil { return err @@ -113,31 +115,36 @@ func (b *BranchManager) CutReleaseBranch() error { return fmt.Errorf("pre-branch cut validation failed: %s", err) } } + if _, err := b.git("fetch", b.remote); err != nil { + return fmt.Errorf("failed to fetch remote %s: %s", b.remote, err) + } + if _, err := b.git("switch", "-f", "-C", b.mainBranch, "--track", fmt.Sprintf("%s/%s", b.remote, b.mainBranch)); err != nil { + return fmt.Errorf("failed to switch to %s: %s", b.mainBranch, err) + } gitVersion, err := command.GitVersion(b.repoRoot, true) if err != nil { return err } ver := version.New(gitVersion) - currentVersion := ver.Semver() - if err := b.CutVersionedBranch(fmt.Sprintf("v%d.%d", currentVersion.Major(), currentVersion.Minor())); err != nil { + if err := b.CutVersionedBranch(ver.Stream()); err != nil { return err } if _, err := b.git("checkout", b.mainBranch); err != nil { return err } - nextVersion := currentVersion.IncMinor() - nextVersionTag := fmt.Sprintf("v%d.%d.%d-%s", nextVersion.Major(), nextVersion.Minor(), nextVersion.Patch(), b.devTagIdentifier) + nextVersion := ver.NextBranchVersion() + nextVersionTag := fmt.Sprintf("%s-%s", nextVersion.FormattedString(), b.devTagIdentifier) logrus.WithField("tag", nextVersionTag).Info("Creating new development tag") - if _, err := b.git("commit", "--allow-empty", "-m", fmt.Sprintf("Begin development on v%d.%d", nextVersion.Major(), nextVersion.Minor())); err != nil { + if _, err := b.git("commit", "--allow-empty", "-m", fmt.Sprintf("Begin development for %s", nextVersion.FormattedString())); err != nil { + return err + } + if _, err := b.git("tag", nextVersionTag); err != nil { return err } if b.publish { if _, err := b.git("push", b.remote, b.mainBranch); err != nil { return err } - if _, err := b.git("tag", nextVersionTag); err != nil { - return err - } if _, err := b.git("push", b.remote, nextVersionTag); err != nil { return err } @@ -146,13 +153,6 @@ func (b *BranchManager) CutReleaseBranch() error { } func (b *BranchManager) PreBranchCutValidation() error { - branch, err := utils.GitBranch(b.repoRoot) - if err != nil { - return err - } - if branch != utils.DefaultBranch { - return fmt.Errorf("not on branch '%s', all new release branches must be cut from %s", utils.DefaultBranch, utils.DefaultBranch) - } if dirty, err := utils.GitIsDirty(b.repoRoot); err != nil { return err } else if dirty {