refactor: move things to packages (#39)

This commit is contained in:
Julian Tölle 2024-08-31 15:23:21 +02:00 committed by GitHub
parent 44184a77f9
commit a0a064d387
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 923 additions and 892 deletions

View file

@ -0,0 +1,110 @@
package versioning
import (
"fmt"
"strings"
"github.com/blang/semver/v4"
"github.com/apricote/releaser-pleaser/internal/commitparser"
"github.com/apricote/releaser-pleaser/internal/git"
)
var _ Strategy = SemVerNextVersion
func SemVerNextVersion(r git.Releases, versionBump VersionBump, nextVersionType NextVersionType) (string, error) {
latest, err := parseSemverWithDefault(r.Latest)
if err != nil {
return "", fmt.Errorf("failed to parse latest version: %w", err)
}
stable, err := parseSemverWithDefault(r.Stable)
if err != nil {
return "", fmt.Errorf("failed to parse stable version: %w", err)
}
// If there is a previous stable release, we use that as the version anchor. Falling back to any pre-releases
// if they are the only tags in the repo.
next := latest
if r.Stable != nil {
next = stable
}
switch versionBump {
case UnknownVersion:
return "", fmt.Errorf("invalid latest bump (unknown)")
case PatchVersion:
err = next.IncrementPatch()
case MinorVersion:
err = next.IncrementMinor()
case MajorVersion:
err = next.IncrementMajor()
}
if err != nil {
return "", err
}
switch nextVersionType {
case NextVersionTypeUndefined, NextVersionTypeNormal:
next.Pre = make([]semver.PRVersion, 0)
case NextVersionTypeAlpha, NextVersionTypeBeta, NextVersionTypeRC:
id := uint64(0)
if len(latest.Pre) >= 2 && latest.Pre[0].String() == nextVersionType.String() {
if latest.Pre[1].String() == "" || !latest.Pre[1].IsNumeric() {
return "", fmt.Errorf("invalid format of previous tag")
}
id = latest.Pre[1].VersionNum + 1
}
setPRVersion(&next, nextVersionType.String(), id)
}
return "v" + next.String(), nil
}
func BumpFromCommits(commits []commitparser.AnalyzedCommit) VersionBump {
bump := UnknownVersion
for _, commit := range commits {
entryBump := UnknownVersion
switch {
case commit.BreakingChange:
entryBump = MajorVersion
case commit.Type == "feat":
entryBump = MinorVersion
case commit.Type == "fix":
entryBump = PatchVersion
}
if entryBump > bump {
bump = entryBump
}
}
return bump
}
func setPRVersion(version *semver.Version, prType string, count uint64) {
version.Pre = []semver.PRVersion{
{VersionStr: prType},
{VersionNum: count, IsNum: true},
}
}
func parseSemverWithDefault(tag *git.Tag) (semver.Version, error) {
version := "v0.0.0"
if tag != nil {
version = tag.Name
}
// The lib can not handle v prefixes
version = strings.TrimPrefix(version, "v")
parsedVersion, err := semver.Parse(version)
if err != nil {
return semver.Version{}, fmt.Errorf("failed to parse version %q: %w", version, err)
}
return parsedVersion, nil
}

View file

@ -0,0 +1,390 @@
package versioning
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/apricote/releaser-pleaser/internal/commitparser"
"github.com/apricote/releaser-pleaser/internal/git"
)
func TestReleases_NextVersion(t *testing.T) {
type args struct {
releases git.Releases
versionBump VersionBump
nextVersionType NextVersionType
}
tests := []struct {
name string
args args
want string
wantErr assert.ErrorAssertionFunc
}{
{
name: "simple bump (major)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: MajorVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v2.0.0",
wantErr: assert.NoError,
},
{
name: "simple bump (minor)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: MinorVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v1.2.0",
wantErr: assert.NoError,
},
{
name: "simple bump (patch)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v1.1.2",
wantErr: assert.NoError,
},
{
name: "normal to prerelease (major)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: MajorVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v2.0.0-rc.0",
wantErr: assert.NoError,
},
{
name: "normal to prerelease (minor)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: MinorVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v1.2.0-rc.0",
wantErr: assert.NoError,
},
{
name: "normal to prerelease (patch)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v1.1.2-rc.0",
wantErr: assert.NoError,
},
{
name: "prerelease bump (major)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v2.0.0-rc.0"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: MajorVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v2.0.0-rc.1",
wantErr: assert.NoError,
},
{
name: "prerelease bump (minor)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.2.0-rc.0"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: MinorVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v1.2.0-rc.1",
wantErr: assert.NoError,
},
{
name: "prerelease bump (patch)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.2-rc.0"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v1.1.2-rc.1",
wantErr: assert.NoError,
},
{
name: "prerelease different bump (major)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.2.0-rc.0"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: MajorVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v2.0.0-rc.1",
wantErr: assert.NoError,
},
{
name: "prerelease different bump (minor)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.2-rc.0"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: MinorVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v1.2.0-rc.1",
wantErr: assert.NoError,
},
{
name: "prerelease to prerelease",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1-alpha.2"},
Stable: &git.Tag{Name: "v1.1.0"},
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeRC,
},
want: "v1.1.1-rc.0",
wantErr: assert.NoError,
},
{
name: "prerelease to normal (explicit)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1-alpha.2"},
Stable: &git.Tag{Name: "v1.1.0"},
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeNormal,
},
want: "v1.1.1",
wantErr: assert.NoError,
},
{
name: "prerelease to normal (implicit)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1-alpha.2"},
Stable: &git.Tag{Name: "v1.1.0"},
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v1.1.1",
wantErr: assert.NoError,
},
{
name: "nil tag (major)",
args: args{
releases: git.Releases{
Latest: nil,
Stable: nil,
},
versionBump: MajorVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v1.0.0",
wantErr: assert.NoError,
},
{
name: "nil tag (minor)",
args: args{
releases: git.Releases{
Latest: nil,
Stable: nil,
},
versionBump: MinorVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v0.1.0",
wantErr: assert.NoError,
},
{
name: "nil tag (patch)",
args: args{
releases: git.Releases{
Latest: nil,
Stable: nil,
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v0.0.1",
wantErr: assert.NoError,
},
{
name: "nil stable release (major)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1-rc.0"},
Stable: nil,
},
versionBump: MajorVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v2.0.0",
wantErr: assert.NoError,
},
{
name: "nil stable release (minor)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1-rc.0"},
Stable: nil,
},
versionBump: MinorVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "v1.2.0",
wantErr: assert.NoError,
},
{
name: "nil stable release (patch)",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1-rc.0"},
Stable: nil,
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeUndefined,
},
// TODO: Is this actually correct our should it be v1.1.1?
want: "v1.1.2",
wantErr: assert.NoError,
},
{
name: "error on invalid tag semver",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "foodazzle"},
Stable: &git.Tag{Name: "foodazzle"},
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeRC,
},
want: "",
wantErr: assert.Error,
},
{
name: "error on invalid tag prerelease",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1-rc.foo"},
Stable: &git.Tag{Name: "v1.1.1-rc.foo"},
},
versionBump: PatchVersion,
nextVersionType: NextVersionTypeRC,
},
want: "",
wantErr: assert.Error,
},
{
name: "error on invalid bump",
args: args{
releases: git.Releases{
Latest: &git.Tag{Name: "v1.1.1"},
Stable: &git.Tag{Name: "v1.1.1"},
},
versionBump: UnknownVersion,
nextVersionType: NextVersionTypeUndefined,
},
want: "",
wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := SemVerNextVersion(tt.args.releases, tt.args.versionBump, tt.args.nextVersionType)
if !tt.wantErr(t, err, fmt.Sprintf("SemVerNextVersion(Releases(%v, %v), %v, %v)", tt.args.releases.Latest, tt.args.releases.Stable, tt.args.versionBump, tt.args.nextVersionType)) {
return
}
assert.Equalf(t, tt.want, got, "SemVerNextVersion(Releases(%v, %v), %v, %v)", tt.args.releases.Latest, tt.args.releases.Stable, tt.args.versionBump, tt.args.nextVersionType)
})
}
}
func TestVersionBumpFromCommits(t *testing.T) {
tests := []struct {
name string
analyzedCommits []commitparser.AnalyzedCommit
want VersionBump
}{
{
name: "no entries (unknown)",
analyzedCommits: []commitparser.AnalyzedCommit{},
want: UnknownVersion,
},
{
name: "non-release type (unknown)",
analyzedCommits: []commitparser.AnalyzedCommit{{Type: "docs"}},
want: UnknownVersion,
},
{
name: "single breaking (major)",
analyzedCommits: []commitparser.AnalyzedCommit{{BreakingChange: true}},
want: MajorVersion,
},
{
name: "single feat (minor)",
analyzedCommits: []commitparser.AnalyzedCommit{{Type: "feat"}},
want: MinorVersion,
},
{
name: "single fix (patch)",
analyzedCommits: []commitparser.AnalyzedCommit{{Type: "fix"}},
want: PatchVersion,
},
{
name: "multiple entries (major)",
analyzedCommits: []commitparser.AnalyzedCommit{{Type: "fix"}, {BreakingChange: true}},
want: MajorVersion,
},
{
name: "multiple entries (minor)",
analyzedCommits: []commitparser.AnalyzedCommit{{Type: "fix"}, {Type: "feat"}},
want: MinorVersion,
},
{
name: "multiple entries (patch)",
analyzedCommits: []commitparser.AnalyzedCommit{{Type: "docs"}, {Type: "fix"}},
want: PatchVersion,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, BumpFromCommits(tt.analyzedCommits), "BumpFromCommits(%v)", tt.analyzedCommits)
})
}
}

View file

@ -0,0 +1,56 @@
package versioning
import (
"github.com/leodido/go-conventionalcommits"
"github.com/apricote/releaser-pleaser/internal/git"
)
type Strategy = func(git.Releases, VersionBump, NextVersionType) (string, error)
type VersionBump conventionalcommits.VersionBump
const (
UnknownVersion VersionBump = iota
PatchVersion
MinorVersion
MajorVersion
)
type NextVersionType int
const (
NextVersionTypeUndefined NextVersionType = iota
NextVersionTypeNormal
NextVersionTypeRC
NextVersionTypeBeta
NextVersionTypeAlpha
)
func (n NextVersionType) String() string {
switch n {
case NextVersionTypeUndefined:
return "undefined"
case NextVersionTypeNormal:
return "normal"
case NextVersionTypeRC:
return "rc"
case NextVersionTypeBeta:
return "beta"
case NextVersionTypeAlpha:
return "alpha"
default:
return ""
}
}
func (n NextVersionType) IsPrerelease() bool {
switch n {
case NextVersionTypeRC, NextVersionTypeBeta, NextVersionTypeAlpha:
return true
case NextVersionTypeUndefined, NextVersionTypeNormal:
return false
default:
return false
}
}