Skip to content

Commit fbe76cf

Browse files
committed
fix pre-release checks in constraints
Pre-releases should not be automatically accepted as candidate versions for constraints without a pre-release component. This follows the logic documented for RubyGems which appears to be the inspiration for the constraint operators, and matches the behavior of other version packages. Adding a pre-release component to a pessimistic constraint will limit accepted versions to pre-releases only matching the base segments. Adding a pre-release to other constraint types will accept any comparable version, but pre-releases must still match the base segments.
1 parent 4fe82ae commit fbe76cf

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

constraint.go

+30-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package version
22

33
import (
44
"fmt"
5+
"reflect"
56
"regexp"
67
"strings"
78
)
@@ -113,6 +114,26 @@ func parseSingle(v string) (*Constraint, error) {
113114
}, nil
114115
}
115116

117+
func prereleaseCheck(v, c *Version) bool {
118+
switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; {
119+
case cPre && vPre:
120+
// A constraint with a pre-release can only match a pre-release version
121+
// with the same base segments.
122+
return reflect.DeepEqual(c.Segments64(), v.Segments64())
123+
124+
case !cPre && vPre:
125+
// A constraint without a pre-release can only match a version without a
126+
// pre-release.
127+
return false
128+
129+
case cPre && !vPre:
130+
// OK, except with the pessimistic operator
131+
case !cPre && !vPre:
132+
// OK
133+
}
134+
return true
135+
}
136+
116137
//-------------------------------------------------------------------
117138
// Constraint functions
118139
//-------------------------------------------------------------------
@@ -126,22 +147,27 @@ func constraintNotEqual(v, c *Version) bool {
126147
}
127148

128149
func constraintGreaterThan(v, c *Version) bool {
129-
return v.Compare(c) == 1
150+
return prereleaseCheck(v, c) && v.Compare(c) == 1
130151
}
131152

132153
func constraintLessThan(v, c *Version) bool {
133-
return v.Compare(c) == -1
154+
return prereleaseCheck(v, c) && v.Compare(c) == -1
134155
}
135156

136157
func constraintGreaterThanEqual(v, c *Version) bool {
137-
return v.Compare(c) >= 0
158+
return prereleaseCheck(v, c) && v.Compare(c) >= 0
138159
}
139160

140161
func constraintLessThanEqual(v, c *Version) bool {
141-
return v.Compare(c) <= 0
162+
return prereleaseCheck(v, c) && v.Compare(c) <= 0
142163
}
143164

144165
func constraintPessimistic(v, c *Version) bool {
166+
// Using a pessimistic constraint with a pre-release, restricts versions to pre-releases
167+
if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") {
168+
return false
169+
}
170+
145171
// If the version being checked is naturally less than the constraint, then there
146172
// is no way for the version to be valid against the constraint
147173
if v.LessThan(c) {

constraint_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ func TestConstraintCheck(t *testing.T) {
6262
{"~> 1.0.9.5", "1.0.9.6", true},
6363
{"~> 1.0.9.5", "1.0.9.5.0", true},
6464
{"~> 1.0.9.5", "1.0.9.5.1", true},
65+
{"~> 2.0", "2.1.0-beta", false},
66+
{"~> 2.1.0-a", "2.2.0", false},
67+
{"~> 2.1.0-a", "2.1.0", false},
68+
{"~> 2.1.0-a", "2.1.0-beta", true},
69+
{"~> 2.1.0-a", "2.2.0-alpha", false},
70+
{"> 2.0", "2.1.0-beta", false},
71+
{">= 2.1.0-a", "2.1.0-beta", true},
72+
{">= 2.1.0-a", "2.1.1-beta", false},
73+
{">= 2.0.0", "2.1.0-beta", false},
74+
{">= 2.1.0-a", "2.1.1", true},
75+
{">= 2.1.0-a", "2.1.1-beta", false},
76+
{">= 2.1.0-a", "2.1.0", true},
77+
{"<= 2.1.0-a", "2.0.0", true},
6578
}
6679

6780
for _, tc := range cases {

0 commit comments

Comments
 (0)