File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/gitread/log_test.go
384 lines · 11kb · 133728eaa05486504991b230f9d4c7e986b7defc
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package gitread
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"os"
133728e barerepo 1mo
6
"os/exec"
133728e barerepo 1mo
7
"path/filepath"
133728e barerepo 1mo
8
"testing"
133728e barerepo 1mo
9
133728e barerepo 1mo
10
"github.com/barerepo/server/internal/gitx"
133728e barerepo 1mo
11
"strings"
133728e barerepo 1mo
12
)
133728e barerepo 1mo
13
133728e barerepo 1mo
14
// buildRepo makes a real repository, because only git's actual output exercises the parser.
133728e barerepo 1mo
15
func buildRepo(t *testing.T) string {
133728e barerepo 1mo
16
t.Helper()
133728e barerepo 1mo
17
if _, err := gitx.Version(context.Background()); err != nil {
133728e barerepo 1mo
18
t.Skip("git is not installed")
133728e barerepo 1mo
19
}
133728e barerepo 1mo
20
dir := t.TempDir()
133728e barerepo 1mo
21
run := func(args ...string) {
133728e barerepo 1mo
22
t.Helper()
133728e barerepo 1mo
23
cmd := exec.Command(gitx.Bin, args...)
133728e barerepo 1mo
24
cmd.Dir = dir
133728e barerepo 1mo
25
cmd.Env = append(os.Environ(),
133728e barerepo 1mo
26
"GIT_AUTHOR_NAME=lisa", "GIT_AUTHOR_EMAIL=m@x",
133728e barerepo 1mo
27
"GIT_COMMITTER_NAME=lisa", "GIT_COMMITTER_EMAIL=m@x")
133728e barerepo 1mo
28
if out, err := cmd.CombinedOutput(); err != nil {
133728e barerepo 1mo
29
t.Fatalf("git %v: %v\n%s", args, err, out)
133728e barerepo 1mo
30
}
133728e barerepo 1mo
31
}
133728e barerepo 1mo
32
write := func(name, body string) {
133728e barerepo 1mo
33
t.Helper()
133728e barerepo 1mo
34
if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755); err != nil {
133728e barerepo 1mo
35
t.Fatal(err)
133728e barerepo 1mo
36
}
133728e barerepo 1mo
37
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
133728e barerepo 1mo
38
t.Fatal(err)
133728e barerepo 1mo
39
}
133728e barerepo 1mo
40
}
133728e barerepo 1mo
41
run("init", "-q", "-b", "master")
133728e barerepo 1mo
42
write("config.go", "package main\n\nfunc Load() {}\n")
133728e barerepo 1mo
43
run("add", "-A")
133728e barerepo 1mo
44
run("commit", "-qm", "first")
133728e barerepo 1mo
45
write("config.go", "package main\n\nfunc Load() error {\n\treturn nil\n}\n")
133728e barerepo 1mo
46
write("irc/conn.go", "package irc\n")
133728e barerepo 1mo
47
run("add", "-A")
133728e barerepo 1mo
48
run("commit", "-qm", "second commit\n\nwith a body that explains it.")
133728e barerepo 1mo
49
return dir
133728e barerepo 1mo
50
}
133728e barerepo 1mo
51
133728e barerepo 1mo
52
func TestLog(t *testing.T) {
133728e barerepo 1mo
53
dir := buildRepo(t)
133728e barerepo 1mo
54
commits, err := Log(context.Background(), dir, "HEAD", "", 10)
133728e barerepo 1mo
55
if err != nil {
133728e barerepo 1mo
56
t.Fatal(err)
133728e barerepo 1mo
57
}
133728e barerepo 1mo
58
if len(commits) != 2 {
133728e barerepo 1mo
59
t.Fatalf("got %d commits, want 2", len(commits))
133728e barerepo 1mo
60
}
133728e barerepo 1mo
61
// The log page draws no diff, so the hunks below come from the commit page's read.
133728e barerepo 1mo
62
shown, err := Show(context.Background(), dir, commits[0].SHA)
133728e barerepo 1mo
63
if err != nil || shown == nil {
133728e barerepo 1mo
64
t.Fatalf("Show(%s) = %v, %v", commits[0].SHA, shown, err)
133728e barerepo 1mo
65
}
133728e barerepo 1mo
66
133728e barerepo 1mo
67
// Newest first, per chapter 24.
133728e barerepo 1mo
68
c := commits[0]
133728e barerepo 1mo
69
if c.Subject != "second commit" {
133728e barerepo 1mo
70
t.Errorf("Subject = %q", c.Subject)
133728e barerepo 1mo
71
}
133728e barerepo 1mo
72
if c.Body != "with a body that explains it." {
133728e barerepo 1mo
73
t.Errorf("Body = %q", c.Body)
133728e barerepo 1mo
74
}
133728e barerepo 1mo
75
if c.Author != "lisa" {
133728e barerepo 1mo
76
t.Errorf("Author = %q", c.Author)
133728e barerepo 1mo
77
}
133728e barerepo 1mo
78
if c.When.IsZero() {
133728e barerepo 1mo
79
t.Error("When is zero")
133728e barerepo 1mo
80
}
133728e barerepo 1mo
81
if len(c.Short) == 0 || len(c.SHA) != 40 {
133728e barerepo 1mo
82
t.Errorf("SHA = %q, Short = %q", c.SHA, c.Short)
133728e barerepo 1mo
83
}
133728e barerepo 1mo
84
if len(c.Files) != 2 {
133728e barerepo 1mo
85
t.Fatalf("got %d files, want 2: %+v", len(c.Files), c.Files)
133728e barerepo 1mo
86
}
133728e barerepo 1mo
87
// git orders the diff by path, so config.go comes before irc/conn.go.
133728e barerepo 1mo
88
if c.Files[0].Path != "config.go" || c.Files[1].Path != "irc/conn.go" {
133728e barerepo 1mo
89
t.Errorf("paths = %q, %q", c.Files[0].Path, c.Files[1].Path)
133728e barerepo 1mo
90
}
133728e barerepo 1mo
91
if c.Add == 0 || c.Del == 0 {
133728e barerepo 1mo
92
t.Errorf("Add = %d, Del = %d, want both non-zero", c.Add, c.Del)
133728e barerepo 1mo
93
}
133728e barerepo 1mo
94
133728e barerepo 1mo
95
// The hunk must survive with its header and its line kinds intact.
133728e barerepo 1mo
96
f := shown.Files[0]
133728e barerepo 1mo
97
if len(f.Hunks) == 0 {
133728e barerepo 1mo
98
t.Fatal("config.go has no hunks")
133728e barerepo 1mo
99
}
133728e barerepo 1mo
100
h := f.Hunks[0]
133728e barerepo 1mo
101
if h.Header[:2] != "@@" {
133728e barerepo 1mo
102
t.Errorf("hunk header = %q", h.Header)
133728e barerepo 1mo
103
}
133728e barerepo 1mo
104
var kinds string
133728e barerepo 1mo
105
for _, l := range h.Lines {
133728e barerepo 1mo
106
kinds += string(l.Kind)
133728e barerepo 1mo
107
}
133728e barerepo 1mo
108
for _, want := range []byte{' ', '+', '-'} {
133728e barerepo 1mo
109
if !contains(kinds, want) {
133728e barerepo 1mo
110
t.Errorf("no %q lines in %q", want, kinds)
133728e barerepo 1mo
111
}
133728e barerepo 1mo
112
}
133728e barerepo 1mo
113
// The first commit added a file, so it has no deletions.
133728e barerepo 1mo
114
if commits[1].Del != 0 {
133728e barerepo 1mo
115
t.Errorf("the first commit reports %d deletions", commits[1].Del)
133728e barerepo 1mo
116
}
133728e barerepo 1mo
117
}
133728e barerepo 1mo
118
133728e barerepo 1mo
119
// A revision that is really an argument must never reach git.
133728e barerepo 1mo
120
func TestLogRefusesFlags(t *testing.T) {
133728e barerepo 1mo
121
dir := buildRepo(t)
133728e barerepo 1mo
122
for _, rev := range []string{"--upload-pack=/bin/sh", "-x", "master; id", "HEAD@{0}"} {
133728e barerepo 1mo
123
got, err := Log(context.Background(), dir, rev, "", 10)
133728e barerepo 1mo
124
if err != nil || got != nil {
133728e barerepo 1mo
125
t.Errorf("Log(%q) = %v, %v; want nil, nil", rev, got, err)
133728e barerepo 1mo
126
}
133728e barerepo 1mo
127
}
133728e barerepo 1mo
128
}
133728e barerepo 1mo
129
133728e barerepo 1mo
130
func contains(s string, b byte) bool {
133728e barerepo 1mo
131
for i := 0; i < len(s); i++ {
133728e barerepo 1mo
132
if s[i] == b {
133728e barerepo 1mo
133
return true
133728e barerepo 1mo
134
}
133728e barerepo 1mo
135
}
133728e barerepo 1mo
136
return false
133728e barerepo 1mo
137
}
133728e barerepo 1mo
138
133728e barerepo 1mo
139
func TestOpenAndBlame(t *testing.T) {
133728e barerepo 1mo
140
dir := buildRepo(t)
133728e barerepo 1mo
141
f, err := Open(context.Background(), dir, "HEAD", "config.go")
133728e barerepo 1mo
142
if err != nil || f == nil {
133728e barerepo 1mo
143
t.Fatalf("Open: %v %v", f, err)
133728e barerepo 1mo
144
}
133728e barerepo 1mo
145
if len(f.Lines) != 5 {
133728e barerepo 1mo
146
t.Fatalf("got %d lines, want 5: %+v", len(f.Lines), f.Lines)
133728e barerepo 1mo
147
}
133728e barerepo 1mo
148
if f.Binary || f.TooBig {
133728e barerepo 1mo
149
t.Errorf("Binary = %v, TooBig = %v", f.Binary, f.TooBig)
133728e barerepo 1mo
150
}
133728e barerepo 1mo
151
if f.Lines[0].Text != "package main" {
133728e barerepo 1mo
152
t.Errorf("line 1 = %q", f.Lines[0].Text)
133728e barerepo 1mo
153
}
133728e barerepo 1mo
154
if f.Lines[0].Number != 1 || f.Lines[4].Number != 5 {
133728e barerepo 1mo
155
t.Error("line numbers are wrong")
133728e barerepo 1mo
156
}
133728e barerepo 1mo
157
// Blame in the gutter on every line, always. Chapter 24.
133728e barerepo 1mo
158
for _, l := range f.Lines {
133728e barerepo 1mo
159
if l.Short == "" || l.Author != "lisa" || l.When.IsZero() {
133728e barerepo 1mo
160
t.Errorf("line %d has no blame: %+v", l.Number, l)
133728e barerepo 1mo
161
break
133728e barerepo 1mo
162
}
133728e barerepo 1mo
163
}
133728e barerepo 1mo
164
if f, err := Open(context.Background(), dir, "HEAD", "nope.go"); err != nil || f != nil {
133728e barerepo 1mo
165
t.Errorf("a missing file returned %v, %v", f, err)
133728e barerepo 1mo
166
}
133728e barerepo 1mo
167
for _, bad := range []string{"../../etc/passwd", "-rf", "a/../../b"} {
133728e barerepo 1mo
168
if f, _ := Open(context.Background(), dir, "HEAD", bad); f != nil {
133728e barerepo 1mo
169
t.Errorf("Open(%q) returned a file", bad)
133728e barerepo 1mo
170
}
133728e barerepo 1mo
171
}
133728e barerepo 1mo
172
}
133728e barerepo 1mo
173
133728e barerepo 1mo
174
func TestOpenDetectsBinary(t *testing.T) {
133728e barerepo 1mo
175
dir := buildRepo(t)
133728e barerepo 1mo
176
if err := os.WriteFile(filepath.Join(dir, "blob.bin"),
133728e barerepo 1mo
177
append([]byte("gif89a"), 0, 1, 2, 3), 0o644); err != nil {
133728e barerepo 1mo
178
t.Fatal(err)
133728e barerepo 1mo
179
}
133728e barerepo 1mo
180
cmd := exec.Command(gitx.Bin, "add", "-A")
133728e barerepo 1mo
181
cmd.Dir = dir
133728e barerepo 1mo
182
cmd.Run()
133728e barerepo 1mo
183
cmd = exec.Command(gitx.Bin, "-c", "user.email=m@x", "-c", "user.name=lisa", "commit", "-qm", "binary")
133728e barerepo 1mo
184
cmd.Dir = dir
133728e barerepo 1mo
185
if out, err := cmd.CombinedOutput(); err != nil {
133728e barerepo 1mo
186
t.Fatalf("commit: %v\n%s", err, out)
133728e barerepo 1mo
187
}
133728e barerepo 1mo
188
f, err := Open(context.Background(), dir, "HEAD", "blob.bin")
133728e barerepo 1mo
189
if err != nil || f == nil {
133728e barerepo 1mo
190
t.Fatalf("Open: %v %v", f, err)
133728e barerepo 1mo
191
}
133728e barerepo 1mo
192
if !f.Binary {
133728e barerepo 1mo
193
t.Error("a file with a null byte was not detected as binary")
133728e barerepo 1mo
194
}
133728e barerepo 1mo
195
if len(f.Lines) != 0 {
133728e barerepo 1mo
196
t.Error("binary content was rendered into lines")
133728e barerepo 1mo
197
}
133728e barerepo 1mo
198
}
133728e barerepo 1mo
199
133728e barerepo 1mo
200
func TestCompare(t *testing.T) {
133728e barerepo 1mo
201
dir := buildRepo(t)
133728e barerepo 1mo
202
c, err := Compare(context.Background(), dir, "HEAD~1", "HEAD")
133728e barerepo 1mo
203
if err != nil {
133728e barerepo 1mo
204
t.Fatal(err)
133728e barerepo 1mo
205
}
133728e barerepo 1mo
206
if c.Missing != "" {
133728e barerepo 1mo
207
t.Fatalf("Missing = %q", c.Missing)
133728e barerepo 1mo
208
}
133728e barerepo 1mo
209
if c.Commits != 1 {
133728e barerepo 1mo
210
t.Errorf("Commits = %d, want 1", c.Commits)
133728e barerepo 1mo
211
}
133728e barerepo 1mo
212
if len(c.Files) != 2 {
133728e barerepo 1mo
213
t.Errorf("got %d files, want 2", len(c.Files))
133728e barerepo 1mo
214
}
133728e barerepo 1mo
215
if c.Add == 0 {
133728e barerepo 1mo
216
t.Error("Add = 0")
133728e barerepo 1mo
217
}
133728e barerepo 1mo
218
if c.Conflict {
133728e barerepo 1mo
219
t.Error("a fast-forward comparison reported conflicts")
133728e barerepo 1mo
220
}
133728e barerepo 1mo
221
// A side that does not exist is named, not swallowed.
221 of 384 lines · the whole file
history · rawbarerepo 0.1.0