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