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
14 changes: 8 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
tags: ["v*"]

permissions:
contents: write
contents: read

jobs:
test:
Expand All @@ -16,10 +16,10 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.26.x"
- run: go test ./...
Expand All @@ -29,14 +29,16 @@ jobs:
name: Release
runs-on: macos-latest
needs: test
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.26.x"
- uses: goreleaser/goreleaser-action@v6
- uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
with:
distribution: goreleaser
version: "~> v2"
Expand Down
105 changes: 65 additions & 40 deletions internal/app/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,46 +54,7 @@ func NewRootCommand() *cobra.Command {
return tui.Run(cmd.Context(), cfg)
},
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
loaded, err := config.Load()
if err != nil {
cfgPath, _ := config.Path()
return fmt.Errorf("cannot load config at %q: %w\nhint: ensure %s is writable", cfgPath, err, filepath.Dir(cfgPath))
}
cfg = loaded

if opt.DBPath == "" {
if cfg.DBPath != "" {
resolved, resolveErr := config.ExpandPath(cfg.DBPath)
if resolveErr != nil {
return resolveErr
}
opt.DBPath = resolved
} else {
dbPath, dbErr := config.Path()
if dbErr != nil {
return dbErr
}
opt.DBPath = filepath.Join(filepath.Dir(dbPath), "goeverything.db")
}
}
if opt.Batch <= 0 {
opt.Batch = 2000
}
if opt.Workers <= 0 {
opt.Workers = scanner.DefaultWorkerCount()
}
if len(opt.Exclude) == 0 {
opt.Exclude = cfg.Excludes
if len(opt.Exclude) == 0 {
opt.Exclude = scanner.DefaultExcludes()
}
}
if err := os.MkdirAll(filepath.Dir(opt.DBPath), 0o755); err != nil {
return fmt.Errorf("cannot create db dir %q: %w\nhint: ensure the configured data directory is writable or pass --db", filepath.Dir(opt.DBPath), err)
}
cfg.DBPath = opt.DBPath
cfg.Excludes = opt.Exclude
return nil
return prepareOptions(&opt, &cfg)
},
}

Expand All @@ -109,6 +70,70 @@ func NewRootCommand() *cobra.Command {
return cmd
}

func prepareOptions(opt *options, cfg *config.Config) error {
if err := loadOptionsConfig(cfg); err != nil {
return err
}
if err := resolveDBPath(opt, *cfg); err != nil {
return err
}
applyOptionDefaults(opt, *cfg)
return ensureDBDirectory(opt, cfg)
}

func loadOptionsConfig(cfg *config.Config) error {
loaded, err := config.Load()
if err == nil {
*cfg = loaded
return nil
}
cfgPath, _ := config.Path()
return fmt.Errorf("cannot load config at %q: %w\nhint: ensure %s is writable", cfgPath, err, filepath.Dir(cfgPath))
}

func resolveDBPath(opt *options, cfg config.Config) error {
if opt.DBPath != "" {
return nil
}
if cfg.DBPath != "" {
resolved, err := config.ExpandPath(cfg.DBPath)
if err != nil {
return err
}
opt.DBPath = resolved
return nil
}
dbPath, err := config.Path()
if err != nil {
return err
}
opt.DBPath = filepath.Join(filepath.Dir(dbPath), "goeverything.db")
return nil
}

func applyOptionDefaults(opt *options, cfg config.Config) {
if opt.Batch <= 0 {
opt.Batch = 2000
}
if opt.Workers <= 0 {
opt.Workers = scanner.DefaultWorkerCount()
}
if len(opt.Exclude) == 0 {
opt.Exclude = cfg.Excludes
if len(opt.Exclude) == 0 {
opt.Exclude = scanner.DefaultExcludes()
}
}
}

func ensureDBDirectory(opt *options, cfg *config.Config) error {
if err := os.MkdirAll(filepath.Dir(opt.DBPath), 0o755); err != nil {
return fmt.Errorf("cannot create db dir %q: %w\nhint: ensure the configured data directory is writable or pass --db", filepath.Dir(opt.DBPath), err)
}
cfg.DBPath, cfg.Excludes = opt.DBPath, opt.Exclude
return nil
}

func newScanCommand(opt *options) *cobra.Command {
command := &cobra.Command{
Use: "scan",
Expand Down
70 changes: 42 additions & 28 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,8 @@ func Load() (Config, error) {
}

cfg := defaults()
data, err := os.ReadFile(path)
data, err := readConfigData(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
for _, candidate := range legacyPaths() {
if candidate == path {
continue
}
data, err = os.ReadFile(candidate)
if err == nil {
// The legacy file is migrated into the portable location below.
break
}
if !errors.Is(err, os.ErrNotExist) {
return Config{}, err
}
}
}
if errors.Is(err, os.ErrNotExist) {
if saveErr := Save(cfg); saveErr != nil {
return Config{}, saveErr
}
return cfg, nil
}
return Config{}, err
}
if len(data) == 0 {
Expand All @@ -77,13 +56,11 @@ func Load() (Config, error) {
return cfg, nil
}

if err := json.Unmarshal(data, &cfg); err != nil {
var old legacyConfig
if err2 := json.Unmarshal(data, &old); err2 != nil {
return Config{}, err
}
cfg = fromLegacy(old)
decoded, err := decodeConfig(data, cfg)
if err != nil {
return Config{}, err
}
cfg = decoded

cfg.normalize()
if err := Save(cfg); err != nil {
Expand All @@ -92,6 +69,43 @@ func Load() (Config, error) {
return cfg, nil
}

func readConfigData(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err == nil || !errors.Is(err, os.ErrNotExist) {
return data, err
}
for _, candidate := range legacyPaths() {
if candidate == path {
continue
}
data, err = os.ReadFile(candidate)
if err == nil {
// The legacy file is migrated into the portable location below.
return data, nil
}
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
}
return nil, nil
}

func decodeConfig(data []byte, cfg Config) (Config, error) {
err := json.Unmarshal(data, &cfg)
if err == nil {
return cfg, nil
}
return decodeLegacyConfig(data, err)
}

func decodeLegacyConfig(data []byte, currentErr error) (Config, error) {
old := legacyConfig{}
if err := json.Unmarshal(data, &old); err != nil {
return Config{}, currentErr
}
return fromLegacy(old), nil
}

func Save(cfg Config) error {
cfg.normalize()
path, err := Path()
Expand Down
27 changes: 15 additions & 12 deletions internal/db/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ import (
"time"
)

const migrationsTableName = "goose_db_version"
const (
createMigrationsTableSQL = `CREATE TABLE IF NOT EXISTS goose_db_version (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version_id INTEGER NOT NULL,
is_applied INTEGER NOT NULL,
tstamp TIMESTAMP DEFAULT (datetime('now'))
)`
recordMigrationSQL = `INSERT INTO goose_db_version(version_id, is_applied) VALUES (?, 1)`
listMigrationStatesSQL = `SELECT version_id, is_applied, tstamp
FROM goose_db_version
ORDER BY id`
)

//go:embed migrations/*.sql
var embeddedMigrations embed.FS
Expand Down Expand Up @@ -62,8 +73,7 @@ func applyMigrations(ctx context.Context, sqlDB *sql.DB) error {
_ = tx.Rollback()
return fmt.Errorf("apply migration %s: %w", item.Name, err)
}
if _, err := tx.ExecContext(ctx, `INSERT INTO `+migrationsTableName+`(version_id, is_applied)
VALUES (?, 1)`, item.Version); err != nil {
if _, err := tx.ExecContext(ctx, recordMigrationSQL, item.Version); err != nil {
_ = tx.Rollback()
return fmt.Errorf("record migration %s: %w", item.Name, err)
}
Expand Down Expand Up @@ -129,19 +139,12 @@ type migrationStatus struct {
}

func ensureMigrationTable(ctx context.Context, sqlDB *sql.DB) error {
_, err := sqlDB.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS `+migrationsTableName+` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version_id INTEGER NOT NULL,
is_applied INTEGER NOT NULL,
tstamp TIMESTAMP DEFAULT (datetime('now'))
)`)
_, err := sqlDB.ExecContext(ctx, createMigrationsTableSQL)
return err
}

func migrationStates(ctx context.Context, sqlDB *sql.DB) (map[int64]migrationState, error) {
rows, err := sqlDB.QueryContext(ctx, `SELECT version_id, is_applied, tstamp
FROM `+migrationsTableName+`
ORDER BY id ASC`)
rows, err := sqlDB.QueryContext(ctx, listMigrationStatesSQL)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading