Git backed by object storage because you can't stop me
git
object-storage
kefka
1package main
2
3import (
4 "context"
5 "net"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/go-git/go-billy/v6/memfs"
14 "github.com/go-git/go-git/v6/plumbing/transport"
15)
16
17// TestDaemonPushCreatesRepo reproduces "git push git://host/new.git" against a
18// path that does not exist yet. The daemon must create the bare repository on
19// demand and the result must clone back cleanly.
20func TestDaemonPushCreatesRepo(t *testing.T) {
21 if _, err := exec.LookPath("git"); err != nil {
22 t.Skip("git not installed")
23 }
24
25 fs := memfs.New()
26 d := &daemon{
27 fs: fs,
28 loader: transport.NewFilesystemLoader(fs, false),
29 allowPush: true,
30 }
31
32 ctx, cancel := context.WithCancel(context.Background())
33 defer cancel()
34
35 ln, err := net.Listen("tcp", "127.0.0.1:0")
36 if err != nil {
37 t.Fatalf("listen: %v", err)
38 }
39
40 srvErr := make(chan error, 1)
41 go func() { srvErr <- d.Serve(ctx, ln) }()
42
43 remote := "git://" + ln.Addr().String() + "/test.git"
44
45 work := t.TempDir()
46 runGit(t, work, "init", "-b", "main")
47 runGit(t, work, "config", "user.email", "test@example.com")
48 runGit(t, work, "config", "user.name", "Test")
49 runGit(t, work, "commit", "--allow-empty", "-m", "initial")
50
51 // The repository does not exist yet; the push must create it.
52 runGit(t, work, "push", remote, "main")
53
54 if _, err := fs.Stat("/test.git/config"); err != nil {
55 t.Fatalf("expected bare repo to be created on push, but %q is missing: %v", "/test.git/config", err)
56 }
57
58 // Round-trip: a clone must recover the pushed commit.
59 dst := t.TempDir()
60 runGit(t, dst, "clone", remote, "cloned")
61 head := strings.TrimSpace(runGit(t, filepath.Join(dst, "cloned"), "rev-parse", "HEAD"))
62 if head == "" {
63 t.Fatal("cloned repository has no HEAD")
64 }
65
66 cancel()
67 _ = ln.Close()
68 select {
69 case <-srvErr:
70 case <-time.After(2 * time.Second):
71 t.Fatal("daemon did not shut down")
72 }
73}
74
75// TestDaemonPushDisabled confirms that pushes are rejected (not silently
76// creating repos) when allowPush is false.
77func TestDaemonPushDisabled(t *testing.T) {
78 if _, err := exec.LookPath("git"); err != nil {
79 t.Skip("git not installed")
80 }
81
82 fs := memfs.New()
83 d := &daemon{
84 fs: fs,
85 loader: transport.NewFilesystemLoader(fs, false),
86 allowPush: false,
87 }
88
89 ctx, cancel := context.WithCancel(context.Background())
90 defer cancel()
91
92 ln, err := net.Listen("tcp", "127.0.0.1:0")
93 if err != nil {
94 t.Fatalf("listen: %v", err)
95 }
96 go func() { _ = d.Serve(ctx, ln) }()
97
98 remote := "git://" + ln.Addr().String() + "/test.git"
99
100 work := t.TempDir()
101 runGit(t, work, "init", "-b", "main")
102 runGit(t, work, "config", "user.email", "test@example.com")
103 runGit(t, work, "config", "user.name", "Test")
104 runGit(t, work, "commit", "--allow-empty", "-m", "initial")
105
106 if out, err := tryGit(work, "push", remote, "main"); err == nil {
107 t.Fatalf("expected push to be rejected when allowPush is false, got success:\n%s", out)
108 }
109
110 if _, err := fs.Stat("/test.git/config"); err == nil {
111 t.Fatal("repository must not be created when push is disabled")
112 }
113}
114
115func runGit(t *testing.T, dir string, args ...string) string {
116 t.Helper()
117 out, err := tryGit(dir, args...)
118 if err != nil {
119 t.Fatalf("git %v: %v\n%s", args, err, out)
120 }
121 return out
122}
123
124func tryGit(dir string, args ...string) (string, error) {
125 cmd := exec.Command("git", args...)
126 cmd.Dir = dir
127 cmd.Env = append(os.Environ(),
128 "GIT_CONFIG_GLOBAL=/dev/null",
129 "GIT_CONFIG_SYSTEM=/dev/null",
130 "GIT_TERMINAL_PROMPT=0",
131 )
132 out, err := cmd.CombinedOutput()
133 return string(out), err
134}