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
39func Config(opts ...setupOpt) config {
40 config := config{}
41 for _, o := range opts {
42 o(&config)
43 }
44 return config
45}
46
47// setup hooks for all users
48//
49// directory structure is typically like so:
50//
51// did:plc:foobar/repo1
52// did:plc:foobar/repo2
53// did:web:barbaz/repo1
54func Setup(config config) error {
55 // iterate over all directories in current directory:
56 userDirs, err := os.ReadDir(config.scanPath)
57 if err != nil {
58 return err
59 }
60
61 for _, user := range userDirs {
62 if !user.IsDir() {
63 continue
64 }
65
66 did := user.Name()
67 if !strings.HasPrefix(did, "did:") {
68 continue
69 }
70
71 userPath := filepath.Join(config.scanPath, did)
72 if err := SetupUser(config, userPath); err != nil {
73 return err
74 }
75 }
76
77 return nil
78}
79
80// setup hooks in /scanpath/did:plc:user
81func SetupUser(config config, userPath string) error {
82 repos, err := os.ReadDir(userPath)
83 if err != nil {
84 return err
85 }
86
87 for _, repo := range repos {
88 if !repo.IsDir() {
89 continue
90 }
91
92 path := filepath.Join(userPath, repo.Name())
93 if err := SetupRepo(config, path); err != nil {
94 if errors.Is(err, ErrNoGitRepo) {
95 continue
96 }
97 return err
98 }
99 }
100
101 return nil
102}
103
104// setup hook in /scanpath/did:plc:user/repo
105func SetupRepo(config config, path string) error {
106 if _, err := git.PlainOpen(path); err != nil {
107 return fmt.Errorf("%s: %w", path, ErrNoGitRepo)
108 }
109
110 preReceiveD := filepath.Join(path, "hooks", "post-receive.d")
111 if err := os.MkdirAll(preReceiveD, 0755); err != nil {
112 return fmt.Errorf("%s: %w", preReceiveD, ErrCreatingHookDir)
113 }
114
115 notify := filepath.Join(preReceiveD, "40-notify.sh")
116 if err := mkHook(config, notify); err != nil {
117 return fmt.Errorf("%s: %w", notify, ErrCreatingHook)
118 }
119
120 delegate := filepath.Join(path, "hooks", "post-receive")
121 if err := mkDelegate(delegate); err != nil {
122 return fmt.Errorf("%s: %w", delegate, ErrCreatingDelegate)
123 }
124
125 return nil
126}
127
128func mkHook(config config, hookPath string) error {
129 executablePath, err := os.Executable()
130 if err != nil {
131 return err
132 }
133
134 hookContent := fmt.Sprintf(`#!/usr/bin/env bash
135# AUTO GENERATED BY KNOT, DO NOT MODIFY
136%s hook -git-dir "$GIT_DIR" -user-did "$GIT_USER_DID" -user-handle "$GIT_USER_HANDLE" -internal-api "%s" post-recieve
137 `, executablePath, config.internalApi)
138
139 return os.WriteFile(hookPath, []byte(hookContent), 0755)
140}
141
142func mkDelegate(path string) error {
143 content := fmt.Sprintf(`#!/usr/bin/env bash
144# AUTO GENERATED BY KNOT, DO NOT MODIFY
145data=$(cat)
146exitcodes=""
147hookname=$(basename $0)
148GIT_DIR="$PWD"
149
150for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
151 test -x "${hook}" && test -f "${hook}" || continue
152 echo "${data}" | "${hook}"
153 exitcodes="${exitcodes} $?"
154done
155
156for i in ${exitcodes}; do
157 [ ${i} -eq 0 ] || exit ${i}
158done
159 `)
160
161 return os.WriteFile(path, []byte(content), 0755)
162}