1package main
2
3import (
4 "os"
5 "os/exec"
6 "path/filepath"
7 "strings"
8 "testing"
9)
10
11// isolateGit makes git invocations hermetic: inherited GIT_* variables are
12// dropped and no user or system configuration is read.
13func isolateGit(t *testing.T) {
14 t.Helper()
15 if _, err := exec.LookPath("git"); err != nil {
16 t.Skip("git not installed")
17 }
18 for _, kv := range os.Environ() {
19 if k, _, _ := strings.Cut(kv, "="); strings.HasPrefix(k, "GIT_") {
20 t.Setenv(k, "") // registers restoration of the original value
21 os.Unsetenv(k)
22 }
23 }
24 t.Setenv("GIT_CONFIG_GLOBAL", "/dev/null")
25 t.Setenv("GIT_CONFIG_NOSYSTEM", "1")
26}
27
28// TestGitFetcherLocal exercises the real git implementation against a local
29// repository; no network is involved.
30func TestGitFetcherLocal(t *testing.T) {
31 isolateGit(t)
32 repo := t.TempDir()
33 run := func(args ...string) string {
34 t.Helper()
35 out, err := git(repo, append([]string{
36 "-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false",
37 }, args...)...)
38 if err != nil {
39 t.Fatal(err)
40 }
41 return out
42 }
43 run("init", "-q", "-b", "master")
44 if err := os.MkdirAll(filepath.Join(repo, "protocol"), 0o755); err != nil {
45 t.Fatal(err)
46 }
47 write := func(content string) {
48 if err := os.WriteFile(filepath.Join(repo, "protocol", "wayland.xml"), []byte(content), 0o644); err != nil {
49 t.Fatal(err)
50 }
51 }
52 write("one")
53 run("add", ".")
54 run("commit", "-q", "-m", "one")
55 first := run("rev-parse", "HEAD")
56 run("tag", "-a", "-m", "release", "1.0.0") // annotated: tag object != commit
57 write("two")
58 run("commit", "-q", "-am", "two")
59 second := run("rev-parse", "HEAD")
60 run("tag", "1.2.0") // lightweight
61 run("tag", "1.3.1") // odd minor, patch < 90: accepted
62 run("tag", "1.5.90") // release candidate: rejected
63 run("tag", "1.4.91") // release candidate on an even minor: rejected
64 run("tag", "1.26.91")
65 run("tag", "not-a-version")
66
67 src := upstream{
68 ID: "t", URL: repo, Kind: PinTag, TagRe: tagThreeParts, TagOK: waylandTagOK,
69 Match: exactly(Stable, "protocol/wayland.xml"),
70 }
71 var f gitFetcher
72
73 ref, commit, err := f.Resolve(src, "")
74 if err != nil || ref != "1.3.1" || commit != second {
75 t.Fatalf("latest = %q %q, %v; want 1.3.1 %s", ref, commit, err, second)
76 }
77 _, commit, err = f.Resolve(src, "1.0.0")
78 if err != nil || commit != first {
79 t.Fatalf("annotated tag resolved to %q (%v), want peeled commit %s", commit, err, first)
80 }
81 if _, _, err := f.Resolve(src, "9.9.9"); err == nil {
82 t.Error("expected error for missing tag")
83 }
84
85 check := func(src upstream, ref, commit, want string) {
86 t.Helper()
87 dir := t.TempDir()
88 if err := f.Checkout(src, ref, commit, dir); err != nil {
89 t.Fatal(err)
90 }
91 b, err := os.ReadFile(filepath.Join(dir, "protocol", "wayland.xml"))
92 if err != nil || string(b) != want {
93 t.Errorf("content = %q, %v; want %q", b, err, want)
94 }
95 }
96 check(src, "1.0.0", first, "one")
97 check(src, "1.2.0", second, "two")
98
99 // Branch source: HEAD of the branch, then a pinned older commit.
100 branch := upstream{ID: "b", URL: repo, Kind: PinBranch, Branch: "master", Match: src.Match}
101 ref, commit, err = f.Resolve(branch, "")
102 if err != nil || ref != "master" || commit != second {
103 t.Fatalf("branch = %q %q, %v", ref, commit, err)
104 }
105 check(branch, ref, commit, "two")
106 ref, commit, err = f.Resolve(branch, first)
107 if err != nil || ref != "master" || commit != first {
108 t.Fatalf("pinned branch = %q %q, %v", ref, commit, err)
109 }
110 // Branch moved past the pinned commit: falls back to fetching the commit.
111 // (Local repos need uploadpack.allowAnySHA1InWant for non-tip commits.)
112 run("config", "uploadpack.allowAnySHA1InWant", "true")
113 check(branch, ref, commit, "one")
114
115 // A mismatching commit is rejected.
116 if err := f.Checkout(src, "1.0.0", second, t.TempDir()); err == nil {
117 t.Error("expected error when checkout does not match resolved commit")
118 }
119}