Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: hashicorp/go-version
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.2.1
Choose a base ref
...
head repository: hashicorp/go-version
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.4.0
Choose a head ref
  • 11 commits
  • 6 files changed
  • 5 contributors

Commits on Oct 29, 2020

  1. Switch to docker mirror

    mdeggies committed Oct 29, 2020
    Copy the full SHA
    d16dd1c View commit details

Commits on Nov 9, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9e69307 View commit details

Commits on Mar 31, 2021

  1. add Core func

    Adds a Core function to Version, which returns a new Version created from only
    the MAJOR.MINOR.PATCH segments of the original version, without prerelease or
    metadata strings.
    
    Consumers of go-version often want to treat prereleases of versions as equal to
    non-prerelease versions, e.g. 0.15.0-dev as equal to 0.15.0. The present
    functionality is intended as an easy way to opt in to this behaviour.
    kmoe committed Mar 31, 2021

    Verified

    This commit was signed with the committer’s verified signature.
    Copy the full SHA
    7da126b View commit details
  2. Merge pull request #85 from hashicorp/base-version-func

    add Core func
    kmoe authored Mar 31, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3307340 View commit details
  3. create CHANGELOG.md

    kmoe authored Mar 31, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    c2de231 View commit details

Commits on Nov 18, 2021

  1. Verified

    This commit was signed with the committer’s verified signature.
    radeksimko Radek Simko
    Copy the full SHA
    43df615 View commit details

Commits on Nov 22, 2021

  1. Verified

    This commit was signed with the committer’s verified signature.
    radeksimko Radek Simko
    Copy the full SHA
    feceee7 View commit details
  2. Merge pull request #87 from hashicorp/f-must-constraint

    Introduce `MustConstraints()`
    radeksimko authored Nov 22, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ac9bfc9 View commit details
  3. Merge pull request #88 from hashicorp/constraints-equality

    Constraint(s): introduce `Equals()` and `sort.Interface`
    radeksimko authored Nov 22, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d7f6c9b View commit details
  4. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b991f91 View commit details

Commits on Jan 5, 2022

  1. Update CHANGELOG.md

    radeksimko authored Jan 5, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4cadd97 View commit details
Showing with 273 additions and 16 deletions.
  1. +1 −1 .circleci/config.yml
  2. +32 −0 CHANGELOG.md
  3. +97 −11 constraint.go
  4. +101 −0 constraint_test.go
  5. +10 −4 version.go
  6. +32 −0 version_test.go
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ version: 2.1

references:
images:
go: &GOLANG_IMAGE circleci/golang:latest
go: &GOLANG_IMAGE docker.mirror.hashicorp.services/circleci/golang:1.15.3
environments:
tmp: &TEST_RESULTS_PATH /tmp/test-results # path to where test results are saved

32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 1.4.0 (January 5, 2021)

FEATURES:

- Introduce `MustConstraints()` ([#87](https://github.com/hashicorp/go-version/pull/87))
- `Constraints`: Introduce `Equals()` and `sort.Interface` methods ([#88](https://github.com/hashicorp/go-version/pull/88))

# 1.3.0 (March 31, 2021)

Please note that CHANGELOG.md does not exist in the source code prior to this release.

FEATURES:
- Add `Core` function to return a version without prerelease or metadata ([#85](https://github.com/hashicorp/go-version/pull/85))

# 1.2.1 (June 17, 2020)

BUG FIXES:
- Prevent `Version.Equal` method from panicking on `nil` encounter ([#73](https://github.com/hashicorp/go-version/pull/73))

# 1.2.0 (April 23, 2019)

FEATURES:
- Add `GreaterThanOrEqual` and `LessThanOrEqual` helper methods ([#53](https://github.com/hashicorp/go-version/pull/53))

# 1.1.0 (Jan 07, 2019)

FEATURES:
- Add `NewSemver` constructor ([#45](https://github.com/hashicorp/go-version/pull/45))

# 1.0.0 (August 24, 2018)

Initial release.
108 changes: 97 additions & 11 deletions constraint.go
Original file line number Diff line number Diff line change
@@ -4,37 +4,48 @@ import (
"fmt"
"reflect"
"regexp"
"sort"
"strings"
)

// Constraint represents a single constraint for a version, such as
// ">= 1.0".
type Constraint struct {
f constraintFunc
op operator
check *Version
original string
}

func (c *Constraint) Equals(con *Constraint) bool {
return c.op == con.op && c.check.Equal(con.check)
}

// Constraints is a slice of constraints. We make a custom type so that
// we can add methods to it.
type Constraints []*Constraint

type constraintFunc func(v, c *Version) bool

var constraintOperators map[string]constraintFunc
var constraintOperators map[string]constraintOperation

type constraintOperation struct {
op operator
f constraintFunc
}

var constraintRegexp *regexp.Regexp

func init() {
constraintOperators = map[string]constraintFunc{
"": constraintEqual,
"=": constraintEqual,
"!=": constraintNotEqual,
">": constraintGreaterThan,
"<": constraintLessThan,
">=": constraintGreaterThanEqual,
"<=": constraintLessThanEqual,
"~>": constraintPessimistic,
constraintOperators = map[string]constraintOperation{
"": {op: equal, f: constraintEqual},
"=": {op: equal, f: constraintEqual},
"!=": {op: notEqual, f: constraintNotEqual},
">": {op: greaterThan, f: constraintGreaterThan},
"<": {op: lessThan, f: constraintLessThan},
">=": {op: greaterThanEqual, f: constraintGreaterThanEqual},
"<=": {op: lessThanEqual, f: constraintLessThanEqual},
"~>": {op: pessimistic, f: constraintPessimistic},
}

ops := make([]string, 0, len(constraintOperators))
@@ -66,6 +77,16 @@ func NewConstraint(v string) (Constraints, error) {
return Constraints(result), nil
}

// MustConstraints is a helper that wraps a call to a function
// returning (Constraints, error) and panics if error is non-nil.
func MustConstraints(c Constraints, err error) Constraints {
if err != nil {
panic(err)
}

return c
}

// Check tests if a version satisfies all the constraints.
func (cs Constraints) Check(v *Version) bool {
for _, c := range cs {
@@ -77,6 +98,56 @@ func (cs Constraints) Check(v *Version) bool {
return true
}

// Equals compares Constraints with other Constraints
// for equality. This may not represent logical equivalence
// of compared constraints.
// e.g. even though '>0.1,>0.2' is logically equivalent
// to '>0.2' it is *NOT* treated as equal.
//
// Missing operator is treated as equal to '=', whitespaces
// are ignored and constraints are sorted before comaparison.
func (cs Constraints) Equals(c Constraints) bool {
if len(cs) != len(c) {
return false
}

// make copies to retain order of the original slices
left := make(Constraints, len(cs))
copy(left, cs)
sort.Stable(left)
right := make(Constraints, len(c))
copy(right, c)
sort.Stable(right)

// compare sorted slices
for i, con := range left {
if !con.Equals(right[i]) {
return false
}
}

return true
}

func (cs Constraints) Len() int {
return len(cs)
}

func (cs Constraints) Less(i, j int) bool {
if cs[i].op < cs[j].op {
return true
}
if cs[i].op > cs[j].op {
return false
}

return cs[i].check.LessThan(cs[j].check)
}

func (cs Constraints) Swap(i, j int) {
cs[i], cs[j] = cs[j], cs[i]
}

// Returns the string format of the constraints
func (cs Constraints) String() string {
csStr := make([]string, len(cs))
@@ -107,8 +178,11 @@ func parseSingle(v string) (*Constraint, error) {
return nil, err
}

cop := constraintOperators[matches[1]]

return &Constraint{
f: constraintOperators[matches[1]],
f: cop.f,
op: cop.op,
check: check,
original: v,
}, nil
@@ -138,6 +212,18 @@ func prereleaseCheck(v, c *Version) bool {
// Constraint functions
//-------------------------------------------------------------------

type operator rune

const (
equal operator = '='
notEqual operator = '≠'
greaterThan operator = '>'
lessThan operator = '<'
greaterThanEqual operator = '≥'
lessThanEqual operator = '≤'
pessimistic operator = '~'
)

func constraintEqual(v, c *Version) bool {
return v.Equal(c)
}
101 changes: 101 additions & 0 deletions constraint_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package version

import (
"fmt"
"reflect"
"sort"
"testing"
)

@@ -97,6 +100,104 @@ func TestConstraintCheck(t *testing.T) {
}
}

func TestConstraintEqual(t *testing.T) {
cases := []struct {
leftConstraint string
rightConstraint string
expectedEqual bool
}{
{
"0.0.1",
"0.0.1",
true,
},
{ // whitespaces
" 0.0.1 ",
"0.0.1",
true,
},
{ // equal op implied
"=0.0.1 ",
"0.0.1",
true,
},
{ // version difference
"=0.0.1",
"=0.0.2",
false,
},
{ // operator difference
">0.0.1",
"=0.0.1",
false,
},
{ // different order
">0.1.0, <=1.0.0",
"<=1.0.0, >0.1.0",
true,
},
}

for _, tc := range cases {
leftCon, err := NewConstraint(tc.leftConstraint)
if err != nil {
t.Fatalf("err: %s", err)
}
rightCon, err := NewConstraint(tc.rightConstraint)
if err != nil {
t.Fatalf("err: %s", err)
}

actual := leftCon.Equals(rightCon)
if actual != tc.expectedEqual {
t.Fatalf("Constraints: %s vs %s\nExpected: %t\nActual: %t",
tc.leftConstraint, tc.rightConstraint, tc.expectedEqual, actual)
}
}
}

func TestConstraint_sort(t *testing.T) {
cases := []struct {
constraint string
expectedConstraints string
}{
{
">= 0.1.0,< 1.12",
"< 1.12,>= 0.1.0",
},
{
"< 1.12,>= 0.1.0",
"< 1.12,>= 0.1.0",
},
{
"< 1.12,>= 0.1.0,0.2.0",
"< 1.12,0.2.0,>= 0.1.0",
},
{
">1.0,>0.1.0,>0.3.0,>0.2.0",
">0.1.0,>0.2.0,>0.3.0,>1.0",
},
}

for i, tc := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
c, err := NewConstraint(tc.constraint)
if err != nil {
t.Fatalf("err: %s", err)
}

sort.Sort(c)

actual := c.String()

if !reflect.DeepEqual(actual, tc.expectedConstraints) {
t.Fatalf("unexpected order\nexpected: %#v\nactual: %#v",
tc.expectedConstraints, actual)
}
})
}
}

func TestConstraintsString(t *testing.T) {
cases := []struct {
constraint string
14 changes: 10 additions & 4 deletions version.go
Original file line number Diff line number Diff line change
@@ -64,16 +64,14 @@ func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
}
segmentsStr := strings.Split(matches[1], ".")
segments := make([]int64, len(segmentsStr))
si := 0
for i, str := range segmentsStr {
val, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return nil, fmt.Errorf(
"Error parsing version: %s", err)
}

segments[i] = int64(val)
si++
segments[i] = val
}

// Even though we could support more than three segments, if we
@@ -92,7 +90,7 @@ func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
metadata: matches[10],
pre: pre,
segments: segments,
si: si,
si: len(segmentsStr),
original: v,
}, nil
}
@@ -278,6 +276,14 @@ func comparePrereleases(v string, other string) int {
return 0
}

// Core returns a new version constructed from only the MAJOR.MINOR.PATCH
// segments of the version, without prerelease or metadata.
func (v *Version) Core() *Version {
segments := v.Segments64()
segmentsOnly := fmt.Sprintf("%d.%d.%d", segments[0], segments[1], segments[2])
return Must(NewVersion(segmentsOnly))
}

// Equal tests if two versions are equal.
func (v *Version) Equal(o *Version) bool {
if v == nil || o == nil {
32 changes: 32 additions & 0 deletions version_test.go
Original file line number Diff line number Diff line change
@@ -87,6 +87,38 @@ func TestNewSemver(t *testing.T) {
}
}

func TestCore(t *testing.T) {
cases := []struct {
v1 string
v2 string
}{
{"1.2.3", "1.2.3"},
{"2.3.4-alpha1", "2.3.4"},
{"3.4.5alpha1", "3.4.5"},
{"1.2.3-2", "1.2.3"},
{"4.5.6-beta1+meta", "4.5.6"},
{"5.6.7.1.2.3", "5.6.7"},
}

for _, tc := range cases {
v1, err := NewVersion(tc.v1)
if err != nil {
t.Fatalf("error for version %q: %s", tc.v1, err)
}
v2, err := NewVersion(tc.v2)
if err != nil {
t.Fatalf("error for version %q: %s", tc.v2, err)
}

actual := v1.Core()
expected := v2

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected: %s\nactual: %s", expected, actual)
}
}
}

func TestVersionCompare(t *testing.T) {
cases := []struct {
v1 string