2020-12-02 13:56:04 +09:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-28 03:20:29 +09:00
// SPDX-License-Identifier: MIT
2020-12-02 13:56:04 +09:00
package doctor
import (
"context"
2023-11-05 21:48:32 +09:00
actions_model "code.gitea.io/gitea/models/actions"
2022-08-25 11:31:57 +09:00
activities_model "code.gitea.io/gitea/models/activities"
2024-04-06 07:52:39 +09:00
auth_model "code.gitea.io/gitea/models/auth"
2021-09-19 20:49:59 +09:00
"code.gitea.io/gitea/models/db"
2022-06-13 18:37:59 +09:00
issues_model "code.gitea.io/gitea/models/issues"
2020-12-02 13:56:04 +09:00
"code.gitea.io/gitea/models/migrations"
2021-11-19 22:39:57 +09:00
repo_model "code.gitea.io/gitea/models/repo"
2020-12-02 13:56:04 +09:00
"code.gitea.io/gitea/modules/log"
2021-03-18 15:06:40 +09:00
"code.gitea.io/gitea/modules/setting"
2020-12-02 13:56:04 +09:00
)
2021-09-28 03:07:19 +09:00
type consistencyCheck struct {
Name string
2022-11-19 17:12:33 +09:00
Counter func ( context . Context ) ( int64 , error )
Fixer func ( context . Context ) ( int64 , error )
2021-09-28 03:07:19 +09:00
FixedMessage string
}
2020-12-02 13:56:04 +09:00
2022-01-20 08:26:57 +09:00
func ( c * consistencyCheck ) Run ( ctx context . Context , logger log . Logger , autofix bool ) error {
2022-11-19 17:12:33 +09:00
count , err := c . Counter ( ctx )
2021-09-15 04:41:40 +09:00
if err != nil {
2021-09-28 03:07:19 +09:00
logger . Critical ( "Error: %v whilst counting %s" , err , c . Name )
2021-09-15 04:41:40 +09:00
return err
}
if count > 0 {
if autofix {
2021-09-28 03:07:19 +09:00
var fixed int64
2022-11-19 17:12:33 +09:00
if fixed , err = c . Fixer ( ctx ) ; err != nil {
2021-09-28 03:07:19 +09:00
logger . Critical ( "Error: %v whilst fixing %s" , err , c . Name )
2021-09-15 04:41:40 +09:00
return err
}
2021-09-28 03:07:19 +09:00
prompt := "Deleted"
if c . FixedMessage != "" {
prompt = c . FixedMessage
2020-12-02 13:56:04 +09:00
}
2021-09-28 03:07:19 +09: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 04:41:40 +09:00
}
} else {
2021-09-28 03:07:19 +09:00
logger . Warn ( "Found %d %s" , count , c . Name )
2021-09-15 04:41:40 +09:00
}
}
2021-09-28 03:07:19 +09:00
return nil
}
2021-09-15 04:41:40 +09:00
2022-11-19 17:12:33 +09:00
func asFixer ( fn func ( ctx context . Context ) error ) func ( ctx context . Context ) ( int64 , error ) {
return func ( ctx context . Context ) ( int64 , error ) {
err := fn ( ctx )
2021-09-28 03:07:19 +09:00
return - 1 , err
2020-12-02 13:56:04 +09:00
}
2021-09-28 03:07:19 +09:00
}
2020-12-02 13:56:04 +09:00
2021-09-28 03:07:19 +09:00
func genericOrphanCheck ( name , subject , refobject , joincond string ) consistencyCheck {
return consistencyCheck {
Name : name ,
2022-11-19 17:12:33 +09:00
Counter : func ( ctx context . Context ) ( int64 , error ) {
return db . CountOrphanedObjects ( ctx , subject , refobject , joincond )
2021-09-28 03:07:19 +09:00
} ,
2022-11-19 17:12:33 +09:00
Fixer : func ( ctx context . Context ) ( int64 , error ) {
err := db . DeleteOrphanedObjects ( ctx , subject , refobject , joincond )
2021-09-28 03:07:19 +09:00
return - 1 , err
} ,
2021-02-10 11:50:44 +09:00
}
2021-09-28 03:07:19 +09:00
}
2021-03-19 22:25:14 +09:00
2022-01-20 08:26:57 +09:00
func checkDBConsistency ( ctx context . Context , logger log . Logger , autofix bool ) error {
2021-09-28 03:07:19 +09:00
// make sure DB version is uptodate
2022-01-20 08:26:57 +09:00
if err := db . InitEngineWithMigration ( ctx , migrations . EnsureUpToDate ) ; err != nil {
2021-09-28 03:07:19 +09: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 22:25:14 +09:00
return err
}
2021-09-28 03:07:19 +09:00
consistencyChecks := [ ] consistencyCheck {
{
// find labels without existing repo or org
Name : "Orphaned Labels without existing repository or organisation" ,
2022-06-13 18:37:59 +09:00
Counter : issues_model . CountOrphanedLabels ,
Fixer : asFixer ( issues_model . DeleteOrphanedLabels ) ,
2021-09-28 03:07:19 +09:00
} ,
{
// find IssueLabels without existing label
Name : "Orphaned Issue Labels without existing label" ,
2022-06-13 18:37:59 +09:00
Counter : issues_model . CountOrphanedIssueLabels ,
Fixer : asFixer ( issues_model . DeleteOrphanedIssueLabels ) ,
2021-09-28 03:07:19 +09:00
} ,
{
// find issues without existing repository
Name : "Orphaned Issues without existing repository" ,
2022-06-13 18:37:59 +09:00
Counter : issues_model . CountOrphanedIssues ,
Fixer : asFixer ( issues_model . DeleteOrphanedIssues ) ,
2021-09-28 03:07:19 +09:00
} ,
// find releases without existing repository
genericOrphanCheck ( "Orphaned Releases without existing repository" ,
2023-09-23 21:57:39 +09:00
"release" , "repository" , "`release`.repo_id=repository.id" ) ,
2021-09-28 03:07:19 +09:00
// find pulls without existing issues
genericOrphanCheck ( "Orphaned PullRequests without existing issue" ,
"pull_request" , "issue" , "pull_request.issue_id=issue.id" ) ,
2022-05-18 09:34:32 +09: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 03:07:19 +09: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 22:39:57 +09:00
Counter : repo_model . CountOrphanedAttachments ,
Fixer : asFixer ( repo_model . DeleteOrphanedAttachments ) ,
2021-09-28 03:07:19 +09:00
} ,
// find null archived repositories
{
Name : "Repositories with is_archived IS NULL" ,
2022-08-25 11:31:57 +09:00
Counter : repo_model . CountNullArchivedRepository ,
Fixer : repo_model . FixNullArchivedRepository ,
2021-09-28 03:07:19 +09:00
FixedMessage : "Fixed" ,
} ,
// find label comments with empty labels
{
Name : "Label comments with empty labels" ,
2022-06-13 18:37:59 +09:00
Counter : issues_model . CountCommentTypeLabelWithEmptyLabel ,
Fixer : issues_model . FixCommentTypeLabelWithEmptyLabel ,
2021-09-28 03:07:19 +09:00
FixedMessage : "Fixed" ,
} ,
// find label comments with labels from outside the repository
{
Name : "Label comments with labels from outside the repository" ,
2022-06-13 18:37:59 +09:00
Counter : issues_model . CountCommentTypeLabelWithOutsideLabels ,
Fixer : issues_model . FixCommentTypeLabelWithOutsideLabels ,
2021-09-28 03:07:19 +09:00
FixedMessage : "Removed" ,
} ,
// find issue_label with labels from outside the repository
{
Name : "IssueLabels with Labels from outside the repository" ,
2022-06-13 18:37:59 +09:00
Counter : issues_model . CountIssueLabelWithOutsideLabels ,
Fixer : issues_model . FixIssueLabelWithOutsideLabels ,
2021-09-28 03:07:19 +09:00
FixedMessage : "Removed" ,
} ,
2022-05-10 09:49:01 +09:00
{
Name : "Action with created_unix set as an empty string" ,
2022-08-25 11:31:57 +09:00
Counter : activities_model . CountActionCreatedUnixString ,
Fixer : activities_model . FixActionCreatedUnixString ,
2022-05-10 09:49:01 +09:00
FixedMessage : "Set to zero" ,
} ,
2023-11-05 21:48:32 +09:00
{
Name : "Action Runners without existing owner" ,
Counter : actions_model . CountRunnersWithoutBelongingOwner ,
Fixer : actions_model . FixRunnersWithoutBelongingOwner ,
FixedMessage : "Removed" ,
} ,
2023-12-19 00:32:08 +09:00
{
Name : "Topics with empty repository count" ,
Counter : repo_model . CountOrphanedTopics ,
Fixer : repo_model . DeleteOrphanedTopics ,
FixedMessage : "Removed" ,
} ,
2024-04-06 07:52:39 +09:00
{
Name : "Orphaned OAuth2Application without existing User" ,
Counter : auth_model . CountOrphanedOAuth2Applications ,
Fixer : auth_model . DeleteOrphanedOAuth2Applications ,
FixedMessage : "Removed" ,
} ,
2021-03-19 22:25:14 +09:00
}
2020-12-02 13:56:04 +09:00
// TODO: function to recalc all counters
2023-03-07 19:51:06 +09:00
if setting . Database . Type . IsPostgreSQL ( ) {
2021-09-28 03:07:19 +09: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" ) ,
2023-09-28 11:07:33 +09:00
// find branches without existing repository
genericOrphanCheck ( "Branches without existing repository" ,
"branch" , "repository" , "branch.repo_id=repository.id" ) ,
2021-09-28 03:07:19 +09:00
// 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 08:52:57 +09:00
"collaboration" , "user" , "collaboration.user_id=`user`.id" ) ,
2021-09-28 03:07:19 +09: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 08:52:57 +09:00
"access" , "user" , "access.user_id=`user`.id" ) ,
2021-09-28 03:07:19 +09:00
// find access without repository
genericOrphanCheck ( "Access entries without existing repository" ,
"access" , "repository" , "access.repo_id=repository.id" ) ,
2022-05-10 09:49:01 +09:00
// find action without repository
genericOrphanCheck ( "Action entries without existing repository" ,
"action" , "repository" , "action.repo_id=repository.id" ) ,
2023-09-28 12:03:08 +09:00
// find action without user
genericOrphanCheck ( "Action entries without existing user" ,
"action" , "user" , "action.act_user_id=`user`.id" ) ,
2022-05-11 20:16:35 +09:00
// find OAuth2Grant without existing user
genericOrphanCheck ( "Orphaned OAuth2Grant without existing User" ,
2022-05-20 16:36:34 +09:00
"oauth2_grant" , "user" , "oauth2_grant.user_id=`user`.id" ) ,
2022-05-11 20:16:35 +09: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" ) ,
2022-06-18 12:31:00 +09:00
// find stopwatches without existing user
genericOrphanCheck ( "Orphaned Stopwatches without existing User" ,
2022-06-19 08:26:22 +09:00
"stopwatch" , "user" , "stopwatch.user_id=`user`.id" ) ,
2022-06-18 12:31:00 +09:00
// find stopwatches without existing issue
genericOrphanCheck ( "Orphaned Stopwatches without existing Issue" ,
2022-06-19 08:26:22 +09:00
"stopwatch" , "issue" , "stopwatch.issue_id=`issue`.id" ) ,
2022-11-18 23:23:34 +09:00
// find redirects without existing user.
genericOrphanCheck ( "Orphaned Redirects without existing redirect user" ,
"user_redirect" , "user" , "user_redirect.redirect_user_id=`user`.id" ) ,
2024-04-02 23:34:57 +09:00
// find archive download count without existing release
genericOrphanCheck ( "Archive download count without existing Release" ,
"repo_archive_download_count" , "release" , "repo_archive_download_count.release_id=release.id" ) ,
2021-09-28 03:07:19 +09:00
)
for _ , c := range consistencyChecks {
2022-01-20 08:26:57 +09:00
if err := c . Run ( ctx , logger , autofix ) ; err != nil {
2021-05-01 04:10:39 +09:00
return err
2021-03-18 15:06:40 +09:00
}
2021-05-01 04:10:39 +09:00
}
2020-12-02 13:56:04 +09:00
return nil
}
func init ( ) {
Register ( & Check {
Title : "Check consistency of database" ,
Name : "check-db-consistency" ,
IsDefault : false ,
Run : checkDBConsistency ,
Priority : 3 ,
} )
}