hook.go

  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}