Merge pull request #26783 from hashicorp/alisdair/normalize-version-constraints-before-deduplication

getproviders: Normalize versions before dedupe
This commit is contained in:
Alisdair McDiarmid 2020-11-04 09:42:19 -05:00 committed by GitHub
commit fd43bc7847
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 24 additions and 17 deletions

View File

@ -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()

View File

@ -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