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.

misc.go 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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"
  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/log"
  17. "code.gitea.io/gitea/modules/repository"
  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 := repository.CheckDelegateHooks(repo.RepoPath())
  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 := repository.CreateDelegateHooks(repo.RepoPath()); 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(); 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. r, err := git.OpenRepository(ctx, repo.RepoPath())
  85. if err != nil {
  86. return err
  87. }
  88. defer r.Close()
  89. if autofix {
  90. _, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions", "true").RunStdString(&git.RunOpts{Dir: r.Path})
  91. return err
  92. }
  93. value, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions").RunStdString(&git.RunOpts{Dir: r.Path})
  94. if err != nil {
  95. return err
  96. }
  97. result, valid := git.ParseBool(strings.TrimSpace(value))
  98. if !result || !valid {
  99. numNeedUpdate++
  100. logger.Info("%s: does not have receive.advertisePushOptions set correctly: %q", repo.FullName(), value)
  101. }
  102. return nil
  103. }); err != nil {
  104. logger.Critical("Unable to EnablePushOptions: %v", err)
  105. return err
  106. }
  107. if autofix {
  108. logger.Info("Enabled push options for %d repositories.", numRepos)
  109. } else {
  110. logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate)
  111. }
  112. return nil
  113. }
  114. func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) error {
  115. numRepos := 0
  116. numNeedUpdate := 0
  117. cache, err := lru.New[int64, any](512)
  118. if err != nil {
  119. logger.Critical("Unable to create cache: %v", err)
  120. return err
  121. }
  122. if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
  123. numRepos++
  124. if owner, has := cache.Get(repo.OwnerID); has {
  125. repo.Owner = owner.(*user_model.User)
  126. } else {
  127. if err := repo.LoadOwner(ctx); err != nil {
  128. return err
  129. }
  130. cache.Add(repo.OwnerID, repo.Owner)
  131. }
  132. // Create/Remove git-daemon-export-ok for git-daemon...
  133. daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`)
  134. isExist, err := util.IsExist(daemonExportFile)
  135. if err != nil {
  136. log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
  137. return err
  138. }
  139. isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic
  140. if isPublic != isExist {
  141. numNeedUpdate++
  142. if autofix {
  143. if !isPublic && isExist {
  144. if err = util.Remove(daemonExportFile); err != nil {
  145. log.Error("Failed to remove %s: %v", daemonExportFile, err)
  146. }
  147. } else if isPublic && !isExist {
  148. if f, err := os.Create(daemonExportFile); err != nil {
  149. log.Error("Failed to create %s: %v", daemonExportFile, err)
  150. } else {
  151. f.Close()
  152. }
  153. }
  154. }
  155. }
  156. return nil
  157. }); err != nil {
  158. logger.Critical("Unable to checkDaemonExport: %v", err)
  159. return err
  160. }
  161. if autofix {
  162. logger.Info("Updated git-daemon-export-ok files for %d of %d repositories.", numNeedUpdate, numRepos)
  163. } else {
  164. logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate)
  165. }
  166. return nil
  167. }
  168. func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) error {
  169. numRepos := 0
  170. numNeedUpdate := 0
  171. numWritten := 0
  172. if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
  173. numRepos++
  174. commitGraphExists := func() (bool, error) {
  175. // Check commit-graph exists
  176. commitGraphFile := path.Join(repo.RepoPath(), `objects/info/commit-graph`)
  177. isExist, err := util.IsExist(commitGraphFile)
  178. if err != nil {
  179. logger.Error("Unable to check if %s exists. Error: %v", commitGraphFile, err)
  180. return false, err
  181. }
  182. if !isExist {
  183. commitGraphsDir := path.Join(repo.RepoPath(), `objects/info/commit-graphs`)
  184. isExist, err = util.IsExist(commitGraphsDir)
  185. if err != nil {
  186. logger.Error("Unable to check if %s exists. Error: %v", commitGraphsDir, err)
  187. return false, err
  188. }
  189. }
  190. return isExist, nil
  191. }
  192. isExist, err := commitGraphExists()
  193. if err != nil {
  194. return err
  195. }
  196. if !isExist {
  197. numNeedUpdate++
  198. if autofix {
  199. if err := git.WriteCommitGraph(ctx, repo.RepoPath()); err != nil {
  200. logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err)
  201. return err
  202. }
  203. isExist, err := commitGraphExists()
  204. if err != nil {
  205. return err
  206. }
  207. if isExist {
  208. numWritten++
  209. logger.Info("Commit-graph written: %s", repo.FullName())
  210. } else {
  211. logger.Warn("No commit-graph written: %s", repo.FullName())
  212. }
  213. }
  214. }
  215. return nil
  216. }); err != nil {
  217. logger.Critical("Unable to checkCommitGraph: %v", err)
  218. return err
  219. }
  220. if autofix {
  221. logger.Info("Wrote commit-graph files for %d of %d repositories.", numWritten, numRepos)
  222. } else {
  223. logger.Info("Checked %d repositories, %d without commit-graphs.", numRepos, numNeedUpdate)
  224. }
  225. return nil
  226. }
  227. func init() {
  228. Register(&Check{
  229. Title: "Check if SCRIPT_TYPE is available",
  230. Name: "script-type",
  231. IsDefault: false,
  232. Run: checkScriptType,
  233. Priority: 5,
  234. })
  235. Register(&Check{
  236. Title: "Check if hook files are up-to-date and executable",
  237. Name: "hooks",
  238. IsDefault: false,
  239. Run: checkHooks,
  240. Priority: 6,
  241. })
  242. Register(&Check{
  243. Title: "Recalculate Stars number for all user",
  244. Name: "recalculate-stars-number",
  245. IsDefault: false,
  246. Run: checkUserStarNum,
  247. Priority: 6,
  248. })
  249. Register(&Check{
  250. Title: "Check that all git repositories have receive.advertisePushOptions set to true",
  251. Name: "enable-push-options",
  252. IsDefault: false,
  253. Run: checkEnablePushOptions,
  254. Priority: 7,
  255. })
  256. Register(&Check{
  257. Title: "Check git-daemon-export-ok files",
  258. Name: "check-git-daemon-export-ok",
  259. IsDefault: false,
  260. Run: checkDaemonExport,
  261. Priority: 8,
  262. })
  263. Register(&Check{
  264. Title: "Check commit-graphs",
  265. Name: "check-commit-graphs",
  266. IsDefault: false,
  267. Run: checkCommitGraph,
  268. Priority: 9,
  269. })
  270. }