Skip to content

Commit

Permalink
[release-tool] refactor update branch command and manager (projectcal…
Browse files Browse the repository at this point in the history
…ico#9616)

* update branching and versioning

* address review feedback
  • Loading branch information
radTuti committed Dec 20, 2024
1 parent 08a0e9a commit 7643c8e
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 36 deletions.
10 changes: 2 additions & 8 deletions release/cmd/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
Expand All @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions release/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
}
)

Expand Down Expand Up @@ -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
Expand Down
68 changes: 57 additions & 11 deletions release/internal/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,55 @@ 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 {
ver := semver.MustParse(string(*v))
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.
Expand All @@ -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-<devTagSuffix>-N-gCOMMIT" or "vX.Y.Z-<devTagSuffix>-N-gCOMMIT-dirty".
// The dev tag suffix is expected to be in one of the following formats:
// - vX.Y.Z-<devTagSuffix>-N-gCOMMIT
// - vX.Y.Z-<devTagSuffix>-N-gCOMMIT-dirty
// - vX.Y.Z-A.B-<devTagSuffix>-N-gCOMMIT
// - vX.Y.Z-A.B-<devTagSuffix>-N-gCOMMIT-dirty
//
// where vX.Y.Z is the semver version, <devTagSuffix> 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-<devTagSuffix>-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-<devTagSuffix>-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-<devTagSuffix>" 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
Expand Down
34 changes: 17 additions & 17 deletions release/pkg/manager/branch/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <releaseBranchPrefix>-<stream>
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
Expand All @@ -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
}
Expand All @@ -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 {
Expand Down

0 comments on commit 7643c8e

Please sign in to comment.