v0.1.0 5228465

refs_fix_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 2

↑↓ moveenter openesc close