forked from tangled.org/core
this repo has no description
at master 3.7 kB view raw
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 136push_options=() 137for ((i=0; i<GIT_PUSH_OPTION_COUNT; i++)); do 138 option_var="GIT_PUSH_OPTION_$i" 139 push_options+=(-push-option "${!option_var}") 140done 141%s hook -git-dir "$GIT_DIR" -user-did "$GIT_USER_DID" -user-handle "$GIT_USER_HANDLE" -internal-api "%s" "${push_options[@]}" post-recieve 142 `, executablePath, config.internalApi) 143 144 return os.WriteFile(hookPath, []byte(hookContent), 0755) 145} 146 147func mkDelegate(path string) error { 148 content := fmt.Sprintf(`#!/usr/bin/env bash 149# AUTO GENERATED BY KNOT, DO NOT MODIFY 150data=$(cat) 151exitcodes="" 152hookname=$(basename $0) 153GIT_DIR="$PWD" 154 155for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do 156 test -x "${hook}" && test -f "${hook}" || continue 157 echo "${data}" | "${hook}" 158 exitcodes="${exitcodes} $?" 159done 160 161for i in ${exitcodes}; do 162 [ ${i} -eq 0 ] || exit ${i} 163done 164 `) 165 166 return os.WriteFile(path, []byte(content), 0755) 167}