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