gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package gitrepo
  4. import (
  5. "context"
  6. "fmt"
  7. "os"
  8. "path/filepath"
  9. "runtime"
  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. // although only new git (>=2.29) supports proc-receive, it's still good to create its hook, in case the user upgrades git
  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. return hookNames, hookTpls, giteaHookTpls
  90. }
  91. // CreateDelegateHooks creates all the hooks scripts for the repo
  92. func CreateDelegateHooks(_ context.Context, repo Repository) (err error) {
  93. return createDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
  94. }
  95. func createDelegateHooks(hookDir string) (err error) {
  96. hookNames, hookTpls, giteaHookTpls := getHookTemplates()
  97. for i, hookName := range hookNames {
  98. oldHookPath := filepath.Join(hookDir, hookName)
  99. newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
  100. if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil {
  101. return fmt.Errorf("create hooks dir '%s': %w", filepath.Join(hookDir, hookName+".d"), err)
  102. }
  103. // WARNING: This will override all old server-side hooks
  104. if err = util.Remove(oldHookPath); err != nil && !os.IsNotExist(err) {
  105. return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %w ", oldHookPath, err)
  106. }
  107. if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o777); err != nil {
  108. return fmt.Errorf("write old hook file '%s': %w", oldHookPath, err)
  109. }
  110. if err = ensureExecutable(oldHookPath); err != nil {
  111. return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
  112. }
  113. if err = util.Remove(newHookPath); err != nil && !os.IsNotExist(err) {
  114. return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %w", newHookPath, err)
  115. }
  116. if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o777); err != nil {
  117. return fmt.Errorf("write new hook file '%s': %w", newHookPath, err)
  118. }
  119. if err = ensureExecutable(newHookPath); err != nil {
  120. return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
  121. }
  122. }
  123. return nil
  124. }
  125. func checkExecutable(filename string) bool {
  126. // windows has no concept of a executable bit
  127. if runtime.GOOS == "windows" {
  128. return true
  129. }
  130. fileInfo, err := os.Stat(filename)
  131. if err != nil {
  132. return false
  133. }
  134. return (fileInfo.Mode() & 0o100) > 0
  135. }
  136. func ensureExecutable(filename string) error {
  137. fileInfo, err := os.Stat(filename)
  138. if err != nil {
  139. return err
  140. }
  141. if (fileInfo.Mode() & 0o100) > 0 {
  142. return nil
  143. }
  144. mode := fileInfo.Mode() | 0o100
  145. return os.Chmod(filename, mode)
  146. }
  147. // CheckDelegateHooks checks the hooks scripts for the repo
  148. func CheckDelegateHooks(_ context.Context, repo Repository) ([]string, error) {
  149. return checkDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
  150. }
  151. func checkDelegateHooks(hookDir string) ([]string, error) {
  152. hookNames, hookTpls, giteaHookTpls := getHookTemplates()
  153. results := make([]string, 0, 10)
  154. for i, hookName := range hookNames {
  155. oldHookPath := filepath.Join(hookDir, hookName)
  156. newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
  157. cont := false
  158. isExist, err := util.IsExist(oldHookPath)
  159. if err != nil {
  160. results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath, err))
  161. }
  162. if err == nil && !isExist {
  163. results = append(results, fmt.Sprintf("old hook file %s does not exist", oldHookPath))
  164. cont = true
  165. }
  166. isExist, err = util.IsExist(oldHookPath + ".d")
  167. if err != nil {
  168. results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath+".d", err))
  169. }
  170. if err == nil && !isExist {
  171. results = append(results, fmt.Sprintf("hooks directory %s does not exist", oldHookPath+".d"))
  172. cont = true
  173. }
  174. isExist, err = util.IsExist(newHookPath)
  175. if err != nil {
  176. results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", newHookPath, err))
  177. }
  178. if err == nil && !isExist {
  179. results = append(results, fmt.Sprintf("new hook file %s does not exist", newHookPath))
  180. cont = true
  181. }
  182. if cont {
  183. continue
  184. }
  185. contents, err := os.ReadFile(oldHookPath)
  186. if err != nil {
  187. return results, err
  188. }
  189. if string(contents) != hookTpls[i] {
  190. results = append(results, fmt.Sprintf("old hook file %s is out of date", oldHookPath))
  191. }
  192. if !checkExecutable(oldHookPath) {
  193. results = append(results, fmt.Sprintf("old hook file %s is not executable", oldHookPath))
  194. }
  195. contents, err = os.ReadFile(newHookPath)
  196. if err != nil {
  197. return results, err
  198. }
  199. if string(contents) != giteaHookTpls[i] {
  200. results = append(results, fmt.Sprintf("new hook file %s is out of date", newHookPath))
  201. }
  202. if !checkExecutable(newHookPath) {
  203. results = append(results, fmt.Sprintf("new hook file %s is not executable", newHookPath))
  204. }
  205. }
  206. return results, nil
  207. }