diff --git a/README.md b/README.md index 71dcad1..a45fc23 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,8 @@ autotag --strict-match - Use `-T/--pre-release-timestmap=` to append **timestamp** to the version. Allowed timetstamp formats are `datetime` (YYYYMMDDHHMMSS) or `epoch` (UNIX epoch timestamp in seconds). + +- Use `--pre-release-number` to append pre-release number to the version. Pre-release is also mentioned in the [SemVer](https://semver.org/#spec-item-9) spec. Note: `--pre-release-number` is used only when option `--pre-release-timestmap` isn't enabled. ### Build metadata diff --git a/autotag.go b/autotag.go index 07eecfa..e7429e1 100644 --- a/autotag.go +++ b/autotag.go @@ -99,6 +99,15 @@ type GitRepoConfig struct { // v1.2.3-pre.1499308568 PreReleaseTimestampLayout string + // PreReleaseNumber is the optional flag that's used to tell program append a + // build number to the git tag as second part of prerelease. + // + // Assuming PreReleaseName is set to `pre`, the PreReleaseBuildNumber is appended to + // that value separated by a period (`.`): + // + // v1.2.3-pre.1 + PreReleaseNumber bool + // BuildMetadata is an optional string appended by a plus sign and a series of dot separated // identifiers immediately following the patch or pre-release version. Identifiers MUST comprise // only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Build metadata @@ -146,11 +155,13 @@ type GitRepo struct { branch string branchID string // commit id of the branch latest commit (where we will apply the tag) + curPreReleaseVer *version.Version latestTagVersion *version.Version latestTagCommit *git.Commit preReleaseName string preReleaseTimestampLayout string + preReleaseNumber bool buildMetadata string scheme string @@ -214,6 +225,7 @@ func NewRepo(cfg GitRepoConfig) (*GitRepo, error) { branch: cfg.Branch, preReleaseName: cfg.PreReleaseName, preReleaseTimestampLayout: cfg.PreReleaseTimestampLayout, + preReleaseNumber: cfg.PreReleaseNumber, buildMetadata: cfg.BuildMetadata, scheme: cfg.Scheme, prefix: cfg.Prefix, @@ -310,6 +322,13 @@ func (r *GitRepo) parseTags() error { r.latestTagCommit = versions[version] } + // stamps latest tag for pre-release + if r.preReleaseName != "" && version.Prerelease() != "" && r.curPreReleaseVer == nil { + if strings.HasPrefix(version.Prerelease(), fmt.Sprintf("%s.", r.preReleaseName)) { + r.curPreReleaseVer = version + } + } + if len(version.Prerelease()) == 0 { r.currentVersion = version r.currentTag = versions[version] @@ -365,7 +384,7 @@ func (r *GitRepo) retrieveBranchInfo() error { return nil } -func preReleaseVersion(v *version.Version, name, tsLayout string) (*version.Version, error) { +func preReleaseVersion(v, curPrereleaseVer *version.Version, name, tsLayout string, autoIncrease bool) (*version.Version, error) { if len(name) == 0 && len(tsLayout) == 0 { return v, nil } @@ -405,6 +424,33 @@ func preReleaseVersion(v *version.Version, name, tsLayout string) (*version.Vers if _, err := buf.WriteString(timestamp); err != nil { return nil, err } + } else { + if len(name) > 0 && autoIncrease { + // Write the `.` character + if buf.Len() > 0 { + if _, err := buf.WriteString("."); err != nil { + return nil, err + } + } + + prereleaseNumber := "1" + if curPrereleaseVer != nil { + prerelease := curPrereleaseVer.Prerelease() + prereleaseParts := strings.Split(prerelease, ".") + if len(prereleaseParts) == 2 { + currentPrereleaseNumber, err := strconv.ParseUint(prereleaseParts[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("prerelease build number must be a unsigned integer") + } + + prereleaseNumber = strconv.FormatUint(currentPrereleaseNumber+1, 10) + } + } + + if _, err := buf.WriteString(prereleaseNumber); err != nil { + return nil, err + } + } } verStr := fmt.Sprintf("%s-%s", v.String(), buf.String()) @@ -466,7 +512,7 @@ func (r *GitRepo) calcVersion() error { // append pre-release-name and/or pre-release-timestamp to the version if len(r.preReleaseName) > 0 || len(r.preReleaseTimestampLayout) > 0 { - if r.newVersion, err = preReleaseVersion(r.newVersion, r.preReleaseName, r.preReleaseTimestampLayout); err != nil { + if r.newVersion, err = preReleaseVersion(r.newVersion, r.curPreReleaseVer, r.preReleaseName, r.preReleaseTimestampLayout, r.preReleaseNumber); err != nil { return err } } diff --git a/autotag/main.go b/autotag/main.go index a988a0f..47183b1 100644 --- a/autotag/main.go +++ b/autotag/main.go @@ -18,6 +18,7 @@ type Options struct { RepoPath string `short:"r" long:"repo" description:"Path to the repo" default:"./" ` PreReleaseName string `short:"p" long:"pre-release-name" description:"create a pre-release tag"` PreReleaseTimestamp string `short:"T" long:"pre-release-timestamp" description:"create a pre-release tag and append a timestamp (can be: datetime|epoch)"` + PreReleaseNumber bool `long:"pre-release-number" description:"create a pre-release tag and append a pre-release number"` BuildMetadata string `short:"m" long:"build-metadata" description:"optional SemVer build metadata to append to the version with '+' character"` Scheme string `short:"s" long:"scheme" description:"The commit message scheme to use (can be: autotag|conventional)" default:"autotag"` NoVersionPrefix bool `short:"e" long:"empty-version-prefix" description:"Do not prepend v to version tag"` @@ -46,6 +47,7 @@ func main() { Branch: opts.Branch, PreReleaseName: opts.PreReleaseName, PreReleaseTimestampLayout: opts.PreReleaseTimestamp, + PreReleaseNumber: opts.PreReleaseNumber, BuildMetadata: opts.BuildMetadata, Scheme: opts.Scheme, Prefix: !opts.NoVersionPrefix, diff --git a/autotag_test.go b/autotag_test.go index 556c61c..40bbf40 100644 --- a/autotag_test.go +++ b/autotag_test.go @@ -37,6 +37,9 @@ type testRepoSetup struct { // (optional) the prerelease timestamp format to use, eg: "epoch". If not set, no prerelease timestamp will be used preReleaseTimestampLayout string + // (optional) will optional append prerelease number in second part of prerelease (default: false) + preReleaseNumber bool + // (optional) build metadata to append to the version buildMetadata string @@ -103,6 +106,7 @@ func newTestRepo(t *testing.T, setup testRepoSetup) (GitRepo, error) { Branch: branch, PreReleaseName: setup.preReleaseName, PreReleaseTimestampLayout: setup.preReleaseTimestampLayout, + PreReleaseNumber: setup.preReleaseNumber, BuildMetadata: setup.buildMetadata, Scheme: setup.scheme, Prefix: !setup.disablePrefix, @@ -437,6 +441,81 @@ func TestPatch(t *testing.T) { } } +func TestPrereleaseNumberFirstTime(t *testing.T) { + r, err := newTestRepo(t, testRepoSetup{ + preReleaseNumber: true, + preReleaseName: "dev", + initialTag: "v1.0.1", + }) + if err != nil { + t.Fatal("Error creating repo: ", err) + } + defer cleanupTestRepo(t, r.repo) + + v := r.LatestVersion() + + if v != "1.0.2-dev.1" { + t.Fatalf("Prerelease number bump failed expected '1.0.2-dev.1' got '%s' \n", v) + } +} + +func TestPrereleaseNumber(t *testing.T) { + r, err := newTestRepo(t, testRepoSetup{ + preReleaseNumber: true, + preReleaseName: "dev", + initialTag: "v1.0.1", + extraTags: []string{"v1.0.2-dev.1"}, + }) + if err != nil { + t.Fatal("Error creating repo: ", err) + } + defer cleanupTestRepo(t, r.repo) + + v := r.LatestVersion() + + if v != "1.0.2-dev.2" { + t.Fatalf("Prerelease number bump failed expected '1.0.2-dev.2' got '%s' \n", v) + } +} + +func TestPrereleaseNumberWithExtraTags(t *testing.T) { + r, err := newTestRepo(t, testRepoSetup{ + preReleaseNumber: true, + preReleaseName: "dev", + initialTag: "v1.0.1", + extraTags: []string{"v1.0.2-dev.1", "v1.0.2-next.1"}, + }) + if err != nil { + t.Fatal("Error creating repo: ", err) + } + defer cleanupTestRepo(t, r.repo) + + v := r.LatestVersion() + + if v != "1.0.2-dev.2" { + t.Fatalf("Prerelease number bump failed expected '1.0.2-dev.2' got '%s' \n", v) + } +} + +func TestPrereleaseNumberWithNewVersion(t *testing.T) { + r, err := newTestRepo(t, testRepoSetup{ + preReleaseNumber: true, + preReleaseName: "dev", + initialTag: "v1.0.1", + extraTags: []string{"v1.0.2-dev.1", "v1.0.2"}, + }) + if err != nil { + t.Fatal("Error creating repo: ", err) + } + defer cleanupTestRepo(t, r.repo) + + v := r.LatestVersion() + + if v != "1.0.3-dev.1" { + t.Fatalf("Prerelease number bump failed expected '1.0.3-dev.1' got '%s' \n", v) + } +} + func TestBuildNumberFirstTime(t *testing.T) { r, err := newTestRepo(t, testRepoSetup{ buildNumber: true,