aboutsummaryrefslogtreecommitdiffstats
path: root/modules/doctor
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2024-01-20 10:07:31 +0800
committerGitHub <noreply@github.com>2024-01-20 10:07:31 +0800
commit62f995203ab3397a6d995afb80767b33b1f00a1f (patch)
tree3c83f15d05f28ba31a660539cea042b24d8ef310 /modules/doctor
parentd68a613ba8fd860863a3465b5b5945b191b87b25 (diff)
downloadgitea-62f995203ab3397a6d995afb80767b33b1f00a1f.tar.gz
gitea-62f995203ab3397a6d995afb80767b33b1f00a1f.zip
Move doctor package from modules to services (#28856)
Diffstat (limited to 'modules/doctor')
-rw-r--r--modules/doctor/authorizedkeys.go96
-rw-r--r--modules/doctor/breaking.go97
-rw-r--r--modules/doctor/checkOldArchives.go59
-rw-r--r--modules/doctor/dbconsistency.go245
-rw-r--r--modules/doctor/dbversion.go42
-rw-r--r--modules/doctor/doctor.go127
-rw-r--r--modules/doctor/fix16961.go322
-rw-r--r--modules/doctor/fix16961_test.go271
-rw-r--r--modules/doctor/fix8312.go61
-rw-r--r--modules/doctor/heads.go88
-rw-r--r--modules/doctor/lfs.go51
-rw-r--r--modules/doctor/mergebase.go114
-rw-r--r--modules/doctor/misc.go298
-rw-r--r--modules/doctor/paths.go124
-rw-r--r--modules/doctor/repository.go80
-rw-r--r--modules/doctor/storage.go270
-rw-r--r--modules/doctor/usertype.go41
17 files changed, 0 insertions, 2386 deletions
diff --git a/modules/doctor/authorizedkeys.go b/modules/doctor/authorizedkeys.go
deleted file mode 100644
index 050a4e7974..0000000000
--- a/modules/doctor/authorizedkeys.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "bufio"
- "bytes"
- "context"
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- asymkey_model "code.gitea.io/gitea/models/asymkey"
- "code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-)
-
-const tplCommentPrefix = `# gitea public key`
-
-func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) error {
- if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
- return nil
- }
-
- fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
- f, err := os.Open(fPath)
- if err != nil {
- if !autofix {
- logger.Critical("Unable to open authorized_keys file. ERROR: %v", err)
- return fmt.Errorf("Unable to open authorized_keys file. ERROR: %w", err)
- }
- logger.Warn("Unable to open authorized_keys. (ERROR: %v). Attempting to rewrite...", err)
- if err = asymkey_model.RewriteAllPublicKeys(ctx); err != nil {
- logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
- return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
- }
- }
- defer f.Close()
-
- linesInAuthorizedKeys := make(container.Set[string])
-
- scanner := bufio.NewScanner(f)
- for scanner.Scan() {
- line := scanner.Text()
- if strings.HasPrefix(line, tplCommentPrefix) {
- continue
- }
- linesInAuthorizedKeys.Add(line)
- }
- f.Close()
-
- // now we regenerate and check if there are any lines missing
- regenerated := &bytes.Buffer{}
- if err := asymkey_model.RegeneratePublicKeys(ctx, regenerated); err != nil {
- logger.Critical("Unable to regenerate authorized_keys file. ERROR: %v", err)
- return fmt.Errorf("Unable to regenerate authorized_keys file. ERROR: %w", err)
- }
- scanner = bufio.NewScanner(regenerated)
- for scanner.Scan() {
- line := scanner.Text()
- if strings.HasPrefix(line, tplCommentPrefix) {
- continue
- }
- if linesInAuthorizedKeys.Contains(line) {
- continue
- }
- if !autofix {
- logger.Critical(
- "authorized_keys file %q is out of date.\nRegenerate it with:\n\t\"%s\"\nor\n\t\"%s\"",
- fPath,
- "gitea admin regenerate keys",
- "gitea doctor --run authorized-keys --fix")
- return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`)
- }
- logger.Warn("authorized_keys is out of date. Attempting rewrite...")
- err = asymkey_model.RewriteAllPublicKeys(ctx)
- if err != nil {
- logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
- return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
- }
- }
- return nil
-}
-
-func init() {
- Register(&Check{
- Title: "Check if OpenSSH authorized_keys file is up-to-date",
- Name: "authorized-keys",
- IsDefault: true,
- Run: checkAuthorizedKeys,
- Priority: 4,
- })
-}
diff --git a/modules/doctor/breaking.go b/modules/doctor/breaking.go
deleted file mode 100644
index 77e3d4e8ef..0000000000
--- a/modules/doctor/breaking.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
- "fmt"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/log"
-
- "xorm.io/builder"
-)
-
-func iterateUserAccounts(ctx context.Context, each func(*user.User) error) error {
- err := db.Iterate(
- ctx,
- builder.Gt{"id": 0},
- func(ctx context.Context, bean *user.User) error {
- return each(bean)
- },
- )
- return err
-}
-
-// Since 1.16.4 new restrictions has been set on email addresses. However users with invalid email
-// addresses would be currently facing a error due to their invalid email address.
-// Ref: https://github.com/go-gitea/gitea/pull/19085 & https://github.com/go-gitea/gitea/pull/17688
-func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
- // We could use quirky SQL to get all users that start without a [a-zA-Z0-9], but that would mean
- // DB provider-specific SQL and only works _now_. So instead we iterate through all user accounts
- // and use the user.ValidateEmail function to be future-proof.
- var invalidUserCount int64
- if err := iterateUserAccounts(ctx, func(u *user.User) error {
- // Only check for users, skip
- if u.Type != user.UserTypeIndividual {
- return nil
- }
-
- if err := user.ValidateEmail(u.Email); err != nil {
- invalidUserCount++
- logger.Warn("User[id=%d name=%q] have not a valid e-mail: %v", u.ID, u.Name, err)
- }
- return nil
- }); err != nil {
- return fmt.Errorf("iterateUserAccounts: %w", err)
- }
-
- if invalidUserCount == 0 {
- logger.Info("All users have a valid e-mail.")
- } else {
- logger.Warn("%d user(s) have a non-valid e-mail.", invalidUserCount)
- }
- return nil
-}
-
-// From time to time Gitea makes changes to the reserved usernames and which symbols
-// are allowed for various reasons. This check helps with detecting users that, according
-// to our reserved names, don't have a valid username.
-func checkUserName(ctx context.Context, logger log.Logger, _ bool) error {
- var invalidUserCount int64
- if err := iterateUserAccounts(ctx, func(u *user.User) error {
- if err := user.IsUsableUsername(u.Name); err != nil {
- invalidUserCount++
- logger.Warn("User[id=%d] does not have a valid username: %v", u.ID, err)
- }
- return nil
- }); err != nil {
- return fmt.Errorf("iterateUserAccounts: %w", err)
- }
-
- if invalidUserCount == 0 {
- logger.Info("All users have a valid username.")
- } else {
- logger.Warn("%d user(s) have a non-valid username.", invalidUserCount)
- }
- return nil
-}
-
-func init() {
- Register(&Check{
- Title: "Check if users has an valid email address",
- Name: "check-user-email",
- IsDefault: false,
- Run: checkUserEmail,
- Priority: 9,
- })
- Register(&Check{
- Title: "Check if users have a valid username",
- Name: "check-user-names",
- IsDefault: false,
- Run: checkUserName,
- Priority: 9,
- })
-}
diff --git a/modules/doctor/checkOldArchives.go b/modules/doctor/checkOldArchives.go
deleted file mode 100644
index 390dfb43aa..0000000000
--- a/modules/doctor/checkOldArchives.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
- "os"
- "path/filepath"
-
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
-)
-
-func checkOldArchives(ctx context.Context, logger log.Logger, autofix bool) error {
- numRepos := 0
- numReposUpdated := 0
- err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
- if repo.IsEmpty {
- return nil
- }
-
- p := filepath.Join(repo.RepoPath(), "archives")
- isDir, err := util.IsDir(p)
- if err != nil {
- log.Warn("check if %s is directory failed: %v", p, err)
- }
- if isDir {
- numRepos++
- if autofix {
- if err := os.RemoveAll(p); err == nil {
- numReposUpdated++
- } else {
- log.Warn("remove %s failed: %v", p, err)
- }
- }
- }
- return nil
- })
-
- if autofix {
- logger.Info("%d / %d old archives in repository deleted", numReposUpdated, numRepos)
- } else {
- logger.Info("%d old archives in repository need to be deleted", numRepos)
- }
-
- return err
-}
-
-func init() {
- Register(&Check{
- Title: "Check old archives",
- Name: "check-old-archives",
- IsDefault: false,
- Run: checkOldArchives,
- Priority: 7,
- })
-}
diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go
deleted file mode 100644
index e2dcb63f33..0000000000
--- a/modules/doctor/dbconsistency.go
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
-
- actions_model "code.gitea.io/gitea/models/actions"
- activities_model "code.gitea.io/gitea/models/activities"
- "code.gitea.io/gitea/models/db"
- issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/models/migrations"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-)
-
-type consistencyCheck struct {
- Name string
- Counter func(context.Context) (int64, error)
- Fixer func(context.Context) (int64, error)
- FixedMessage string
-}
-
-func (c *consistencyCheck) Run(ctx context.Context, logger log.Logger, autofix bool) error {
- count, err := c.Counter(ctx)
- 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(ctx); 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(ctx context.Context) error) func(ctx context.Context) (int64, error) {
- return func(ctx context.Context) (int64, error) {
- err := fn(ctx)
- return -1, err
- }
-}
-
-func genericOrphanCheck(name, subject, refobject, joincond string) consistencyCheck {
- return consistencyCheck{
- Name: name,
- Counter: func(ctx context.Context) (int64, error) {
- return db.CountOrphanedObjects(ctx, subject, refobject, joincond)
- },
- Fixer: func(ctx context.Context) (int64, error) {
- err := db.DeleteOrphanedObjects(ctx, subject, refobject, joincond)
- return -1, err
- },
- }
-}
-
-func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) error {
- // make sure DB version is uptodate
- if err := db.InitEngineWithMigration(ctx, 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: issues_model.CountOrphanedLabels,
- Fixer: asFixer(issues_model.DeleteOrphanedLabels),
- },
- {
- // find IssueLabels without existing label
- Name: "Orphaned Issue Labels without existing label",
- Counter: issues_model.CountOrphanedIssueLabels,
- Fixer: asFixer(issues_model.DeleteOrphanedIssueLabels),
- },
- {
- // find issues without existing repository
- Name: "Orphaned Issues without existing repository",
- Counter: issues_model.CountOrphanedIssues,
- Fixer: asFixer(issues_model.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 pull requests without base repository
- genericOrphanCheck("Pull request entries without existing base repository",
- "pull_request", "repository", "pull_request.base_repo_id=repository.id"),
- // 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",
- Counter: repo_model.CountOrphanedAttachments,
- Fixer: asFixer(repo_model.DeleteOrphanedAttachments),
- },
- // find null archived repositories
- {
- Name: "Repositories with is_archived IS NULL",
- Counter: repo_model.CountNullArchivedRepository,
- Fixer: repo_model.FixNullArchivedRepository,
- FixedMessage: "Fixed",
- },
- // find label comments with empty labels
- {
- Name: "Label comments with empty labels",
- Counter: issues_model.CountCommentTypeLabelWithEmptyLabel,
- Fixer: issues_model.FixCommentTypeLabelWithEmptyLabel,
- FixedMessage: "Fixed",
- },
- // find label comments with labels from outside the repository
- {
- Name: "Label comments with labels from outside the repository",
- Counter: issues_model.CountCommentTypeLabelWithOutsideLabels,
- Fixer: issues_model.FixCommentTypeLabelWithOutsideLabels,
- FixedMessage: "Removed",
- },
- // find issue_label with labels from outside the repository
- {
- Name: "IssueLabels with Labels from outside the repository",
- Counter: issues_model.CountIssueLabelWithOutsideLabels,
- Fixer: issues_model.FixIssueLabelWithOutsideLabels,
- FixedMessage: "Removed",
- },
- {
- Name: "Action with created_unix set as an empty string",
- Counter: activities_model.CountActionCreatedUnixString,
- Fixer: activities_model.FixActionCreatedUnixString,
- FixedMessage: "Set to zero",
- },
- {
- Name: "Action Runners without existing owner",
- Counter: actions_model.CountRunnersWithoutBelongingOwner,
- Fixer: actions_model.FixRunnersWithoutBelongingOwner,
- FixedMessage: "Removed",
- },
- {
- Name: "Topics with empty repository count",
- Counter: repo_model.CountOrphanedTopics,
- Fixer: repo_model.DeleteOrphanedTopics,
- FixedMessage: "Removed",
- },
- }
-
- // TODO: function to recalc all counters
-
- if setting.Database.Type.IsPostgreSQL() {
- 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 branches without existing repository
- genericOrphanCheck("Branches without existing repository",
- "branch", "repository", "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"),
- // find action without repository
- genericOrphanCheck("Action entries without existing repository",
- "action", "repository", "action.repo_id=repository.id"),
- // find action without user
- genericOrphanCheck("Action entries without existing user",
- "action", "user", "action.act_user_id=`user`.id"),
- // find OAuth2Grant without existing user
- genericOrphanCheck("Orphaned OAuth2Grant without existing User",
- "oauth2_grant", "user", "oauth2_grant.user_id=`user`.id"),
- // find OAuth2Application without existing user
- genericOrphanCheck("Orphaned OAuth2Application without existing User",
- "oauth2_application", "user", "oauth2_application.uid=`user`.id"),
- // find OAuth2AuthorizationCode without existing OAuth2Grant
- genericOrphanCheck("Orphaned OAuth2AuthorizationCode without existing OAuth2Grant",
- "oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"),
- // find stopwatches without existing user
- genericOrphanCheck("Orphaned Stopwatches without existing User",
- "stopwatch", "user", "stopwatch.user_id=`user`.id"),
- // find stopwatches without existing issue
- genericOrphanCheck("Orphaned Stopwatches without existing Issue",
- "stopwatch", "issue", "stopwatch.issue_id=`issue`.id"),
- // find redirects without existing user.
- genericOrphanCheck("Orphaned Redirects without existing redirect user",
- "user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
- )
-
- for _, c := range consistencyChecks {
- if err := c.Run(ctx, 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,
- })
-}
diff --git a/modules/doctor/dbversion.go b/modules/doctor/dbversion.go
deleted file mode 100644
index 2b20cb2340..0000000000
--- a/modules/doctor/dbversion.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/models/migrations"
- "code.gitea.io/gitea/modules/log"
-)
-
-func checkDBVersion(ctx context.Context, logger log.Logger, autofix bool) error {
- logger.Info("Expected database version: %d", migrations.ExpectedVersion())
- if err := db.InitEngineWithMigration(ctx, migrations.EnsureUpToDate); err != nil {
- if !autofix {
- logger.Critical("Error: %v during ensure up to date", err)
- return err
- }
- logger.Warn("Got Error: %v during ensure up to date", err)
- logger.Warn("Attempting to migrate to the latest DB version to fix this.")
-
- err = db.InitEngineWithMigration(ctx, migrations.Migrate)
- if err != nil {
- logger.Critical("Error: %v during migration", err)
- }
- return err
- }
- return nil
-}
-
-func init() {
- Register(&Check{
- Title: "Check Database Version",
- Name: "check-db-version",
- IsDefault: true,
- Run: checkDBVersion,
- AbortIfFailed: false,
- Priority: 2,
- })
-}
diff --git a/modules/doctor/doctor.go b/modules/doctor/doctor.go
deleted file mode 100644
index 559f8e06da..0000000000
--- a/modules/doctor/doctor.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
- "fmt"
- "os"
- "sort"
- "strings"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-)
-
-// Check represents a Doctor check
-type Check struct {
- Title string
- Name string
- IsDefault bool
- Run func(ctx context.Context, logger log.Logger, autofix bool) error
- AbortIfFailed bool
- SkipDatabaseInitialization bool
- Priority int
-}
-
-func initDBSkipLogger(ctx context.Context) error {
- setting.MustInstalled()
- setting.LoadDBSetting()
- if err := db.InitEngine(ctx); err != nil {
- return fmt.Errorf("db.InitEngine: %w", err)
- }
- // some doctor sub-commands need to use git command
- if err := git.InitFull(ctx); err != nil {
- return fmt.Errorf("git.InitFull: %w", err)
- }
- return nil
-}
-
-type doctorCheckLogger struct {
- colorize bool
-}
-
-var _ log.BaseLogger = (*doctorCheckLogger)(nil)
-
-func (d *doctorCheckLogger) Log(skip int, level log.Level, format string, v ...any) {
- _, _ = fmt.Fprintf(os.Stdout, format+"\n", v...)
-}
-
-func (d *doctorCheckLogger) GetLevel() log.Level {
- return log.TRACE
-}
-
-type doctorCheckStepLogger struct {
- colorize bool
-}
-
-var _ log.BaseLogger = (*doctorCheckStepLogger)(nil)
-
-func (d *doctorCheckStepLogger) Log(skip int, level log.Level, format string, v ...any) {
- levelChar := fmt.Sprintf("[%s]", strings.ToUpper(level.String()[0:1]))
- var levelArg any = levelChar
- if d.colorize {
- levelArg = log.NewColoredValue(levelChar, level.ColorAttributes()...)
- }
- args := append([]any{levelArg}, v...)
- _, _ = fmt.Fprintf(os.Stdout, " - %s "+format+"\n", args...)
-}
-
-func (d *doctorCheckStepLogger) GetLevel() log.Level {
- return log.TRACE
-}
-
-// Checks is the list of available commands
-var Checks []*Check
-
-// RunChecks runs the doctor checks for the provided list
-func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error {
- SortChecks(checks)
- // the checks output logs by a special logger, they do not use the default logger
- logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize})
- loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize})
- dbIsInit := false
- for i, check := range checks {
- if !dbIsInit && !check.SkipDatabaseInitialization {
- // Only open database after the most basic configuration check
- if err := initDBSkipLogger(ctx); err != nil {
- logger.Error("Error whilst initializing the database: %v", err)
- logger.Error("Check if you are using the right config file. You can use a --config directive to specify one.")
- return nil
- }
- dbIsInit = true
- }
- logger.Info("\n[%d] %s", i+1, check.Title)
- if err := check.Run(ctx, loggerStep, autofix); err != nil {
- if check.AbortIfFailed {
- logger.Critical("FAIL")
- return err
- }
- logger.Error("ERROR")
- } else {
- logger.Info("OK")
- }
- }
- logger.Info("\nAll done (checks: %d).", len(checks))
- return nil
-}
-
-// Register registers a command with the list
-func Register(command *Check) {
- Checks = append(Checks, command)
-}
-
-func SortChecks(checks []*Check) {
- sort.SliceStable(checks, func(i, j int) bool {
- if checks[i].Priority == checks[j].Priority {
- return checks[i].Name < checks[j].Name
- }
- if checks[i].Priority == 0 {
- return false
- }
- return checks[i].Priority < checks[j].Priority
- })
-}
diff --git a/modules/doctor/fix16961.go b/modules/doctor/fix16961.go
deleted file mode 100644
index d3f36d8d5c..0000000000
--- a/modules/doctor/fix16961.go
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
-
- "code.gitea.io/gitea/models/db"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/json"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/timeutil"
-
- "xorm.io/builder"
-)
-
-// #16831 revealed that the dump command that was broken in 1.14.3-1.14.6 and 1.15.0 (#15885).
-// This led to repo_unit and login_source cfg not being converted to JSON in the dump
-// Unfortunately although it was hoped that there were only a few users affected it
-// appears that many users are affected.
-
-// We therefore need to provide a doctor command to fix this repeated issue #16961
-
-func parseBool16961(bs []byte) (bool, error) {
- if bytes.EqualFold(bs, []byte("%!s(bool=false)")) {
- return false, nil
- }
-
- if bytes.EqualFold(bs, []byte("%!s(bool=true)")) {
- return true, nil
- }
-
- return false, fmt.Errorf("unexpected bool format: %s", string(bs))
-}
-
-func fixUnitConfig16961(bs []byte, cfg *repo_model.UnitConfig) (fixed bool, err error) {
- err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
- if err == nil {
- return false, nil
- }
-
- // Handle #16961
- if string(bs) != "&{}" && len(bs) != 0 {
- return false, err
- }
-
- return true, nil
-}
-
-func fixExternalWikiConfig16961(bs []byte, cfg *repo_model.ExternalWikiConfig) (fixed bool, err error) {
- err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
- if err == nil {
- return false, nil
- }
-
- if len(bs) < 3 {
- return false, err
- }
- if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
- return false, err
- }
- cfg.ExternalWikiURL = string(bs[2 : len(bs)-1])
- return true, nil
-}
-
-func fixExternalTrackerConfig16961(bs []byte, cfg *repo_model.ExternalTrackerConfig) (fixed bool, err error) {
- err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
- if err == nil {
- return false, nil
- }
- // Handle #16961
- if len(bs) < 3 {
- return false, err
- }
-
- if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
- return false, err
- }
-
- parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
- if len(parts) != 3 {
- return false, err
- }
-
- cfg.ExternalTrackerURL = string(bytes.Join(parts[:len(parts)-2], []byte{' '}))
- cfg.ExternalTrackerFormat = string(parts[len(parts)-2])
- cfg.ExternalTrackerStyle = string(parts[len(parts)-1])
- return true, nil
-}
-
-func fixPullRequestsConfig16961(bs []byte, cfg *repo_model.PullRequestsConfig) (fixed bool, err error) {
- err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
- if err == nil {
- return false, nil
- }
-
- // Handle #16961
- if len(bs) < 3 {
- return false, err
- }
-
- if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
- return false, err
- }
-
- // PullRequestsConfig was the following in 1.14
- // type PullRequestsConfig struct {
- // IgnoreWhitespaceConflicts bool
- // AllowMerge bool
- // AllowRebase bool
- // AllowRebaseMerge bool
- // AllowSquash bool
- // AllowManualMerge bool
- // AutodetectManualMerge bool
- // }
- //
- // 1.15 added in addition:
- // DefaultDeleteBranchAfterMerge bool
- // DefaultMergeStyle MergeStyle
- parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
- if len(parts) < 7 {
- return false, err
- }
-
- var parseErr error
- cfg.IgnoreWhitespaceConflicts, parseErr = parseBool16961(parts[0])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- cfg.AllowMerge, parseErr = parseBool16961(parts[1])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- cfg.AllowRebase, parseErr = parseBool16961(parts[2])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- cfg.AllowRebaseMerge, parseErr = parseBool16961(parts[3])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- cfg.AllowSquash, parseErr = parseBool16961(parts[4])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- cfg.AllowManualMerge, parseErr = parseBool16961(parts[5])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- cfg.AutodetectManualMerge, parseErr = parseBool16961(parts[6])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
-
- // 1.14 unit
- if len(parts) == 7 {
- return true, nil
- }
-
- if len(parts) < 9 {
- return false, err
- }
-
- cfg.DefaultDeleteBranchAfterMerge, parseErr = parseBool16961(parts[7])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
-
- cfg.DefaultMergeStyle = repo_model.MergeStyle(string(bytes.Join(parts[8:], []byte{' '})))
- return true, nil
-}
-
-func fixIssuesConfig16961(bs []byte, cfg *repo_model.IssuesConfig) (fixed bool, err error) {
- err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
- if err == nil {
- return false, nil
- }
-
- // Handle #16961
- if len(bs) < 3 {
- return false, err
- }
-
- if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
- return false, err
- }
-
- parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
- if len(parts) != 3 {
- return false, err
- }
- var parseErr error
- cfg.EnableTimetracker, parseErr = parseBool16961(parts[0])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- cfg.AllowOnlyContributorsToTrackTime, parseErr = parseBool16961(parts[1])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- cfg.EnableDependencies, parseErr = parseBool16961(parts[2])
- if parseErr != nil {
- return false, errors.Join(err, parseErr)
- }
- return true, nil
-}
-
-func fixBrokenRepoUnit16961(repoUnit *repo_model.RepoUnit, bs []byte) (fixed bool, err error) {
- // Shortcut empty or null values
- if len(bs) == 0 {
- return false, nil
- }
-
- switch repoUnit.Type {
- case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects:
- cfg := &repo_model.UnitConfig{}
- repoUnit.Config = cfg
- if fixed, err := fixUnitConfig16961(bs, cfg); !fixed {
- return false, err
- }
- case unit.TypeExternalWiki:
- cfg := &repo_model.ExternalWikiConfig{}
- repoUnit.Config = cfg
-
- if fixed, err := fixExternalWikiConfig16961(bs, cfg); !fixed {
- return false, err
- }
- case unit.TypeExternalTracker:
- cfg := &repo_model.ExternalTrackerConfig{}
- repoUnit.Config = cfg
- if fixed, err := fixExternalTrackerConfig16961(bs, cfg); !fixed {
- return false, err
- }
- case unit.TypePullRequests:
- cfg := &repo_model.PullRequestsConfig{}
- repoUnit.Config = cfg
-
- if fixed, err := fixPullRequestsConfig16961(bs, cfg); !fixed {
- return false, err
- }
- case unit.TypeIssues:
- cfg := &repo_model.IssuesConfig{}
- repoUnit.Config = cfg
- if fixed, err := fixIssuesConfig16961(bs, cfg); !fixed {
- return false, err
- }
- default:
- panic(fmt.Sprintf("unrecognized repo unit type: %v", repoUnit.Type))
- }
- return true, nil
-}
-
-func fixBrokenRepoUnits16961(ctx context.Context, logger log.Logger, autofix bool) error {
- // RepoUnit describes all units of a repository
- type RepoUnit struct {
- ID int64
- RepoID int64
- Type unit.Type
- Config []byte
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
- }
-
- count := 0
-
- err := db.Iterate(
- ctx,
- builder.Gt{
- "id": 0,
- },
- func(ctx context.Context, unit *RepoUnit) error {
- bs := unit.Config
- repoUnit := &repo_model.RepoUnit{
- ID: unit.ID,
- RepoID: unit.RepoID,
- Type: unit.Type,
- CreatedUnix: unit.CreatedUnix,
- }
-
- if fixed, err := fixBrokenRepoUnit16961(repoUnit, bs); !fixed {
- return err
- }
-
- count++
- if !autofix {
- return nil
- }
-
- return repo_model.UpdateRepoUnit(ctx, repoUnit)
- },
- )
- if err != nil {
- logger.Critical("Unable to iterate across repounits to fix the broken units: Error %v", err)
- return err
- }
-
- if !autofix {
- if count == 0 {
- logger.Info("Found no broken repo_units")
- } else {
- logger.Warn("Found %d broken repo_units", count)
- }
- return nil
- }
- logger.Info("Fixed %d broken repo_units", count)
-
- return nil
-}
-
-func init() {
- Register(&Check{
- Title: "Check for incorrectly dumped repo_units (See #16961)",
- Name: "fix-broken-repo-units",
- IsDefault: false,
- Run: fixBrokenRepoUnits16961,
- Priority: 7,
- })
-}
diff --git a/modules/doctor/fix16961_test.go b/modules/doctor/fix16961_test.go
deleted file mode 100644
index 498ed9c8d5..0000000000
--- a/modules/doctor/fix16961_test.go
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "testing"
-
- repo_model "code.gitea.io/gitea/models/repo"
-
- "github.com/stretchr/testify/assert"
-)
-
-func Test_fixUnitConfig_16961(t *testing.T) {
- tests := []struct {
- name string
- bs string
- wantFixed bool
- wantErr bool
- }{
- {
- name: "empty",
- bs: "",
- wantFixed: true,
- wantErr: false,
- },
- {
- name: "normal: {}",
- bs: "{}",
- wantFixed: false,
- wantErr: false,
- },
- {
- name: "broken but fixable: &{}",
- bs: "&{}",
- wantFixed: true,
- wantErr: false,
- },
- {
- name: "broken but unfixable: &{asdasd}",
- bs: "&{asdasd}",
- wantFixed: false,
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- gotFixed, err := fixUnitConfig16961([]byte(tt.bs), &repo_model.UnitConfig{})
- if (err != nil) != tt.wantErr {
- t.Errorf("fixUnitConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if gotFixed != tt.wantFixed {
- t.Errorf("fixUnitConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
- }
- })
- }
-}
-
-func Test_fixExternalWikiConfig_16961(t *testing.T) {
- tests := []struct {
- name string
- bs string
- expected string
- wantFixed bool
- wantErr bool
- }{
- {
- name: "normal: {\"ExternalWikiURL\":\"http://someurl\"}",
- bs: "{\"ExternalWikiURL\":\"http://someurl\"}",
- expected: "http://someurl",
- wantFixed: false,
- wantErr: false,
- },
- {
- name: "broken: &{http://someurl}",
- bs: "&{http://someurl}",
- expected: "http://someurl",
- wantFixed: true,
- wantErr: false,
- },
- {
- name: "broken but unfixable: http://someurl",
- bs: "http://someurl",
- wantFixed: false,
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cfg := &repo_model.ExternalWikiConfig{}
- gotFixed, err := fixExternalWikiConfig16961([]byte(tt.bs), cfg)
- if (err != nil) != tt.wantErr {
- t.Errorf("fixExternalWikiConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if gotFixed != tt.wantFixed {
- t.Errorf("fixExternalWikiConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
- }
- if cfg.ExternalWikiURL != tt.expected {
- t.Errorf("fixExternalWikiConfig_16961().ExternalWikiURL = %v, want %v", cfg.ExternalWikiURL, tt.expected)
- }
- })
- }
-}
-
-func Test_fixExternalTrackerConfig_16961(t *testing.T) {
- tests := []struct {
- name string
- bs string
- expected repo_model.ExternalTrackerConfig
- wantFixed bool
- wantErr bool
- }{
- {
- name: "normal",
- bs: `{"ExternalTrackerURL":"a","ExternalTrackerFormat":"b","ExternalTrackerStyle":"c"}`,
- expected: repo_model.ExternalTrackerConfig{
- ExternalTrackerURL: "a",
- ExternalTrackerFormat: "b",
- ExternalTrackerStyle: "c",
- },
- wantFixed: false,
- wantErr: false,
- },
- {
- name: "broken",
- bs: "&{a b c}",
- expected: repo_model.ExternalTrackerConfig{
- ExternalTrackerURL: "a",
- ExternalTrackerFormat: "b",
- ExternalTrackerStyle: "c",
- },
- wantFixed: true,
- wantErr: false,
- },
- {
- name: "broken - too many fields",
- bs: "&{a b c d}",
- wantFixed: false,
- wantErr: true,
- },
- {
- name: "broken - wrong format",
- bs: "a b c d}",
- wantFixed: false,
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cfg := &repo_model.ExternalTrackerConfig{}
- gotFixed, err := fixExternalTrackerConfig16961([]byte(tt.bs), cfg)
- if (err != nil) != tt.wantErr {
- t.Errorf("fixExternalTrackerConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if gotFixed != tt.wantFixed {
- t.Errorf("fixExternalTrackerConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
- }
- if cfg.ExternalTrackerFormat != tt.expected.ExternalTrackerFormat {
- t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerFormat = %v, want %v", tt.expected.ExternalTrackerFormat, cfg.ExternalTrackerFormat)
- }
- if cfg.ExternalTrackerStyle != tt.expected.ExternalTrackerStyle {
- t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerStyle = %v, want %v", tt.expected.ExternalTrackerStyle, cfg.ExternalTrackerStyle)
- }
- if cfg.ExternalTrackerURL != tt.expected.ExternalTrackerURL {
- t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerURL = %v, want %v", tt.expected.ExternalTrackerURL, cfg.ExternalTrackerURL)
- }
- })
- }
-}
-
-func Test_fixPullRequestsConfig_16961(t *testing.T) {
- tests := []struct {
- name string
- bs string
- expected repo_model.PullRequestsConfig
- wantFixed bool
- wantErr bool
- }{
- {
- name: "normal",
- bs: `{"IgnoreWhitespaceConflicts":false,"AllowMerge":false,"AllowRebase":false,"AllowRebaseMerge":false,"AllowSquash":false,"AllowManualMerge":false,"AutodetectManualMerge":false,"DefaultDeleteBranchAfterMerge":false,"DefaultMergeStyle":""}`,
- },
- {
- name: "broken - 1.14",
- bs: `&{%!s(bool=false) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=false) %!s(bool=false)}`,
- expected: repo_model.PullRequestsConfig{
- IgnoreWhitespaceConflicts: false,
- AllowMerge: true,
- AllowRebase: true,
- AllowRebaseMerge: true,
- AllowSquash: true,
- AllowManualMerge: false,
- AutodetectManualMerge: false,
- },
- wantFixed: true,
- },
- {
- name: "broken - 1.15",
- bs: `&{%!s(bool=false) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=false) %!s(bool=false) %!s(bool=false) merge}`,
- expected: repo_model.PullRequestsConfig{
- AllowMerge: true,
- AllowRebase: true,
- AllowRebaseMerge: true,
- AllowSquash: true,
- DefaultMergeStyle: repo_model.MergeStyleMerge,
- },
- wantFixed: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cfg := &repo_model.PullRequestsConfig{}
- gotFixed, err := fixPullRequestsConfig16961([]byte(tt.bs), cfg)
- if (err != nil) != tt.wantErr {
- t.Errorf("fixPullRequestsConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if gotFixed != tt.wantFixed {
- t.Errorf("fixPullRequestsConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
- }
- assert.EqualValues(t, &tt.expected, cfg)
- })
- }
-}
-
-func Test_fixIssuesConfig_16961(t *testing.T) {
- tests := []struct {
- name string
- bs string
- expected repo_model.IssuesConfig
- wantFixed bool
- wantErr bool
- }{
- {
- name: "normal",
- bs: `{"EnableTimetracker":true,"AllowOnlyContributorsToTrackTime":true,"EnableDependencies":true}`,
- expected: repo_model.IssuesConfig{
- EnableTimetracker: true,
- AllowOnlyContributorsToTrackTime: true,
- EnableDependencies: true,
- },
- },
- {
- name: "broken",
- bs: `&{%!s(bool=true) %!s(bool=true) %!s(bool=true)}`,
- expected: repo_model.IssuesConfig{
- EnableTimetracker: true,
- AllowOnlyContributorsToTrackTime: true,
- EnableDependencies: true,
- },
- wantFixed: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cfg := &repo_model.IssuesConfig{}
- gotFixed, err := fixIssuesConfig16961([]byte(tt.bs), cfg)
- if (err != nil) != tt.wantErr {
- t.Errorf("fixIssuesConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if gotFixed != tt.wantFixed {
- t.Errorf("fixIssuesConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
- }
- assert.EqualValues(t, &tt.expected, cfg)
- })
- }
-}
diff --git a/modules/doctor/fix8312.go b/modules/doctor/fix8312.go
deleted file mode 100644
index 4fc049873a..0000000000
--- a/modules/doctor/fix8312.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/models/db"
- org_model "code.gitea.io/gitea/models/organization"
- "code.gitea.io/gitea/models/perm"
- "code.gitea.io/gitea/modules/log"
-
- "xorm.io/builder"
-)
-
-func fixOwnerTeamCreateOrgRepo(ctx context.Context, logger log.Logger, autofix bool) error {
- count := 0
-
- err := db.Iterate(
- ctx,
- builder.Eq{"authorize": perm.AccessModeOwner, "can_create_org_repo": false},
- func(ctx context.Context, team *org_model.Team) error {
- team.CanCreateOrgRepo = true
- count++
-
- if !autofix {
- return nil
- }
-
- return models.UpdateTeam(ctx, team, false, false)
- },
- )
- if err != nil {
- logger.Critical("Unable to iterate across repounits to fix incorrect can_create_org_repo: Error %v", err)
- return err
- }
-
- if !autofix {
- if count == 0 {
- logger.Info("Found no team with incorrect can_create_org_repo")
- } else {
- logger.Warn("Found %d teams with incorrect can_create_org_repo", count)
- }
- return nil
- }
- logger.Info("Fixed %d teams with incorrect can_create_org_repo", count)
-
- return nil
-}
-
-func init() {
- Register(&Check{
- Title: "Check for incorrect can_create_org_repo for org owner teams",
- Name: "fix-owner-team-create-org-repo",
- IsDefault: false,
- Run: fixOwnerTeamCreateOrgRepo,
- Priority: 7,
- })
-}
diff --git a/modules/doctor/heads.go b/modules/doctor/heads.go
deleted file mode 100644
index 41fca01d57..0000000000
--- a/modules/doctor/heads.go
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
-
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
-)
-
-func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) error {
- numRepos := 0
- numHeadsBroken := 0
- numDefaultBranchesBroken := 0
- numReposUpdated := 0
- err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
- numRepos++
- _, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
-
- head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
-
- // what we expect: default branch is valid, and HEAD points to it
- if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
- return nil
- }
-
- if headErr != nil {
- numHeadsBroken++
- }
- if defaultBranchErr != nil {
- numDefaultBranchesBroken++
- }
-
- // if default branch is broken, let the user fix that in the UI
- if defaultBranchErr != nil {
- logger.Warn("Default branch for %s/%s doesn't point to a valid commit", repo.OwnerName, repo.Name)
- return nil
- }
-
- // if we're not autofixing, that's all we can do
- if !autofix {
- return nil
- }
-
- // otherwise, let's try fixing HEAD
- err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()})
- if err != nil {
- logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
- return nil
- }
- numReposUpdated++
- return nil
- })
- if err != nil {
- logger.Critical("Error when fixing repo HEADs: %v", err)
- }
-
- if autofix {
- logger.Info("Out of %d repos, HEADs for %d are now fixed and HEADS for %d are still broken", numRepos, numReposUpdated, numDefaultBranchesBroken+numHeadsBroken-numReposUpdated)
- } else {
- if numHeadsBroken == 0 && numDefaultBranchesBroken == 0 {
- logger.Info("All %d repos have their HEADs in the correct state", numRepos)
- } else {
- if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
- logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
- } else if numHeadsBroken != 0 && numDefaultBranchesBroken == 0 {
- logger.Warn("HEADs are broken for %d/%d repos", numHeadsBroken, numRepos)
- } else {
- logger.Critical("Out of %d repos, HEADS are broken for %d and default branches are broken for %d", numRepos, numHeadsBroken, numDefaultBranchesBroken)
- }
- }
- }
-
- return err
-}
-
-func init() {
- Register(&Check{
- Title: "Synchronize repo HEADs",
- Name: "synchronize-repo-heads",
- IsDefault: true,
- Run: synchronizeRepoHeads,
- Priority: 7,
- })
-}
diff --git a/modules/doctor/lfs.go b/modules/doctor/lfs.go
deleted file mode 100644
index 5f110b8f97..0000000000
--- a/modules/doctor/lfs.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
- "fmt"
- "time"
-
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/services/repository"
-)
-
-func init() {
- Register(&Check{
- Title: "Garbage collect LFS",
- Name: "gc-lfs",
- IsDefault: false,
- Run: garbageCollectLFSCheck,
- AbortIfFailed: false,
- SkipDatabaseInitialization: false,
- Priority: 1,
- })
-}
-
-func garbageCollectLFSCheck(ctx context.Context, logger log.Logger, autofix bool) error {
- if !setting.LFS.StartServer {
- return fmt.Errorf("LFS support is disabled")
- }
-
- if err := repository.GarbageCollectLFSMetaObjects(ctx, repository.GarbageCollectLFSMetaObjectsOptions{
- LogDetail: logger.Info,
- AutoFix: autofix,
- // Only attempt to garbage collect lfs meta objects older than a week as the order of git lfs upload
- // and git object upload is not necessarily guaranteed. It's possible to imagine a situation whereby
- // an LFS object is uploaded but the git branch is not uploaded immediately, or there are some rapid
- // changes in new branches that might lead to lfs objects becoming temporarily unassociated with git
- // objects.
- //
- // It is likely that a week is potentially excessive but it should definitely be enough that any
- // unassociated LFS object is genuinely unassociated.
- OlderThan: time.Now().Add(-24 * time.Hour * 7),
- // We don't set the UpdatedLessRecentlyThan because we want to do a full GC
- }); err != nil {
- return err
- }
-
- return checkStorage(&checkStorageOptions{LFS: true})(ctx, logger, autofix)
-}
diff --git a/modules/doctor/mergebase.go b/modules/doctor/mergebase.go
deleted file mode 100644
index de460c4190..0000000000
--- a/modules/doctor/mergebase.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
- "fmt"
- "strings"
-
- "code.gitea.io/gitea/models/db"
- issues_model "code.gitea.io/gitea/models/issues"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
-
- "xorm.io/builder"
-)
-
-func iteratePRs(ctx context.Context, repo *repo_model.Repository, each func(*repo_model.Repository, *issues_model.PullRequest) error) error {
- return db.Iterate(
- ctx,
- builder.Eq{"base_repo_id": repo.ID},
- func(ctx context.Context, bean *issues_model.PullRequest) error {
- return each(repo, bean)
- },
- )
-}
-
-func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) error {
- numRepos := 0
- numPRs := 0
- numPRsUpdated := 0
- err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
- numRepos++
- return iteratePRs(ctx, repo, func(repo *repo_model.Repository, pr *issues_model.PullRequest) error {
- numPRs++
- pr.BaseRepo = repo
- repoPath := repo.RepoPath()
-
- oldMergeBase := pr.MergeBase
-
- if !pr.HasMerged {
- var err error
- pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base").AddDashesAndList(pr.BaseBranch, pr.GetGitRefName()).RunStdString(&git.RunOpts{Dir: repoPath})
- if err != nil {
- var err2 error
- pr.MergeBase, _, err2 = git.NewCommand(ctx, "rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath})
- if err2 != nil {
- logger.Warn("Unable to get merge base for PR ID %d, #%d onto %s in %s/%s. Error: %v & %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err, err2)
- return nil
- }
- }
- } else {
- parentsString, _, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
- if err != nil {
- logger.Warn("Unable to get parents for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
- return nil
- }
- parents := strings.Split(strings.TrimSpace(parentsString), " ")
- if len(parents) < 2 {
- return nil
- }
-
- refs := append([]string{}, parents[1:]...)
- refs = append(refs, pr.GetGitRefName())
- cmd := git.NewCommand(ctx, "merge-base").AddDashesAndList(refs...)
- pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath})
- if err != nil {
- logger.Warn("Unable to get merge base for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
- return nil
- }
- }
- pr.MergeBase = strings.TrimSpace(pr.MergeBase)
- if pr.MergeBase != oldMergeBase {
- if autofix {
- if err := pr.UpdateCols(ctx, "merge_base"); err != nil {
- logger.Critical("Failed to update merge_base. ERROR: %v", err)
- return fmt.Errorf("Failed to update merge_base. ERROR: %w", err)
- }
- } else {
- logger.Info("#%d onto %s in %s/%s: MergeBase should be %s but is %s", pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, oldMergeBase, pr.MergeBase)
- }
- numPRsUpdated++
- }
- return nil
- })
- })
-
- if autofix {
- logger.Info("%d PR mergebases updated of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
- } else {
- if numPRsUpdated == 0 {
- logger.Info("All %d PRs in %d repos have a correct mergebase", numPRs, numRepos)
- } else if err == nil {
- logger.Critical("%d PRs with incorrect mergebases of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
- return fmt.Errorf("%d PRs with incorrect mergebases of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
- } else {
- logger.Warn("%d PRs with incorrect mergebases of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
- }
- }
-
- return err
-}
-
-func init() {
- Register(&Check{
- Title: "Recalculate merge bases",
- Name: "recalculate-merge-bases",
- IsDefault: false,
- Run: checkPRMergeBase,
- Priority: 7,
- })
-}
diff --git a/modules/doctor/misc.go b/modules/doctor/misc.go
deleted file mode 100644
index f0b5966b54..0000000000
--- a/modules/doctor/misc.go
+++ /dev/null
@@ -1,298 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
- "fmt"
- "os"
- "os/exec"
- "path"
- "strings"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/models/db"
- repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/repository"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
-
- lru "github.com/hashicorp/golang-lru/v2"
- "xorm.io/builder"
-)
-
-func iterateRepositories(ctx context.Context, each func(*repo_model.Repository) error) error {
- err := db.Iterate(
- ctx,
- builder.Gt{"id": 0},
- func(ctx context.Context, bean *repo_model.Repository) error {
- return each(bean)
- },
- )
- return err
-}
-
-func checkScriptType(ctx context.Context, logger log.Logger, autofix bool) error {
- path, err := exec.LookPath(setting.ScriptType)
- if err != nil {
- logger.Critical("ScriptType \"%q\" is not on the current PATH. Error: %v", setting.ScriptType, err)
- return fmt.Errorf("ScriptType \"%q\" is not on the current PATH. Error: %w", setting.ScriptType, err)
- }
- logger.Info("ScriptType %s is on the current PATH at %s", setting.ScriptType, path)
- return nil
-}
-
-func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error {
- if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
- results, err := repository.CheckDelegateHooks(repo.RepoPath())
- if err != nil {
- logger.Critical("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err)
- return fmt.Errorf("Unable to check delegate hooks for repo %-v. ERROR: %w", repo, err)
- }
- if len(results) > 0 && autofix {
- logger.Warn("Regenerated hooks for %s", repo.FullName())
- if err := repository.CreateDelegateHooks(repo.RepoPath()); err != nil {
- logger.Critical("Unable to recreate delegate hooks for %-v. ERROR: %v", repo, err)
- return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", repo, err)
- }
- }
- for _, result := range results {
- logger.Warn(result)
- }
- return nil
- }); err != nil {
- logger.Critical("Errors noted whilst checking delegate hooks.")
- return err
- }
- return nil
-}
-
-func checkUserStarNum(ctx context.Context, logger log.Logger, autofix bool) error {
- if autofix {
- if err := models.DoctorUserStarNum(ctx); err != nil {
- logger.Critical("Unable update User Stars numbers")
- return err
- }
- logger.Info("Updated User Stars numbers.")
- } else {
- logger.Info("No check available for User Stars numbers (skipped)")
- }
- return nil
-}
-
-func checkEnablePushOptions(ctx context.Context, logger log.Logger, autofix bool) error {
- numRepos := 0
- numNeedUpdate := 0
-
- if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
- numRepos++
- r, err := git.OpenRepository(ctx, repo.RepoPath())
- if err != nil {
- return err
- }
- defer r.Close()
-
- if autofix {
- _, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions", "true").RunStdString(&git.RunOpts{Dir: r.Path})
- return err
- }
-
- value, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions").RunStdString(&git.RunOpts{Dir: r.Path})
- if err != nil {
- return err
- }
-
- result, valid := git.ParseBool(strings.TrimSpace(value))
- if !result || !valid {
- numNeedUpdate++
- logger.Info("%s: does not have receive.advertisePushOptions set correctly: %q", repo.FullName(), value)
- }
- return nil
- }); err != nil {
- logger.Critical("Unable to EnablePushOptions: %v", err)
- return err
- }
-
- if autofix {
- logger.Info("Enabled push options for %d repositories.", numRepos)
- } else {
- logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate)
- }
-
- return nil
-}
-
-func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) error {
- numRepos := 0
- numNeedUpdate := 0
- cache, err := lru.New[int64, any](512)
- if err != nil {
- logger.Critical("Unable to create cache: %v", err)
- return err
- }
- if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
- numRepos++
-
- if owner, has := cache.Get(repo.OwnerID); has {
- repo.Owner = owner.(*user_model.User)
- } else {
- if err := repo.LoadOwner(ctx); err != nil {
- return err
- }
- cache.Add(repo.OwnerID, repo.Owner)
- }
-
- // Create/Remove git-daemon-export-ok for git-daemon...
- daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`)
- isExist, err := util.IsExist(daemonExportFile)
- if err != nil {
- log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
- return err
- }
- isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic
-
- if isPublic != isExist {
- numNeedUpdate++
- if autofix {
- if !isPublic && isExist {
- if err = util.Remove(daemonExportFile); err != nil {
- log.Error("Failed to remove %s: %v", daemonExportFile, err)
- }
- } else if isPublic && !isExist {
- if f, err := os.Create(daemonExportFile); err != nil {
- log.Error("Failed to create %s: %v", daemonExportFile, err)
- } else {
- f.Close()
- }
- }
- }
- }
- return nil
- }); err != nil {
- logger.Critical("Unable to checkDaemonExport: %v", err)
- return err
- }
-
- if autofix {
- logger.Info("Updated git-daemon-export-ok files for %d of %d repositories.", numNeedUpdate, numRepos)
- } else {
- logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate)
- }
-
- return nil
-}
-
-func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) error {
- numRepos := 0
- numNeedUpdate := 0
- numWritten := 0
- if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
- numRepos++
-
- commitGraphExists := func() (bool, error) {
- // Check commit-graph exists
- commitGraphFile := path.Join(repo.RepoPath(), `objects/info/commit-graph`)
- isExist, err := util.IsExist(commitGraphFile)
- if err != nil {
- logger.Error("Unable to check if %s exists. Error: %v", commitGraphFile, err)
- return false, err
- }
-
- if !isExist {
- commitGraphsDir := path.Join(repo.RepoPath(), `objects/info/commit-graphs`)
- isExist, err = util.IsExist(commitGraphsDir)
- if err != nil {
- logger.Error("Unable to check if %s exists. Error: %v", commitGraphsDir, err)
- return false, err
- }
- }
- return isExist, nil
- }
-
- isExist, err := commitGraphExists()
- if err != nil {
- return err
- }
- if !isExist {
- numNeedUpdate++
- if autofix {
- if err := git.WriteCommitGraph(ctx, repo.RepoPath()); err != nil {
- logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err)
- return err
- }
- isExist, err := commitGraphExists()
- if err != nil {
- return err
- }
- if isExist {
- numWritten++
- logger.Info("Commit-graph written: %s", repo.FullName())
- } else {
- logger.Warn("No commit-graph written: %s", repo.FullName())
- }
- }
- }
- return nil
- }); err != nil {
- logger.Critical("Unable to checkCommitGraph: %v", err)
- return err
- }
-
- if autofix {
- logger.Info("Wrote commit-graph files for %d of %d repositories.", numWritten, numRepos)
- } else {
- logger.Info("Checked %d repositories, %d without commit-graphs.", numRepos, numNeedUpdate)
- }
-
- return nil
-}
-
-func init() {
- Register(&Check{
- Title: "Check if SCRIPT_TYPE is available",
- Name: "script-type",
- IsDefault: false,
- Run: checkScriptType,
- Priority: 5,
- })
- Register(&Check{
- Title: "Check if hook files are up-to-date and executable",
- Name: "hooks",
- IsDefault: false,
- Run: checkHooks,
- Priority: 6,
- })
- Register(&Check{
- Title: "Recalculate Stars number for all user",
- Name: "recalculate-stars-number",
- IsDefault: false,
- Run: checkUserStarNum,
- Priority: 6,
- })
- Register(&Check{
- Title: "Check that all git repositories have receive.advertisePushOptions set to true",
- Name: "enable-push-options",
- IsDefault: false,
- Run: checkEnablePushOptions,
- Priority: 7,
- })
- Register(&Check{
- Title: "Check git-daemon-export-ok files",
- Name: "check-git-daemon-export-ok",
- IsDefault: false,
- Run: checkDaemonExport,
- Priority: 8,
- })
- Register(&Check{
- Title: "Check commit-graphs",
- Name: "check-commit-graphs",
- IsDefault: false,
- Run: checkCommitGraph,
- Priority: 9,
- })
-}
diff --git a/modules/doctor/paths.go b/modules/doctor/paths.go
deleted file mode 100644
index 3f62d587ab..0000000000
--- a/modules/doctor/paths.go
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
- "fmt"
- "os"
-
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-)
-
-type configurationFile struct {
- Name string
- Path string
- IsDirectory bool
- Required bool
- Writable bool
-}
-
-func checkConfigurationFile(logger log.Logger, autofix bool, fileOpts configurationFile) error {
- logger.Info(`%-26s %q`, log.NewColoredValue(fileOpts.Name+":", log.Reset), fileOpts.Path)
- fi, err := os.Stat(fileOpts.Path)
- if err != nil {
- if os.IsNotExist(err) && autofix && fileOpts.IsDirectory {
- if err := os.MkdirAll(fileOpts.Path, 0o777); err != nil {
- logger.Error(" Directory does not exist and could not be created. ERROR: %v", err)
- return fmt.Errorf("Configuration directory: \"%q\" does not exist and could not be created. ERROR: %w", fileOpts.Path, err)
- }
- fi, err = os.Stat(fileOpts.Path)
- }
- }
- if err != nil {
- if fileOpts.Required {
- logger.Error(" Is REQUIRED but is not accessible. ERROR: %v", err)
- return fmt.Errorf("Configuration file \"%q\" is not accessible but is required. Error: %w", fileOpts.Path, err)
- }
- logger.Warn(" NOTICE: is not accessible (Error: %v)", err)
- // this is a non-critical error
- return nil
- }
-
- if fileOpts.IsDirectory && !fi.IsDir() {
- logger.Error(" ERROR: not a directory")
- return fmt.Errorf("Configuration directory \"%q\" is not a directory. Error: %w", fileOpts.Path, err)
- } else if !fileOpts.IsDirectory && !fi.Mode().IsRegular() {
- logger.Error(" ERROR: not a regular file")
- return fmt.Errorf("Configuration file \"%q\" is not a regular file. Error: %w", fileOpts.Path, err)
- } else if fileOpts.Writable {
- if err := isWritableDir(fileOpts.Path); err != nil {
- logger.Error(" ERROR: is required to be writable but is not writable: %v", err)
- return fmt.Errorf("Configuration file \"%q\" is required to be writable but is not. Error: %w", fileOpts.Path, err)
- }
- }
- return nil
-}
-
-func checkConfigurationFiles(ctx context.Context, logger log.Logger, autofix bool) error {
- if fi, err := os.Stat(setting.CustomConf); err != nil || !fi.Mode().IsRegular() {
- logger.Error("Failed to find configuration file at '%s'.", setting.CustomConf)
- logger.Error("If you've never ran Gitea yet, this is normal and '%s' will be created for you on first run.", setting.CustomConf)
- logger.Error("Otherwise check that you are running this command from the correct path and/or provide a `--config` parameter.")
- logger.Critical("Cannot proceed without a configuration file")
- return err
- }
-
- setting.MustInstalled()
-
- configurationFiles := []configurationFile{
- {"Configuration File Path", setting.CustomConf, false, true, false},
- {"Repository Root Path", setting.RepoRootPath, true, true, true},
- {"Data Root Path", setting.AppDataPath, true, true, true},
- {"Custom File Root Path", setting.CustomPath, true, false, false},
- {"Work directory", setting.AppWorkPath, true, true, false},
- {"Log Root Path", setting.Log.RootPath, true, true, true},
- }
-
- if !setting.HasBuiltinBindata {
- configurationFiles = append(configurationFiles, configurationFile{"Static File Root Path", setting.StaticRootPath, true, true, false})
- }
-
- numberOfErrors := 0
- for _, configurationFile := range configurationFiles {
- if err := checkConfigurationFile(logger, autofix, configurationFile); err != nil {
- numberOfErrors++
- }
- }
-
- if numberOfErrors > 0 {
- logger.Critical("Please check your configuration files and try again.")
- return fmt.Errorf("%d configuration files with errors", numberOfErrors)
- }
-
- return nil
-}
-
-func isWritableDir(path string) error {
- // There's no platform-independent way of checking if a directory is writable
- // https://stackoverflow.com/questions/20026320/how-to-tell-if-folder-exists-and-is-writable
-
- tmpFile, err := os.CreateTemp(path, "doctors-order")
- if err != nil {
- return err
- }
- if err := os.Remove(tmpFile.Name()); err != nil {
- fmt.Printf("Warning: can't remove temporary file: '%s'\n", tmpFile.Name()) //nolint:forbidigo
- }
- tmpFile.Close()
- return nil
-}
-
-func init() {
- Register(&Check{
- Title: "Check paths and basic configuration",
- Name: "paths",
- IsDefault: true,
- Run: checkConfigurationFiles,
- AbortIfFailed: true,
- SkipDatabaseInitialization: true,
- Priority: 1,
- })
-}
diff --git a/modules/doctor/repository.go b/modules/doctor/repository.go
deleted file mode 100644
index 6c33426636..0000000000
--- a/modules/doctor/repository.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
-
- "code.gitea.io/gitea/models/db"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/storage"
- repo_service "code.gitea.io/gitea/services/repository"
-
- "xorm.io/builder"
-)
-
-func handleDeleteOrphanedRepos(ctx context.Context, logger log.Logger, autofix bool) error {
- test := &consistencyCheck{
- Name: "Repos with no existing owner",
- Counter: countOrphanedRepos,
- Fixer: deleteOrphanedRepos,
- FixedMessage: "Deleted all content related to orphaned repos",
- }
- return test.Run(ctx, logger, autofix)
-}
-
-// countOrphanedRepos count repository where user of owner_id do not exist
-func countOrphanedRepos(ctx context.Context) (int64, error) {
- return db.CountOrphanedObjects(ctx, "repository", "user", "repository.owner_id=`user`.id")
-}
-
-// deleteOrphanedRepos delete repository where user of owner_id do not exist
-func deleteOrphanedRepos(ctx context.Context) (int64, error) {
- if err := storage.Init(); err != nil {
- return 0, err
- }
-
- batchSize := db.MaxBatchInsertSize("repository")
- e := db.GetEngine(ctx)
- var deleted int64
- adminUser := &user_model.User{IsAdmin: true}
-
- for {
- select {
- case <-ctx.Done():
- return deleted, ctx.Err()
- default:
- var ids []int64
- if err := e.Table("`repository`").
- Join("LEFT", "`user`", "repository.owner_id=`user`.id").
- Where(builder.IsNull{"`user`.id"}).
- Select("`repository`.id").Limit(batchSize).Find(&ids); err != nil {
- return deleted, err
- }
-
- // if we don't get ids we have deleted them all
- if len(ids) == 0 {
- return deleted, nil
- }
-
- for _, id := range ids {
- if err := repo_service.DeleteRepositoryDirectly(ctx, adminUser, id, true); err != nil {
- return deleted, err
- }
- deleted++
- }
- }
- }
-}
-
-func init() {
- Register(&Check{
- Title: "Deleted all content related to orphaned repos",
- Name: "delete-orphaned-repos",
- IsDefault: false,
- Run: handleDeleteOrphanedRepos,
- Priority: 4,
- })
-}
diff --git a/modules/doctor/storage.go b/modules/doctor/storage.go
deleted file mode 100644
index f338537864..0000000000
--- a/modules/doctor/storage.go
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
- "errors"
- "io/fs"
- "strings"
-
- "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/log"
- packages_module "code.gitea.io/gitea/modules/packages"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/storage"
- "code.gitea.io/gitea/modules/util"
-)
-
-type commonStorageCheckOptions struct {
- storer storage.ObjectStorage
- isOrphaned func(path string, obj storage.Object, stat fs.FileInfo) (bool, error)
- name string
-}
-
-func commonCheckStorage(ctx context.Context, logger log.Logger, autofix bool, opts *commonStorageCheckOptions) error {
- totalCount, orphanedCount := 0, 0
- totalSize, orphanedSize := int64(0), int64(0)
-
- var pathsToDelete []string
- if err := opts.storer.IterateObjects("", func(p string, obj storage.Object) error {
- defer obj.Close()
-
- totalCount++
- stat, err := obj.Stat()
- if err != nil {
- return err
- }
- totalSize += stat.Size()
-
- orphaned, err := opts.isOrphaned(p, obj, stat)
- if err != nil {
- return err
- }
- if orphaned {
- orphanedCount++
- orphanedSize += stat.Size()
- if autofix {
- pathsToDelete = append(pathsToDelete, p)
- }
- }
- return nil
- }); err != nil {
- logger.Error("Error whilst iterating %s storage: %v", opts.name, err)
- return err
- }
-
- if orphanedCount > 0 {
- if autofix {
- var deletedNum int
- for _, p := range pathsToDelete {
- if err := opts.storer.Delete(p); err != nil {
- log.Error("Error whilst deleting %s from %s storage: %v", p, opts.name, err)
- } else {
- deletedNum++
- }
- }
- logger.Info("Deleted %d/%d orphaned %s(s)", deletedNum, orphanedCount, opts.name)
- } else {
- logger.Warn("Found %d/%d (%s/%s) orphaned %s(s)", orphanedCount, totalCount, base.FileSize(orphanedSize), base.FileSize(totalSize), opts.name)
- }
- } else {
- logger.Info("Found %d (%s) %s(s)", totalCount, base.FileSize(totalSize), opts.name)
- }
- return nil
-}
-
-type checkStorageOptions struct {
- All bool
- Attachments bool
- LFS bool
- Avatars bool
- RepoAvatars bool
- RepoArchives bool
- Packages bool
-}
-
-// checkStorage will return a doctor check function to check the requested storage types for "orphaned" stored object/files and optionally delete them
-func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger log.Logger, autofix bool) error {
- return func(ctx context.Context, logger log.Logger, autofix bool) error {
- if err := storage.Init(); err != nil {
- logger.Error("storage.Init failed: %v", err)
- return err
- }
-
- if opts.Attachments || opts.All {
- if err := commonCheckStorage(ctx, logger, autofix,
- &commonStorageCheckOptions{
- storer: storage.Attachments,
- isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
- exists, err := repo.ExistAttachmentsByUUID(ctx, stat.Name())
- return !exists, err
- },
- name: "attachment",
- }); err != nil {
- return err
- }
- }
-
- if opts.LFS || opts.All {
- if !setting.LFS.StartServer {
- logger.Info("LFS isn't enabled (skipped)")
- return nil
- }
- if err := commonCheckStorage(ctx, logger, autofix,
- &commonStorageCheckOptions{
- storer: storage.LFS,
- isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
- // The oid of an LFS stored object is the name but with all the path.Separators removed
- oid := strings.ReplaceAll(path, "/", "")
- exists, err := git.ExistsLFSObject(ctx, oid)
- return !exists, err
- },
- name: "LFS file",
- }); err != nil {
- return err
- }
- }
-
- if opts.Avatars || opts.All {
- if err := commonCheckStorage(ctx, logger, autofix,
- &commonStorageCheckOptions{
- storer: storage.Avatars,
- isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
- exists, err := user.ExistsWithAvatarAtStoragePath(ctx, path)
- return !exists, err
- },
- name: "avatar",
- }); err != nil {
- return err
- }
- }
-
- if opts.RepoAvatars || opts.All {
- if err := commonCheckStorage(ctx, logger, autofix,
- &commonStorageCheckOptions{
- storer: storage.RepoAvatars,
- isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
- exists, err := repo.ExistsWithAvatarAtStoragePath(ctx, path)
- return !exists, err
- },
- name: "repo avatar",
- }); err != nil {
- return err
- }
- }
-
- if opts.RepoArchives || opts.All {
- if err := commonCheckStorage(ctx, logger, autofix,
- &commonStorageCheckOptions{
- storer: storage.RepoAvatars,
- isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
- exists, err := repo.ExistsRepoArchiverWithStoragePath(ctx, path)
- if err == nil || errors.Is(err, util.ErrInvalidArgument) {
- // invalid arguments mean that the object is not a valid repo archiver and it should be removed
- return !exists, nil
- }
- return !exists, err
- },
- name: "repo archive",
- }); err != nil {
- return err
- }
- }
-
- if opts.Packages || opts.All {
- if !setting.Packages.Enabled {
- logger.Info("Packages isn't enabled (skipped)")
- return nil
- }
- if err := commonCheckStorage(ctx, logger, autofix,
- &commonStorageCheckOptions{
- storer: storage.Packages,
- isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
- key, err := packages_module.RelativePathToKey(path)
- if err != nil {
- // If there is an error here then the relative path does not match a valid package
- // Therefore it is orphaned by default
- return true, nil
- }
-
- exists, err := packages.ExistPackageBlobWithSHA(ctx, string(key))
-
- return !exists, err
- },
- name: "package blob",
- }); err != nil {
- return err
- }
- }
-
- return nil
- }
-}
-
-func init() {
- Register(&Check{
- Title: "Check if there are orphaned storage files",
- Name: "storages",
- IsDefault: false,
- Run: checkStorage(&checkStorageOptions{All: true}),
- AbortIfFailed: false,
- SkipDatabaseInitialization: false,
- Priority: 1,
- })
-
- Register(&Check{
- Title: "Check if there are orphaned attachments in storage",
- Name: "storage-attachments",
- IsDefault: false,
- Run: checkStorage(&checkStorageOptions{Attachments: true}),
- AbortIfFailed: false,
- SkipDatabaseInitialization: false,
- Priority: 1,
- })
-
- Register(&Check{
- Title: "Check if there are orphaned lfs files in storage",
- Name: "storage-lfs",
- IsDefault: false,
- Run: checkStorage(&checkStorageOptions{LFS: true}),
- AbortIfFailed: false,
- SkipDatabaseInitialization: false,
- Priority: 1,
- })
-
- Register(&Check{
- Title: "Check if there are orphaned avatars in storage",
- Name: "storage-avatars",
- IsDefault: false,
- Run: checkStorage(&checkStorageOptions{Avatars: true, RepoAvatars: true}),
- AbortIfFailed: false,
- SkipDatabaseInitialization: false,
- Priority: 1,
- })
-
- Register(&Check{
- Title: "Check if there are orphaned archives in storage",
- Name: "storage-archives",
- IsDefault: false,
- Run: checkStorage(&checkStorageOptions{RepoArchives: true}),
- AbortIfFailed: false,
- SkipDatabaseInitialization: false,
- Priority: 1,
- })
-
- Register(&Check{
- Title: "Check if there are orphaned package blobs in storage",
- Name: "storage-packages",
- IsDefault: false,
- Run: checkStorage(&checkStorageOptions{Packages: true}),
- AbortIfFailed: false,
- SkipDatabaseInitialization: false,
- Priority: 1,
- })
-}
diff --git a/modules/doctor/usertype.go b/modules/doctor/usertype.go
deleted file mode 100644
index ab32b78e62..0000000000
--- a/modules/doctor/usertype.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package doctor
-
-import (
- "context"
-
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/log"
-)
-
-func checkUserType(ctx context.Context, logger log.Logger, autofix bool) error {
- count, err := user_model.CountWrongUserType(ctx)
- if err != nil {
- logger.Critical("Error: %v whilst counting wrong user types")
- return err
- }
- if count > 0 {
- if autofix {
- if count, err = user_model.FixWrongUserType(ctx); err != nil {
- logger.Critical("Error: %v whilst fixing wrong user types")
- return err
- }
- logger.Info("%d users with wrong type fixed", count)
- } else {
- logger.Warn("%d users with wrong type exist", count)
- }
- }
- return nil
-}
-
-func init() {
- Register(&Check{
- Title: "Check if user with wrong type exist",
- Name: "check-user-type",
- IsDefault: true,
- Run: checkUserType,
- Priority: 3,
- })
-}