2020-12-02 12:56:04 +08:00
// 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"
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2020-12-02 12:56:04 +08:00
"code.gitea.io/gitea/models/migrations"
2021-11-19 21:39:57 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2020-12-02 12:56:04 +08:00
"code.gitea.io/gitea/modules/log"
2021-03-18 14:06:40 +08:00
"code.gitea.io/gitea/modules/setting"
2020-12-02 12:56:04 +08:00
)
2021-09-28 02:07:19 +08:00
type consistencyCheck struct {
Name string
Counter func ( ) ( int64 , error )
Fixer func ( ) ( int64 , error )
FixedMessage string
}
2020-12-02 12:56:04 +08:00
2022-01-20 07:26:57 +08:00
func ( c * consistencyCheck ) Run ( ctx context . Context , logger log . Logger , autofix bool ) error {
2021-09-28 02:07:19 +08:00
count , err := c . Counter ( )
2021-09-15 03:41:40 +08:00
if err != nil {
2021-09-28 02:07:19 +08:00
logger . Critical ( "Error: %v whilst counting %s" , err , c . Name )
2021-09-15 03:41:40 +08:00
return err
}
if count > 0 {
if autofix {
2021-09-28 02:07:19 +08:00
var fixed int64
if fixed , err = c . Fixer ( ) ; err != nil {
logger . Critical ( "Error: %v whilst fixing %s" , err , c . Name )
2021-09-15 03:41:40 +08:00
return err
}
2021-09-28 02:07:19 +08:00
prompt := "Deleted"
if c . FixedMessage != "" {
prompt = c . FixedMessage
2020-12-02 12:56:04 +08:00
}
2021-09-28 02:07:19 +08:00
if fixed < 0 {
logger . Info ( prompt + " %d %s" , count , c . Name )
} else {
logger . Info ( prompt + " %d/%d %s" , fixed , count , c . Name )
2021-09-15 03:41:40 +08:00
}
} else {
2021-09-28 02:07:19 +08:00
logger . Warn ( "Found %d %s" , count , c . Name )
2021-09-15 03:41:40 +08:00
}
}
2021-09-28 02:07:19 +08:00
return nil
}
2021-09-15 03:41:40 +08:00
2021-09-28 02:07:19 +08:00
func asFixer ( fn func ( ) error ) func ( ) ( int64 , error ) {
return func ( ) ( int64 , error ) {
err := fn ( )
return - 1 , err
2020-12-02 12:56:04 +08:00
}
2021-09-28 02:07:19 +08:00
}
2020-12-02 12:56:04 +08:00
2021-09-28 02:07:19 +08:00
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
} ,
2021-02-10 10:50:44 +08:00
}
2021-09-28 02:07:19 +08:00
}
2021-03-19 21:25:14 +08:00
2022-01-20 07:26:57 +08:00
func checkDBConsistency ( ctx context . Context , logger log . Logger , autofix bool ) error {
2021-09-28 02:07:19 +08:00
// make sure DB version is uptodate
2022-01-20 07:26:57 +08:00
if err := db . InitEngineWithMigration ( ctx , migrations . EnsureUpToDate ) ; err != nil {
2021-09-28 02:07:19 +08:00
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" )
2021-03-19 21:25:14 +08:00
return err
}
2021-09-28 02:07:19 +08:00
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" ) ,
2022-05-18 08:34:32 +08:00
// find pull requests without base repository
genericOrphanCheck ( "Pull request entries without existing base repository" ,
"pull_request" , "repository" , "pull_request.base_repo_id=repository.id" ) ,
2021-09-28 02:07:19 +08:00
// 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" ,
2021-11-19 21:39:57 +08:00
Counter : repo_model . CountOrphanedAttachments ,
Fixer : asFixer ( repo_model . DeleteOrphanedAttachments ) ,
2021-09-28 02:07:19 +08:00
} ,
// 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" ,
} ,
2022-05-10 08:49:01 +08:00
{
Name : "Action with created_unix set as an empty string" ,
Counter : models . CountActionCreatedUnixString ,
Fixer : models . FixActionCreatedUnixString ,
FixedMessage : "Set to zero" ,
} ,
2021-03-19 21:25:14 +08:00
}
2020-12-02 12:56:04 +08:00
// TODO: function to recalc all counters
2021-03-18 14:06:40 +08:00
if setting . Database . UsePostgreSQL {
2021-09-28 02:07:19 +08:00
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" ,
2021-12-23 07:52:57 +08:00
"collaboration" , "user" , "collaboration.user_id=`user`.id" ) ,
2021-09-28 02:07:19 +08:00
// 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" ,
2021-12-23 07:52:57 +08:00
"access" , "user" , "access.user_id=`user`.id" ) ,
2021-09-28 02:07:19 +08:00
// find access without repository
genericOrphanCheck ( "Access entries without existing repository" ,
"access" , "repository" , "access.repo_id=repository.id" ) ,
2022-05-10 08:49:01 +08:00
// find action without repository
genericOrphanCheck ( "Action entries without existing repository" ,
"action" , "repository" , "action.repo_id=repository.id" ) ,
2022-05-11 19:16:35 +08:00
// find OAuth2Grant without existing user
genericOrphanCheck ( "Orphaned OAuth2Grant without existing User" ,
2022-05-20 15:36:34 +08:00
"oauth2_grant" , "user" , "oauth2_grant.user_id=`user`.id" ) ,
2022-05-11 19:16:35 +08:00
// find OAuth2Application without existing user
genericOrphanCheck ( "Orphaned OAuth2Application without existing User" ,
2022-05-20 15:36:34 +08:00
"oauth2_application" , "user" , "oauth2_application.uid=`user`.id" ) ,
2022-05-11 19:16:35 +08:00
// find OAuth2AuthorizationCode without existing OAuth2Grant
genericOrphanCheck ( "Orphaned OAuth2AuthorizationCode without existing OAuth2Grant" ,
"oauth2_authorization_code" , "oauth2_grant" , "oauth2_authorization_code.grant_id=oauth2_grant.id" ) ,
2021-09-28 02:07:19 +08:00
)
for _ , c := range consistencyChecks {
2022-01-20 07:26:57 +08:00
if err := c . Run ( ctx , logger , autofix ) ; err != nil {
2021-05-01 03:10:39 +08:00
return err
2021-03-18 14:06:40 +08:00
}
2021-05-01 03:10:39 +08:00
}
2020-12-02 12:56:04 +08:00
return nil
}
func init ( ) {
Register ( & Check {
Title : "Check consistency of database" ,
Name : "check-db-consistency" ,
IsDefault : false ,
Run : checkDBConsistency ,
Priority : 3 ,
} )
}