1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package gitcli
4
5import (
6 "context"
7 "fmt"
8 "regexp"
9 "strconv"
10)
11
12// Version is a git release number.
13type Version struct{ Major, Minor, Patch int }
14
15// MinVersion is the oldest supported git.
16var MinVersion = Version{Major: 2, Minor: 45}
17
18// String returns "major.minor.patch".
19func (v Version) String() string { return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) }
20
21// Less reports whether v is older than o.
22func (v Version) Less(o Version) bool {
23 if v.Major != o.Major {
24 return v.Major < o.Major
25 }
26 if v.Minor != o.Minor {
27 return v.Minor < o.Minor
28 }
29 return v.Patch < o.Patch
30}
31
32var versionRE = regexp.MustCompile(`^git version (\d+)\.(\d+)(?:\.(\d+))?`)
33
34// ParseVersion parses the output of `git version`, such as
35// "git version 2.45.0", "git version 2.39.5 (Apple Git-154)" or
36// "git version 2.46.0.rc1".
37func ParseVersion(out string) (Version, error) {
38 m := versionRE.FindStringSubmatch(out)
39 if m == nil {
40 return Version{}, fmt.Errorf("unrecognized git version output %q", out)
41 }
42 var v Version
43 for i, dst := range []*int{&v.Major, &v.Minor, &v.Patch} {
44 if m[i+1] == "" {
45 continue
46 }
47 n, err := strconv.Atoi(m[i+1])
48 if err != nil {
49 return Version{}, fmt.Errorf("unrecognized git version output %q: %w", out, err)
50 }
51 *dst = n
52 }
53 return v, nil
54}
55
56// CheckVersion runs `git version` and fails if git is missing, unreadable or
57// older than MinVersion. It returns the version found.
58func (g *Git) CheckVersion(ctx context.Context) (Version, error) {
59 out, err := g.run(ctx, "version")
60 if err != nil {
61 return Version{}, fmt.Errorf("run %q: %w", g.bin, err)
62 }
63 v, err := ParseVersion(out)
64 if err != nil {
65 return Version{}, err
66 }
67 if v.Less(MinVersion) {
68 return v, fmt.Errorf("git %s is older than the required %s", v, MinVersion)
69 }
70 return v, nil
71}