forked from tangled.org/core
this repo has no description
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=${GIT_DIR:-$(dirname $0)/..} 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}