1package hook
2
3import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "fmt"
8 "net/http"
9 "os"
10 "strings"
11
12 "github.com/urfave/cli/v3"
13)
14
15type HookResponse struct {
16 Messages []string `json:"messages"`
17}
18
19// The hook command is nested like so:
20//
21// knot hook --[flags] [hook]
22func Command() *cli.Command {
23 return &cli.Command{
24 Name: "hook",
25 Usage: "run git hooks",
26 Flags: []cli.Flag{
27 &cli.StringFlag{
28 Name: "git-dir",
29 Usage: "base directory for git repos",
30 },
31 &cli.StringFlag{
32 Name: "user-did",
33 Usage: "git user's did",
34 },
35 &cli.StringFlag{
36 Name: "user-handle",
37 Usage: "git user's handle",
38 },
39 &cli.StringFlag{
40 Name: "internal-api",
41 Usage: "endpoint for the internal API",
42 Value: "http://localhost:5444",
43 },
44 &cli.StringSliceFlag{
45 Name: "push-option",
46 Usage: "any push option from git",
47 },
48 },
49 Commands: []*cli.Command{
50 {
51 Name: "post-recieve",
52 Usage: "sends a post-recieve hook to the knot (waits for stdin)",
53 Action: postRecieve,
54 },
55 },
56 }
57}
58
59func postRecieve(ctx context.Context, cmd *cli.Command) error {
60 gitDir := cmd.String("git-dir")
61 userDid := cmd.String("user-did")
62 userHandle := cmd.String("user-handle")
63 endpoint := cmd.String("internal-api")
64 pushOptions := cmd.StringSlice("push-option")
65
66 payloadReader := bufio.NewReader(os.Stdin)
67 payload, _ := payloadReader.ReadString('\n')
68
69 client := &http.Client{}
70
71 req, err := http.NewRequest("POST", "http://"+endpoint+"/hooks/post-receive", strings.NewReader(payload))
72 if err != nil {
73 return fmt.Errorf("failed to create request: %w", err)
74 }
75
76 req.Header.Set("Content-Type", "text/plain; charset=utf-8")
77 req.Header.Set("X-Git-Dir", gitDir)
78 req.Header.Set("X-Git-User-Did", userDid)
79 req.Header.Set("X-Git-User-Handle", userHandle)
80 if pushOptions != nil {
81 for _, option := range pushOptions {
82 req.Header.Add("X-Git-Push-Option", option)
83 }
84 }
85
86 resp, err := client.Do(req)
87 if err != nil {
88 return fmt.Errorf("failed to execute request: %w", err)
89 }
90 defer resp.Body.Close()
91
92 if resp.StatusCode != http.StatusOK {
93 return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
94 }
95
96 var data HookResponse
97 if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
98 return fmt.Errorf("failed to decode response: %w", err)
99 }
100
101 for _, message := range data.Messages {
102 fmt.Println(message)
103 }
104
105 return nil
106}