diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c036c..9777c7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [6.8.1] - ? +## [6.9.0] - ? +### Added +* New flag `-target-revision` that can be used to select which version component will be bumped + if version sontains pre-release. Possible values are `patch` (default), `minor` and `major`. + ### Fixed * An error was issued when invoked from subfolder of the repository whereas `git-describe` ususally succeeds in such cases. diff --git a/README.md b/README.md index 45ed081..dfc623c 100644 --- a/README.md +++ b/README.md @@ -101,16 +101,17 @@ plus character. A valid format string is e.g.: `x.y+m` The output and parsing of `git-semver` can be controlled with the following options. -| Name | Description | -| --- | --- | -| `-format` | Format string as described [here](#formatting) | -| `-no-minor` | Exclude minor version and all following components | -| `-no-patch` | Exclude patch version and all following components | -| `-no-pre` | Exclude pre-release version and all following components | -| `-no-meta`/`-no-hash` | Exclude build metadata | -| `-prefix` | Prefix string for version e.g.: v | -| `-set-meta` | Set buildmeta to this value | -| `-guard` | Ignore shorthand formats for pre-release versions | +| Name | Description | +| --- | --- | +| `-format` | Format string as described [here](#formatting) | +| `-no-minor` | Exclude minor version and all following components | +| `-no-patch` | Exclude patch version and all following components | +| `-no-pre` | Exclude pre-release version and all following components | +| `-no-meta`/`-no-hash` | Exclude build metadata | +| `-prefix` | Prefix string for version e.g.: v | +| `-set-meta` | Set buildmeta to this value | +| `-guard` | Ignore shorthand formats for pre-release versions | +| `-target-release` | Bump `patch` (default), `minor` or `major` of pre-release versions | #### Examples @@ -132,6 +133,12 @@ v3.5.2 $ git-semver -set-meta custom 3.5.2+custom + +$ git-semver -target-release minor +3.6.0-dev.22+8eaec5d3 + +$ git-semver -target-release major +4.0.0-dev.22+8eaec5d3 ``` ### Release safeguard diff --git a/main.go b/main.go index 8d4fff6..2bdbb36 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ type Config struct { excludeMinor bool guardRelease bool matchPattern string + targetRelease version.TargetRelease args []string stderr io.Writer stdout io.Writer @@ -34,6 +35,8 @@ func parseFlags(progname string, args []string) (*Config, string, error) { cfg Config ) + cfg.targetRelease = version.DefaultTargetRelease + flags := flag.NewFlagSet(progname, flag.ContinueOnError) flags.SetOutput(&buf) flags.StringVar(&cfg.prefix, "prefix", "", "prefix of version string e.g. v (default: none)") @@ -47,6 +50,7 @@ func parseFlags(progname string, args []string) (*Config, string, error) { flags.BoolVar(&cfg.excludeMinor, "no-minor", false, "exclude pre-release version (default: false)") flags.BoolVar(&cfg.excludePrefix, "no-prefix", false, "exclude version prefix (default: false)") flags.BoolVar(&cfg.guardRelease, "guard", false, "ignore shorthand options if version contains pre-release (default: false)") + flags.Var(&cfg.targetRelease, "target-release", "set which version component (major, minor or patch) will be bumped if version contains pre-release (default: patch)") flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [opts] []\n\nOptions:\n", progname) flags.PrintDefaults() @@ -56,6 +60,7 @@ func parseFlags(progname string, args []string) (*Config, string, error) { if err != nil { return nil, buf.String(), err } + cfg.args = flags.Args() cfg.stderr = os.Stderr cfg.stdout = os.Stdout @@ -112,7 +117,7 @@ func handle(cfg *Config, repoPath string) int { if cfg.excludePrefix { v.Prefix = "" } - s, err := v.Format(selectFormat(cfg, v)) + s, err := v.Format(selectFormat(cfg, v), cfg.targetRelease) if err != nil { fmt.Fprintln(cfg.stderr, err) return 1 diff --git a/version/version.go b/version/version.go index f366bf1..cf4f5ca 100644 --- a/version/version.go +++ b/version/version.go @@ -1,6 +1,7 @@ package version import ( + "errors" "fmt" "regexp" "strconv" @@ -19,6 +20,44 @@ const ( NoMinorFormat = "x" ) +// Enum that specifies which version component should be bumped +type TargetRelease int + +const ( + TargetPatch TargetRelease = iota + TargetMinor + TargetMajor +) + +func (t *TargetRelease) String() string { + switch *t { + case TargetPatch: + return "patch" + case TargetMinor: + return "minor" + case TargetMajor: + return "major" + default: + panic(fmt.Errorf("unexpected TargetRevision value %v", *t)) + } +} + +func (t *TargetRelease) Set(value string) error { + switch value { + case "patch": + *t = TargetPatch + case "minor": + *t = TargetMinor + case "major": + *t = TargetMajor + default: + return errors.New(`parse error`) + } + return nil +} + +const DefaultTargetRelease = TargetPatch + type buffer []byte func (b *buffer) AppendInt(i int, sep byte) { @@ -52,7 +91,7 @@ type Version struct { // * m -> metadata // x, y and z are separated by a dot. p is seprated by a hyphen and m by a plus sing. // E.g.: x.y.z-p+m or x.y -func (v Version) Format(format string) (string, error) { +func (v Version) Format(format string, target TargetRelease) (string, error) { re := regexp.MustCompile( `(?Px)(?P\.y)?(?P\.z)?(?P
-p)?(?P\+m)?`)
 
@@ -63,6 +102,24 @@ func (v Version) Format(format string) (string, error) {
 
 	var buf buffer
 
+	major := v.Major
+	minor := v.Minor
+	patch := v.Patch
+
+	if v.Commits > 0 && v.preRelease == "" {
+		switch target {
+		case TargetMajor:
+			major++
+			minor = 0
+			patch = 0
+		case TargetMinor:
+			minor++
+			patch = 0
+		case TargetPatch:
+			patch++
+		}
+	}
+
 	names := re.SubexpNames()
 	for i := 0; i < len(matches); i++ {
 		if len(matches[i]) == 0 {
@@ -70,14 +127,10 @@ func (v Version) Format(format string) (string, error) {
 		}
 		switch names[i] {
 		case "major":
-			buf.AppendInt(v.Major, '.')
+			buf.AppendInt(major, '.')
 		case "minor":
-			buf.AppendInt(v.Minor, '.')
+			buf.AppendInt(minor, '.')
 		case "patch":
-			patch := v.Patch
-			if v.Commits > 0 && v.preRelease == "" {
-				patch++
-			}
 			buf.AppendInt(patch, '.')
 		case "pre":
 			buf.AppendString(v.PreRelease(), '-')
@@ -85,11 +138,12 @@ func (v Version) Format(format string) (string, error) {
 			buf.AppendString(v.Meta, '+')
 		}
 	}
+
 	return v.Prefix + string(buf), nil
 }
 
 func (v Version) String() string {
-	result, err := v.Format(FullFormat)
+	result, err := v.Format(FullFormat, DefaultTargetRelease)
 	if err != nil {
 		return ""
 	}
diff --git a/version/version_test.go b/version/version_test.go
index e1bb363..6bd5702 100644
--- a/version/version_test.go
+++ b/version/version_test.go
@@ -129,40 +129,65 @@ func TestFormat(t *testing.T) {
 		f string
 		p string
 		s string
+		t TargetRelease
 	}{
 		{
 			FullFormat,
 			"",
 			"1.2.4-dev.10+fcf2c8f",
+			DefaultTargetRelease,
 		},
 		{
 			NoMetaFormat,
 			"",
 			"1.2.4-dev.10",
+			DefaultTargetRelease,
 		},
 		{
 			NoPreFormat,
 			"",
 			"1.2.4",
+			DefaultTargetRelease,
 		},
 		{
 			NoPatchFormat,
 			"",
 			"1.2",
+			DefaultTargetRelease,
 		},
 		{
 			NoMinorFormat,
 			"v",
 			"v1",
+			DefaultTargetRelease,
 		},
 		{
 			"x.y-p",
 			"v",
 			"v1.2-dev.10",
+			DefaultTargetRelease,
+		},
+		{
+			FullFormat,
+			"",
+			"1.2.4-dev.10+fcf2c8f",
+			TargetPatch,
+		},
+		{
+			FullFormat,
+			"",
+			"1.3.0-dev.10+fcf2c8f",
+			TargetMinor,
+		},
+		{
+			FullFormat,
+			"",
+			"2.0.0-dev.10+fcf2c8f",
+			TargetMajor,
 		},
 	} {
 		v.Prefix = test.p
-		s, err := v.Format(test.f)
+		s, err := v.Format(test.f, test.t)
 		assert.NoError(err)
 		assert.Equal(test.s, s)
 	}
@@ -170,7 +195,54 @@ func TestFormat(t *testing.T) {
 
 func TestInvalidFormat(t *testing.T) {
 	v := Version{Major: 1, Minor: 2, Patch: 3}
-	s, err := v.Format("q")
+	s, err := v.Format("q", DefaultTargetRelease)
 	assert.EqualError(t, err, "invalid format: q")
 	assert.Equal(t, "", s)
 }
+
+func TestTargetReleaseToString(t *testing.T) {
+	assert.PanicsWithError(t, "unexpected TargetRevision value 8", func() {
+		v := TargetRelease(8)
+		v.String()
+	})
+
+	{
+		v := TargetPatch
+		assert.Equal(t, "patch", v.String())
+	}
+
+	{
+		v := TargetMinor
+		assert.Equal(t, "minor", v.String())
+	}
+
+	{
+		v := TargetMajor
+		assert.Equal(t, "major", v.String())
+	}
+}
+
+func TestTargetReleaseFromString(t *testing.T) {
+	{
+		var v TargetRelease
+		assert.EqualError(t, v.Set("foo"), "parse error")
+	}
+
+	{
+		var v TargetRelease
+		assert.NoError(t, v.Set("patch"))
+		assert.Equal(t, TargetPatch, v)
+	}
+
+	{
+		var v TargetRelease
+		assert.NoError(t, v.Set("minor"))
+		assert.Equal(t, TargetMinor, v)
+	}
+
+	{
+		var v TargetRelease
+		assert.NoError(t, v.Set("major"))
+		assert.Equal(t, TargetMajor, v)
+	}
+}