gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package doctor
  4. import (
  5. "context"
  6. actions_model "code.gitea.io/gitea/models/actions"
  7. activities_model "code.gitea.io/gitea/models/activities"
  8. "code.gitea.io/gitea/models/db"
  9. issues_model "code.gitea.io/gitea/models/issues"
  10. "code.gitea.io/gitea/models/migrations"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. secret_model "code.gitea.io/gitea/models/secret"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/setting"
  15. issue_service "code.gitea.io/gitea/services/issue"
  16. )
  17. type consistencyCheck struct {
  18. Name string
  19. Counter func(context.Context) (int64, error)
  20. Fixer func(context.Context) (int64, error)
  21. FixedMessage string
  22. }
  23. func (c *consistencyCheck) Run(ctx context.Context, logger log.Logger, autofix bool) error {
  24. count, err := c.Counter(ctx)
  25. if err != nil {
  26. logger.Critical("Error: %v whilst counting %s", err, c.Name)
  27. return err
  28. }
  29. if count > 0 {
  30. if autofix {
  31. var fixed int64
  32. if fixed, err = c.Fixer(ctx); err != nil {
  33. logger.Critical("Error: %v whilst fixing %s", err, c.Name)
  34. return err
  35. }
  36. prompt := "Deleted"
  37. if c.FixedMessage != "" {
  38. prompt = c.FixedMessage
  39. }
  40. if fixed < 0 {
  41. logger.Info(prompt+" %d %s", count, c.Name)
  42. } else {
  43. logger.Info(prompt+" %d/%d %s", fixed, count, c.Name)
  44. }
  45. } else {
  46. logger.Warn("Found %d %s", count, c.Name)
  47. }
  48. }
  49. return nil
  50. }
  51. func asFixer(fn func(ctx context.Context) error) func(ctx context.Context) (int64, error) {
  52. return func(ctx context.Context) (int64, error) {
  53. err := fn(ctx)
  54. return -1, err
  55. }
  56. }
  57. func genericOrphanCheck(name, subject, refObject, joinCond string) consistencyCheck {
  58. return consistencyCheck{
  59. Name: name,
  60. Counter: func(ctx context.Context) (int64, error) {
  61. return db.CountOrphanedObjects(ctx, subject, refObject, joinCond)
  62. },
  63. Fixer: func(ctx context.Context) (int64, error) {
  64. err := db.DeleteOrphanedObjects(ctx, subject, refObject, joinCond)
  65. return -1, err
  66. },
  67. }
  68. }
  69. func prepareDBConsistencyChecks() []consistencyCheck {
  70. consistencyChecks := []consistencyCheck{
  71. {
  72. // find labels without existing repo or org
  73. Name: "Orphaned Labels without existing repository or organisation",
  74. Counter: issues_model.CountOrphanedLabels,
  75. Fixer: asFixer(issues_model.DeleteOrphanedLabels),
  76. },
  77. {
  78. // find IssueLabels without existing label
  79. Name: "Orphaned Issue Labels without existing label",
  80. Counter: issues_model.CountOrphanedIssueLabels,
  81. Fixer: asFixer(issues_model.DeleteOrphanedIssueLabels),
  82. },
  83. {
  84. // find issues without existing repository
  85. Name: "Orphaned Issues without existing repository",
  86. Counter: issues_model.CountOrphanedIssues,
  87. Fixer: asFixer(issue_service.DeleteOrphanedIssues),
  88. },
  89. // find releases without existing repository
  90. genericOrphanCheck("Orphaned Releases without existing repository",
  91. "release", "repository", "`release`.repo_id=repository.id"),
  92. // find pulls without existing issues
  93. genericOrphanCheck("Orphaned PullRequests without existing issue",
  94. "pull_request", "issue", "pull_request.issue_id=issue.id"),
  95. // find pull requests without base repository
  96. genericOrphanCheck("Pull request entries without existing base repository",
  97. "pull_request", "repository", "pull_request.base_repo_id=repository.id"),
  98. // find tracked times without existing issues/pulls
  99. genericOrphanCheck("Orphaned TrackedTimes without existing issue",
  100. "tracked_time", "issue", "tracked_time.issue_id=issue.id"),
  101. // find attachments without existing issues or releases
  102. {
  103. Name: "Orphaned Attachments without existing issues or releases",
  104. Counter: repo_model.CountOrphanedAttachments,
  105. Fixer: asFixer(repo_model.DeleteOrphanedAttachments),
  106. },
  107. // find null archived repositories
  108. {
  109. Name: "Repositories with is_archived IS NULL",
  110. Counter: repo_model.CountNullArchivedRepository,
  111. Fixer: repo_model.FixNullArchivedRepository,
  112. FixedMessage: "Fixed",
  113. },
  114. // find label comments with empty labels
  115. {
  116. Name: "Label comments with empty labels",
  117. Counter: issues_model.CountCommentTypeLabelWithEmptyLabel,
  118. Fixer: issues_model.FixCommentTypeLabelWithEmptyLabel,
  119. FixedMessage: "Fixed",
  120. },
  121. // find label comments with labels from outside the repository
  122. {
  123. Name: "Label comments with labels from outside the repository",
  124. Counter: issues_model.CountCommentTypeLabelWithOutsideLabels,
  125. Fixer: issues_model.FixCommentTypeLabelWithOutsideLabels,
  126. FixedMessage: "Removed",
  127. },
  128. // find issue_label with labels from outside the repository
  129. {
  130. Name: "IssueLabels with Labels from outside the repository",
  131. Counter: issues_model.CountIssueLabelWithOutsideLabels,
  132. Fixer: issues_model.FixIssueLabelWithOutsideLabels,
  133. FixedMessage: "Removed",
  134. },
  135. {
  136. Name: "Action with created_unix set as an empty string",
  137. Counter: activities_model.CountActionCreatedUnixString,
  138. Fixer: activities_model.FixActionCreatedUnixString,
  139. FixedMessage: "Set to zero",
  140. },
  141. {
  142. Name: "Action Runners without existing owner",
  143. Counter: actions_model.CountRunnersWithoutBelongingOwner,
  144. Fixer: actions_model.FixRunnersWithoutBelongingOwner,
  145. FixedMessage: "Removed",
  146. },
  147. {
  148. Name: "Action Runners without existing repository",
  149. Counter: actions_model.CountRunnersWithoutBelongingRepo,
  150. Fixer: actions_model.FixRunnersWithoutBelongingRepo,
  151. FixedMessage: "Removed",
  152. },
  153. {
  154. Name: "Topics with empty repository count",
  155. Counter: repo_model.CountOrphanedTopics,
  156. Fixer: repo_model.DeleteOrphanedTopics,
  157. FixedMessage: "Removed",
  158. },
  159. {
  160. Name: "Repository level Runners with non-zero owner_id",
  161. Counter: actions_model.CountWrongRepoLevelRunners,
  162. Fixer: actions_model.UpdateWrongRepoLevelRunners,
  163. FixedMessage: "Corrected",
  164. },
  165. {
  166. Name: "Repository level Variables with non-zero owner_id",
  167. Counter: actions_model.CountWrongRepoLevelVariables,
  168. Fixer: actions_model.UpdateWrongRepoLevelVariables,
  169. FixedMessage: "Corrected",
  170. },
  171. {
  172. Name: "Repository level Secrets with non-zero owner_id",
  173. Counter: secret_model.CountWrongRepoLevelSecrets,
  174. Fixer: secret_model.UpdateWrongRepoLevelSecrets,
  175. FixedMessage: "Corrected",
  176. },
  177. }
  178. // TODO: function to recalc all counters
  179. if setting.Database.Type.IsPostgreSQL() {
  180. consistencyChecks = append(consistencyChecks, consistencyCheck{
  181. Name: "Sequence values",
  182. Counter: db.CountBadSequences,
  183. Fixer: asFixer(db.FixBadSequences),
  184. FixedMessage: "Updated",
  185. })
  186. }
  187. consistencyChecks = append(consistencyChecks,
  188. // find protected branches without existing repository
  189. genericOrphanCheck("Protected Branches without existing repository",
  190. "protected_branch", "repository", "protected_branch.repo_id=repository.id"),
  191. // find branches without existing repository
  192. genericOrphanCheck("Branches without existing repository",
  193. "branch", "repository", "branch.repo_id=repository.id"),
  194. // find LFS locks without existing repository
  195. genericOrphanCheck("LFS locks without existing repository",
  196. "lfs_lock", "repository", "lfs_lock.repo_id=repository.id"),
  197. // find collaborations without users
  198. genericOrphanCheck("Collaborations without existing user",
  199. "collaboration", "user", "collaboration.user_id=`user`.id"),
  200. // find collaborations without repository
  201. genericOrphanCheck("Collaborations without existing repository",
  202. "collaboration", "repository", "collaboration.repo_id=repository.id"),
  203. // find access without users
  204. genericOrphanCheck("Access entries without existing user",
  205. "access", "user", "access.user_id=`user`.id"),
  206. // find access without repository
  207. genericOrphanCheck("Access entries without existing repository",
  208. "access", "repository", "access.repo_id=repository.id"),
  209. // find action without repository
  210. genericOrphanCheck("Action entries without existing repository",
  211. "action", "repository", "action.repo_id=repository.id"),
  212. // find action without user
  213. genericOrphanCheck("Action entries without existing user",
  214. "action", "user", "action.act_user_id=`user`.id"),
  215. // find OAuth2Grant without existing user
  216. genericOrphanCheck("Orphaned OAuth2Grant without existing User",
  217. "oauth2_grant", "user", "oauth2_grant.user_id=`user`.id"),
  218. // find OAuth2Application without existing user
  219. genericOrphanCheck("Orphaned OAuth2Application without existing User",
  220. "oauth2_application", "user", "oauth2_application.uid=0 OR oauth2_application.uid=`user`.id"),
  221. // find OAuth2AuthorizationCode without existing OAuth2Grant
  222. genericOrphanCheck("Orphaned OAuth2AuthorizationCode without existing OAuth2Grant",
  223. "oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"),
  224. // find stopwatches without existing user
  225. genericOrphanCheck("Orphaned Stopwatches without existing User",
  226. "stopwatch", "user", "stopwatch.user_id=`user`.id"),
  227. // find stopwatches without existing issue
  228. genericOrphanCheck("Orphaned Stopwatches without existing Issue",
  229. "stopwatch", "issue", "stopwatch.issue_id=`issue`.id"),
  230. // find redirects without existing user.
  231. genericOrphanCheck("Orphaned Redirects without existing redirect user",
  232. "user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
  233. )
  234. return consistencyChecks
  235. }
  236. func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) error {
  237. // make sure DB version is uptodate
  238. if err := db.InitEngineWithMigration(ctx, migrations.EnsureUpToDate); err != nil {
  239. logger.Critical("Model version on the database does not match the current Gitea version. Model consistency will not be checked until the database is upgraded")
  240. return err
  241. }
  242. consistencyChecks := prepareDBConsistencyChecks()
  243. for _, c := range consistencyChecks {
  244. if err := c.Run(ctx, logger, autofix); err != nil {
  245. return err
  246. }
  247. }
  248. return nil
  249. }
  250. func init() {
  251. Register(&Check{
  252. Title: "Check consistency of database",
  253. Name: "check-db-consistency",
  254. IsDefault: false,
  255. Run: checkDBConsistency,
  256. Priority: 3,
  257. })
  258. }