Improve doctor cli behavior (#28422)
1. Do not sort the "checks" slice again and again when "Register", it just wastes CPU when the Gitea instance runs 2. If a check doesn't exist, tell the end user 3. Add some tests
This commit is contained in:
parent
537fa69962
commit
f2a309e6c8
3 changed files with 66 additions and 34 deletions
|
@ -14,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/migrations"
|
"code.gitea.io/gitea/models/migrations"
|
||||||
migrate_base "code.gitea.io/gitea/models/migrations/base"
|
migrate_base "code.gitea.io/gitea/models/migrations/base"
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/doctor"
|
"code.gitea.io/gitea/modules/doctor"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -22,6 +23,19 @@ import (
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CmdDoctor represents the available doctor sub-command.
|
||||||
|
var CmdDoctor = &cli.Command{
|
||||||
|
Name: "doctor",
|
||||||
|
Usage: "Diagnose and optionally fix problems",
|
||||||
|
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||||
|
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
cmdDoctorCheck,
|
||||||
|
cmdRecreateTable,
|
||||||
|
cmdDoctorConvert,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var cmdDoctorCheck = &cli.Command{
|
var cmdDoctorCheck = &cli.Command{
|
||||||
Name: "check",
|
Name: "check",
|
||||||
Usage: "Diagnose and optionally fix problems",
|
Usage: "Diagnose and optionally fix problems",
|
||||||
|
@ -60,19 +74,6 @@ var cmdDoctorCheck = &cli.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdDoctor represents the available doctor sub-command.
|
|
||||||
var CmdDoctor = &cli.Command{
|
|
||||||
Name: "doctor",
|
|
||||||
Usage: "Diagnose and optionally fix problems",
|
|
||||||
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
|
||||||
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
cmdDoctorCheck,
|
|
||||||
cmdRecreateTable,
|
|
||||||
cmdDoctorConvert,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdRecreateTable = &cli.Command{
|
var cmdRecreateTable = &cli.Command{
|
||||||
Name: "recreate-table",
|
Name: "recreate-table",
|
||||||
Usage: "Recreate tables from XORM definitions and copy the data.",
|
Usage: "Recreate tables from XORM definitions and copy the data.",
|
||||||
|
@ -177,6 +178,7 @@ func runDoctorCheck(ctx *cli.Context) error {
|
||||||
if ctx.IsSet("list") {
|
if ctx.IsSet("list") {
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
||||||
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
|
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
|
||||||
|
doctor.SortChecks(doctor.Checks)
|
||||||
for _, check := range doctor.Checks {
|
for _, check := range doctor.Checks {
|
||||||
if check.IsDefault {
|
if check.IsDefault {
|
||||||
_, _ = w.Write([]byte{'*'})
|
_, _ = w.Write([]byte{'*'})
|
||||||
|
@ -192,25 +194,19 @@ func runDoctorCheck(ctx *cli.Context) error {
|
||||||
|
|
||||||
var checks []*doctor.Check
|
var checks []*doctor.Check
|
||||||
if ctx.Bool("all") {
|
if ctx.Bool("all") {
|
||||||
checks = doctor.Checks
|
checks = make([]*doctor.Check, len(doctor.Checks))
|
||||||
|
copy(checks, doctor.Checks)
|
||||||
} else if ctx.IsSet("run") {
|
} else if ctx.IsSet("run") {
|
||||||
addDefault := ctx.Bool("default")
|
addDefault := ctx.Bool("default")
|
||||||
names := ctx.StringSlice("run")
|
runNamesSet := container.SetOf(ctx.StringSlice("run")...)
|
||||||
for i, name := range names {
|
|
||||||
names[i] = strings.ToLower(strings.TrimSpace(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, check := range doctor.Checks {
|
for _, check := range doctor.Checks {
|
||||||
if addDefault && check.IsDefault {
|
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
|
||||||
checks = append(checks, check)
|
checks = append(checks, check)
|
||||||
continue
|
runNamesSet.Remove(check.Name)
|
||||||
}
|
|
||||||
for _, name := range names {
|
|
||||||
if name == check.Name {
|
|
||||||
checks = append(checks, check)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(runNamesSet) > 0 {
|
||||||
|
return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ","))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, check := range doctor.Checks {
|
for _, check := range doctor.Checks {
|
||||||
|
@ -219,6 +215,5 @@ func runDoctorCheck(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
|
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
|
||||||
}
|
}
|
||||||
|
|
33
cmd/doctor_test.go
Normal file
33
cmd/doctor_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/doctor"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDoctorRun(t *testing.T) {
|
||||||
|
doctor.Register(&doctor.Check{
|
||||||
|
Title: "Test Check",
|
||||||
|
Name: "test-check",
|
||||||
|
Run: func(ctx context.Context, logger log.Logger, autofix bool) error { return nil },
|
||||||
|
|
||||||
|
SkipDatabaseInitialization: true,
|
||||||
|
})
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Commands = []*cli.Command{cmdDoctorCheck}
|
||||||
|
err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
|
||||||
|
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
|
||||||
|
err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
|
||||||
|
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
|
||||||
|
}
|
|
@ -79,6 +79,7 @@ var Checks []*Check
|
||||||
|
|
||||||
// RunChecks runs the doctor checks for the provided list
|
// RunChecks runs the doctor checks for the provided list
|
||||||
func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error {
|
func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error {
|
||||||
|
SortChecks(checks)
|
||||||
// the checks output logs by a special logger, they do not use the default logger
|
// the checks output logs by a special logger, they do not use the default logger
|
||||||
logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize})
|
logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize})
|
||||||
loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize})
|
loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize})
|
||||||
|
@ -104,20 +105,23 @@ func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) err
|
||||||
logger.Info("OK")
|
logger.Info("OK")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Info("\nAll done.")
|
logger.Info("\nAll done (checks: %d).", len(checks))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register registers a command with the list
|
// Register registers a command with the list
|
||||||
func Register(command *Check) {
|
func Register(command *Check) {
|
||||||
Checks = append(Checks, command)
|
Checks = append(Checks, command)
|
||||||
sort.SliceStable(Checks, func(i, j int) bool {
|
|
||||||
if Checks[i].Priority == Checks[j].Priority {
|
|
||||||
return Checks[i].Name < Checks[j].Name
|
|
||||||
}
|
}
|
||||||
if Checks[i].Priority == 0 {
|
|
||||||
|
func SortChecks(checks []*Check) {
|
||||||
|
sort.SliceStable(checks, func(i, j int) bool {
|
||||||
|
if checks[i].Priority == checks[j].Priority {
|
||||||
|
return checks[i].Name < checks[j].Name
|
||||||
|
}
|
||||||
|
if checks[i].Priority == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return Checks[i].Priority < Checks[j].Priority
|
return checks[i].Priority < checks[j].Priority
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue