Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

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