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