1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package githttp
4
5import (
6 "encoding/json"
7 "errors"
8 "fmt"
9
10 "bnema.dev/ermite/internal/domain"
11)
12
13// Hook names, as used by git and by `ermite hook`.
14const (
15 HookPreReceive = "pre-receive"
16 HookPostReceive = "post-receive"
17)
18
19// maxHookMessage bounds one line of the hook protocol, request or reply.
20const maxHookMessage = 32 << 20
21
22// HookRequest is the one JSON line a hook sends over the socket.
23//
24// {"token":"<hook token>","hook":"pre-receive","updates":[{"old":"<oid>","new":"<oid>","ref":"refs/heads/main"}]}
25//
26// The repository is not sent: the token is bound to it.
27type HookRequest struct {
28 Token string `json:"token"`
29 Hook string `json:"hook"`
30 Updates []HookUpdate `json:"updates"`
31 // ObjectsDir and Alternates are GIT_OBJECT_DIRECTORY and
32 // GIT_ALTERNATE_OBJECT_DIRECTORIES of the hook: where the objects of a push
33 // in quarantine are (pre-receive only).
34 ObjectsDir string `json:"objects_dir,omitempty"`
35 Alternates string `json:"alternates,omitempty"`
36}
37
38// HookUpdate is one ref update, as git feeds it to the hook.
39type HookUpdate struct {
40 Old string `json:"old"`
41 New string `json:"new"`
42 Ref string `json:"ref"`
43}
44
45// HookReply is the one JSON line ermite answers. Message is shown to the
46// pusher (on stderr of the hook, so git prefixes it with "remote:").
47//
48// {"ok":true} {"ok":false,"message":"push rejected: ..."}
49type HookReply struct {
50 OK bool `json:"ok"`
51 Message string `json:"message,omitempty"`
52}
53
54// refUpdates checks the request fields that do not need the token.
55func (r HookRequest) refUpdates() ([]domain.RefUpdate, error) {
56 switch r.Hook {
57 case HookPreReceive, HookPostReceive:
58 default:
59 return nil, fmt.Errorf("unknown hook %q", r.Hook)
60 }
61 if r.Token == "" {
62 return nil, errors.New("missing token")
63 }
64 out := make([]domain.RefUpdate, 0, len(r.Updates))
65 for _, u := range r.Updates {
66 up, err := domain.NewRefUpdate(u.Old, u.New, u.Ref)
67 if err != nil {
68 return nil, err
69 }
70 out = append(out, up)
71 }
72 return out, nil
73}
74
75func encodeLine(v any) ([]byte, error) {
76 b, err := json.Marshal(v)
77 if err != nil {
78 return nil, err
79 }
80 return append(b, '\n'), nil
81}