diff --git a/models/actions/run_test.go b/models/actions/run_test.go index e1c884518f..e82cbe84b5 100644 --- a/models/actions/run_test.go +++ b/models/actions/run_test.go @@ -32,7 +32,7 @@ func TestUpdateRepoRunsNumbers(t *testing.T) { err = UpdateRepoRunsNumbers(t.Context(), repo) assert.NoError(t, err) repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - assert.Equal(t, 5, repo.NumActionRuns) + assert.Equal(t, 4, repo.NumActionRuns) assert.Equal(t, 3, repo.NumClosedActionRuns) } diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml index 63fd011e65..b7d1201189 100644 --- a/models/fixtures/action_run.yml +++ b/models/fixtures/action_run.yml @@ -140,25 +140,4 @@ need_approval: 0 approved_by: 0 -- - id: 805 - title: "update actions" - repo_id: 4 - owner_id: 1 - workflow_id: "artifact.yaml" - index: 191 - trigger_user_id: 1 - ref: "refs/heads/master" - commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0" - event: "push" - trigger_event: "push" - is_fork_pull_request: 0 - status: 5 - started: 1683636528 - stopped: 1683636626 - created: 1683636108 - updated: 1683636626 - need_approval: 0 - approved_by: 0 - # DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml index 82023f122a..2a4e64285f 100644 --- a/models/fixtures/action_run_job.yml +++ b/models/fixtures/action_run_job.yml @@ -130,19 +130,4 @@ started: 1683636528 stopped: 1683636626 -- - id: 206 - run_id: 805 - repo_id: 4 - owner_id: 1 - commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 - is_fork_pull_request: 0 - name: job_2 - attempt: 1 - job_id: job_2 - task_id: 56 - status: 3 - started: 1683636528 - stopped: 1683636626 - # DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml index 52a37da730..13efe378c4 100644 --- a/models/fixtures/action_task.yml +++ b/models/fixtures/action_task.yml @@ -178,24 +178,4 @@ log_size: 0 log_expired: 0 -- - id: 56 - attempt: 1 - runner_id: 1 - status: 3 # 3 is the status code for "cancelled" - started: 1683636528 - stopped: 1683636626 - repo_id: 4 - owner_id: 1 - commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 - is_fork_pull_request: 0 - token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4240c64a69a2cc1508825121b7b8394e48e00b1bf3718b2aaaab - token_salt: eeeeeeee - token_last_eight: eeeeeeee - log_filename: artifact-test2/2f/47.log - log_in_storage: 1 - log_length: 707 - log_size: 90179 - log_expired: 0 - # DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/user/user.go b/models/user/user.go index 41cf89ad30..ade747401a 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1462,16 +1462,6 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { return false } -// CountWrongUserType count OrgUser who have wrong type -func CountWrongUserType(ctx context.Context) (int64, error) { - return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User)) -} - -// FixWrongUserType fix OrgUser who have wrong type -func FixWrongUserType(ctx context.Context) (int64, error) { - return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1}) -} - func GetOrderByName() string { if setting.UI.DefaultShowFullName { return "full_name, name" diff --git a/services/doctor/actions.go b/services/doctor/actions.go index cd3d19b724..28e26c88eb 100644 --- a/services/doctor/actions.go +++ b/services/doctor/actions.go @@ -7,17 +7,12 @@ import ( "context" "fmt" - actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" repo_service "code.gitea.io/gitea/services/repository" - - "xorm.io/builder" ) func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bool) error { @@ -64,95 +59,6 @@ func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bo return nil } -func fixUnfinishedRunStatus(ctx context.Context, logger log.Logger, autofix bool) error { - total := 0 - inconsistent := 0 - fixed := 0 - - cond := builder.In("status", []actions_model.Status{ - actions_model.StatusWaiting, - actions_model.StatusRunning, - actions_model.StatusBlocked, - }).And(builder.Lt{"updated": timeutil.TimeStampNow().AddDuration(-setting.Actions.ZombieTaskTimeout)}) - - err := db.Iterate( - ctx, - cond, - func(ctx context.Context, run *actions_model.ActionRun) error { - total++ - - jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) - if err != nil { - return fmt.Errorf("GetRunJobsByRunID: %w", err) - } - expected := actions_model.AggregateJobStatus(jobs) - if expected == run.Status { - return nil - } - - inconsistent++ - logger.Warn("Run %d (repo_id=%d, index=%d) has status %s, expected %s", run.ID, run.RepoID, run.Index, run.Status, expected) - - if !autofix { - return nil - } - - run.Started, run.Stopped = getRunTimestampsFromJobs(run, expected, jobs) - run.Status = expected - - if err := actions_model.UpdateRun(ctx, run, "status", "started", "stopped"); err != nil { - return fmt.Errorf("UpdateRun: %w", err) - } - fixed++ - - return nil - }, - ) - if err != nil { - logger.Critical("Unable to iterate unfinished runs: %v", err) - return err - } - - if inconsistent == 0 { - logger.Info("Checked %d unfinished runs; all statuses are consistent.", total) - return nil - } - - if autofix { - logger.Info("Checked %d unfinished runs; fixed %d of %d runs.", total, fixed, inconsistent) - } else { - logger.Warn("Checked %d unfinished runs; found %d runs need to be fixed", total, inconsistent) - } - - return nil -} - -func getRunTimestampsFromJobs(run *actions_model.ActionRun, newStatus actions_model.Status, jobs actions_model.ActionJobList) (started, stopped timeutil.TimeStamp) { - started = run.Started - if (newStatus.IsRunning() || newStatus.IsDone()) && started.IsZero() { - var earliest timeutil.TimeStamp - for _, job := range jobs { - if job.Started > 0 && (earliest.IsZero() || job.Started < earliest) { - earliest = job.Started - } - } - started = earliest - } - - stopped = run.Stopped - if newStatus.IsDone() && stopped.IsZero() { - var latest timeutil.TimeStamp - for _, job := range jobs { - if job.Stopped > latest { - latest = job.Stopped - } - } - stopped = latest - } - - return started, stopped -} - func init() { Register(&Check{ Title: "Disable the actions unit for all mirrors", @@ -161,11 +67,4 @@ func init() { Run: disableMirrorActionsUnit, Priority: 9, }) - Register(&Check{ - Title: "Fix inconsistent status for unfinished actions runs", - Name: "fix-actions-unfinished-run-status", - IsDefault: false, - Run: fixUnfinishedRunStatus, - Priority: 9, - }) } diff --git a/services/doctor/actions_test.go b/services/doctor/actions_test.go deleted file mode 100644 index b2fd3d0d55..0000000000 --- a/services/doctor/actions_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package doctor - -import ( - "testing" - - actions_model "code.gitea.io/gitea/models/actions" - "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/log" - - "github.com/stretchr/testify/assert" -) - -func Test_fixUnfinishedRunStatus(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - fixUnfinishedRunStatus(t.Context(), log.GetLogger(log.DEFAULT), true) - - // check if the run is cancelled by id - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 805}) - assert.Equal(t, actions_model.StatusCancelled, run.Status) -} diff --git a/services/doctor/breaking.go b/services/doctor/breaking.go deleted file mode 100644 index 77e3d4e8ef..0000000000 --- a/services/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/services/doctor/dbversion.go b/services/doctor/dbversion.go deleted file mode 100644 index 34279a45e7..0000000000 --- a/services/doctor/dbversion.go +++ /dev/null @@ -1,43 +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" - "code.gitea.io/gitea/services/versioned_migration" -) - -func checkDBVersion(ctx context.Context, logger log.Logger, autofix bool) error { - logger.Info("Expected database version: %d", migrations.ExpectedDBVersion()) - 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, versioned_migration.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/services/doctor/fix16961.go b/services/doctor/fix16961.go deleted file mode 100644 index 63e33350ad..0000000000 --- a/services/doctor/fix16961.go +++ /dev/null @@ -1,328 +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 - } - - var cfg any - err = json.UnmarshalHandleDoubleEncode(bs, &cfg) - if err == nil { - 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.UpdateRepoUnitConfig(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/services/doctor/fix16961_test.go b/services/doctor/fix16961_test.go deleted file mode 100644 index 11a128620c..0000000000 --- a/services/doctor/fix16961_test.go +++ /dev/null @@ -1,265 +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: "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.Equal(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.Equal(t, &tt.expected, cfg) - }) - } -} diff --git a/services/doctor/mergebase.go b/services/doctor/mergebase.go deleted file mode 100644 index a76ed8afb7..0000000000 --- a/services/doctor/mergebase.go +++ /dev/null @@ -1,115 +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/git/gitcmd" - "code.gitea.io/gitea/modules/gitrepo" - "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 - - oldMergeBase := pr.MergeBase - - if !pr.HasMerged { - var err error - pr.MergeBase, _, err = gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, pr.GetGitHeadRefName())) - if err != nil { - var err2 error - pr.MergeBase, _, err2 = gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch)) - 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 := gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID)) - 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.GetGitHeadRefName()) - cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = gitrepo.RunCmdString(ctx, repo, cmd) - 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/services/doctor/misc.go b/services/doctor/misc.go index 8765cfa025..71f3efa4b1 100644 --- a/services/doctor/misc.go +++ b/services/doctor/misc.go @@ -6,17 +6,13 @@ package doctor import ( "context" "fmt" - "os/exec" - "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/gitrepo" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" lru "github.com/hashicorp/golang-lru/v2" @@ -34,16 +30,6 @@ func iterateRepositories(ctx context.Context, each func(*repo_model.Repository) 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 := gitrepo.CheckDelegateHooks(ctx, repo) @@ -82,42 +68,6 @@ func checkUserStarNum(ctx context.Context, logger log.Logger, autofix bool) erro 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++ - - if autofix { - return gitrepo.GitConfigSet(ctx, repo, "receive.advertisePushOptions", "true") - } - - value, err := gitrepo.GitConfigGet(ctx, repo, "receive.advertisePushOptions") - 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 @@ -244,13 +194,6 @@ func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) erro } 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", @@ -265,13 +208,6 @@ func init() { 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", diff --git a/services/doctor/paths.go b/services/doctor/paths.go deleted file mode 100644 index 4214c36b1a..0000000000 --- a/services/doctor/paths.go +++ /dev/null @@ -1,123 +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 { - log.Warn("can't remove temporary file: %q", tmpFile.Name()) - } - _ = 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/services/doctor/usertype.go b/services/doctor/usertype.go deleted file mode 100644 index ab32b78e62..0000000000 --- a/services/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, - }) -}