1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6 "errors"
7 "os"
8 "testing"
9 "time"
10)
11
12func TestCorruptRootRefIsNotFound(t *testing.T) {
13 f := newFx(t)
14 f.write("a", []byte("a"), 0o644)
15 c := mustID(t, f.commit("one"))
16 writeFile(t, f.dir+"/GARBAGE", "this is not a ref\n")
17 writeFile(t, f.dir+"/GOOD_REF", c.String()+"\n")
18 writeFile(t, f.dir+"/refs/heads/sym", "ref: GARBAGE\n")
19 r := f.open()
20 if _, err := r.ResolveRef("GARBAGE"); !errors.Is(err, ErrNotFound) {
21 t.Errorf("ResolveRef(GARBAGE) = %v, want ErrNotFound", err)
22 }
23 if _, err := r.LookupRef("GARBAGE"); !errors.Is(err, ErrNotFound) {
24 t.Errorf("LookupRef(GARBAGE) = %v, want ErrNotFound", err)
25 }
26 if _, err := r.Resolve("GARBAGE"); !errors.Is(err, ErrNotFound) {
27 t.Errorf("Resolve(GARBAGE) = %v, want ErrNotFound", err)
28 }
29 if got, err := r.ResolveRef("GOOD_REF"); err != nil || got != c {
30 t.Errorf("ResolveRef(GOOD_REF) = %s, %v", got, err)
31 }
32 // A symbolic ref to a broken pseudo-ref designates nothing; it does not
33 // break the listing.
34 n := 0
35 for ref, err := range r.Refs("refs/heads/") {
36 if err != nil {
37 t.Fatal(err)
38 }
39 if ref.Name == "refs/heads/sym" {
40 t.Errorf("sym resolved to %s", ref.ID)
41 }
42 n++
43 }
44 if n != 1 {
45 t.Errorf("%d branches listed, want 1", n)
46 }
47 // A broken loose ref under refs/ stays corrupt, and HEAD too.
48 writeFile(t, f.dir+"/refs/heads/broken", "nonsense\n")
49 if _, err := r.ResolveRef("refs/heads/broken"); !errors.Is(err, ErrCorrupt) {
50 t.Errorf("ResolveRef(broken) = %v, want ErrCorrupt", err)
51 }
52 writeFile(t, f.dir+"/HEAD", "nonsense\n")
53 if _, err := r.ResolveRef("HEAD"); !errors.Is(err, ErrCorrupt) {
54 t.Errorf("ResolveRef(HEAD) = %v, want ErrCorrupt", err)
55 }
56}
57
58func TestClosedRepoRefs(t *testing.T) {
59 f := newFx(t)
60 f.write("a", []byte("a"), 0o644)
61 f.commit("one")
62 r := f.open()
63 _ = r.Close()
64 if _, _, err := r.Head(); !errors.Is(err, ErrClosed) {
65 t.Errorf("Head = %v", err)
66 }
67 if _, err := r.ResolveRef("HEAD"); !errors.Is(err, ErrClosed) {
68 t.Errorf("ResolveRef = %v", err)
69 }
70 if _, err := r.LookupRef("refs/heads/main"); !errors.Is(err, ErrClosed) {
71 t.Errorf("LookupRef = %v", err)
72 }
73 if _, err := r.Resolve("main"); !errors.Is(err, ErrClosed) {
74 t.Errorf("Resolve = %v", err)
75 }
76 n := 0
77 for _, err := range r.Refs("") {
78 n++
79 if !errors.Is(err, ErrClosed) {
80 t.Errorf("Refs = %v", err)
81 }
82 }
83 if n != 1 {
84 t.Errorf("Refs yielded %d times, want one error", n)
85 }
86}
87
88func TestLookupRefID(t *testing.T) {
89 f := newFx(t)
90 f.write("a", []byte("a"), 0o644)
91 head := mustID(t, f.commit("one"))
92 f.git("tag", "light")
93 f.git("update-ref", "refs/heads/packed", head.String())
94 f.git("pack-refs", "--all")
95 f.git("update-ref", "refs/heads/loose", head.String())
96 f.git("symbolic-ref", "refs/heads/sym", "refs/heads/loose")
97 // packed-refs written just now is inside the racy window and is parsed
98 // again at each lookup; the steady state is the one to measure.
99 old := time.Now().Add(-time.Hour)
100 if err := os.Chtimes(f.dir+"/packed-refs", old, old); err != nil {
101 t.Fatal(err)
102 }
103 r := f.open()
104 for _, name := range []string{"HEAD", "refs/heads/loose", "refs/heads/packed", "refs/tags/light", "refs/heads/sym"} {
105 id, ok, err := r.LookupRefID(name)
106 if err != nil || !ok || id != head {
107 t.Errorf("LookupRefID(%q) = %v, %v, %v", name, id, ok, err)
108 }
109 if want, err := r.ResolveRef(name); err != nil || want != id {
110 t.Errorf("ResolveRef(%q) = %v, %v", name, want, err)
111 }
112 }
113 // A miss is not an error, whatever the reason the name does not exist:
114 // no such file, a directory in its place, a file where a directory is
115 // expected, an invalid name, a name outside refs/.
116 for _, name := range []string{"refs/heads/none", "refs/heads", "refs/heads/loose/under", "refs/tags/none", "refs/heads/a..b", "../config", "", "refs/heads/x.lock", "orig_head"} {
117 id, ok, err := r.LookupRefID(name)
118 if err != nil || ok || !id.IsZero() {
119 t.Errorf("LookupRefID(%q) = %v, %v, %v, want a quiet miss", name, id, ok, err)
120 }
121 if _, err := r.ResolveRef(name); !errors.Is(err, ErrNotFound) {
122 t.Errorf("ResolveRef(%q) = %v, want ErrNotFound", name, err)
123 }
124 }
125 if raceEnabled {
126 return
127 }
128 for _, name := range []string{"refs/heads/none", "refs/tags/none", "refs/remotes/origin/none"} {
129 if got := testing.AllocsPerRun(50, func() { _, _, _ = r.LookupRefID(name) }); got != 0 {
130 t.Errorf("LookupRefID(%q) allocates %v on a miss, want 0", name, got)
131 }
132 }
133 if got := testing.AllocsPerRun(50, func() { _, _, _ = r.LookupRefID("refs/heads/loose") }); got != 0 {
134 t.Errorf("LookupRefID of a loose ref allocates %v, want 0", got)
135 }
136 if got := testing.AllocsPerRun(50, func() { _, _, _ = r.LookupRefID("refs/heads/packed") }); got != 0 {
137 t.Errorf("LookupRefID of a packed ref allocates %v, want 0", got)
138 }
139 _ = r.Close()
140 if _, _, err := r.LookupRefID("refs/heads/loose"); !errors.Is(err, ErrClosed) {
141 t.Errorf("closed: %v", err)
142 }
143}