192 lines
6.1 KiB
Go
192 lines
6.1 KiB
Go
// 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/migrations"
|
|
"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 := models.NewEngine(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 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",
|
|
},
|
|
}
|
|
|
|
// TODO: function to recalc all counters
|
|
|
|
if setting.Database.UsePostgreSQL {
|
|
consistencyChecks = append(consistencyChecks, consistencyCheck{
|
|
Name: "Sequence values",
|
|
Counter: models.CountBadSequences,
|
|
Fixer: asFixer(models.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"),
|
|
)
|
|
|
|
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,
|
|
})
|
|
}
|