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

barerepo / server / internal/repo/repo_test.go
297 lines · 9.4kb · 133728eaa05486504991b230f9d4c7e986b7defc
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package repo
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"os"
133728e barerepo 1mo
6
"path/filepath"
133728e barerepo 1mo
7
"strings"
133728e barerepo 1mo
8
"testing"
133728e barerepo 1mo
9
"time"
133728e barerepo 1mo
10
133728e barerepo 1mo
11
"github.com/barerepo/server/internal/gitx"
133728e barerepo 1mo
12
)
133728e barerepo 1mo
13
133728e barerepo 1mo
14
// TestDirRefusesEscape covers the oldest attack there is, from chapter 45.4's list.
133728e barerepo 1mo
15
func TestDirRefusesEscape(t *testing.T) {
133728e barerepo 1mo
16
root := t.TempDir()
133728e barerepo 1mo
17
bad := [][2]string{
133728e barerepo 1mo
18
{"john", "../../etc"},
133728e barerepo 1mo
19
{"../../etc", "johnbot"},
133728e barerepo 1mo
20
{"john", ".."},
133728e barerepo 1mo
21
{"john", "a/b"},
133728e barerepo 1mo
22
{"john", ""},
133728e barerepo 1mo
23
{"john", "John"},
133728e barerepo 1mo
24
{"admin", "johnbot"},
133728e barerepo 1mo
25
{"", "johnbot"},
133728e barerepo 1mo
26
{"john", "sub/../../../../etc"},
133728e barerepo 1mo
27
}
133728e barerepo 1mo
28
for _, c := range bad {
133728e barerepo 1mo
29
if dir, err := Dir(root, c[0], c[1]); err == nil {
133728e barerepo 1mo
30
t.Errorf("Dir(%q, %q) = %q, want an error", c[0], c[1], dir)
133728e barerepo 1mo
31
}
133728e barerepo 1mo
32
}
133728e barerepo 1mo
33
dir, err := Dir(root, "john", "johnbot")
133728e barerepo 1mo
34
if err != nil {
133728e barerepo 1mo
35
t.Fatalf("Dir: %v", err)
133728e barerepo 1mo
36
}
133728e barerepo 1mo
37
// A reserved name is a route only at the top level, so john/runner is somebody's repository.
133728e barerepo 1mo
38
for _, name := range []string{"runner", "admin", "new", "raw"} {
133728e barerepo 1mo
39
if _, err := Dir(root, "john", name); err != nil {
133728e barerepo 1mo
40
t.Errorf("Dir(john, %q) = %v, want a repository named after a route to be allowed", name, err)
133728e barerepo 1mo
41
}
133728e barerepo 1mo
42
}
133728e barerepo 1mo
43
if want := filepath.Join(root, "john", "johnbot.git"); dir != want {
133728e barerepo 1mo
44
t.Errorf("Dir = %q, want %q", dir, want)
133728e barerepo 1mo
45
}
133728e barerepo 1mo
46
}
133728e barerepo 1mo
47
133728e barerepo 1mo
48
func TestCreate(t *testing.T) {
133728e barerepo 1mo
49
if _, err := gitx.Version(context.Background()); err != nil {
133728e barerepo 1mo
50
t.Skip("git is not installed")
133728e barerepo 1mo
51
}
133728e barerepo 1mo
52
ctx := context.Background()
133728e barerepo 1mo
53
root := t.TempDir()
133728e barerepo 1mo
54
133728e barerepo 1mo
55
// Rule 6: HEAD is what was asked for, never a hardcoded name or git's local default.
133728e barerepo 1mo
56
dir, err := Create(ctx, root, "john", "johnbot", "trunk", "/usr/local/bin/barerepo")
133728e barerepo 1mo
57
if err != nil {
133728e barerepo 1mo
58
t.Fatalf("Create: %v", err)
133728e barerepo 1mo
59
}
133728e barerepo 1mo
60
if b, err := HeadBranch(ctx, dir); err != nil || b != "trunk" {
133728e barerepo 1mo
61
t.Errorf("HeadBranch = %q, %v, want trunk", b, err)
133728e barerepo 1mo
62
}
133728e barerepo 1mo
63
if !IsEmpty(ctx, dir) {
133728e barerepo 1mo
64
t.Error("a new repository is not empty")
133728e barerepo 1mo
65
}
133728e barerepo 1mo
66
if !Exists(root, "john", "johnbot") {
133728e barerepo 1mo
67
t.Error("Exists = false after Create")
133728e barerepo 1mo
68
}
133728e barerepo 1mo
69
if _, err := Create(ctx, root, "john", "johnbot", "trunk", ""); err == nil {
133728e barerepo 1mo
70
t.Error("creating the same repository twice was allowed")
133728e barerepo 1mo
71
}
133728e barerepo 1mo
72
133728e barerepo 1mo
73
// Chapter 41.5: two hooks, no samples, no update hook.
133728e barerepo 1mo
74
entries, err := os.ReadDir(filepath.Join(dir, "hooks"))
133728e barerepo 1mo
75
if err != nil {
133728e barerepo 1mo
76
t.Fatal(err)
133728e barerepo 1mo
77
}
133728e barerepo 1mo
78
var names []string
133728e barerepo 1mo
79
for _, e := range entries {
133728e barerepo 1mo
80
names = append(names, e.Name())
133728e barerepo 1mo
81
}
133728e barerepo 1mo
82
if strings.Join(names, ",") != "post-receive,pre-receive,proc-receive" {
133728e barerepo 1mo
83
t.Errorf("hooks = %v, want exactly pre-receive, post-receive and proc-receive", names)
133728e barerepo 1mo
84
}
133728e barerepo 1mo
85
// The value is a prefix, and a glob matches nothing and fails silently.
133728e barerepo 1mo
86
got, err := gitx.Run(ctx, dir, "config", "--get", "receive.procReceiveRefs")
133728e barerepo 1mo
87
if err != nil || strings.TrimSpace(got) != "refs/proposals" {
133728e barerepo 1mo
88
t.Errorf("receive.procReceiveRefs = %q, %v; want refs/proposals", got, err)
133728e barerepo 1mo
89
}
133728e barerepo 1mo
90
body, err := os.ReadFile(filepath.Join(dir, "hooks", "pre-receive"))
133728e barerepo 1mo
91
if err != nil {
133728e barerepo 1mo
92
t.Fatal(err)
133728e barerepo 1mo
93
}
133728e barerepo 1mo
94
if want := "#!/bin/sh\nexec /usr/local/bin/barerepo hook pre-receive\n"; string(body) != want {
133728e barerepo 1mo
95
t.Errorf("pre-receive = %q, want %q", body, want)
133728e barerepo 1mo
96
}
133728e barerepo 1mo
97
if fi, _ := os.Stat(filepath.Join(dir, "hooks", "pre-receive")); fi.Mode()&0o100 == 0 {
133728e barerepo 1mo
98
t.Error("pre-receive is not executable")
133728e barerepo 1mo
99
}
133728e barerepo 1mo
100
}
133728e barerepo 1mo
101
133728e barerepo 1mo
102
// A ref name that is really a git argument must never reach a command.
133728e barerepo 1mo
103
func TestCreateRefusesFlagAsBranch(t *testing.T) {
133728e barerepo 1mo
104
if _, err := gitx.Version(context.Background()); err != nil {
133728e barerepo 1mo
105
t.Skip("git is not installed")
133728e barerepo 1mo
106
}
133728e barerepo 1mo
107
root := t.TempDir()
133728e barerepo 1mo
108
for _, branch := range []string{"--upload-pack=/bin/sh", "-x", "a b", "a..b"} {
133728e barerepo 1mo
109
if _, err := Create(context.Background(), root, "john", "johnbot", branch, ""); err == nil {
133728e barerepo 1mo
110
t.Errorf("branch %q was accepted", branch)
133728e barerepo 1mo
111
}
133728e barerepo 1mo
112
}
133728e barerepo 1mo
113
}
133728e barerepo 1mo
114
133728e barerepo 1mo
115
// A deleted repository waits out its window with the name still claimed. Chapter 44.4.
133728e barerepo 1mo
116
func TestTrash(t *testing.T) {
133728e barerepo 1mo
117
if _, err := gitx.Version(context.Background()); err != nil {
133728e barerepo 1mo
118
t.Skip("git is not installed")
133728e barerepo 1mo
119
}
133728e barerepo 1mo
120
root := t.TempDir()
133728e barerepo 1mo
121
ctx := context.Background()
133728e barerepo 1mo
122
if _, err := Create(ctx, root, "john", "johnbot", "master", ""); err != nil {
133728e barerepo 1mo
123
t.Fatal(err)
133728e barerepo 1mo
124
}
133728e barerepo 1mo
125
now := time.Now()
133728e barerepo 1mo
126
if _, err := Trash(root, "john", "johnbot", now); err != nil {
133728e barerepo 1mo
127
t.Fatal(err)
133728e barerepo 1mo
128
}
133728e barerepo 1mo
129
if Exists(root, "john", "johnbot") {
133728e barerepo 1mo
130
t.Error("the repository is still being served")
133728e barerepo 1mo
131
}
133728e barerepo 1mo
132
if !InTrash(root, "john", "johnbot") {
133728e barerepo 1mo
133
t.Error("the name is not held while the window runs")
133728e barerepo 1mo
134
}
133728e barerepo 1mo
135
// Nothing inside the window is erased.
133728e barerepo 1mo
136
if n, err := EmptyTrash(root, 30*24*time.Hour, now); err != nil || n != 0 {
133728e barerepo 1mo
137
t.Errorf("erased %d inside the window (%v)", n, err)
133728e barerepo 1mo
138
}
133728e barerepo 1mo
139
if !InTrash(root, "john", "johnbot") {
133728e barerepo 1mo
140
t.Error("the copy was erased early")
133728e barerepo 1mo
141
}
133728e barerepo 1mo
142
// Past it, it goes.
133728e barerepo 1mo
143
if n, err := EmptyTrash(root, 30*24*time.Hour, now.Add(31*24*time.Hour)); err != nil || n != 1 {
133728e barerepo 1mo
144
t.Errorf("erased %d past the window (%v)", n, err)
133728e barerepo 1mo
145
}
133728e barerepo 1mo
146
if InTrash(root, "john", "johnbot") {
133728e barerepo 1mo
147
t.Error("the copy outlived the window")
133728e barerepo 1mo
148
}
133728e barerepo 1mo
149
}
133728e barerepo 1mo
150
133728e barerepo 1mo
151
func TestMove(t *testing.T) {
133728e barerepo 1mo
152
if _, err := gitx.Version(context.Background()); err != nil {
133728e barerepo 1mo
153
t.Skip("git is not installed")
133728e barerepo 1mo
154
}
133728e barerepo 1mo
155
root := t.TempDir()
133728e barerepo 1mo
156
ctx := context.Background()
133728e barerepo 1mo
157
if _, err := Create(ctx, root, "john", "johnbot", "master", ""); err != nil {
133728e barerepo 1mo
158
t.Fatal(err)
133728e barerepo 1mo
159
}
133728e barerepo 1mo
160
if err := Move(root, "john", "johnbot", "lisa", "johnbot"); err != nil {
133728e barerepo 1mo
161
t.Fatal(err)
133728e barerepo 1mo
162
}
133728e barerepo 1mo
163
if Exists(root, "john", "johnbot") || !Exists(root, "lisa", "johnbot") {
133728e barerepo 1mo
164
t.Error("the directory did not move")
133728e barerepo 1mo
165
}
133728e barerepo 1mo
166
// Moving onto a name that exists must not clobber it.
133728e barerepo 1mo
167
if _, err := Create(ctx, root, "john", "other", "master", ""); err != nil {
133728e barerepo 1mo
168
t.Fatal(err)
133728e barerepo 1mo
169
}
133728e barerepo 1mo
170
if err := Move(root, "john", "other", "lisa", "johnbot"); err == nil {
133728e barerepo 1mo
171
t.Error("a move over an existing repository was allowed")
133728e barerepo 1mo
172
}
133728e barerepo 1mo
173
}
133728e barerepo 1mo
174
133728e barerepo 1mo
175
// A copy borrows objects, so Detach is what stops a delete taking its history too.
133728e barerepo 1mo
176
func TestCopyAndDetach(t *testing.T) {
133728e barerepo 1mo
177
ctx := context.Background()
133728e barerepo 1mo
178
if _, err := gitx.Version(ctx); err != nil {
133728e barerepo 1mo
179
t.Skip("git is not installed")
133728e barerepo 1mo
180
}
133728e barerepo 1mo
181
root := t.TempDir()
133728e barerepo 1mo
182
src, err := Create(ctx, root, "john", "johnbot", "master", "")
133728e barerepo 1mo
183
if err != nil {
133728e barerepo 1mo
184
t.Fatal(err)
133728e barerepo 1mo
185
}
133728e barerepo 1mo
186
// One commit, and a proposal ref that must not travel.
133728e barerepo 1mo
187
blob, err := gitx.RunStdin(ctx, src, "hello\n", "hash-object", "-w", "--stdin")
133728e barerepo 1mo
188
if err != nil {
133728e barerepo 1mo
189
t.Fatal(err)
133728e barerepo 1mo
190
}
133728e barerepo 1mo
191
tree, err := gitx.RunStdin(ctx, src,
133728e barerepo 1mo
192
"100644 blob "+strings.TrimSpace(blob)+"\tREADME\n", "mktree")
133728e barerepo 1mo
193
if err != nil {
133728e barerepo 1mo
194
t.Fatal(err)
133728e barerepo 1mo
195
}
133728e barerepo 1mo
196
commit, err := gitx.RunStdin(ctx, src, "", "commit-tree", strings.TrimSpace(tree), "-m", "first")
133728e barerepo 1mo
197
if err != nil {
133728e barerepo 1mo
198
t.Fatal(err)
133728e barerepo 1mo
199
}
133728e barerepo 1mo
200
sha := strings.TrimSpace(commit)
133728e barerepo 1mo
201
for _, ref := range []string{"refs/heads/master", "refs/proposals/1", "refs/notes/threads/1"} {
133728e barerepo 1mo
202
if _, err := gitx.Run(ctx, src, "update-ref", ref, sha); err != nil {
133728e barerepo 1mo
203
t.Fatal(err)
133728e barerepo 1mo
204
}
133728e barerepo 1mo
205
}
133728e barerepo 1mo
206
133728e barerepo 1mo
207
if _, err := Copy(ctx, root, "john", "johnbot", "lisa", "johnbot", ""); err != nil {
133728e barerepo 1mo
208
t.Fatal(err)
133728e barerepo 1mo
209
}
133728e barerepo 1mo
210
dst, _ := Dir(root, "lisa", "johnbot")
133728e barerepo 1mo
211
133728e barerepo 1mo
212
// Chapter 21.1: history and notes come across, proposals do not.
133728e barerepo 1mo
213
for _, want := range []string{"refs/heads/master", "refs/notes/threads/1"} {
133728e barerepo 1mo
214
if _, err := gitx.Run(ctx, dst, "rev-parse", "--verify", "--quiet", want); err != nil {
133728e barerepo 1mo
215
t.Errorf("%s did not come across", want)
215 of 297 lines · the whole file
history · rawbarerepo 0.1.0