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