v0.1.0 5228465

walkrace_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6	"bytes"
7	"context"
8	"fmt"
9	"os/exec"
10	"slices"
11	"strings"
12	"sync"
13	"sync/atomic"
14	"testing"
15	"time"
16)
17
18// gitQuiet runs git in the fixture without touching the testing.T, so it can
19// be used from goroutines.
20func (w *wfix) gitQuiet(stdin string, args ...string) error {
21	cmd := exec.Command("git", args...)
22	cmd.Dir = w.dir
23	cmd.Env = append(gitEnv(), "GIT_DIR="+w.dir)
24	cmd.Stdin = strings.NewReader(stdin)
25	var errb bytes.Buffer
26	cmd.Stderr = &errb
27	if err := cmd.Run(); err != nil {
28		return fmt.Errorf("git %s: %v: %s", strings.Join(args, " "), err, errb.String())
29	}
30	return nil
31}
32
33// TestWalkConcurrentRefresh runs walks and LastCommits while the repository
34// changes under them: Refresh, new commits arriving by fast-import (a push:
35// new pack, new graph layer), rewrites of the commit-graph (with and without
36// Bloom filters, split and single, or removed) and repacks. Every walk starts
37// from a fixed tip and must give the same answer as before the changes.
38func TestWalkConcurrentRefresh(t *testing.T) {
39	requireGit(t)
40	cs := randomHistory(21, 120)
41	w := buildWFix(t, cs)
42	w.setGraph(wgraphs[4])
43	tipIdx := len(cs) - 1
44	tip := mustID(t, w.ids[tipIdx])
45	paths := []string{"", "a", "d", "d/e/z", "q/r"}
46	want := map[string][]string{}
47	for _, p := range paths {
48		want[p] = w.gitLog(tipIdx, 0, 0, p)
49	}
50	names := []string{"a", "b", "d", "m", "q", "nope"}
51	wantLast := w.lastOracle(tipIdx, "", names)
52
53	r, err := Open(w.dir)
54	if err != nil {
55		t.Fatal(err)
56	}
57	defer func() { _ = r.Close() }()
58
59	dur := 3 * time.Second
60	if testing.Short() {
61		dur = time.Second
62	}
63	deadline := time.Now().Add(dur)
64	var stop atomic.Bool
65	var wg sync.WaitGroup
66	errc := make(chan error, 64)
67	fail := func(err error) {
68		select {
69		case errc <- err:
70		default:
71		}
72		stop.Store(true)
73	}
74
75	for g := 0; g < 6; g++ {
76		wg.Add(1)
77		go func(g int) {
78			defer wg.Done()
79			for i := 0; !stop.Load(); i++ {
80				p := paths[(g+i)%len(paths)]
81				var got []string
82				for c, err := range r.Walk(context.Background(), tip, WalkOptions{Path: p, IDOnly: i%2 == 0}) {
83					if err != nil {
84						fail(fmt.Errorf("walk %q: %w", p, err))
85						return
86					}
87					got = append(got, c.ID.String())
88				}
89				if !slices.Equal(got, want[p]) {
90					fail(fmt.Errorf("walk %q: wrong history (%d commits, want %d)", p, len(got), len(want[p])))
91					return
92				}
93				if i%3 == 0 {
94					res, err := r.LastCommits(context.Background(), tip, "", names)
95					if err != nil {
96						fail(fmt.Errorf("LastCommits: %w", err))
97						return
98					}
99					if !slices.Equal(res, wantLast) {
100						fail(fmt.Errorf("LastCommits: wrong answer"))
101						return
102					}
103				}
104			}
105		}(g)
106	}
107
108	wg.Add(1)
109	go func() { // Refresh as fast as possible
110		defer wg.Done()
111		for !stop.Load() {
112			if err := r.Refresh(); err != nil {
113				fail(fmt.Errorf("Refresh: %w", err))
114				return
115			}
116			time.Sleep(time.Millisecond)
117		}
118	}()
119
120	wg.Add(1)
121	go func() { // the writer
122		defer wg.Done()
123		defer stop.Store(true)
124		var in bytes.Buffer
125		for _, id := range w.ids {
126			fmt.Fprintln(&in, id)
127		}
128		for i := 0; time.Now().Before(deadline) && !stop.Load(); i++ {
129			var err error
130			switch i % 6 {
131			case 0: // a push: a new commit on top of the tip, a new pack
132				err = w.gitQuiet(fmt.Sprintf("commit refs/heads/push%d\ncommitter T <t@example.com> %d +0000\ndata 1\nx\nfrom %s\n\n", i, 1_700_000_000+i, w.ids[tipIdx]), "fast-import", "--quiet")
133			case 1:
134				err = w.gitQuiet(in.String(), "commit-graph", "write", "--stdin-commits", "--split", "--changed-paths")
135			case 2:
136				err = w.gitQuiet("", "repack", "-adq")
137			case 3:
138				err = w.gitQuiet(in.String(), "commit-graph", "write", "--stdin-commits", "--no-changed-paths")
139			case 4:
140				err = w.gitQuiet(in.String(), "commit-graph", "write", "--stdin-commits", "--split=replace", "--changed-paths")
141			case 5:
142				err = w.gitQuiet("", "commit-graph", "write", "--reachable", "--split=no-merge", "--changed-paths")
143			}
144			if err != nil {
145				fail(err)
146				return
147			}
148		}
149	}()
150	wg.Wait()
151	close(errc)
152	for err := range errc {
153		t.Error(err)
154	}
155}

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