Skip to content

Commit

Permalink
feat: add support for calculating the next prerelease tag (#125)
Browse files Browse the repository at this point in the history
* feat: add support for calculating the next prerelease tag

Adds a `prerelease` command which will increment the prerelease version
based on what the next version will be from the git log, taking into
account the current prerelease version from the latest tag.

If `--pre-release` is passed in and contains a number then this will be
used and all calculations will be skipped. If `--pre-release` is passed
in and it matches the pre-release suffix of the current tag then
calculations will still occur.

Example:
```bash
$ svu current
v1.2.3-alpha.2+123

$ svu prerelease
v1.2.3-alpha.3

$ svu prerelease --pre-release alpha.33 --build 243
v1.2.3-alpha.33+243
```

Signed-off-by: Bradley Jones <jones.bradley@me.com>

* chore: change --pre-release flag as a subcommand of prerelease

Signed-off-by: Bradley Jones <bradley.jones@anchore.com>

---------

Signed-off-by: Bradley Jones <jones.bradley@me.com>
Signed-off-by: Bradley Jones <bradley.jones@anchore.com>
  • Loading branch information
bradleyjones authored Sep 12, 2023
1 parent d25f8d6 commit 881d9c3
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 17 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,28 @@ $ svu next --force-patch-increment
v1.2.4
```

### `svu prerelease`

Increases the pre-release of the latest tag and prints it. If a `pre-release`
identifier is passed in and it differs from the current pre-release tag that
the identifier passed in will be used. If the current tag is not a pre-release
tag then passing in `--pre-release` is required.

> alias: `svu pr`
**Examples:**

```bash
$ svu current
v1.2.3-alpha.2+123

$ svu prerelease
v1.2.3-alpha.3

$ svu prerelease --pre-release alpha.33 --build 243
v1.2.3-alpha.33+243
```

## tag mode

By default `svu` will get the latest tag from the current branch. Using the `--tag-mode` flag this behaviour can be altered:
Expand Down
96 changes: 79 additions & 17 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"runtime/debug"
"strconv"
"strings"

"github.com/Masterminds/semver"
Expand All @@ -13,20 +14,28 @@ import (
)

var (
app = kingpin.New("svu", "semantic version util")
nextCmd = app.Command("next", "prints the next version based on the git log").Alias("n").Default()
majorCmd = app.Command("major", "new major version")
minorCmd = app.Command("minor", "new minor version").Alias("m")
patchCmd = app.Command("patch", "new patch version").Alias("p")
currentCmd = app.Command("current", "prints current version").Alias("c")
pattern = app.Flag("pattern", "limits calculations to be based on tags matching the given pattern").String()
prefix = app.Flag("prefix", "set a custom prefix").Default("v").String()
stripPrefix = app.Flag("strip-prefix", "strips the prefix from the tag").Default("false").Bool()
preRelease = app.Flag("pre-release", "adds a pre-release suffix to the version, without the semver mandatory dash prefix").String()
build = app.Flag("build", "adds a build suffix to the version, without the semver mandatory plug prefix").String()
directory = app.Flag("directory", "specifies directory to filter commit messages by").Default("").String()
tagMode = app.Flag("tag-mode", "determines if latest tag of the current or all branches will be used").Default("current-branch").Enum("current-branch", "all-branches")
forcePatchIncrement = nextCmd.Flag("force-patch-increment", "forces a patch version increment regardless of the commit message content").Default("false").Bool()
app = kingpin.New("svu", "semantic version util")
nextCmd = app.Command("next", "prints the next version based on the git log").Alias("n").Default()
majorCmd = app.Command("major", "new major version")
minorCmd = app.Command("minor", "new minor version").Alias("m")
patchCmd = app.Command("patch", "new patch version").Alias("p")
currentCmd = app.Command("current", "prints current version").Alias("c")
preReleaseCmd = app.Command("prerelease", "new pre release version based on the next version calculated from git log").
Alias("pr")
preRelease = preReleaseCmd.Flag("pre-release", "adds a pre-release suffix to the version, without the semver mandatory dash prefix").
String()
pattern = app.Flag("pattern", "limits calculations to be based on tags matching the given pattern").String()
prefix = app.Flag("prefix", "set a custom prefix").Default("v").String()
stripPrefix = app.Flag("strip-prefix", "strips the prefix from the tag").Default("false").Bool()
build = app.Flag("build", "adds a build suffix to the version, without the semver mandatory plug prefix").
String()
directory = app.Flag("directory", "specifies directory to filter commit messages by").Default("").String()
tagMode = app.Flag("tag-mode", "determines if latest tag of the current or all branches will be used").
Default("current-branch").
Enum("current-branch", "all-branches")
forcePatchIncrement = nextCmd.Flag("force-patch-increment", "forces a patch version increment regardless of the commit message content").
Default("false").
Bool()
)

func main() {
Expand Down Expand Up @@ -82,17 +91,70 @@ func nextVersion(cmd string, current *semver.Version, tag, preRelease, build str
}

var err error
result, err = result.SetPrerelease(preRelease)
if err != nil {
return result, err
if cmd == preReleaseCmd.FullCommand() {
next := findNext(current, tag, *directory)
result, err = nextPreRelease(current, &next, preRelease)
if err != nil {
return result, err
}
} else {
result, err = result.SetPrerelease(preRelease)
if err != nil {
return result, err
}
}

result, err = result.SetMetadata(build)
if err != nil {
return result, err
}
return result, nil
}

func nextPreRelease(current, next *semver.Version, preRelease string) (semver.Version, error) {
suffix := ""
if preRelease != "" {
// Check if the suffix already contains a version number, if it does assume the user wants to explicitly set the version so use that
if _, err := strconv.Atoi(suffix); err == nil {
return current.SetPrerelease(suffix)
}
suffix = preRelease

// Check if the prerelease suffix is the same as the current prerelease
preSuffix := strings.Split(current.Prerelease(), ".")[0]
if preSuffix == preRelease {
suffix = current.Prerelease()
}
} else if current.Prerelease() != "" {
suffix = current.Prerelease()
} else {
return *current, fmt.Errorf(
"--pre-release suffix is required to calculate next pre-release version as suffix could not be determined from current version: %s",
current.String(),
)
}

splitSuffix := strings.Split(suffix, ".")
preReleaseName := splitSuffix[0]
preReleaseVersion := 0

currentWithoutPreRelease, _ := current.SetPrerelease("")

if !next.GreaterThan(&currentWithoutPreRelease) {
preReleaseVersion = -1
if len(splitSuffix) == 2 {
preReleaseName = splitSuffix[0]
preReleaseVersion, _ = strconv.Atoi(splitSuffix[1])
} else if len(splitSuffix) > 2 {
preReleaseName = splitSuffix[len(splitSuffix)-1]
}

preReleaseVersion++
}

return next.SetPrerelease(fmt.Sprintf("%s.%d", preReleaseName, preReleaseVersion))
}

func getCurrentVersion(tag string) (*semver.Version, error) {
var current *semver.Version
var err error
Expand Down
90 changes: 90 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"reflect"
"testing"

"github.com/Masterminds/semver"
Expand Down Expand Up @@ -159,3 +160,92 @@ func TestCmd(t *testing.T) {
})
})
}

func Test_nextPreRelease(t *testing.T) {
type args struct {
current *semver.Version
next *semver.Version
preRelease string
}
tests := []struct {
name string
args args
want semver.Version
wantErr bool
}{
{
name: "no current suffix and no suffix supplied",
args: args{
current: semver.MustParse("1.2.3"),
next: semver.MustParse("1.3.0"),
preRelease: "",
},
want: *semver.MustParse("1.3.0"),
wantErr: true,
},
{
name: "supplied suffix overrides current suffix",
args: args{
current: semver.MustParse("1.2.3-alpha.1"),
next: semver.MustParse("1.3.0"),
preRelease: "beta",
},
want: *semver.MustParse("1.3.0-beta.0"),
wantErr: false,
},
{
name: "current suffix is incremented",
args: args{
current: semver.MustParse("1.2.3-alpha.11"),
next: semver.MustParse("1.2.3"),
preRelease: "",
},
want: *semver.MustParse("1.2.3-alpha.12"),
wantErr: false,
},
{
name: "current suffix is incremented when supplied suffix matches current",
args: args{
current: semver.MustParse("1.2.3-alpha.11"),
next: semver.MustParse("1.2.3"),
preRelease: "alpha",
},
want: *semver.MustParse("1.2.3-alpha.12"),
wantErr: false,
},
{
name: "pre release version resets if next version changes",
args: args{
current: semver.MustParse("1.2.3-alpha.11"),
next: semver.MustParse("1.2.4"),
preRelease: "alpha",
},
want: *semver.MustParse("1.2.4-alpha.0"),
wantErr: false,
},
{
name: "increments a current tag that has build metadata",
args: args{
current: semver.MustParse("1.2.3-alpha.1+build.43"),
next: semver.MustParse("1.2.3"),
preRelease: "",
},
want: *semver.MustParse("1.2.3-alpha.2"),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := nextPreRelease(tt.args.current, tt.args.next, tt.args.preRelease)
if tt.wantErr {
if err == nil {
t.Errorf("nextPreRelease() error = %v, wantErr %v", err, tt.wantErr)
}
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("nextPreRelease() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 881d9c3

Please sign in to comment.