internal/deps/version.go

v1.9.0
gitbay/internal/deps/version.go history · blame · raw

91 lines · 2485 bytes

 1// Package deps reports dependencies that are behind their upstream
 2// release. It reads manifests out of a repository's default branch and
 3// asks the ecosystem's registry what the current version is; it never
 4// executes a package manager, so the check needs no runner and no
 5// per-repo configuration beyond opting in.
 6package deps
 7
 8import (
 9	"strconv"
10	"strings"
11)
12
13// Newer reports whether latest is a strictly greater release than current.
14// The four ecosystems agree on the part that matters here — dot-separated
15// numbers, optionally followed by a suffix — so one tolerant comparison
16// covers all of them. Anything it cannot read compares equal, which
17// reports nothing rather than reporting noise.
18func Newer(current, latest string) bool {
19	ca, cs := split(current)
20	la, ls := split(latest)
21	if len(ca) == 0 || len(la) == 0 {
22		return false
23	}
24	for i := 0; i < len(ca) || i < len(la); i++ {
25		c, l := at(ca, i), at(la, i)
26		if c != l {
27			return l > c
28		}
29	}
30	// Same release numbers: the suffix decides. A prerelease is behind the
31	// plain release, which is behind a post-release.
32	return rank(ls) > rank(cs)
33}
34
35// IsPrerelease reports whether v carries a prerelease marker. Registries
36// mostly hand back stable versions already; this keeps the exceptions from
37// being suggested.
38func IsPrerelease(v string) bool {
39	_, suffix := split(v)
40	return rank(suffix) < 0
41}
42
43// split separates the leading dot-separated numbers from whatever follows:
44// "v1.2.3-rc1" becomes ([1 2 3], "-rc1"), "2.0b1" becomes ([2 0], "b1").
45func split(v string) ([]int, string) {
46	v = strings.TrimSpace(v)
47	v = strings.TrimPrefix(v, "v")
48	var nums []int
49	i := 0
50	for i < len(v) {
51		j := i
52		for j < len(v) && v[j] >= '0' && v[j] <= '9' {
53			j++
54		}
55		if j == i {
56			break
57		}
58		n, err := strconv.Atoi(v[i:j])
59		if err != nil {
60			break
61		}
62		nums = append(nums, n)
63		if j < len(v) && v[j] == '.' && j+1 < len(v) && v[j+1] >= '0' && v[j+1] <= '9' {
64			i = j + 1
65			continue
66		}
67		i = j
68		break
69	}
70	return nums, v[i:]
71}
72
73func at(nums []int, i int) int {
74	if i < len(nums) {
75		return nums[i]
76	}
77	return 0
78}
79
80// rank orders the three kinds of suffix a release can carry: -1 for a
81// prerelease, 0 for none, 1 for a PEP 440 post-release. Two prereleases
82// rank equal, which reports nothing rather than guessing at rc1 vs beta2.
83func rank(suffix string) int {
84	switch {
85	case suffix == "":
86		return 0
87	case strings.HasPrefix(strings.TrimLeft(suffix, ".-_"), "post"):
88		return 1
89	}
90	return -1
91}