plugin:update dropped a go.mod require that was not a strict vX.Y.Z and treated it as v0.0.0, so a module at a commit after v0.4.0 (v0.4.1-0.<ts>-<hash>) was moved back to v0.4.0. kiosk.Newer now places a release against a release, a git describe string or a pseudo-version (by the tag it builds on, in Go's version order); plugin:update and the admin Plugins screen badge share it. A pseudo-version with no tag before its commit is left alone with a note naming go get <module>@<tag>.
151 lines
4.8 KiB
Go
151 lines
4.8 KiB
Go
package kiosk
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
validVersionRe = regexp.MustCompile(`^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
|
|
describeRe = regexp.MustCompile(`^(v(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))(?:-[0-9]+-g[0-9a-f]+)?$`)
|
|
pseudoBaseRe = regexp.MustCompile(`^(v[0-9]+\.[0-9]+\.[0-9]+)-`)
|
|
// pseudoNoBaseRe is a pseudo-version with no tag before its commit:
|
|
// vX.0.0-<timestamp>-<hash>, without the "0." or "<pre>.0." of one
|
|
// that builds on a release or a pre-release.
|
|
pseudoNoBaseRe = regexp.MustCompile(`^v[0-9]+\.[0-9]+\.[0-9]+-[0-9]{14}-[0-9a-f]{12}$`)
|
|
)
|
|
|
|
// ValidVersion reports whether v is a release version in the strict form
|
|
// vX.Y.Z: no pre-release, no build metadata, no leading zeros.
|
|
func ValidVersion(v string) bool {
|
|
return validVersionRe.MatchString(v)
|
|
}
|
|
|
|
// CompareVersions compares two valid versions numerically per component
|
|
// and returns -1, 0 or +1. It is only defined for versions ValidVersion
|
|
// accepts; never pass a git describe string (use BaseVersion first).
|
|
func CompareVersions(a, b string) int {
|
|
pa := strings.Split(strings.TrimPrefix(a, "v"), ".")
|
|
pb := strings.Split(strings.TrimPrefix(b, "v"), ".")
|
|
for i := 0; i < len(pa) && i < len(pb); i++ {
|
|
if c := compareNumeric(pa[i], pb[i]); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
switch {
|
|
case len(pa) < len(pb):
|
|
return -1
|
|
case len(pa) > len(pb):
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// compareNumeric compares two decimal strings without leading zeros, of
|
|
// any length.
|
|
func compareNumeric(a, b string) int {
|
|
switch {
|
|
case len(a) != len(b):
|
|
if len(a) < len(b) {
|
|
return -1
|
|
}
|
|
return 1
|
|
case a < b:
|
|
return -1
|
|
case a > b:
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// BaseVersion returns the vX.Y.Z tag a `git describe --tags` string starts
|
|
// from: "v0.3.1" for "v0.3.1" and for "v0.3.1-4-gabc1234". Anything else
|
|
// (a bare commit hash, an empty string, a pre-release tag) gives "".
|
|
func BaseVersion(describe string) string {
|
|
m := describeRe.FindStringSubmatch(describe)
|
|
if m == nil {
|
|
return ""
|
|
}
|
|
return m[1]
|
|
}
|
|
|
|
// Newer reports whether release is an upgrade for a plugin installed at
|
|
// installed, so moving to it never goes back. installed may be a release
|
|
// vX.Y.Z (newer: above it), a git describe string vX.Y.Z-N-g<hash>, a
|
|
// commit after vX.Y.Z (newer: above vX.Y.Z), or a Go pseudo-version, a
|
|
// commit after the tag it builds on: vX.Y.(Z+1)-0.<timestamp>-<hash>
|
|
// after the release vX.Y.Z, vX.Y.Z-<pre>.0.<timestamp>-<hash> after the
|
|
// pre-release vX.Y.Z-<pre>; newer is at or above vX.Y.(Z+1) and vX.Y.Z,
|
|
// Go's own version order. Nothing is newer than an installed version
|
|
// Comparable refuses (a pseudo-version with no tag before its commit, a
|
|
// bare commit hash, a pre-release, ""). release must be a valid release.
|
|
func Newer(release, installed string) bool {
|
|
if !ValidVersion(release) {
|
|
return false
|
|
}
|
|
if base := BaseVersion(installed); base != "" {
|
|
return CompareVersions(release, base) > 0
|
|
}
|
|
if !isPseudoVersion(installed) || pseudoNoBaseRe.MatchString(installed) {
|
|
return false
|
|
}
|
|
core := pseudoBaseRe.FindStringSubmatch(installed)
|
|
if core == nil || !ValidVersion(core[1]) {
|
|
return false
|
|
}
|
|
return CompareVersions(release, core[1]) >= 0
|
|
}
|
|
|
|
// Comparable reports whether Newer can place installed among releases: a
|
|
// release, a git describe string, or a pseudo-version that builds on a
|
|
// release or a pre-release.
|
|
func Comparable(installed string) bool {
|
|
if BaseVersion(installed) != "" {
|
|
return true
|
|
}
|
|
if !isPseudoVersion(installed) || pseudoNoBaseRe.MatchString(installed) {
|
|
return false
|
|
}
|
|
core := pseudoBaseRe.FindStringSubmatch(installed)
|
|
return core != nil && ValidVersion(core[1])
|
|
}
|
|
|
|
// Satisfies reports whether the release have meets the minimum min. An
|
|
// empty minimum is always met. A pseudo-version minimum such as
|
|
// vX.Y.Z-0.<timestamp>-<hash> sorts just below vX.Y.Z, so it is met by
|
|
// vX.Y.Z and later. have must be a valid release; anything else meets
|
|
// only the empty minimum.
|
|
func Satisfies(have, min string) bool {
|
|
if min == "" {
|
|
return true
|
|
}
|
|
if !ValidVersion(have) {
|
|
return false
|
|
}
|
|
if ValidVersion(min) {
|
|
return CompareVersions(have, min) >= 0
|
|
}
|
|
if isPseudoVersion(min) {
|
|
base := pseudoBaseRe.FindStringSubmatch(min)
|
|
return base != nil && ValidVersion(base[1]) && CompareVersions(have, base[1]) >= 0
|
|
}
|
|
return false
|
|
}
|
|
|
|
// NewestCompatible returns the newest release in versions whose framework
|
|
// minimum the given framework version satisfies, and false when there is
|
|
// none. Entries whose Version is not a valid release are skipped.
|
|
func NewestCompatible(versions []RegistryVersion, framework string) (RegistryVersion, bool) {
|
|
var best RegistryVersion
|
|
found := false
|
|
for _, v := range versions {
|
|
if !ValidVersion(v.Version) || !Satisfies(framework, v.Framework) {
|
|
continue
|
|
}
|
|
if !found || CompareVersions(v.Version, best.Version) > 0 {
|
|
best, found = v, true
|
|
}
|
|
}
|
|
return best, found
|
|
}
|