зеркало из https://github.com/golang/pkgsite.git
272 строки
7.8 KiB
Go
272 строки
7.8 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package version handles version types.
|
|
package version
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"golang.org/x/mod/semver"
|
|
)
|
|
|
|
// Type defines the version types a module can have.
|
|
// This must be kept in sync with the 'version_type' database enum.
|
|
type Type string
|
|
|
|
const (
|
|
// TypeRelease is a normal release.
|
|
TypeRelease = Type("release")
|
|
|
|
// TypePrerelease is a version with a prerelease.
|
|
TypePrerelease = Type("prerelease")
|
|
|
|
// TypePseudo appears to have a prerelease of the
|
|
// form <commit date>-<commit hash>.
|
|
TypePseudo = Type("pseudo")
|
|
)
|
|
|
|
const (
|
|
// Latest signifies the latest available version in requests to the
|
|
// proxy client.
|
|
Latest = "latest"
|
|
|
|
// Main represents the main branch.
|
|
Main = "main"
|
|
|
|
// Master represents the master branch.
|
|
Master = "master"
|
|
)
|
|
|
|
func (t Type) String() string {
|
|
return string(t)
|
|
}
|
|
|
|
var pseudoVersionRE = regexp.MustCompile(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+incompatible)?$`)
|
|
|
|
// IsPseudo reports whether a valid version v is a pseudo-version.
|
|
// Modified from src/cmd/go/internal/modfetch.
|
|
func IsPseudo(v string) bool {
|
|
return strings.Count(v, "-") >= 2 && pseudoVersionRE.MatchString(v)
|
|
}
|
|
|
|
// IsIncompatible reports whether a valid version v is an incompatible version.
|
|
func IsIncompatible(v string) bool {
|
|
return strings.HasSuffix(v, "+incompatible")
|
|
}
|
|
|
|
// ParseType returns the Type of a given a version.
|
|
func ParseType(version string) (Type, error) {
|
|
if !semver.IsValid(version) {
|
|
return "", fmt.Errorf("ParseType(%q): invalid semver", version)
|
|
}
|
|
|
|
switch {
|
|
case IsPseudo(version):
|
|
return TypePseudo, nil
|
|
case semver.Prerelease(version) != "":
|
|
return TypePrerelease, nil
|
|
default:
|
|
return TypeRelease, nil
|
|
}
|
|
}
|
|
|
|
// ForSorting returns a string that encodes version, so that comparing two such
|
|
// strings follows SemVer precedence, https://semver.org clause 11. It assumes
|
|
// version is valid. The returned string ends in '~' if and only if the version
|
|
// does not have a prerelease.
|
|
//
|
|
// For examples, see TestForSorting.
|
|
func ForSorting(version string) string {
|
|
bytes := make([]byte, 0, len(version))
|
|
prerelease := false // we are in the prerelease part
|
|
nondigit := false // this part has a non-digit character
|
|
start := 1 // skip 'v'
|
|
last := len(version)
|
|
|
|
// Add the semver component version[start:end] to the result.
|
|
addPart := func(end int) {
|
|
if len(bytes) > 0 {
|
|
// ',' comes before '-' and all letters and digits, so it correctly
|
|
// imposes lexicographic ordering on the parts of the version.
|
|
bytes = append(bytes, ',')
|
|
}
|
|
if nondigit {
|
|
// Prepending the largest printable character '~' to a non-numeric
|
|
// part, along with the fact that encoded numbers never begin with a
|
|
// '~', (see appendNumericPrefix), ensures the semver requirement
|
|
// that numeric identifiers always have lower precedence than
|
|
// non-numeric ones.
|
|
bytes = append(bytes, '~')
|
|
} else {
|
|
bytes = appendNumericPrefix(bytes, end-start)
|
|
}
|
|
bytes = append(bytes, version[start:end]...)
|
|
start = end + 1 // skip over separator character
|
|
nondigit = false
|
|
}
|
|
|
|
loop:
|
|
for i, c := range version[start:] {
|
|
p := i + 1
|
|
switch {
|
|
case c == '.': // end of a part
|
|
addPart(p)
|
|
case c == '-': // first one is start of prerelease
|
|
if !prerelease {
|
|
prerelease = true
|
|
addPart(p)
|
|
} else {
|
|
nondigit = true
|
|
}
|
|
case c == '+': // start of build; nothing after this matters
|
|
last = p
|
|
break loop
|
|
|
|
case c < '0' || c > '9':
|
|
nondigit = true
|
|
}
|
|
}
|
|
if start < last {
|
|
addPart(last)
|
|
}
|
|
if !prerelease {
|
|
// Make sure prereleases appear first.
|
|
bytes = append(bytes, '~')
|
|
}
|
|
return string(bytes)
|
|
}
|
|
|
|
// appendNumericPrefix appends a string representing n to dst.
|
|
// n is the length of a digit string; the value we append is a prefix for the
|
|
// digit string s such that
|
|
//
|
|
// prefix1 + s1 < prefix2 + s2
|
|
//
|
|
// if and only if the integer denoted by s1 is less than the one denoted by s2.
|
|
// In other words, prefix + s is a string that can be compared with other such
|
|
// strings while preserving the ordering of the numbers.
|
|
//
|
|
// If n==1, there is no prefix. (Single-digit numbers are unchanged.)
|
|
// Otherwise, the prefix is a sequence of lower-case letters encoding n.
|
|
// Examples:
|
|
//
|
|
// n prefix
|
|
// 1 <none>
|
|
// 2 a
|
|
// 27 z
|
|
// 28 za
|
|
// 53 zz
|
|
// 54 zza
|
|
//
|
|
// This encoding depends on the ASCII properties that:
|
|
// - digits are ordered numerically
|
|
// - letters are ordered alphabetically
|
|
// - digits order before letters (so "1" < "a10")
|
|
func appendNumericPrefix(dst []byte, n int) []byte {
|
|
n--
|
|
for i := 0; i < n/26; i++ {
|
|
dst = append(dst, 'z')
|
|
}
|
|
if rem := n % 26; rem > 0 {
|
|
dst = append(dst, byte('a'+rem-1))
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// Later reports whether v1 is later than v2, using semver but preferring
|
|
// release versions to pre-release versions, and both to pseudo-versions.
|
|
func Later(v1, v2 string) bool {
|
|
rel1 := semver.Prerelease(v1) == ""
|
|
rel2 := semver.Prerelease(v2) == ""
|
|
if rel1 && rel2 {
|
|
return semver.Compare(v1, v2) > 0
|
|
}
|
|
if rel1 != rel2 {
|
|
return rel1
|
|
}
|
|
// Both are pre-release.
|
|
pseudo1 := IsPseudo(v1)
|
|
pseudo2 := IsPseudo(v2)
|
|
if pseudo1 == pseudo2 {
|
|
return semver.Compare(v1, v2) > 0
|
|
}
|
|
return !pseudo1
|
|
}
|
|
|
|
// LatestVersion finds the latest version of a module using the same algorithm as the
|
|
// Go command. It prefers tagged release versions to tagged pre-release
|
|
// versions, and both of those to pseudo-versions. If versions is empty, LatestVersion
|
|
// returns the empty string.
|
|
//
|
|
// hasGoMod should report whether the version it is given has a go.mod file.
|
|
// LatestVersion returns the latest incompatible version only if the latest compatible
|
|
// version does not have a go.mod file.
|
|
//
|
|
// The meaning of latest is defined at
|
|
// https://golang.org/ref/mod#version-queries. That definition does not deal
|
|
// with retractions, or with a subtlety involving incompatible versions. The
|
|
// actual definition is embodied in the go command's queryMatcher.filterVersions
|
|
// method. This function is a re-implementation and specialization of that
|
|
// method at Go version 1.16
|
|
// (https://go.googlesource.com/go/+/refs/tags/go1.16/src/cmd/go/internal/modload/query.go#441).
|
|
func LatestVersion(versions []string, hasGoMod func(v string) (bool, error)) (v string, err error) {
|
|
latest := LatestOf(versions)
|
|
if latest == "" {
|
|
return "", nil
|
|
}
|
|
|
|
// If the latest is a compatible version, use it.
|
|
if !IsIncompatible(latest) {
|
|
return latest, nil
|
|
}
|
|
// The latest version is incompatible. If there is a go.mod file at the
|
|
// latest compatible tagged version, assume the module author has adopted
|
|
// proper versioning, and use that latest compatible version. Otherwise, use
|
|
// this incompatible version.
|
|
latestCompat := LatestOf(RemoveIf(versions, func(v string) bool { return IsIncompatible(v) || IsPseudo(v) }))
|
|
if latestCompat == "" {
|
|
// No compatible versions; use the latest (incompatible) version.
|
|
return latest, nil
|
|
}
|
|
latestCompatHasGoMod, err := hasGoMod(latestCompat)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if latestCompatHasGoMod {
|
|
return latestCompat, nil
|
|
}
|
|
return latest, nil
|
|
}
|
|
|
|
// LatestOf returns the latest version of a module from a list of versions, using
|
|
// the go command's definition of latest: semver is observed, except that
|
|
// release versions are preferred to prerelease, and both are preferred to pseudo-versions.
|
|
// If versions is empty, the empty string is returned.
|
|
func LatestOf(versions []string) string {
|
|
if len(versions) == 0 {
|
|
return ""
|
|
}
|
|
latest := versions[0]
|
|
for _, v := range versions[1:] {
|
|
if Later(v, latest) {
|
|
latest = v
|
|
}
|
|
}
|
|
return latest
|
|
}
|
|
|
|
// RemoveIf returns a copy of s that omits all values for which f returns true.
|
|
func RemoveIf(s []string, f func(string) bool) []string {
|
|
var r []string
|
|
for _, x := range s {
|
|
if !f(x) {
|
|
r = append(r, x)
|
|
}
|
|
}
|
|
return r
|
|
}
|