gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package doctor
  4. import (
  5. "context"
  6. "fmt"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "strings"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/models/db"
  13. repo_model "code.gitea.io/gitea/models/repo"
  14. user_model "code.gitea.io/gitea/models/user"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/gitrepo"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/modules/util"
  21. lru "github.com/hashicorp/golang-lru/v2"
  22. "xorm.io/builder"
  23. )
  24. func iterateRepositories(ctx context.Context, each func(*repo_model.Repository) error) error {
  25. err := db.Iterate(
  26. ctx,
  27. builder.Gt{"id": 0},
  28. func(ctx context.Context, bean *repo_model.Repository) error {
  29. return each(bean)
  30. },
  31. )
  32. return err
  33. }
  34. func checkScriptType(ctx context.Context, logger log.Logger, autofix bool) error {
  35. path, err := exec.LookPath(setting.ScriptType)
  36. if err != nil {
  37. logger.Critical("ScriptType \"%q\" is not on the current PATH. Error: %v", setting.ScriptType, err)
  38. return fmt.Errorf("ScriptType \"%q\" is not on the current PATH. Error: %w", setting.ScriptType, err)
  39. }
  40. logger.Info("ScriptType %s is on the current PATH at %s", setting.ScriptType, path)
  41. return nil
  42. }
  43. func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error {
  44. if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
  45. results, err := gitrepo.CheckDelegateHooks(ctx, repo)
  46. if err != nil {
  47. logger.Critical("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err)
  48. return fmt.Errorf("Unable to check delegate hooks for repo %-v. ERROR: %w", repo, err)
  49. }
  50. if len(results) > 0 && autofix {
  51. logger.Warn("Regenerated hooks for %s", repo.FullName())
  52. if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
  53. logger.Critical("Unable to recreate delegate hooks for %-v. ERROR: %v", repo, err)
  54. return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", repo, err)
  55. }
  56. }
  57. for _, result := range results {
  58. logger.Warn(result)
  59. }
  60. return nil
  61. }); err != nil {
  62. logger.Critical("Errors noted whilst checking delegate hooks.")
  63. return err
  64. }
  65. return nil
  66. }
  67. func checkUserStarNum(ctx context.Context, logger log.Logger, autofix bool) error {
  68. if autofix {
  69. if err := models.DoctorUserStarNum(ctx); err != nil {
  70. logger.Critical("Unable update User Stars numbers")
  71. return err
  72. }
  73. logger.Info("Updated User Stars numbers.")
  74. } else {
  75. logger.Info("No check available for User Stars numbers (skipped)")
  76. }
  77. return nil
  78. }
  79. func checkEnablePushOptions(ctx context.Context, logger log.Logger, autofix bool) error {
  80. numRepos := 0
  81. numNeedUpdate := 0
  82. if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
  83. numRepos++
  84. if autofix {
  85. return gitrepo.GitConfigSet(ctx, repo, "receive.advertisePushOptions", "true")
  86. }
  87. value, err := gitrepo.GitConfigGet(ctx, repo, "receive.advertisePushOptions")
  88. if err != nil {
  89. return err
  90. }
  91. result, valid := git.ParseBool(strings.TrimSpace(value))
  92. if !result || !valid {
  93. numNeedUpdate++
  94. logger.Info("%s: does not have receive.advertisePushOptions set correctly: %q", repo.FullName(), value)
  95. }
  96. return nil
  97. }); err != nil {
  98. logger.Critical("Unable to EnablePushOptions: %v", err)
  99. return err
  100. }
  101. if autofix {
  102. logger.Info("Enabled push options for %d repositories.", numRepos)
  103. } else {
  104. logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate)
  105. }
  106. return nil
  107. }
  108. func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) error {
  109. numRepos := 0
  110. numNeedUpdate := 0
  111. cache, err := lru.New[int64, any](512)
  112. if err != nil {
  113. logger.Critical("Unable to create cache: %v", err)
  114. return err
  115. }
  116. if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
  117. numRepos++
  118. if owner, has := cache.Get(repo.OwnerID); has {
  119. repo.Owner = owner.(*user_model.User)
  120. } else {
  121. if err := repo.LoadOwner(ctx); err != nil {
  122. return err
  123. }
  124. cache.Add(repo.OwnerID, repo.Owner)
  125. }
  126. // Create/Remove git-daemon-export-ok for git-daemon...
  127. daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`)
  128. isExist, err := util.IsExist(daemonExportFile)
  129. if err != nil {
  130. log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
  131. return err
  132. }
  133. isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic
  134. if isPublic != isExist {
  135. numNeedUpdate++
  136. if autofix {
  137. if !isPublic && isExist {
  138. if err = util.Remove(daemonExportFile); err != nil {
  139. log.Error("Failed to remove %s: %v", daemonExportFile, err)
  140. }
  141. } else if isPublic && !isExist {
  142. if f, err := os.Create(daemonExportFile); err != nil {
  143. log.Error("Failed to create %s: %v", daemonExportFile, err)
  144. } else {
  145. f.Close()
  146. }
  147. }
  148. }
  149. }
  150. return nil
  151. }); err != nil {
  152. logger.Critical("Unable to checkDaemonExport: %v", err)
  153. return err
  154. }
  155. if autofix {
  156. logger.Info("Updated git-daemon-export-ok files for %d of %d repositories.", numNeedUpdate, numRepos)
  157. } else {
  158. logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate)
  159. }
  160. return nil
  161. }
  162. func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) error {
  163. numRepos := 0
  164. numNeedUpdate := 0
  165. numWritten := 0
  166. if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
  167. numRepos++
  168. commitGraphExists := func() (bool, error) {
  169. // Check commit-graph exists
  170. commitGraphFile := filepath.Join(repo.RepoPath(), `objects/info/commit-graph`)
  171. isExist, err := util.IsExist(commitGraphFile)
  172. if err != nil {
  173. logger.Error("Unable to check if %s exists. Error: %v", commitGraphFile, err)
  174. return false, err
  175. }
  176. if !isExist {
  177. commitGraphsDir := filepath.Join(repo.RepoPath(), `objects/info/commit-graphs`)
  178. isExist, err = util.IsExist(commitGraphsDir)
  179. if err != nil {
  180. logger.Error("Unable to check if %s exists. Error: %v", commitGraphsDir, err)
  181. return false, err
  182. }
  183. }
  184. return isExist, nil
  185. }
  186. isExist, err := commitGraphExists()
  187. if err != nil {
  188. return err
  189. }
  190. if !isExist {
  191. numNeedUpdate++
  192. if autofix {
  193. if err := git.WriteCommitGraph(ctx, repo.RepoPath()); err != nil {
  194. logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err)
  195. return err
  196. }
  197. isExist, err := commitGraphExists()
  198. if err != nil {
  199. return err
  200. }
  201. if isExist {
  202. numWritten++
  203. logger.Info("Commit-graph written: %s", repo.FullName())
  204. } else {
  205. logger.Warn("No commit-graph written: %s", repo.FullName())
  206. }
  207. }
  208. }
  209. return nil
  210. }); err != nil {
  211. logger.Critical("Unable to checkCommitGraph: %v", err)
  212. return err
  213. }
  214. if autofix {
  215. logger.Info("Wrote commit-graph files for %d of %d repositories.", numWritten, numRepos)
  216. } else {
  217. logger.Info("Checked %d repositories, %d without commit-graphs.", numRepos, numNeedUpdate)
  218. }
  219. return nil
  220. }
  221. func init() {
  222. Register(&Check{
  223. Title: "Check if SCRIPT_TYPE is available",
  224. Name: "script-type",
  225. IsDefault: false,
  226. Run: checkScriptType,
  227. Priority: 5,
  228. })
  229. Register(&Check{
  230. Title: "Check if hook files are up-to-date and executable",
  231. Name: "hooks",
  232. IsDefault: false,
  233. Run: checkHooks,
  234. Priority: 6,
  235. })
  236. Register(&Check{
  237. Title: "Recalculate Stars number for all user",
  238. Name: "recalculate-stars-number",
  239. IsDefault: false,
  240. Run: checkUserStarNum,
  241. Priority: 6,
  242. })
  243. Register(&Check{
  244. Title: "Check that all git repositories have receive.advertisePushOptions set to true",
  245. Name: "enable-push-options",
  246. IsDefault: false,
  247. Run: checkEnablePushOptions,
  248. Priority: 7,
  249. })
  250. Register(&Check{
  251. Title: "Check git-daemon-export-ok files",
  252. Name: "check-git-daemon-export-ok",
  253. IsDefault: false,
  254. Run: checkDaemonExport,
  255. Priority: 8,
  256. })
  257. Register(&Check{
  258. Title: "Check commit-graphs",
  259. Name: "check-commit-graphs",
  260. IsDefault: false,
  261. Run: checkCommitGraph,
  262. Priority: 9,
  263. })
  264. }