mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-06 14:58:32 +02:00
Remove unneeded doctor sub-commands (#37156)
Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
parent
980a8995bc
commit
c10a5b908a
@ -32,7 +32,7 @@ func TestUpdateRepoRunsNumbers(t *testing.T) {
|
|||||||
err = UpdateRepoRunsNumbers(t.Context(), repo)
|
err = UpdateRepoRunsNumbers(t.Context(), repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
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)
|
assert.Equal(t, 3, repo.NumClosedActionRuns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -140,25 +140,4 @@
|
|||||||
need_approval: 0
|
need_approval: 0
|
||||||
approved_by: 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
|
# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly
|
||||||
|
|||||||
@ -130,19 +130,4 @@
|
|||||||
started: 1683636528
|
started: 1683636528
|
||||||
stopped: 1683636626
|
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
|
# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly
|
||||||
|
|||||||
@ -178,24 +178,4 @@
|
|||||||
log_size: 0
|
log_size: 0
|
||||||
log_expired: 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
|
# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly
|
||||||
|
|||||||
@ -1462,16 +1462,6 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
|
|||||||
return false
|
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 {
|
func GetOrderByName() string {
|
||||||
if setting.UI.DefaultShowFullName {
|
if setting.UI.DefaultShowFullName {
|
||||||
return "full_name, name"
|
return "full_name, name"
|
||||||
|
|||||||
@ -7,17 +7,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"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"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
|
||||||
"xorm.io/builder"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bool) error {
|
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
|
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() {
|
func init() {
|
||||||
Register(&Check{
|
Register(&Check{
|
||||||
Title: "Disable the actions unit for all mirrors",
|
Title: "Disable the actions unit for all mirrors",
|
||||||
@ -161,11 +67,4 @@ func init() {
|
|||||||
Run: disableMirrorActionsUnit,
|
Run: disableMirrorActionsUnit,
|
||||||
Priority: 9,
|
Priority: 9,
|
||||||
})
|
})
|
||||||
Register(&Check{
|
|
||||||
Title: "Fix inconsistent status for unfinished actions runs",
|
|
||||||
Name: "fix-actions-unfinished-run-status",
|
|
||||||
IsDefault: false,
|
|
||||||
Run: fixUnfinishedRunStatus,
|
|
||||||
Priority: 9,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -6,17 +6,13 @@ package doctor
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
lru "github.com/hashicorp/golang-lru/v2"
|
lru "github.com/hashicorp/golang-lru/v2"
|
||||||
@ -34,16 +30,6 @@ func iterateRepositories(ctx context.Context, each func(*repo_model.Repository)
|
|||||||
return err
|
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 {
|
func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||||
if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
||||||
results, err := gitrepo.CheckDelegateHooks(ctx, repo)
|
results, err := gitrepo.CheckDelegateHooks(ctx, repo)
|
||||||
@ -82,42 +68,6 @@ func checkUserStarNum(ctx context.Context, logger log.Logger, autofix bool) erro
|
|||||||
return nil
|
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 {
|
func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||||
numRepos := 0
|
numRepos := 0
|
||||||
numNeedUpdate := 0
|
numNeedUpdate := 0
|
||||||
@ -244,13 +194,6 @@ func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Register(&Check{
|
|
||||||
Title: "Check if SCRIPT_TYPE is available",
|
|
||||||
Name: "script-type",
|
|
||||||
IsDefault: false,
|
|
||||||
Run: checkScriptType,
|
|
||||||
Priority: 5,
|
|
||||||
})
|
|
||||||
Register(&Check{
|
Register(&Check{
|
||||||
Title: "Check if hook files are up-to-date and executable",
|
Title: "Check if hook files are up-to-date and executable",
|
||||||
Name: "hooks",
|
Name: "hooks",
|
||||||
@ -265,13 +208,6 @@ func init() {
|
|||||||
Run: checkUserStarNum,
|
Run: checkUserStarNum,
|
||||||
Priority: 6,
|
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{
|
Register(&Check{
|
||||||
Title: "Check git-daemon-export-ok files",
|
Title: "Check git-daemon-export-ok files",
|
||||||
Name: "check-git-daemon-export-ok",
|
Name: "check-git-daemon-export-ok",
|
||||||
|
|||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user