Repository: Masterminds/semver
Pull request #113 (2019-09-05 19:58:44)

Update range for ^ to follow npm/js and rust
Previously the ^ syntax below major version 0 round up to 1. Now
0.x.y and 0.x.z are considered compatible when y >= z. This is how
rust and npm/js operate.

The exception to this is when the minor is below 0. At that point
every release is considered not compatible with anything else.
That means 0.0.x and 0.0.y are not considered compatible even when
x > y.

Previously the PHP/composer pattern had been followed.

In practice some tools, like Helm charts, had followed the
npm/rust method for versioning. This bring the ^ in line with
real world usage desires for this library.


Patch:
diff --git a/constraints.go b/constraints.go
index 2344286..bf0b3d5 100644
--- a/constraints.go
+++ b/constraints.go
@@ -383,12 +383,15 @@ func constraintTildeOrEqual(v *Version, c *constraint) bool {
 	return v.Equal(c.con)
 }
 
-// ^* --> (any)
-// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0
-// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0
-// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0
-// ^1.2.3 --> >=1.2.3, <2.0.0
-// ^1.2.0 --> >=1.2.0, <2.0.0
+// ^*      -->  (any)
+// ^1.2.3  -->  >=1.2.3 <2.0.0
+// ^1.2    -->  >=1.2.0 <2.0.0
+// ^1      -->  >=1.0.0 <2.0.0
+// ^0.2.3  -->  >=0.2.3 <0.3.0
+// ^0.2    -->  >=0.2.0 <0.3.0
+// ^0.0.3  -->  >=0.0.3 <0.0.4
+// ^0.0    -->  >=0.0.0 <0.1.0
+// ^0      -->  >=0.0.0 <1.0.0
 func constraintCaret(v *Version, c *constraint) bool {
 	// If there is a pre-release on the version but the constraint isn't looking
 	// for them assume that pre-releases are not compatible. See issue 21 for
@@ -401,11 +404,15 @@ func constraintCaret(v *Version, c *constraint) bool {
 		return false
 	}
 
-	if v.Major() != c.con.Major() {
-		return false
+	if (c.con.Major() > 0 || c.minorDirty) && v.Major() == c.con.Major() {
+		return true
+	} else if (c.con.Minor() > 0 || c.patchDirty) && v.Minor() == c.con.Minor() {
+		return true
+	} else if v.Patch() == c.con.Patch() {
+		return true
 	}
 
-	return true
+	return false
 }
 
 var constraintRangeRegex *regexp.Regexp


Test patch:
diff --git a/constraints_test.go b/constraints_test.go
index 215006c..64e444e 100644
--- a/constraints_test.go
+++ b/constraints_test.go
@@ -133,6 +133,46 @@ func TestConstraintCheck(t *testing.T) {
 		{"2", "2.1.1", true},
 		{"2.1", "2.1.1", true},
 		{"2.1", "2.2.1", false},
+		{"~1.2.3", "1.2.4", true},
+		{"~1.2.3", "1.3.4", false},
+		{"~1.2", "1.2.4", true},
+		{"~1.2", "1.3.4", false},
+		{"~1", "1.2.4", true},
+		{"~1", "2.3.4", false},
+		{"~0.2.3", "0.2.5", true},
+		{"~0.2.3", "0.3.5", false},
+		{"~1.2.3-beta.2", "1.2.3-beta.4", true},
+
+		// This next test is a case that is different from npm/js semver handling.
+		// Their prereleases are only range scoped to patch releases. This is
+		// technically not following semver as docs note. In our case we are
+		// following semver.
+		{"~1.2.3-beta.2", "1.2.4-beta.2", true},
+		{"~1.2.3-beta.2", "1.3.4-beta.2", false},
+		{"^1.2.3", "1.8.9", true},
+		{"^1.2.3", "2.8.9", false},
+		{"^1.2", "1.8.9", true},
+		{"^1.2", "2.8.9", false},
+		{"^1", "1.8.9", true},
+		{"^1", "2.8.9", false},
+		{"^0.2.3", "0.2.5", true},
+		{"^0.2.3", "0.5.6", false},
+		{"^0.2", "0.2.5", true},
+		{"^0.2", "0.5.6", false},
+		{"^0.0.3", "0.0.3", true},
+		{"^0.0.3", "0.0.4", false},
+		{"^0.0", "0.0.3", true},
+		{"^0.0", "0.1.4", false},
+		{"^0", "0.2.3", true},
+		{"^0", "1.1.4", false},
+		{"^0.2.3-beta.2", "0.2.3-beta.4", true},
+
+		// This next test is a case that is different from npm/js semver handling.
+		// Their prereleases are only range scoped to patch releases. This is
+		// technically not following semver as docs note. In our case we are
+		// following semver.
+		{"^0.2.3-beta.2", "0.2.4-beta.2", true},
+		{"^0.2.3-beta.2", "0.3.4-beta.2", false},
 	}
 
 	for _, tc := range tests {
@@ -406,7 +446,8 @@ func TestConstraintsValidate(t *testing.T) {
 		{"^1.x", "1.1.1", true},
 		{"^2.x", "1.1.1", false},
 		{"^1.x", "2.1.1", false},
-		{"^0.0.1", "0.1.3", true},
+		{"^0.0.1", "0.1.3", false},
+		{"^0.0.1", "0.0.1", true},
 		{"~*", "2.1.1", true},
 		{"~1", "2.1.1", false},
 		{"~1", "1.3.5", true},