133728e barerepo 1mo
1
package gitx
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"fmt"
133728e barerepo 1mo
6
"os"
133728e barerepo 1mo
7
"os/exec"
133728e barerepo 1mo
8
"path/filepath"
133728e barerepo 1mo
9
"strings"
133728e barerepo 1mo
10
"testing"
133728e barerepo 1mo
13
func TestValidName(t *testing.T) {
133728e barerepo 1mo
14
good := []string{"john", "j", "lisa-2", "a0", "x" + string(make([]byte, 0))}
133728e barerepo 1mo
15
for _, s := range good {
133728e barerepo 1mo
16
if !ValidName(s) {
133728e barerepo 1mo
17
t.Errorf("ValidName(%q) = false, want true", s)
133728e barerepo 1mo
20
bad := []string{
133728e barerepo 1mo
21
"", "-john", "John", "jo_hn", "j.hn", "../../etc", "j/n",
133728e barerepo 1mo
22
"admin", "new", "signin", "signup", "keys", "tokens", "auth",
133728e barerepo 1mo
23
"inbox", "runner", "raw", "search",
133728e barerepo 1mo
24
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // 41 chars
133728e barerepo 1mo
26
for _, s := range bad {
133728e barerepo 1mo
27
if ValidName(s) {
133728e barerepo 1mo
28
t.Errorf("ValidName(%q) = true, want false", s)
133728e barerepo 1mo
32
// A repository is a second path segment, so a name a route owns at the top shadows nothing.
133728e barerepo 1mo
33
for _, s := range []string{"admin", "new", "signin", "runner", "raw", "api", "about"} {
133728e barerepo 1mo
34
if !ValidRepoName(s) {
133728e barerepo 1mo
35
t.Errorf("ValidRepoName(%q) = false, want a repository to be allowed that name", s)
133728e barerepo 1mo
38
for _, s := range []string{"", "-john", "John", "jo_hn", "j.hn", "../../etc", "j/n",
133728e barerepo 1mo
39
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} {
133728e barerepo 1mo
40
if ValidRepoName(s) {
133728e barerepo 1mo
41
t.Errorf("ValidRepoName(%q) = true, want false", s)
133728e barerepo 1mo
46
func TestValidRef(t *testing.T) {
133728e barerepo 1mo
47
good := []string{
133728e barerepo 1mo
48
"refs/heads/master", "refs/heads/main", "refs/proposals/new",
133728e barerepo 1mo
49
"refs/proposals/7/rev/2", "refs/notes/threads/3", "refs/tags/v1.0.0",
133728e barerepo 1mo
51
for _, s := range good {
133728e barerepo 1mo
52
if !ValidRef(s) {
133728e barerepo 1mo
53
t.Errorf("ValidRef(%q) = false, want true", s)
133728e barerepo 1mo
56
bad := []string{
133728e barerepo 1mo
57
"", "--upload-pack=/bin/sh", "refs/heads/--upload-pack=/bin/sh",
133728e barerepo 1mo
58
"refs/heads/-x", "refs/-/x", "refs/heads/..", "refs/heads/a..b",
133728e barerepo 1mo
59
"refs/heads/.hidden", "refs/heads/x.lock", "refs/heads/a b",
133728e barerepo 1mo
60
"refs/heads/a\x00b", "refs/heads/a\nb", "refs/heads//x",
133728e barerepo 1mo
61
"refs/heads/a~1", "refs/heads/a^", "refs/heads/a:b", "refs/heads/",
133728e barerepo 1mo
63
for _, s := range bad {
133728e barerepo 1mo
64
if ValidRef(s) {
133728e barerepo 1mo
65
t.Errorf("ValidRef(%q) = true, want false", s)
133728e barerepo 1mo
70
func TestValidRev(t *testing.T) {
133728e barerepo 1mo
71
good := []string{
133728e barerepo 1mo
72
"master", "main", "trunk", "refs/heads/master", "refs/proposals/47",
133728e barerepo 1mo
73
"v1.0.0", "a3f9c2", "8b1d44e", "HEAD", "HEAD~2", "master^",
133728e barerepo 1mo
74
"feature/thing",
133728e barerepo 1mo
76
for _, s := range good {
133728e barerepo 1mo
77
if !ValidRev(s) {
133728e barerepo 1mo
78
t.Errorf("ValidRev(%q) = false, want true", s)
133728e barerepo 1mo
81
bad := []string{
133728e barerepo 1mo
82
"", "-master", "--upload-pack=/bin/sh", "master..main", "master...main",
133728e barerepo 1mo
83
"refs/heads/-x", "master; rm -rf /", "master rm", "a\x00b",
133728e barerepo 1mo
84
"$(rm -rf /)", "`id`", "refs//x", "refs/.hidden/x",
133728e barerepo 1mo
85
// reflog syntax reaches unreferenced objects, and has no business arriving from a url.
133728e barerepo 1mo
86
"HEAD@{0}", "master@{yesterday}",
133728e barerepo 1mo
88
for _, s := range bad {
133728e barerepo 1mo
89
if ValidRev(s) {
133728e barerepo 1mo
90
t.Errorf("ValidRev(%q) = true, want false", s)
133728e barerepo 1mo
95
func TestValidPath(t *testing.T) {
133728e barerepo 1mo
96
good := []string{"config.go", "irc/conn.go", ".barerepo/config", "a/b/c/d.txt", "README"}
133728e barerepo 1mo
97
for _, s := range good {
133728e barerepo 1mo
98
if !ValidPath(s) {
133728e barerepo 1mo
99
t.Errorf("ValidPath(%q) = false, want true", s)
133728e barerepo 1mo
100
}
133728e barerepo 1mo
101
}
133728e barerepo 1mo
102
bad := []string{"", "/etc/passwd", "../../etc/passwd", "a/../../b", "a//b", "./x", "-rf", "a\x00b"}
133728e barerepo 1mo
103
for _, s := range bad {
133728e barerepo 1mo
104
if ValidPath(s) {
133728e barerepo 1mo
105
t.Errorf("ValidPath(%q) = true, want false", s)
133728e barerepo 1mo
106
}
133728e barerepo 1mo
107
}
133728e barerepo 1mo
110
// Batch must skip an object that is not there without losing its place in the stream.
133728e barerepo 1mo
111
func TestBatchReadsManyObjectsAndAMissingOne(t *testing.T) {
133728e barerepo 1mo
112
dir := t.TempDir()
133728e barerepo 1mo
113
for _, args := range [][]string{
133728e barerepo 1mo
114
{"init", "-q", "--bare", "-b", "master", dir},
133728e barerepo 1mo
115
} {
133728e barerepo 1mo
116
if out, err := exec.Command("git", args...).CombinedOutput(); err != nil {
133728e barerepo 1mo
117
t.Fatalf("git %v: %v: %s", args, err, out)
133728e barerepo 1mo
118
}
133728e barerepo 1mo
119
}
133728e barerepo 1mo
120
ctx := context.Background()
133728e barerepo 1mo
121
want := map[string]string{}
133728e barerepo 1mo
122
var specs []string
133728e barerepo 1mo
123
for _, body := range []string{"one", "two\nlines", "three\x00with a nul"} {
133728e barerepo 1mo
124
sha, err := RunStdin(ctx, dir, body, "hash-object", "-w", "--stdin")
133728e barerepo 1mo
125
if err != nil {
133728e barerepo 1mo
126
t.Fatal(err)
133728e barerepo 1mo
127
}
133728e barerepo 1mo
128
sha = strings.TrimSpace(sha)
133728e barerepo 1mo
129
specs = append(specs, sha)
133728e barerepo 1mo
130
want[sha] = body
133728e barerepo 1mo
131
}
133728e barerepo 1mo
132
// A spec git cannot resolve must not shift the answers that follow it.
133728e barerepo 1mo
133
specs = append([]string{"0000000000000000000000000000000000000000"}, specs...)
133728e barerepo 1mo
135
got, err := Batch(ctx, dir, specs)
133728e barerepo 1mo
136
if err != nil {
133728e barerepo 1mo
137
t.Fatal(err)
133728e barerepo 1mo
138
}
133728e barerepo 1mo
139
if len(got) != len(want) {
133728e barerepo 1mo
140
t.Fatalf("asked for %d objects and one miss, got %d back", len(want), len(got))
133728e barerepo 1mo
141
}
133728e barerepo 1mo
142
for sha, body := range want {
133728e barerepo 1mo
143
obj := got[sha]
133728e barerepo 1mo
144
if obj == nil {
133728e barerepo 1mo
145
t.Fatalf("%s is missing from the batch", sha)
133728e barerepo 1mo
146
}
133728e barerepo 1mo
147
if obj.Body != body {
133728e barerepo 1mo
148
t.Errorf("%s: body is %q, want %q", sha, obj.Body, body)
133728e barerepo 1mo
149
}
133728e barerepo 1mo
150
if obj.Type != "blob" {
133728e barerepo 1mo
151
t.Errorf("%s: type is %q, want blob", sha, obj.Type)
133728e barerepo 1mo
152
}
133728e barerepo 1mo
153
}
133728e barerepo 1mo
156
// TreeEntries reads twenty binary bytes per entry, so a drifting read loses all the rest.
133728e barerepo 1mo
157
func TestTreeEntriesReadsARealTree(t *testing.T) {
133728e barerepo 1mo
158
dir := t.TempDir()
133728e barerepo 1mo
159
if out, err := exec.Command("git", "init", "-q", "--bare", "-b", "master", dir).CombinedOutput(); err != nil {
133728e barerepo 1mo
160
t.Fatalf("git init: %v: %s", err, out)
133728e barerepo 1mo
161
}
133728e barerepo 1mo
162
ctx := context.Background()
133728e barerepo 1mo
163
var mktree strings.Builder
133728e barerepo 1mo
164
want := map[string]string{}
133728e barerepo 1mo
165
for _, name := range []string{"meta", "0a1b2c3d", "zz-last"} {
133728e barerepo 1mo
166
sha, err := RunStdin(ctx, dir, "body of "+name, "hash-object", "-w", "--stdin")
133728e barerepo 1mo
167
if err != nil {
133728e barerepo 1mo
168
t.Fatal(err)
133728e barerepo 1mo
169
}
133728e barerepo 1mo
170
sha = strings.TrimSpace(sha)
133728e barerepo 1mo
171
want[name] = sha
133728e barerepo 1mo
172
fmt.Fprintf(&mktree, "100644 blob %s\t%s\n", sha, name)
133728e barerepo 1mo
173
}
133728e barerepo 1mo
174
tree, err := RunStdin(ctx, dir, mktree.String(), "mktree")
133728e barerepo 1mo
175
if err != nil {
133728e barerepo 1mo
176
t.Fatal(err)
133728e barerepo 1mo
177
}
133728e barerepo 1mo
178
obj, err := Batch(ctx, dir, []string{strings.TrimSpace(tree)})
133728e barerepo 1mo
179
if err != nil {
133728e barerepo 1mo
180
t.Fatal(err)
133728e barerepo 1mo
181
}
133728e barerepo 1mo
182
got := TreeEntries(obj[strings.TrimSpace(tree)].Body)
133728e barerepo 1mo
183
if len(got) != len(want) {
133728e barerepo 1mo
184
t.Fatalf("tree has %d entries, want %d: %v", len(got), len(want), got)
133728e barerepo 1mo
185
}
133728e barerepo 1mo
186
for name, sha := range want {
133728e barerepo 1mo
187
if got[name] != sha {
133728e barerepo 1mo
188
t.Errorf("%s points at %q, want %q", name, got[name], sha)
133728e barerepo 1mo
189
}
133728e barerepo 1mo
190
}
133728e barerepo 1mo
193
// HEAD holds "ref: refs/heads/master", so a resolver that returns the file returns a name, not a hash.
133728e barerepo 1mo
194
func TestResolveRefFollowsSymbolicRefs(t *testing.T) {
133728e barerepo 1mo
195
dir := t.TempDir()
133728e barerepo 1mo
196
sha := "a3f9c2db1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b"
133728e barerepo 1mo
197
write := func(path, body string) {
133728e barerepo 1mo
198
t.Helper()
133728e barerepo 1mo
199
full := filepath.Join(dir, filepath.FromSlash(path))
133728e barerepo 1mo
200
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
133728e barerepo 1mo
201
t.Fatal(err)
133728e barerepo 1mo
202
}
133728e barerepo 1mo
203
if err := os.WriteFile(full, []byte(body), 0o644); err != nil {
133728e barerepo 1mo
204
t.Fatal(err)
133728e barerepo 1mo
205
}
133728e barerepo 1mo
206
}
133728e barerepo 1mo
207
write("refs/heads/master", sha+"\n")
133728e barerepo 1mo
208
write("HEAD", "ref: refs/heads/master\n")
133728e barerepo 1mo
210
for _, ref := range []string{"HEAD", "refs/heads/master"} {
133728e barerepo 1mo
211
got, err := ResolveRef(dir, ref)
133728e barerepo 1mo
212
if err != nil {
133728e barerepo 1mo
213
t.Fatalf("ResolveRef(%s): %v", ref, err)
133728e barerepo 1mo
214
}
133728e barerepo 1mo
215
if got != sha {
133728e barerepo 1mo
216
t.Errorf("ResolveRef(%s) = %q, want the hash %q", ref, got, sha)
133728e barerepo 1mo
217
}
133728e barerepo 1mo
218
}