Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 104 additions & 26 deletions internal/cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,55 @@ func hasHelpFlag(args []string) bool {
return false
}

func runDoctor(stdout io.Writer, args []string) error {
func parseOptionalRepoRootArg(fs *flag.FlagSet, args []string, repoRootFlag *string, defaultRepoRoot string) (string, error) {
repoRootArg := ""
parseArgs := args
if len(args) > 0 && !strings.HasPrefix(args[0], "-") {
repoRootArg = args[0]
parseArgs = args[1:]
}
if err := fs.Parse(parseArgs); err != nil {
return "", err
}
repoRoot := strings.TrimSpace(*repoRootFlag)
if repoRoot == "" {
repoRoot = strings.TrimSpace(repoRootArg)
}
if repoRoot == "" {
repoRoot = defaultRepoRoot
}
return repoRoot, nil
}

func runDoctor(ctx context.Context, cfg config.Config, stdout io.Writer, args []string) error {
fs := flag.NewFlagSet("doctor", flag.ContinueOnError)
fs.SetOutput(io.Discard)
fix := fs.Bool("fix", false, "apply non-destructive fixes")
if err := fs.Parse(args); err != nil {
repoRootFlag := fs.String("repo-root", "", "repository root to inspect (optional)")

defaultRepoRoot := ""
if config.IsRepoDBDir(cfg.DBDir) {
defaultRepoRoot = "."
}
repoRoot, err := parseOptionalRepoRootArg(fs, args, repoRootFlag, defaultRepoRoot)
if err != nil {
return err
}
report, err := doctor.RunWithFix(*fix)

dbPath := ""
if repoRoot != "" {
canonical, err := store.CanonicalRepoPath(repoRoot)
if err != nil {
return err
}
p, err := dbPathForRepo(cfg, repoRoot, canonical)
if err != nil {
return err
}
dbPath = p
}

report, err := doctor.RunWithFix(*fix, dbPath)
if err != nil {
return err
}
Expand Down Expand Up @@ -1021,28 +1062,34 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s
fs := flag.NewFlagSet("clean", flag.ContinueOnError)
fs.SetOutput(io.Discard)
vacuum := fs.Bool("vacuum", false, "run VACUUM on databases")
ftsOptimize := fs.Bool("fts-optimize", false, "run FTS optimize on databases (symbol_fts)")
repoRootFlag := fs.String("repo-root", "", "repository root to clean")
if err := fs.Parse(args); err != nil {
return err
}
repoRoot := strings.TrimSpace(*repoRootFlag)
if repoRoot == "" && fs.NArg() > 0 {
repoRoot = fs.Arg(0)

defaultRepoRoot := ""
if config.IsRepoDBDir(cfg.DBDir) {
defaultRepoRoot = "."
}
if repoRoot == "" && config.IsRepoDBDir(cfg.DBDir) {
repoRoot = "."
repoRoot, err := parseOptionalRepoRootArg(fs, args, repoRootFlag, defaultRepoRoot)
if err != nil {
return err
}

type dbResult struct {
Path string `json:"path"`
Action string `json:"action"`
SizeBefore int64 `json:"size_before_bytes"`
SizeAfter int64 `json:"size_after_bytes"`
ReclaimedBytes int64 `json:"reclaimed_bytes"`
Vacuumed bool `json:"vacuumed,omitempty"`
FTSOptimized bool `json:"fts_optimized,omitempty"`
FTSOptimizeMS int64 `json:"fts_optimize_ms,omitempty"`
CanonicalRepo string `json:"canonical_repo,omitempty"`
Error string `json:"error,omitempty"`
}
report := map[string]any{
"vacuum": *vacuum,
"dbs": []dbResult{},
"vacuum": *vacuum,
"fts_optimize": *ftsOptimize,
"dbs": []dbResult{},
}
results := make([]dbResult, 0)
var reclaimed int64
Expand All @@ -1058,24 +1105,37 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s
}
res := dbResult{Path: dbPath, CanonicalRepo: canonical}
before := fileSize(dbPath)
res.SizeBefore = before
s, err := store.OpenWithOptions(dbPath, store.OpenOptions{PerformanceProfile: cfg.DBPerformanceProfile})
if err != nil {
return err
}
defer s.Close()
if *ftsOptimize {
dur, err := s.OptimizeFTS(ctx)
if err != nil {
return err
}
res.FTSOptimized = true
res.FTSOptimizeMS = dur.Milliseconds()
}
if *vacuum {
if err := s.Vacuum(ctx); err != nil {
return err
}
after := fileSize(dbPath)
if before > after {
res.ReclaimedBytes = before - after
reclaimed += res.ReclaimedBytes
}
res.Vacuumed = true
res.Action = "vacuumed"
} else if *ftsOptimize {
res.Action = "fts_optimized"
} else {
res.Action = "inspected"
}
after := fileSize(dbPath)
res.SizeAfter = after
if before > after {
res.ReclaimedBytes = before - after
reclaimed += res.ReclaimedBytes
}
results = append(results, res)
report["dbs"] = results
report["reclaimed_bytes"] = reclaimed
Expand All @@ -1095,7 +1155,7 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s
}
dbPath := filepath.Join(cfg.DBDir, entry.Name())
sizeBefore := fileSize(dbPath)
res := dbResult{Path: dbPath}
res := dbResult{Path: dbPath, SizeBefore: sizeBefore}
s, err := store.OpenWithOptions(dbPath, store.OpenOptions{PerformanceProfile: cfg.DBPerformanceProfile})
if err != nil {
res.Action = "skipped"
Expand Down Expand Up @@ -1138,6 +1198,24 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s
results = append(results, res)
continue
}
if !*vacuum {
res.Action = "kept"
}
if *ftsOptimize {
dur, err := s.OptimizeFTS(ctx)
if err != nil {
_ = s.Close()
res.Action = "skipped"
res.Error = err.Error()
results = append(results, res)
continue
}
res.FTSOptimized = true
res.FTSOptimizeMS = dur.Milliseconds()
if !*vacuum && res.Action == "kept" {
res.Action = "fts_optimized"
}
}
if *vacuum {
if err := s.Vacuum(ctx); err != nil {
_ = s.Close()
Expand All @@ -1146,16 +1224,16 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s
results = append(results, res)
continue
}
after := fileSize(dbPath)
if sizeBefore > after {
res.ReclaimedBytes = sizeBefore - after
reclaimed += res.ReclaimedBytes
}
res.Vacuumed = true
res.Action = "vacuumed"
} else {
res.Action = "kept"
}
_ = s.Close()
sizeAfter := fileSize(dbPath)
res.SizeAfter = sizeAfter
if sizeBefore > sizeAfter {
res.ReclaimedBytes = sizeBefore - sizeAfter
reclaimed += res.ReclaimedBytes
}
results = append(results, res)
}

Expand Down
7 changes: 5 additions & 2 deletions internal/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,11 @@ func newCommandList() []*command {
description: "run diagnostics",
usageLines: []string{
" doctor",
" add --repo-root PATH to inspect a repo DB",
" add --fix for non-destructive autofixes",
},
run: func(ctx context.Context, cfg config.Config, stdout, stderr io.Writer, invokedName string, args []string) error {
return runDoctor(stdout, args)
return runDoctor(ctx, cfg, stdout, args)
},
},
{
Expand Down Expand Up @@ -324,13 +325,15 @@ func newCommandList() []*command {
{
name: "clean",
description: "clean index data",
usageLines: []string{" clean [repo-path] [--vacuum]"},
usageLines: []string{" clean [repo-path] [--vacuum] [--fts-optimize]"},
flags: []commandFlag{
{name: "--vacuum", description: "VACUUM the database after cleanup"},
{name: "--fts-optimize", description: "run FTS optimize (symbol_fts)"},
},
examples: []string{
"codegraph clean .",
"codegraph clean . --vacuum",
"codegraph clean . --fts-optimize",
},
run: func(ctx context.Context, cfg config.Config, stdout, stderr io.Writer, invokedName string, args []string) error {
return runClean(ctx, cfg, stdout, args)
Expand Down
54 changes: 51 additions & 3 deletions internal/doctor/doctor.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package doctor

import (
"context"
"database/sql"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/isink17/codegraph/internal/appname"
"github.com/isink17/codegraph/internal/config"
"github.com/isink17/codegraph/internal/gotool"
"github.com/isink17/codegraph/internal/platform"
"github.com/isink17/codegraph/internal/store"
)

type Report struct {
Expand All @@ -22,15 +26,23 @@ type Report struct {
CacheDir string `json:"cache_dir"`
CodegraphOnPath bool `json:"codegraph_on_path"`
CodegraphPath string `json:"codegraph_path,omitempty"`
SQLiteDriver string `json:"sqlite_driver"`
DB *DBInfo `json:"db,omitempty"`
AppliedFixes []string `json:"applied_fixes,omitempty"`
Recommendations []string `json:"recommendations,omitempty"`
}

type DBInfo struct {
Path string `json:"path"`
SizeBytes int64 `json:"size_bytes"`
Pragmas store.DBPragmas `json:"pragmas"`
}

func Run() (Report, error) {
return RunWithFix(false)
return RunWithFix(false, "")
}

func RunWithFix(fix bool) (Report, error) {
func RunWithFix(fix bool, dbPath string) (Report, error) {
paths, err := platform.DefaultPaths()
if err != nil {
return Report{}, err
Expand All @@ -44,7 +56,9 @@ func RunWithFix(fix bool) (Report, error) {
binaryPath, lookErr := exec.LookPath("codegraph")
onPath := lookErr == nil
if lookErr != nil && !errors.Is(lookErr, exec.ErrNotFound) {
return Report{}, lookErr
// Be conservative: doctor should still run even if the PATH lookup found something unusable.
onPath = false
binaryPath = ""
}
appliedFixes := []string{}
if fix {
Expand Down Expand Up @@ -81,6 +95,16 @@ func RunWithFix(fix bool) (Report, error) {
recommendations = append(recommendations, "verify after reopening shell: "+gotool.VerifyCommandHint(appname.BinaryName))
}

var dbInfo *DBInfo
if strings.TrimSpace(dbPath) != "" {
info, err := inspectDB(context.Background(), dbPath)
if err != nil {
recommendations = append(recommendations, "repo DB inspect failed: "+err.Error())
} else {
dbInfo = info
}
}

return Report{
GOOS: runtime.GOOS,
ConfigPath: filepath.Clean(configPath),
Expand All @@ -89,6 +113,8 @@ func RunWithFix(fix bool) (Report, error) {
CacheDir: paths.CacheDir,
CodegraphOnPath: onPath,
CodegraphPath: binaryPath,
SQLiteDriver: store.SQLiteDriverName(),
DB: dbInfo,
AppliedFixes: appliedFixes,
Recommendations: recommendations,
}, nil
Expand All @@ -101,3 +127,25 @@ func firstPathHint() string {
}
return hints[0]
}

func inspectDB(ctx context.Context, dbPath string) (*DBInfo, error) {
st, err := os.Stat(dbPath)
if err != nil {
return nil, err
}
db, err := sql.Open(store.SQLiteDriverName(), dbPath)
if err != nil {
return nil, err
}
defer db.Close()
pragmas, err := store.QueryDBPragmas(ctx, db)
if err != nil {
return nil, err
}

return &DBInfo{
Path: filepath.Clean(dbPath),
SizeBytes: st.Size(),
Pragmas: pragmas,
}, nil
}
Comment thread
isink17 marked this conversation as resolved.
2 changes: 1 addition & 1 deletion internal/doctor/doctor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestRunReportsCodegraphMissingFromPath(t *testing.T) {
func TestRunWithFixCreatesConfig(t *testing.T) {
home := filepath.Join(t.TempDir(), "codegraph-home")
t.Setenv("CODEGRAPH_HOME", home)
report, err := RunWithFix(true)
report, err := RunWithFix(true, "")
if err != nil {
t.Fatalf("RunWithFix(true) error = %v", err)
}
Expand Down
Loading
Loading