1package main
2
3import (
4 "bufio"
5 "bytes"
6 "context"
7 "fmt"
8 "os"
9 "strings"
10
11 "github.com/charmbracelet/soft-serve/server/backend"
12 "github.com/charmbracelet/soft-serve/server/backend/sqlite"
13 "github.com/charmbracelet/soft-serve/server/config"
14 "github.com/charmbracelet/soft-serve/server/hooks"
15 "github.com/spf13/cobra"
16)
17
18var (
19 confixCtxKey = "config"
20 backendCtxKey = "backend"
21)
22
23var (
24 configPath string
25
26 hookCmd = &cobra.Command{
27 Use: "hook",
28 Short: "Run git server hooks",
29 Long: "Handles Soft Serve git server hooks.",
30 Hidden: true,
31 PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
32 cfg, err := config.ParseConfig(configPath)
33 if err != nil {
34 return fmt.Errorf("could not parse config: %w", err)
35 }
36
37 // Set up the backend
38 // TODO: support other backends
39 sb, err := sqlite.NewSqliteBackend(cmd.Context(), cfg)
40 if err != nil {
41 return fmt.Errorf("failed to create sqlite backend: %w", err)
42 }
43
44 cfg = cfg.WithBackend(sb)
45
46 cmd.SetContext(context.WithValue(cmd.Context(), confixCtxKey, cfg))
47 cmd.SetContext(context.WithValue(cmd.Context(), backendCtxKey, sb))
48
49 return nil
50 },
51 }
52
53 hooksRunE = func(cmd *cobra.Command, args []string) error {
54 cfg := cmd.Context().Value(confixCtxKey).(*config.Config)
55 hks := cfg.Backend.(backend.Hooks)
56
57 // This is set in the server before invoking git-receive-pack/git-upload-pack
58 repoName := os.Getenv("SOFT_SERVE_REPO_NAME")
59
60 in := cmd.InOrStdin()
61 out := cmd.OutOrStdout()
62 err := cmd.ErrOrStderr()
63
64 cmdName := cmd.Name()
65 switch cmdName {
66 case hooks.PreReceiveHook, hooks.PostReceiveHook:
67 var buf bytes.Buffer
68 opts := make([]backend.HookArg, 0)
69 scanner := bufio.NewScanner(in)
70 for scanner.Scan() {
71 buf.Write(scanner.Bytes())
72 fields := strings.Fields(scanner.Text())
73 if len(fields) != 3 {
74 return fmt.Errorf("invalid pre-receive hook input: %s", scanner.Text())
75 }
76 opts = append(opts, backend.HookArg{
77 OldSha: fields[0],
78 NewSha: fields[1],
79 RefName: fields[2],
80 })
81 }
82
83 switch cmdName {
84 case hooks.PreReceiveHook:
85 hks.PreReceive(out, err, repoName, opts)
86 case hooks.PostReceiveHook:
87 hks.PostReceive(out, err, repoName, opts)
88 }
89 case hooks.UpdateHook:
90 if len(args) != 3 {
91 return fmt.Errorf("invalid update hook input: %s", args)
92 }
93
94 hks.Update(out, err, repoName, backend.HookArg{
95 OldSha: args[0],
96 NewSha: args[1],
97 RefName: args[2],
98 })
99 case hooks.PostUpdateHook:
100 hks.PostUpdate(out, err, repoName, args...)
101 }
102
103 return nil
104 }
105
106 preReceiveCmd = &cobra.Command{
107 Use: "pre-receive",
108 Short: "Run git pre-receive hook",
109 RunE: hooksRunE,
110 }
111
112 updateCmd = &cobra.Command{
113 Use: "update",
114 Short: "Run git update hook",
115 Args: cobra.ExactArgs(3),
116 RunE: hooksRunE,
117 }
118
119 postReceiveCmd = &cobra.Command{
120 Use: "post-receive",
121 Short: "Run git post-receive hook",
122 RunE: hooksRunE,
123 }
124
125 postUpdateCmd = &cobra.Command{
126 Use: "post-update",
127 Short: "Run git post-update hook",
128 RunE: hooksRunE,
129 }
130)
131
132func init() {
133 hookCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "path to config file")
134 hookCmd.AddCommand(
135 preReceiveCmd,
136 updateCmd,
137 postReceiveCmd,
138 postUpdateCmd,
139 )
140}