code.gitea.io/gitea@v1.19.3/modules/repository/hooks.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package repository 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 "runtime" 11 12 "code.gitea.io/gitea/modules/git" 13 "code.gitea.io/gitea/modules/setting" 14 "code.gitea.io/gitea/modules/util" 15 ) 16 17 func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) { 18 hookNames = []string{"pre-receive", "update", "post-receive"} 19 hookTpls = []string{ 20 // for pre-receive 21 fmt.Sprintf(`#!/usr/bin/env %s 22 # AUTO GENERATED BY GITEA, DO NOT MODIFY 23 data=$(cat) 24 exitcodes="" 25 hookname=$(basename $0) 26 GIT_DIR=${GIT_DIR:-$(dirname $0)/..} 27 28 for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do 29 test -x "${hook}" && test -f "${hook}" || continue 30 echo "${data}" | "${hook}" 31 exitcodes="${exitcodes} $?" 32 done 33 34 for i in ${exitcodes}; do 35 [ ${i} -eq 0 ] || exit ${i} 36 done 37 `, setting.ScriptType), 38 39 // for update 40 fmt.Sprintf(`#!/usr/bin/env %s 41 # AUTO GENERATED BY GITEA, DO NOT MODIFY 42 exitcodes="" 43 hookname=$(basename $0) 44 GIT_DIR=${GIT_DIR:-$(dirname $0/..)} 45 46 for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do 47 test -x "${hook}" && test -f "${hook}" || continue 48 "${hook}" $1 $2 $3 49 exitcodes="${exitcodes} $?" 50 done 51 52 for i in ${exitcodes}; do 53 [ ${i} -eq 0 ] || exit ${i} 54 done 55 `, setting.ScriptType), 56 57 // for post-receive 58 fmt.Sprintf(`#!/usr/bin/env %s 59 # AUTO GENERATED BY GITEA, DO NOT MODIFY 60 data=$(cat) 61 exitcodes="" 62 hookname=$(basename $0) 63 GIT_DIR=${GIT_DIR:-$(dirname $0)/..} 64 65 for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do 66 test -x "${hook}" && test -f "${hook}" || continue 67 echo "${data}" | "${hook}" 68 exitcodes="${exitcodes} $?" 69 done 70 71 for i in ${exitcodes}; do 72 [ ${i} -eq 0 ] || exit ${i} 73 done 74 `, setting.ScriptType), 75 } 76 77 giteaHookTpls = []string{ 78 // for pre-receive 79 fmt.Sprintf(`#!/usr/bin/env %s 80 # AUTO GENERATED BY GITEA, DO NOT MODIFY 81 %s hook --config=%s pre-receive 82 `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), 83 84 // for update 85 fmt.Sprintf(`#!/usr/bin/env %s 86 # AUTO GENERATED BY GITEA, DO NOT MODIFY 87 %s hook --config=%s update $1 $2 $3 88 `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), 89 90 // for post-receive 91 fmt.Sprintf(`#!/usr/bin/env %s 92 # AUTO GENERATED BY GITEA, DO NOT MODIFY 93 %s hook --config=%s post-receive 94 `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), 95 } 96 97 if git.SupportProcReceive { 98 hookNames = append(hookNames, "proc-receive") 99 hookTpls = append(hookTpls, 100 fmt.Sprintf(`#!/usr/bin/env %s 101 # AUTO GENERATED BY GITEA, DO NOT MODIFY 102 %s hook --config=%s proc-receive 103 `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf))) 104 giteaHookTpls = append(giteaHookTpls, "") 105 } 106 107 return hookNames, hookTpls, giteaHookTpls 108 } 109 110 // CreateDelegateHooks creates all the hooks scripts for the repo 111 func CreateDelegateHooks(repoPath string) error { 112 return createDelegateHooks(repoPath) 113 } 114 115 // createDelegateHooks creates all the hooks scripts for the repo 116 func createDelegateHooks(repoPath string) (err error) { 117 hookNames, hookTpls, giteaHookTpls := getHookTemplates() 118 hookDir := filepath.Join(repoPath, "hooks") 119 120 for i, hookName := range hookNames { 121 oldHookPath := filepath.Join(hookDir, hookName) 122 newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") 123 124 if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { 125 return fmt.Errorf("create hooks dir '%s': %w", filepath.Join(hookDir, hookName+".d"), err) 126 } 127 128 // WARNING: This will override all old server-side hooks 129 if err = util.Remove(oldHookPath); err != nil && !os.IsNotExist(err) { 130 return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %w ", oldHookPath, err) 131 } 132 if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o777); err != nil { 133 return fmt.Errorf("write old hook file '%s': %w", oldHookPath, err) 134 } 135 136 if err = ensureExecutable(oldHookPath); err != nil { 137 return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err) 138 } 139 140 if err = util.Remove(newHookPath); err != nil && !os.IsNotExist(err) { 141 return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %w", newHookPath, err) 142 } 143 if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o777); err != nil { 144 return fmt.Errorf("write new hook file '%s': %w", newHookPath, err) 145 } 146 147 if err = ensureExecutable(newHookPath); err != nil { 148 return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err) 149 } 150 } 151 152 return nil 153 } 154 155 func checkExecutable(filename string) bool { 156 // windows has no concept of a executable bit 157 if runtime.GOOS == "windows" { 158 return true 159 } 160 fileInfo, err := os.Stat(filename) 161 if err != nil { 162 return false 163 } 164 return (fileInfo.Mode() & 0o100) > 0 165 } 166 167 func ensureExecutable(filename string) error { 168 fileInfo, err := os.Stat(filename) 169 if err != nil { 170 return err 171 } 172 if (fileInfo.Mode() & 0o100) > 0 { 173 return nil 174 } 175 mode := fileInfo.Mode() | 0o100 176 return os.Chmod(filename, mode) 177 } 178 179 // CheckDelegateHooks checks the hooks scripts for the repo 180 func CheckDelegateHooks(repoPath string) ([]string, error) { 181 hookNames, hookTpls, giteaHookTpls := getHookTemplates() 182 183 hookDir := filepath.Join(repoPath, "hooks") 184 results := make([]string, 0, 10) 185 186 for i, hookName := range hookNames { 187 oldHookPath := filepath.Join(hookDir, hookName) 188 newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") 189 190 cont := false 191 isExist, err := util.IsExist(oldHookPath) 192 if err != nil { 193 results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath, err)) 194 } 195 if err == nil && !isExist { 196 results = append(results, fmt.Sprintf("old hook file %s does not exist", oldHookPath)) 197 cont = true 198 } 199 isExist, err = util.IsExist(oldHookPath + ".d") 200 if err != nil { 201 results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath+".d", err)) 202 } 203 if err == nil && !isExist { 204 results = append(results, fmt.Sprintf("hooks directory %s does not exist", oldHookPath+".d")) 205 cont = true 206 } 207 isExist, err = util.IsExist(newHookPath) 208 if err != nil { 209 results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", newHookPath, err)) 210 } 211 if err == nil && !isExist { 212 results = append(results, fmt.Sprintf("new hook file %s does not exist", newHookPath)) 213 cont = true 214 } 215 if cont { 216 continue 217 } 218 contents, err := os.ReadFile(oldHookPath) 219 if err != nil { 220 return results, err 221 } 222 if string(contents) != hookTpls[i] { 223 results = append(results, fmt.Sprintf("old hook file %s is out of date", oldHookPath)) 224 } 225 if !checkExecutable(oldHookPath) { 226 results = append(results, fmt.Sprintf("old hook file %s is not executable", oldHookPath)) 227 } 228 contents, err = os.ReadFile(newHookPath) 229 if err != nil { 230 return results, err 231 } 232 if string(contents) != giteaHookTpls[i] { 233 results = append(results, fmt.Sprintf("new hook file %s is out of date", newHookPath)) 234 } 235 if !checkExecutable(newHookPath) { 236 results = append(results, fmt.Sprintf("new hook file %s is not executable", newHookPath)) 237 } 238 } 239 return results, nil 240 }