6d6e112bc6e042feafac4c128ae39d96b2e34143
- Author
- Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
- Committer
- GitHub <noreply@github.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/.editorconfig b/.editorconfig
2new file mode 100644
3index 0000000000000000000000000000000000000000..d9c9c2fd60d48c909e761b6f6e0f6bc7600216c9
4--- /dev/null
5+++ b/.editorconfig
6@@ -0,0 +1,13 @@
7+root = true
8+
9+[*]
10+charset=utf-8
11+end_of_line=lf
12+insert_final_newline=true
13+trim_trailing_whitespace=true
14+indent_size=2
15+indent_style=space
16+
17+[*.go]
18+indent_size=4
19+indent_style=tab
20diff --git a/.gitattributes b/.gitattributes
21new file mode 100644
22index 0000000000000000000000000000000000000000..6f952299270eed506a1dd30028ab8e0f1be7ef7f
23--- /dev/null
24+++ b/.gitattributes
25@@ -0,0 +1,2 @@
26+# To prevent CRLF breakages on Windows for fragile files, like testdata.
27+* -text
28diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
29index 8985110722eb708e717ca51d2f18be33ea8b066e..fb6b091140633fe1e1c541bc242b3941f2873ddd 100644
30--- a/.github/workflows/build.yml
31+++ b/.github/workflows/build.yml
32@@ -25,4 +25,4 @@ jobs:
33 run: go test -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt ./... -timeout 5m
34 - uses: codecov/codecov-action@v3
35 with:
36- file: ./coverage.txt
37+ file: ./coverage.txt
38diff --git a/go.mod b/go.mod
39index 7bb2d9ab3c84d859eeadf339d47f252312dbace0..aa047c9382a1a8096b2310a5b51e4854c775f180 100644
40--- a/go.mod
41+++ b/go.mod
42@@ -31,6 +31,7 @@ require (
43 github.com/muesli/roff v0.1.0
44 github.com/prometheus/client_golang v1.15.1
45 github.com/robfig/cron/v3 v3.0.1
46+ github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97
47 github.com/spf13/cobra v1.7.0
48 go.uber.org/automaxprocs v1.5.2
49 goji.io v2.0.2+incompatible
50@@ -73,12 +74,11 @@ require (
51 github.com/prometheus/procfs v0.9.0 // indirect
52 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
53 github.com/rivo/uniseg v0.2.0 // indirect
54- github.com/rogpeppe/go-internal v1.10.0 // indirect
55 github.com/sahilm/fuzzy v0.1.0 // indirect
56 github.com/spf13/pflag v1.0.5 // indirect
57 github.com/yuin/goldmark v1.5.2 // indirect
58 github.com/yuin/goldmark-emoji v1.0.1 // indirect
59- golang.org/x/mod v0.8.0 // indirect
60+ golang.org/x/mod v0.9.0 // indirect
61 golang.org/x/net v0.10.0 // indirect
62 golang.org/x/sys v0.8.0 // indirect
63 golang.org/x/term v0.8.0 // indirect
64diff --git a/go.sum b/go.sum
65index 3d6d4e70bcf85e47b9650c2a1bb73a018b63de66..15bb643da3cc32b12bfd529e8b91eb251769d323 100644
66--- a/go.sum
67+++ b/go.sum
68@@ -153,8 +153,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
69 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
70 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
71 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
72-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
73-github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
74+github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY=
75+github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
76 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
77 github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
78 github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
79@@ -185,8 +185,8 @@ goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMI
80 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
81 golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
82 golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
83-golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
84-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
85+golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
86+golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
87 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
88 golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
89 golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
90diff --git a/server/backend/sqlite/user.go b/server/backend/sqlite/user.go
91index 7306c5cfa4e7d6f57ffd58258960eb15eadb8b55..5e977a6a7af677b27618eb4baa20f6904f578e3e 100644
92--- a/server/backend/sqlite/user.go
93+++ b/server/backend/sqlite/user.go
94@@ -42,7 +42,8 @@ func (u *User) PublicKeys() []ssh.PublicKey {
95 if err := tx.Select(&keyStrings, `SELECT public_key
96 FROM public_key
97 INNER JOIN user ON user.id = public_key.user_id
98- WHERE user.username = ?;`, u.username); err != nil {
99+ WHERE user.username = ?
100+ ORDER BY public_key.id asc;`, u.username); err != nil {
101 return err
102 }
103
104diff --git a/server/cmd/repo.go b/server/cmd/repo.go
105index a072f19b88f88ede048c1176429842ee07026733..ae87361b585f1cc87123e32f3e410f9bce14cd4c 100644
106--- a/server/cmd/repo.go
107+++ b/server/cmd/repo.go
108@@ -1,6 +1,11 @@
109 package cmd
110
111-import "github.com/spf13/cobra"
112+import (
113+ "fmt"
114+ "strings"
115+
116+ "github.com/spf13/cobra"
117+)
118
119 func repoCommand() *cobra.Command {
120 cmd := &cobra.Command{
121@@ -53,9 +58,12 @@ func repoCommand() *cobra.Command {
122
123 branches, _ := r.Branches()
124 tags, _ := r.Tags()
125- cmd.Println("Project Name:", rr.ProjectName())
126+
127+ // project name and description are optional, handle trailing
128+ // whitespace to avoid breaking tests.
129+ cmd.Println(strings.TrimSpace(fmt.Sprint("Project Name: ", rr.ProjectName())))
130 cmd.Println("Repository:", rr.Name())
131- cmd.Println("Description:", rr.Description())
132+ cmd.Println(strings.TrimSpace(fmt.Sprint("Description: ", rr.Description())))
133 cmd.Println("Private:", rr.IsPrivate())
134 cmd.Println("Hidden:", rr.IsHidden())
135 cmd.Println("Mirror:", rr.IsMirror())
136diff --git a/server/hooks/hooks.go b/server/hooks/hooks.go
137index f7606bdd599d66ffcc0bb4cac24684fb1b1a3ca8..c625769f27b31039fe3657969b1e37af6791d61b 100644
138--- a/server/hooks/hooks.go
139+++ b/server/hooks/hooks.go
140@@ -3,6 +3,7 @@ package hooks
141 import (
142 "bytes"
143 "context"
144+ "flag"
145 "fmt"
146 "os"
147 "path/filepath"
148@@ -28,8 +29,13 @@ const (
149 // - post-update
150 //
151 // This function should be called by the backend when a repository is created.
152-// TODO: support context
153-func GenerateHooks(ctx context.Context, cfg *config.Config, repo string) error {
154+// TODO: support context.
155+func GenerateHooks(_ context.Context, cfg *config.Config, repo string) error {
156+ // TODO: support git hook tests.
157+ if flag.Lookup("test.v") != nil {
158+ log.WithPrefix("backend.hooks").Warn("refusing to set up hooks when in test")
159+ return nil
160+ }
161 repo = utils.SanitizeRepo(repo) + ".git"
162 hooksPath := filepath.Join(cfg.DataPath, "repos", repo, "hooks")
163 if err := os.MkdirAll(hooksPath, os.ModePerm); err != nil {
164@@ -136,10 +142,9 @@ done
165 `
166 )
167
168-var (
169- // hooksTmpl is the soft-serve hook that will be run by the git hooks
170- // inside the hooks directory.
171- hooksTmpl = template.Must(template.New("hooks").Parse(`#!/usr/bin/env bash
172+// hooksTmpl is the soft-serve hook that will be run by the git hooks
173+// inside the hooks directory.
174+var hooksTmpl = template.Must(template.New("hooks").Parse(`#!/usr/bin/env bash
175 # AUTO GENERATED BY SOFT SERVE, DO NOT MODIFY
176 if [ -z "$SOFT_SERVE_REPO_NAME" ]; then
177 echo "Warning: SOFT_SERVE_REPO_NAME not defined. Skipping hooks."
178@@ -149,4 +154,3 @@ fi
179 {{ $env }} \{{ end }}
180 {{ .Executable }} hook --config "{{ .Config }}" {{ .Hook }} {{ .Args }}
181 `))
182-)
183diff --git a/server/jobs.go b/server/jobs.go
184index 9cd23a23f1ad78fc1eed2f32ae8f63e6f5b497bb..239b08d8bf6f1c89876c6f2b447ba8774c4b7729 100644
185--- a/server/jobs.go
186+++ b/server/jobs.go
187@@ -51,7 +51,6 @@ func (s *Server) mirrorJob() func() {
188 if _, err := cmd.RunInDir(r.Path); err != nil {
189 logger.Error("error running git remote update", "repo", name, "err", err)
190 }
191-
192 })
193 }
194 }
195diff --git a/server/server.go b/server/server.go
196index 690c9650ba37c47e50780704225c0b05ab625331..1c0470af365ea9a8ba70158b298262dd26073f7a 100644
197--- a/server/server.go
198+++ b/server/server.go
199@@ -4,6 +4,7 @@ import (
200 "context"
201 "errors"
202 "fmt"
203+ "io"
204 "net/http"
205
206 "github.com/charmbracelet/log"
207@@ -158,6 +159,9 @@ func (s *Server) Shutdown(ctx context.Context) error {
208 s.Cron.Stop()
209 return nil
210 })
211+ if closer, ok := s.Backend.(io.Closer); ok {
212+ defer closer.Close() // nolint: errcheck
213+ }
214 return errg.Wait()
215 }
216
217@@ -172,5 +176,8 @@ func (s *Server) Close() error {
218 s.Cron.Stop()
219 return nil
220 })
221+ if closer, ok := s.Backend.(io.Closer); ok {
222+ defer closer.Close() // nolint: errcheck
223+ }
224 return errg.Wait()
225 }
226diff --git a/server/test/test.go b/server/test/test.go
227index a9d4d0eca7ac48899385dd23254c66f1249e2855..bfaac42c594791536d233685a87bf28f37dc2ba5 100644
228--- a/server/test/test.go
229+++ b/server/test/test.go
230@@ -1,11 +1,29 @@
231 package test
232
233-import "net"
234+import (
235+ "net"
236+ "sync"
237+)
238+
239+var (
240+ used = map[int]struct{}{}
241+ lock sync.Mutex
242+)
243
244 // RandomPort returns a random port number.
245 // This is mainly used for testing.
246 func RandomPort() int {
247 addr, _ := net.Listen("tcp", ":0") //nolint:gosec
248 _ = addr.Close()
249- return addr.Addr().(*net.TCPAddr).Port
250+ port := addr.Addr().(*net.TCPAddr).Port
251+ lock.Lock()
252+
253+ if _, ok := used[port]; ok {
254+ lock.Unlock()
255+ return RandomPort()
256+ }
257+
258+ used[port] = struct{}{}
259+ lock.Unlock()
260+ return port
261 }
262diff --git a/server/utils/utils.go b/server/utils/utils.go
263index f3b01eb30cbf16da473366aeaf0be22e77b016e0..a98cb139c51e853c45a6c9630e277f9bd313403f 100644
264--- a/server/utils/utils.go
265+++ b/server/utils/utils.go
266@@ -2,7 +2,7 @@ package utils
267
268 import (
269 "fmt"
270- "path/filepath"
271+ "path"
272 "strings"
273 "unicode"
274 )
275@@ -10,7 +10,9 @@ import (
276 // SanitizeRepo returns a sanitized version of the given repository name.
277 func SanitizeRepo(repo string) string {
278 repo = strings.TrimPrefix(repo, "/")
279- repo = filepath.Clean(repo)
280+ // We're using path instead of filepath here because this is not OS dependent
281+ // looking at you Windows
282+ repo = path.Clean(repo)
283 repo = strings.TrimSuffix(repo, ".git")
284 return repo
285 }
286diff --git a/server/utils/utils_test.go b/server/utils/utils_test.go
287new file mode 100644
288index 0000000000000000000000000000000000000000..33c1c7cda5af50436c0e0237cfbb287b36f832bc
289--- /dev/null
290+++ b/server/utils/utils_test.go
291@@ -0,0 +1,56 @@
292+package utils
293+
294+import "testing"
295+
296+func TestValidateRepo(t *testing.T) {
297+ t.Run("valid", func(t *testing.T) {
298+ for _, repo := range []string{
299+ "lower",
300+ "Upper",
301+ "with-dash",
302+ "with/slash",
303+ "withnumb3r5",
304+ "with.dot",
305+ "with_underline",
306+ } {
307+ t.Run(repo, func(t *testing.T) {
308+ if err := ValidateRepo(repo); err != nil {
309+ t.Errorf("expected no error, got %v", err)
310+ }
311+ })
312+ }
313+ })
314+ t.Run("invalid", func(t *testing.T) {
315+ for _, repo := range []string{
316+ "with$",
317+ "with@",
318+ "with!",
319+ } {
320+ t.Run(repo, func(t *testing.T) {
321+ if err := ValidateRepo(repo); err == nil {
322+ t.Error("expected an error, got nil")
323+ }
324+ })
325+ }
326+ })
327+}
328+
329+func TestSanitizeRepo(t *testing.T) {
330+ cases := []struct {
331+ in, out string
332+ }{
333+ {"lower", "lower"},
334+ {"Upper", "Upper"},
335+ {"with/slash", "with/slash"},
336+ {"with.dot", "with.dot"},
337+ {"/with_forward_slash", "with_forward_slash"},
338+ {"withgitsuffix.git", "withgitsuffix"},
339+ }
340+ for _, c := range cases {
341+ t.Run(c.in, func(t *testing.T) {
342+ if got := SanitizeRepo(c.in); got != c.out {
343+ t.Errorf("expected %q, got %q", c.out, got)
344+ }
345+ })
346+ }
347+}
348diff --git a/testscript/script_test.go b/testscript/script_test.go
349new file mode 100644
350index 0000000000000000000000000000000000000000..477edc033823c43b8df3722113f59a493d1a83d0
351--- /dev/null
352+++ b/testscript/script_test.go
353@@ -0,0 +1,229 @@
354+package testscript
355+
356+import (
357+ "bytes"
358+ "context"
359+ "flag"
360+ "fmt"
361+ "net"
362+ "os"
363+ "path/filepath"
364+ "strings"
365+ "sync"
366+ "testing"
367+ "time"
368+
369+ "github.com/charmbracelet/keygen"
370+ "github.com/charmbracelet/soft-serve/server"
371+ "github.com/charmbracelet/soft-serve/server/config"
372+ "github.com/charmbracelet/soft-serve/server/test"
373+ "github.com/rogpeppe/go-internal/testscript"
374+ "golang.org/x/crypto/ssh"
375+)
376+
377+var update = flag.Bool("update", false, "update script files")
378+
379+func TestScript(t *testing.T) {
380+ flag.Parse()
381+ var lock sync.Mutex
382+
383+ mkkey := func(name string) (string, *keygen.SSHKeyPair) {
384+ path := filepath.Join(t.TempDir(), name)
385+ pair, err := keygen.New(path, keygen.WithKeyType(keygen.Ed25519), keygen.WithWrite())
386+ if err != nil {
387+ t.Fatal(err)
388+ }
389+ return path, pair
390+ }
391+
392+ key, admin1 := mkkey("admin1")
393+ _, admin2 := mkkey("admin2")
394+ _, user1 := mkkey("user1")
395+
396+ testscript.Run(t, testscript.Params{
397+ Dir: "./testdata/",
398+ UpdateScripts: *update,
399+ Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
400+ "soft": cmdSoft(admin1.Signer()),
401+ "git": cmdGit(key),
402+ "mkreadme": cmdMkReadme,
403+ "dos2unix": cmdDos2Unix,
404+ },
405+ Setup: func(e *testscript.Env) error {
406+ sshPort := test.RandomPort()
407+ e.Setenv("SSH_PORT", fmt.Sprintf("%d", sshPort))
408+ e.Setenv("ADMIN1_AUTHORIZED_KEY", admin1.AuthorizedKey())
409+ e.Setenv("ADMIN2_AUTHORIZED_KEY", admin2.AuthorizedKey())
410+ e.Setenv("USER1_AUTHORIZED_KEY", user1.AuthorizedKey())
411+ e.Setenv("SSH_KNOWN_HOSTS_FILE", filepath.Join(t.TempDir(), "known_hosts"))
412+ e.Setenv("SSH_KNOWN_CONFIG_FILE", filepath.Join(t.TempDir(), "config"))
413+ data := t.TempDir()
414+ cfg := config.Config{
415+ Name: "Test Soft Serve",
416+ DataPath: data,
417+ InitialAdminKeys: []string{admin1.AuthorizedKey()},
418+ SSH: config.SSHConfig{
419+ ListenAddr: fmt.Sprintf("localhost:%d", sshPort),
420+ PublicURL: fmt.Sprintf("ssh://localhost:%d", sshPort),
421+ KeyPath: filepath.Join(data, "ssh", "soft_serve_host_ed25519"),
422+ ClientKeyPath: filepath.Join(data, "ssh", "soft_serve_client_ed25519"),
423+ },
424+ Git: config.GitConfig{
425+ ListenAddr: fmt.Sprintf("localhost:%d", test.RandomPort()),
426+ IdleTimeout: 3,
427+ MaxConnections: 32,
428+ },
429+ HTTP: config.HTTPConfig{
430+ ListenAddr: fmt.Sprintf("localhost:%d", test.RandomPort()),
431+ PublicURL: fmt.Sprintf("http://localhost:%d", test.RandomPort()),
432+ },
433+ Stats: config.StatsConfig{
434+ ListenAddr: fmt.Sprintf("localhost:%d", test.RandomPort()),
435+ },
436+ Log: config.LogConfig{
437+ Format: "text",
438+ TimeFormat: time.DateTime,
439+ },
440+ }
441+ ctx := config.WithContext(context.Background(), &cfg)
442+
443+ // prevent race condition in lipgloss...
444+ // this will probably be autofixed when we start using the colors
445+ // from the ssh session instead of the server.
446+ // XXX: take another look at this soon
447+ lock.Lock()
448+ srv, err := server.NewServer(ctx)
449+ if err != nil {
450+ return err
451+ }
452+ lock.Unlock()
453diff --git a/testscript/testdata/mirror.txtar b/testscript/testdata/mirror.txtar
454new file mode 100644
455index 0000000000000000000000000000000000000000..7dcdb6c59b40aa11edc15f6d7f90d1414f4aabc5
456--- /dev/null
457+++ b/testscript/testdata/mirror.txtar
458@@ -0,0 +1,96 @@
459+# vi: set ft=conf
460+
461+# convert crlf to lf on windows
462+[windows] dos2unix info1.txt info2.txt tree.txt
463+
464+# import a repo
465+soft repo import --mirror charmbracelet/catwalk https://github.com/charmbracelet/catwalk.git
466+
467+# check repo info
468+soft repo info charmbracelet/catwalk
469+cmp stdout info1.txt
470+
471+# check repo list
472+soft repo list
473+stdout charmbracelet/catwalk
474+
475+# is-mirror?
476+soft repo is-mirror charmbracelet/catwalk
477+stdout true
478+
479+# set project name
480+soft repo project-name charmbracelet/catwalk catwalk
481+soft repo list
482+stdout catwalk
483+
484+
485+# check description
486+soft repo description charmbracelet/catwalk
487+! stdout .
488+
489+# set description
490+soft repo description charmbracelet/catwalk "testing repo"
491+soft repo description charmbracelet/catwalk
492+stdout 'testing repo'
493+
494+# rename
495+soft repo rename charmbracelet/catwalk charmbracelet/test
496+soft repo list
497+stdout charmbracelet/test # TODO: shouldn't this still show the project-name?
498+
499+# check its not private
500+soft repo private charmbracelet/test
501+stdout false
502+
503+# make it private
504+soft repo private charmbracelet/test true
505+soft repo private charmbracelet/test
506+stdout true
507+
508+# check its not hidden
509+soft repo hidden charmbracelet/test
510+stdout false
511+
512+# make it hidden
513+soft repo hidden charmbracelet/test true
514+soft repo hidden charmbracelet/test
515+stdout true
516+
517+# print tree
518+soft repo tree charmbracelet/test
519+cmp stdout tree.txt
520+
521+# check repo info again
522+soft repo info charmbracelet/test
523+cmp stdout info2.txt
524+
525+# get a file
526+soft repo blob charmbracelet/test LICENSE
527+stdout '.*Creative Commons.*'
528+
529+
530+-- info1.txt --
531+Project Name:
532+Repository: charmbracelet/catwalk
533+Description:
534+Private: false
535+Hidden: false
536+Mirror: true
537+Default Branch: main
538+Branches:
539+ - main
540+-- info2.txt --
541+Project Name: catwalk
542+Repository: charmbracelet/test
543+Description: testing repo
544+Private: true
545+Hidden: true
546+Mirror: true
547+Default Branch: main
548+Branches:
549+ - main
550+-- tree.txt --
551+drwxrwxrwx - 30k
552+drwxrwxrwx - 50k
553+-rw-r--r-- 19 kB LICENSE
554+-rw-r--r-- 1.1 kB README.md
555diff --git a/testscript/testdata/repo-collab.txtar b/testscript/testdata/repo-collab.txtar
556new file mode 100644
557index 0000000000000000000000000000000000000000..8f7d501b1922831d34cf3cbf72b44cb47507262c
558--- /dev/null
559+++ b/testscript/testdata/repo-collab.txtar
560@@ -0,0 +1,18 @@
561+# vi: set ft=conf
562+# setup
563+soft repo import test https://github.com/charmbracelet/catwalk.git
564+soft user create foo --key "$USER1_AUTHORIZED_KEY"
565+
566+# list collabs
567+soft repo collab list test
568+! stdout .
569+
570+# add collab
571+soft repo collab add test foo
572+soft repo collab list test
573+stdout 'foo'
574+
575+# remove collab
576+soft repo collab remove test foo
577+soft repo collab list test
578+! stdout .
579diff --git a/testscript/testdata/repo-create.txtar b/testscript/testdata/repo-create.txtar
580new file mode 100644
581index 0000000000000000000000000000000000000000..108790cada62f112e21f385da48fe9d6d816465f
582--- /dev/null
583+++ b/testscript/testdata/repo-create.txtar
584@@ -0,0 +1,78 @@
585+# vi: set ft=conf
586+
587+# convert crlf to lf on windows
588+[windows] dos2unix tree.txt readme.md branch_list.1.txt
589+
590+# create a repo
591+soft repo create repo1 -d 'description' -H -p -n 'repo11'
592+soft repo hidden repo1
593+stdout true
594+soft repo private repo1
595+stdout true
596+soft repo description repo1
597+stdout 'description'
598+
599+# clone repo
600+git clone ssh://localhost:$SSH_PORT/repo1 repo1
601+
602+# create some files, commits, tags...
603+mkreadme ./repo1/README.md
604+git -C repo1 add -A
605+git -C repo1 commit -m 'first'
606+git -C repo1 tag v0.1.0
607+git -C repo1 push origin HEAD
608+git -C repo1 push origin HEAD --tags
609+
610+# list tags
611+soft repo tag list repo1
612+stdout 'v0.1.0'
613+
614+# delete tag
615+soft repo tag delete repo1 v0.1.0
616+soft repo tag list repo1
617+! stdout .
618+
619+# print tree
620+soft repo tree repo1
621+cmp stdout tree.txt
622+
623+# cat blob
624+soft repo blob repo1 README.md
625+cmp stdout readme.md
626+
627+# cat blob that doesn't exist
628+! soft repo blob repo1 README.txt
629+! stdout .
630+stderr '.*revision does not exist.*'
631+
632+# check main branch
633+soft repo branch default repo1
634+stdout master
635+
636+# create a new branch
637+git -C repo1 checkout -b branch1
638+git -C repo1 push origin branch1
639+soft repo branch list repo1
640+cmp stdout branch_list.1.txt
641+
642+# change default branch
643+soft repo branch default repo1 branch1
644+soft repo branch default repo1
645+stdout branch1
646+
647+# cannot delete main branch
648+! soft repo branch delete repo1 branch1
649+
650+# delete other branch
651+soft repo branch delete repo1 master
652+soft repo branch list repo1
653+stdout branch1
654+
655+-- tree.txt --
656+-rw-r--r-- 22 B README.md
657+-- readme.md --
658+# example
659+test project
660+-- branch_list.1.txt --
661+branch1
662+master
663diff --git a/testscript/testdata/repo-import.txt b/testscript/testdata/repo-import.txt
664new file mode 100644
665index 0000000000000000000000000000000000000000..4d73a5a43ef7d34cc42f0a85fdba2a44a313753e
666--- /dev/null
667+++ b/testscript/testdata/repo-import.txt
668@@ -0,0 +1,30 @@
669+# vi: set ft=conf
670+
671+# convert crlf to lf on windows
672+[windows] dos2unix repo3.txt
673+
674+# import private
675+soft repo import --private repo1 https://github.com/charmbracelet/catwalk.git
676+soft repo private repo1
677+stdout 'true'
678+
679+# import hidden
680+soft repo import --hidden repo2 https://github.com/charmbracelet/catwalk.git
681+soft repo hidden repo2
682+stdout 'true'
683+
684+# import with name and description
685+soft repo import --name 'repo33' --description 'descriptive' repo3 https://github.com/charmbracelet/catwalk.git
686+soft repo info repo3
687+cmp stdout repo3.txt
688+
689+-- repo3.txt --
690+Project Name: repo33
691+Repository: repo3
692+Description: descriptive
693+Private: false
694+Hidden: false
695+Mirror: false
696+Default Branch: main
697+Branches:
698+ - main
699diff --git a/testscript/testdata/set-username.txtar b/testscript/testdata/set-username.txtar
700new file mode 100644
701index 0000000000000000000000000000000000000000..03c6e9ce265d302976ac9937db312dd8efca0952
702--- /dev/null
703+++ b/testscript/testdata/set-username.txtar
704@@ -0,0 +1,24 @@
705+# vi: set ft=conf
706+
707+# convert crlf to lf on windows
708+[windows] dos2unix info1.txt info2.txt
709+
710+# get original username
711+soft info
712+cmpenv stdout info1.txt
713+
714+# set another username
715+soft set-username test
716+soft info
717+cmpenv stdout info2.txt
718+
719+-- info1.txt --
720+Username: admin
721+Admin: true
722+Public keys:
723+ $ADMIN1_AUTHORIZED_KEY
724+-- info2.txt --
725+Username: test
726+Admin: true
727+Public keys:
728+ $ADMIN1_AUTHORIZED_KEY
729diff --git a/testscript/testdata/settings.txtar b/testscript/testdata/settings.txtar
730new file mode 100644
731index 0000000000000000000000000000000000000000..606c576c097ae2ccb4656450c368bae077b48900
732--- /dev/null
733+++ b/testscript/testdata/settings.txtar
734@@ -0,0 +1,31 @@
735+# vi: set ft=conf
736+# check default allow-keyless
737+soft settings allow-keyless true
738+soft settings allow-keyless
739+stdout 'true.*'
740+
741+# change allow-keyless and check
742+soft settings allow-keyless false
743+soft settings allow-keyless
744+stdout 'false.*'
745+
746+# check default anon-access
747+soft settings anon-access
748+stdout 'read-only.*'
749+
750+# chaneg anon-access to all available options, and check them
751+soft settings anon-access no-access
752+soft settings anon-access
753+stdout 'no-access.*'
754+
755+soft settings anon-access read-only
756+soft settings anon-access
757+stdout 'read-only.*'
758+
759+soft settings anon-access read-write
760+soft settings anon-access
761+stdout 'read-write.*'
762+
763+soft settings anon-access admin-access
764+soft settings anon-access
765+stdout 'admin-access.*'
766diff --git a/testscript/testdata/user_management.txtar b/testscript/testdata/user_management.txtar
767new file mode 100644
768index 0000000000000000000000000000000000000000..8eade22e52d8af090cd9501ae8d894901b140b6b
769--- /dev/null
770+++ b/testscript/testdata/user_management.txtar
771@@ -0,0 +1,105 @@
772+# vi: set ft=conf
773+
774+# convert crlf to lf on windows
775+[windows] dos2unix info.txt admin_key_list1.txt admin_key_list2.txt list1.txt list2.txt foo_info1.txt foo_info2.txt foo_info3.txt foo_info4.txt foo_info5.txt
776+
777+# add key to admin
778+soft user add-pubkey admin "$ADMIN2_AUTHORIZED_KEY"
779+soft user info admin
780+soft info
781+cmpenv stdout info.txt
782+
783+
784+# list admin pubkeys
785+soft pubkey list
786+cmpenv stdout admin_key_list1.txt
787+
788+# remove key
789+soft pubkey remove $ADMIN2_AUTHORIZED_KEY
790+soft pubkey list
791+cmpenv stdout admin_key_list2.txt
792+
793+# add key back key
794+soft pubkey add $ADMIN2_AUTHORIZED_KEY
795+soft pubkey list
796+cmpenv stdout admin_key_list1.txt
797+
798+# list users
799+soft user list
800+cmpenv stdout list1.txt
801+
802+# create a new user
803+soft user create foo --key "$USER1_AUTHORIZED_KEY"
804+soft user list
805+cmpenv stdout list2.txt
806+
807+# get new user info
808+soft user info foo
809+cmpenv stdout foo_info1.txt
810+
811+# make user admin
812+soft user set-admin foo true
813+soft user info foo
814+cmpenv stdout foo_info2.txt
815+
816+# remove admin
817+soft user set-admin foo false
818+soft user info foo
819+cmpenv stdout foo_info3.txt
820+
821+# remove key from user
822+soft user remove-pubkey foo "$USER1_AUTHORIZED_KEY"
823+soft user info foo
824+cmpenv stdout foo_info4.txt
825+
826+# rename user
827+soft user set-username foo foo2
828+soft user info foo2
829+cmpenv stdout foo_info5.txt
830+
831+# remove user
832+soft user delete foo2
833+! stdout .
834+soft user list
835+cmpenv stdout list1.txt
836+
837+
838+-- info.txt --
839+Username: admin
840+Admin: true
841+Public keys:
842+ $ADMIN1_AUTHORIZED_KEY
843+ $ADMIN2_AUTHORIZED_KEY
844+-- list1.txt --
845+admin
846+-- list2.txt --
847+admin
848+foo
849+-- foo_info1.txt --
850+Username: foo
851+Admin: false
852+Public keys:
853+ $USER1_AUTHORIZED_KEY
854+-- foo_info2.txt --
855+Username: foo
856+Admin: true
857+Public keys:
858+ $USER1_AUTHORIZED_KEY
859+-- foo_info3.txt --
860+Username: foo
861+Admin: false
862+Public keys:
863+ $USER1_AUTHORIZED_KEY
864+-- foo_info4.txt --
865+Username: foo
866+Admin: false
867+Public keys:
868+-- foo_info5.txt --
869+Username: foo2
870+Admin: false