Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release-tool] refactor update branch command and manager #9616

Merged
merged 2 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is value in separating the version rules that OSS uses vs enterprise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, the way the rules are structured apply with OSS and Enterprise although Enterprise is only the first scenario since it creates a new dev tag after every release.

// - 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
Loading