Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
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.
217 lines
7.4 KiB
217 lines
7.4 KiB
// Copyright 2020 The Gitea Authors. All rights reserved. |
|
// Use of this source code is governed by a MIT-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package doctor |
|
|
|
import ( |
|
"context" |
|
|
|
"code.gitea.io/gitea/models" |
|
"code.gitea.io/gitea/models/db" |
|
"code.gitea.io/gitea/models/migrations" |
|
repo_model "code.gitea.io/gitea/models/repo" |
|
"code.gitea.io/gitea/modules/log" |
|
"code.gitea.io/gitea/modules/setting" |
|
) |
|
|
|
type consistencyCheck struct { |
|
Name string |
|
Counter func() (int64, error) |
|
Fixer func() (int64, error) |
|
FixedMessage string |
|
} |
|
|
|
func (c *consistencyCheck) Run(logger log.Logger, autofix bool) error { |
|
count, err := c.Counter() |
|
if err != nil { |
|
logger.Critical("Error: %v whilst counting %s", err, c.Name) |
|
return err |
|
} |
|
if count > 0 { |
|
if autofix { |
|
var fixed int64 |
|
if fixed, err = c.Fixer(); err != nil { |
|
logger.Critical("Error: %v whilst fixing %s", err, c.Name) |
|
return err |
|
} |
|
|
|
prompt := "Deleted" |
|
if c.FixedMessage != "" { |
|
prompt = c.FixedMessage |
|
} |
|
|
|
if fixed < 0 { |
|
logger.Info(prompt+" %d %s", count, c.Name) |
|
} else { |
|
logger.Info(prompt+" %d/%d %s", fixed, count, c.Name) |
|
} |
|
} else { |
|
logger.Warn("Found %d %s", count, c.Name) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func asFixer(fn func() error) func() (int64, error) { |
|
return func() (int64, error) { |
|
err := fn() |
|
return -1, err |
|
} |
|
} |
|
|
|
func genericOrphanCheck(name, subject, refobject, joincond string) consistencyCheck { |
|
return consistencyCheck{ |
|
Name: name, |
|
Counter: func() (int64, error) { |
|
return models.CountOrphanedObjects(subject, refobject, joincond) |
|
}, |
|
Fixer: func() (int64, error) { |
|
err := models.DeleteOrphanedObjects(subject, refobject, joincond) |
|
return -1, err |
|
}, |
|
} |
|
} |
|
|
|
func checkDBConsistency(logger log.Logger, autofix bool) error { |
|
// make sure DB version is uptodate |
|
if err := db.InitEngineWithMigration(context.Background(), migrations.EnsureUpToDate); err != nil { |
|
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") |
|
return err |
|
} |
|
|
|
consistencyChecks := []consistencyCheck{ |
|
{ |
|
// find labels without existing repo or org |
|
Name: "Orphaned Labels without existing repository or organisation", |
|
Counter: models.CountOrphanedLabels, |
|
Fixer: asFixer(models.DeleteOrphanedLabels), |
|
}, |
|
{ |
|
// find IssueLabels without existing label |
|
Name: "Orphaned Issue Labels without existing label", |
|
Counter: models.CountOrphanedIssueLabels, |
|
Fixer: asFixer(models.DeleteOrphanedIssueLabels), |
|
}, |
|
{ |
|
// find issues without existing repository |
|
Name: "Orphaned Issues without existing repository", |
|
Counter: models.CountOrphanedIssues, |
|
Fixer: asFixer(models.DeleteOrphanedIssues), |
|
}, |
|
// find releases without existing repository |
|
genericOrphanCheck("Orphaned Releases without existing repository", |
|
"release", "repository", "release.repo_id=repository.id"), |
|
// find pulls without existing issues |
|
genericOrphanCheck("Orphaned PullRequests without existing issue", |
|
"pull_request", "issue", "pull_request.issue_id=issue.id"), |
|
// find tracked times without existing issues/pulls |
|
genericOrphanCheck("Orphaned TrackedTimes without existing issue", |
|
"tracked_time", "issue", "tracked_time.issue_id=issue.id"), |
|
// find attachments without existing issues or releases |
|
{ |
|
Name: "Orphaned Attachments without existing issues or releases", |
|
Counter: repo_model.CountOrphanedAttachments, |
|
Fixer: asFixer(repo_model.DeleteOrphanedAttachments), |
|
}, |
|
// find null archived repositories |
|
{ |
|
Name: "Repositories with is_archived IS NULL", |
|
Counter: models.CountNullArchivedRepository, |
|
Fixer: models.FixNullArchivedRepository, |
|
FixedMessage: "Fixed", |
|
}, |
|
// find label comments with empty labels |
|
{ |
|
Name: "Label comments with empty labels", |
|
Counter: models.CountCommentTypeLabelWithEmptyLabel, |
|
Fixer: models.FixCommentTypeLabelWithEmptyLabel, |
|
FixedMessage: "Fixed", |
|
}, |
|
// find label comments with labels from outside the repository |
|
{ |
|
Name: "Label comments with labels from outside the repository", |
|
Counter: models.CountCommentTypeLabelWithOutsideLabels, |
|
Fixer: models.FixCommentTypeLabelWithOutsideLabels, |
|
FixedMessage: "Removed", |
|
}, |
|
// find issue_label with labels from outside the repository |
|
{ |
|
Name: "IssueLabels with Labels from outside the repository", |
|
Counter: models.CountIssueLabelWithOutsideLabels, |
|
Fixer: models.FixIssueLabelWithOutsideLabels, |
|
FixedMessage: "Removed", |
|
}, |
|
{ |
|
Name: "Action with created_unix set as an empty string", |
|
Counter: models.CountActionCreatedUnixString, |
|
Fixer: models.FixActionCreatedUnixString, |
|
FixedMessage: "Set to zero", |
|
}, |
|
} |
|
|
|
// TODO: function to recalc all counters |
|
|
|
if setting.Database.UsePostgreSQL { |
|
consistencyChecks = append(consistencyChecks, consistencyCheck{ |
|
Name: "Sequence values", |
|
Counter: db.CountBadSequences, |
|
Fixer: asFixer(db.FixBadSequences), |
|
FixedMessage: "Updated", |
|
}) |
|
} |
|
|
|
consistencyChecks = append(consistencyChecks, |
|
// find protected branches without existing repository |
|
genericOrphanCheck("Protected Branches without existing repository", |
|
"protected_branch", "repository", "protected_branch.repo_id=repository.id"), |
|
// find deleted branches without existing repository |
|
genericOrphanCheck("Deleted Branches without existing repository", |
|
"deleted_branch", "repository", "deleted_branch.repo_id=repository.id"), |
|
// find LFS locks without existing repository |
|
genericOrphanCheck("LFS locks without existing repository", |
|
"lfs_lock", "repository", "lfs_lock.repo_id=repository.id"), |
|
// find collaborations without users |
|
genericOrphanCheck("Collaborations without existing user", |
|
"collaboration", "user", "collaboration.user_id=`user`.id"), |
|
// find collaborations without repository |
|
genericOrphanCheck("Collaborations without existing repository", |
|
"collaboration", "repository", "collaboration.repo_id=repository.id"), |
|
// find access without users |
|
genericOrphanCheck("Access entries without existing user", |
|
"access", "user", "access.user_id=`user`.id"), |
|
// find access without repository |
|
genericOrphanCheck("Access entries without existing repository", |
|
"access", "repository", "access.repo_id=repository.id"), |
|
// find action without repository |
|
genericOrphanCheck("Action entries without existing repository", |
|
"action", "repository", "action.repo_id=repository.id"), |
|
// find OAuth2Grant without existing user |
|
genericOrphanCheck("Orphaned OAuth2Grant without existing User", |
|
"oauth2_grant", "user", "oauth2_grant.user_id=user.id"), |
|
// find OAuth2Application without existing user |
|
genericOrphanCheck("Orphaned OAuth2Application without existing User", |
|
"oauth2_application", "user", "oauth2_application.uid=user.id"), |
|
// find OAuth2AuthorizationCode without existing OAuth2Grant |
|
genericOrphanCheck("Orphaned OAuth2AuthorizationCode without existing OAuth2Grant", |
|
"oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"), |
|
) |
|
|
|
for _, c := range consistencyChecks { |
|
if err := c.Run(logger, autofix); err != nil { |
|
return err |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func init() { |
|
Register(&Check{ |
|
Title: "Check consistency of database", |
|
Name: "check-db-consistency", |
|
IsDefault: false, |
|
Run: checkDBConsistency, |
|
Priority: 3, |
|
}) |
|
}
|
|
|