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

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