You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hooks.go 6.9KB

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