getproviders: Normalize versions before dedupe
When rendering a set of version constraints to a string, we normalize partially-constrained versions. This means converting a version like 2.68.* to 2.68.0. Prior to this commit, this normalization was done after deduplication. This could result in a version constraints string with duplicate entries, if multiple partially-constrained versions are equivalent. This commit fixes this by normalizing before deduplicating and sorting.
This commit is contained in:
parent
3ed08e3566
commit
b1bc0e5d92
|
@ -407,7 +407,18 @@ func VersionConstraintsString(spec VersionConstraints) string {
|
||||||
// and sort them into a consistent order.
|
// and sort them into a consistent order.
|
||||||
sels := make(map[constraints.SelectionSpec]struct{})
|
sels := make(map[constraints.SelectionSpec]struct{})
|
||||||
for _, sel := range spec {
|
for _, sel := range spec {
|
||||||
sels[sel] = struct{}{}
|
// The parser allows writing abbreviated version (such as 2) which
|
||||||
|
// end up being represented in memory with trailing unconstrained parts
|
||||||
|
// (for example 2.*.*). For the purpose of serialization with Ruby
|
||||||
|
// style syntax, these unconstrained parts can all be represented as 0
|
||||||
|
// with no loss of meaning, so we make that conversion here. Doing so
|
||||||
|
// allows us to deduplicate equivalent constraints, such as >= 2.0 and
|
||||||
|
// >= 2.0.0.
|
||||||
|
normalizedSel := constraints.SelectionSpec{
|
||||||
|
Operator: sel.Operator,
|
||||||
|
Boundary: sel.Boundary.ConstrainToZero(),
|
||||||
|
}
|
||||||
|
sels[normalizedSel] = struct{}{}
|
||||||
}
|
}
|
||||||
selsOrder := make([]constraints.SelectionSpec, 0, len(sels))
|
selsOrder := make([]constraints.SelectionSpec, 0, len(sels))
|
||||||
for sel := range sels {
|
for sel := range sels {
|
||||||
|
@ -450,34 +461,26 @@ func VersionConstraintsString(spec VersionConstraints) string {
|
||||||
b.WriteString("??? ")
|
b.WriteString("??? ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The parser allows writing abbreviated version (such as 2) which
|
// We use a different constraint operator to distinguish between the
|
||||||
// end up being represented in memory with trailing unconstrained parts
|
// two types of pessimistic constraint: minor-only and patch-only. For
|
||||||
// (for example 2.*.*). For the purpose of serialization with Ruby
|
// minor-only constraints, we always want to display only the major and
|
||||||
// style syntax, these unconstrained parts can all be represented as 0
|
// minor version components, so we special-case that operator below.
|
||||||
// with no loss of meaning, so we make that conversion here.
|
|
||||||
//
|
|
||||||
// This is possible because we use a different constraint operator to
|
|
||||||
// distinguish between the two types of pessimistic constraint:
|
|
||||||
// minor-only and patch-only. For minor-only constraints, we always
|
|
||||||
// want to display only the major and minor version components, so we
|
|
||||||
// special-case that operator below.
|
|
||||||
//
|
//
|
||||||
// One final edge case is a minor-only constraint specified with only
|
// One final edge case is a minor-only constraint specified with only
|
||||||
// the major version, such as ~> 2. We treat this the same as ~> 2.0,
|
// the major version, such as ~> 2. We treat this the same as ~> 2.0,
|
||||||
// because a major-only pessimistic constraint does not exist: it is
|
// because a major-only pessimistic constraint does not exist: it is
|
||||||
// logically identical to >= 2.0.0.
|
// logically identical to >= 2.0.0.
|
||||||
boundary := sel.Boundary.ConstrainToZero()
|
|
||||||
if sel.Operator == constraints.OpGreaterThanOrEqualMinorOnly {
|
if sel.Operator == constraints.OpGreaterThanOrEqualMinorOnly {
|
||||||
// The minor-pessimistic syntax uses only two version components.
|
// The minor-pessimistic syntax uses only two version components.
|
||||||
fmt.Fprintf(&b, "%s.%s", boundary.Major, boundary.Minor)
|
fmt.Fprintf(&b, "%s.%s", sel.Boundary.Major, sel.Boundary.Minor)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(&b, "%s.%s.%s", boundary.Major, boundary.Minor, boundary.Patch)
|
fmt.Fprintf(&b, "%s.%s.%s", sel.Boundary.Major, sel.Boundary.Minor, sel.Boundary.Patch)
|
||||||
}
|
}
|
||||||
if sel.Boundary.Prerelease != "" {
|
if sel.Boundary.Prerelease != "" {
|
||||||
b.WriteString("-" + boundary.Prerelease)
|
b.WriteString("-" + sel.Boundary.Prerelease)
|
||||||
}
|
}
|
||||||
if sel.Boundary.Metadata != "" {
|
if sel.Boundary.Metadata != "" {
|
||||||
b.WriteString("+" + boundary.Metadata)
|
b.WriteString("+" + sel.Boundary.Metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
|
|
|
@ -53,6 +53,10 @@ func TestVersionConstraintsString(t *testing.T) {
|
||||||
MustParseVersionConstraints(">= 1.2.3, 1.2.3, ~> 1.2, 1.2.3"),
|
MustParseVersionConstraints(">= 1.2.3, 1.2.3, ~> 1.2, 1.2.3"),
|
||||||
"~> 1.2, >= 1.2.3, 1.2.3",
|
"~> 1.2, >= 1.2.3, 1.2.3",
|
||||||
},
|
},
|
||||||
|
"equivalent duplicates removed": {
|
||||||
|
MustParseVersionConstraints(">= 2.68, >= 2.68.0"),
|
||||||
|
">= 2.68.0",
|
||||||
|
},
|
||||||
"consistent ordering, exhaustive": {
|
"consistent ordering, exhaustive": {
|
||||||
// This weird jumble is just to exercise the different sort
|
// This weird jumble is just to exercise the different sort
|
||||||
// ordering codepaths. Hopefully nothing quite this horrific
|
// ordering codepaths. Hopefully nothing quite this horrific
|
||||||
|
|
Loading…
Reference in New Issue