forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
1// heavily inspired by gitea's model
2
3package hook
4
5import (
6 "errors"
7 "fmt"
8 "os"
9 "path/filepath"
10 "strings"
11
12 "github.com/go-git/go-git/v5"
13)
14
15var ErrNoGitRepo = errors.New("not a git repo")
16var ErrCreatingHookDir = errors.New("failed to create hooks directory")
17var ErrCreatingHook = errors.New("failed to create hook")
18var ErrCreatingDelegate = errors.New("failed to create delegate hook")
19
20type config struct {
21 scanPath string
22 internalApi string
23}
24
25type setupOpt func(*config)
26
27func WithScanPath(scanPath string) setupOpt {
28 return func(c *config) {
29 c.scanPath = scanPath
30 }
31}
32
33func WithInternalApi(api string) setupOpt {
34 return func(c *config) {
35 c.internalApi = api
36 }
37}
38
39// setup hooks for all users
40//
41// directory structure is typically like so:
42//
43// did:plc:foobar/repo1
44// did:plc:foobar/repo2
45// did:web:barbaz/repo1
46func Setup(opts ...setupOpt) error {
47 config := config{}
48 for _, o := range opts {
49 o(&config)
50 }
51 // iterate over all directories in current directory:
52 userDirs, err := os.ReadDir(config.scanPath)
53 if err != nil {
54 return err
55 }
56
57 for _, user := range userDirs {
58 if !user.IsDir() {
59 continue
60 }
61
62 did := user.Name()
63 if !strings.HasPrefix(did, "did:") {
64 continue
65 }
66
67 userPath := filepath.Join(config.scanPath, did)
68 if err := setupUser(&config, userPath); err != nil {
69 return err
70 }
71 }
72
73 return nil
74}
75
76// setup hooks in /scanpath/did:plc:user
77func setupUser(config *config, userPath string) error {
78 repos, err := os.ReadDir(userPath)
79 if err != nil {
80 return err
81 }
82
83 for _, repo := range repos {
84 if !repo.IsDir() {
85 continue
86 }
87
88 path := filepath.Join(userPath, repo.Name())
89 if err := setup(config, path); err != nil {
90 if errors.Is(err, ErrNoGitRepo) {
91 continue
92 }
93 return err
94 }
95 }
96
97 return nil
98}
99
100// setup hook in /scanpath/did:plc:user/repo
101func setup(config *config, path string) error {
102 if _, err := git.PlainOpen(path); err != nil {
103 return fmt.Errorf("%s: %w", path, ErrNoGitRepo)
104 }
105
106 preReceiveD := filepath.Join(path, "hooks", "post-receive.d")
107 if err := os.MkdirAll(preReceiveD, 0755); err != nil {
108 return fmt.Errorf("%s: %w", preReceiveD, ErrCreatingHookDir)
109 }
110
111 notify := filepath.Join(preReceiveD, "40-notify.sh")
112 if err := mkHook(config, notify); err != nil {
113 return fmt.Errorf("%s: %w", notify, ErrCreatingHook)
114 }
115
116 delegate := filepath.Join(path, "hooks", "post-receive")
117 if err := mkDelegate(delegate); err != nil {
118 return fmt.Errorf("%s: %w", delegate, ErrCreatingDelegate)
119 }
120
121 return nil
122}
123
124func mkHook(config *config, hookPath string) error {
125 executablePath, err := os.Executable()
126 if err != nil {
127 return err
128 }
129
130 hookContent := fmt.Sprintf(`#!/usr/bin/env bash
131# AUTO GENERATED BY KNOT, DO NOT MODIFY
132%s hook -git-dir "$GIT_DIR" -user-did "$GIT_USER_DID" -user-handle "$GIT_USER_HANDLE" -internal-api "%s" post-recieve
133 `, executablePath, config.internalApi)
134
135 return os.WriteFile(hookPath, []byte(hookContent), 0755)
136}
137
138func mkDelegate(path string) error {
139 content := fmt.Sprintf(`#!/usr/bin/env bash
140# AUTO GENERATED BY KNOT, DO NOT MODIFY
141data=$(cat)
142exitcodes=""
143hookname=$(basename $0)
144GIT_DIR="$PWD"
145
146for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
147 test -x "${hook}" && test -f "${hook}" || continue
148 echo "${data}" | "${hook}"
149 exitcodes="${exitcodes} $?"
150done
151
152for i in ${exitcodes}; do
153 [ ${i} -eq 0 ] || exit ${i}
154done
155 `)
156
157 return os.WriteFile(path, []byte(content), 0755)
158}