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