Remove version field from metadata.json
- Removes noisy version mismatch warnings on every bd upgrade - Version field in metadata.json was redundant with daemon version checking via RPC - Daemon version mismatches still detected via HealthResponse - Removes checkVersionMismatch() function and related test file - Updates .beads/.gitignore to properly ignore merge artifacts Amp-Thread-ID: https://ampcode.com/threads/T-7ba8aff2-97a0-4d0c-9008-e858bdfadd61 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
4
.beads/.gitignore
vendored
4
.beads/.gitignore
vendored
@@ -18,3 +18,7 @@ bd.db
|
|||||||
!*.jsonl
|
!*.jsonl
|
||||||
!metadata.json
|
!metadata.json
|
||||||
!config.json
|
!config.json
|
||||||
|
|
||||||
|
# 3-way merge snapshot files (local-only, for deletion tracking)
|
||||||
|
beads.base.jsonl
|
||||||
|
beads.left.jsonl
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"database": "beads.db",
|
"database": "beads.db",
|
||||||
"version": "0.23.0",
|
|
||||||
"jsonl_export": "beads.jsonl"
|
"jsonl_export": "beads.jsonl"
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/steveyegge/beads/internal/beads"
|
"github.com/steveyegge/beads/internal/beads"
|
||||||
"github.com/steveyegge/beads/internal/debug"
|
"github.com/steveyegge/beads/internal/debug"
|
||||||
"github.com/steveyegge/beads/internal/types"
|
"github.com/steveyegge/beads/internal/types"
|
||||||
"golang.org/x/mod/semver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// outputJSON outputs data as pretty-printed JSON
|
// outputJSON outputs data as pretty-printed JSON
|
||||||
@@ -238,60 +237,7 @@ func autoImportIfNewer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkVersionMismatch checks if the binary version matches the database version
|
|
||||||
// and warns the user if they're running an outdated binary
|
|
||||||
func checkVersionMismatch() {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Get the database version (version that last wrote to this DB)
|
|
||||||
dbVersion, err := store.GetMetadata(ctx, "bd_version")
|
|
||||||
if err != nil {
|
|
||||||
// Metadata error - skip check (shouldn't happen, but be defensive)
|
|
||||||
debug.Logf("version check skipped, metadata error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no version stored, this is an old database - store current version and continue
|
|
||||||
if dbVersion == "" {
|
|
||||||
_ = store.SetMetadata(ctx, "bd_version", Version)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare versions: warn if binary is older than database
|
|
||||||
if dbVersion != Version {
|
|
||||||
yellow := color.New(color.FgYellow, color.Bold).SprintFunc()
|
|
||||||
fmt.Fprintf(os.Stderr, "\n%s\n", yellow("⚠️ WARNING: Version mismatch detected!"))
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", yellow(fmt.Sprintf("⚠️ Your bd binary (v%s) differs from the database version (v%s)", Version, dbVersion)))
|
|
||||||
|
|
||||||
// Use semantic version comparison (requires v prefix)
|
|
||||||
binaryVer := "v" + Version
|
|
||||||
dbVer := "v" + dbVersion
|
|
||||||
|
|
||||||
// semver.Compare returns -1 if binaryVer < dbVer, 0 if equal, 1 if binaryVer > dbVer
|
|
||||||
cmp := semver.Compare(binaryVer, dbVer)
|
|
||||||
|
|
||||||
if cmp < 0 {
|
|
||||||
// Binary is older than database
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", yellow("⚠️ Your binary appears to be OUTDATED."))
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n\n", yellow("⚠️ Some features may not work correctly. Rebuild: go build -o bd ./cmd/bd"))
|
|
||||||
} else if cmp > 0 {
|
|
||||||
// Binary is newer than database
|
|
||||||
// Migrations should have already run in sqlite.New() - verify they succeeded
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", yellow("⚠️ Your binary appears NEWER than the database."))
|
|
||||||
|
|
||||||
// Note: Schema probe already ran in sqlite.New() (bd-ckvw)
|
|
||||||
// If we got here, migrations succeeded. Update version.
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n\n", yellow("⚠️ Database schema has been verified and upgraded."))
|
|
||||||
|
|
||||||
// Update stored version to current (only after schema verification passed)
|
|
||||||
_ = store.SetMetadata(ctx, "bd_version", Version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always update the version metadata to track last-used version
|
|
||||||
// This is safe even if versions match (idempotent operation)
|
|
||||||
_ = store.SetMetadata(ctx, "bd_version", Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// markDirtyAndScheduleFlush marks the database as dirty and schedules a flush
|
// markDirtyAndScheduleFlush marks the database as dirty and schedules a flush
|
||||||
// markDirtyAndScheduleFlush marks the database as dirty and schedules a debounced
|
// markDirtyAndScheduleFlush marks the database as dirty and schedules a debounced
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheckVersionMismatch_NoVersion(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
tmpDB := tmpDir + "/test.db"
|
|
||||||
|
|
||||||
sqliteStore, err := sqlite.New(tmpDB)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create store: %v", err)
|
|
||||||
}
|
|
||||||
defer sqliteStore.Close()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Set prefix to initialize DB
|
|
||||||
if err := sqliteStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
|
||||||
t.Fatalf("Failed to set prefix: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save and restore global store
|
|
||||||
oldStore := store
|
|
||||||
store = sqliteStore
|
|
||||||
defer func() { store = oldStore }()
|
|
||||||
|
|
||||||
// Should not panic when no version is set
|
|
||||||
checkVersionMismatch()
|
|
||||||
|
|
||||||
// Should have set the version
|
|
||||||
version, err := sqliteStore.GetMetadata(ctx, "bd_version")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get version: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if version != Version {
|
|
||||||
t.Errorf("Expected version %s, got %s", Version, version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckVersionMismatch_SameVersion(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
tmpDB := tmpDir + "/test.db"
|
|
||||||
|
|
||||||
sqliteStore, err := sqlite.New(tmpDB)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create store: %v", err)
|
|
||||||
}
|
|
||||||
defer sqliteStore.Close()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Set prefix to initialize DB
|
|
||||||
if err := sqliteStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
|
||||||
t.Fatalf("Failed to set prefix: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set same version
|
|
||||||
if err := sqliteStore.SetMetadata(ctx, "bd_version", Version); err != nil {
|
|
||||||
t.Fatalf("Failed to set version: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save and restore global store
|
|
||||||
oldStore := store
|
|
||||||
store = sqliteStore
|
|
||||||
defer func() { store = oldStore }()
|
|
||||||
|
|
||||||
// Should not print warning (we can't easily test stderr, but ensure no panic)
|
|
||||||
checkVersionMismatch()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckVersionMismatch_OlderBinary(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
tmpDB := tmpDir + "/test.db"
|
|
||||||
|
|
||||||
sqliteStore, err := sqlite.New(tmpDB)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create store: %v", err)
|
|
||||||
}
|
|
||||||
defer sqliteStore.Close()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Set prefix to initialize DB
|
|
||||||
if err := sqliteStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
|
||||||
t.Fatalf("Failed to set prefix: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a newer version in DB
|
|
||||||
if err := sqliteStore.SetMetadata(ctx, "bd_version", "99.99.99"); err != nil {
|
|
||||||
t.Fatalf("Failed to set version: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save and restore global store
|
|
||||||
oldStore := store
|
|
||||||
store = sqliteStore
|
|
||||||
defer func() { store = oldStore }()
|
|
||||||
|
|
||||||
// Should print warning (we can't easily test stderr, but ensure no panic)
|
|
||||||
checkVersionMismatch()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckVersionMismatch_NewerBinary(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
tmpDB := tmpDir + "/test.db"
|
|
||||||
|
|
||||||
sqliteStore, err := sqlite.New(tmpDB)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create store: %v", err)
|
|
||||||
}
|
|
||||||
defer sqliteStore.Close()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Set prefix to initialize DB
|
|
||||||
if err := sqliteStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
|
||||||
t.Fatalf("Failed to set prefix: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set an older version in DB
|
|
||||||
if err := sqliteStore.SetMetadata(ctx, "bd_version", "0.1.0"); err != nil {
|
|
||||||
t.Fatalf("Failed to set version: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save and restore global store
|
|
||||||
oldStore := store
|
|
||||||
store = sqliteStore
|
|
||||||
defer func() { store = oldStore }()
|
|
||||||
|
|
||||||
// Should print warning and update version
|
|
||||||
checkVersionMismatch()
|
|
||||||
|
|
||||||
// Check that version was updated
|
|
||||||
version, err := sqliteStore.GetMetadata(ctx, "bd_version")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get version: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if version != Version {
|
|
||||||
t.Errorf("Expected version to be updated to %s, got %s", Version, version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckVersionMismatch_DebugMode(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
tmpDB := tmpDir + "/test.db"
|
|
||||||
|
|
||||||
sqliteStore, err := sqlite.New(tmpDB)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create store: %v", err)
|
|
||||||
}
|
|
||||||
defer sqliteStore.Close()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Set prefix to initialize DB
|
|
||||||
if err := sqliteStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
|
||||||
t.Fatalf("Failed to set prefix: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save and restore global store
|
|
||||||
oldStore := store
|
|
||||||
store = sqliteStore
|
|
||||||
defer func() { store = oldStore }()
|
|
||||||
|
|
||||||
// Set debug mode
|
|
||||||
os.Setenv("BD_DEBUG", "1")
|
|
||||||
defer os.Unsetenv("BD_DEBUG")
|
|
||||||
|
|
||||||
// Close the store to trigger metadata error
|
|
||||||
sqliteStore.Close()
|
|
||||||
|
|
||||||
// Should not panic even with error in debug mode
|
|
||||||
checkVersionMismatch()
|
|
||||||
}
|
|
||||||
@@ -75,7 +75,6 @@ func ensureStoreActive() error {
|
|||||||
storeActive = true
|
storeActive = true
|
||||||
storeMutex.Unlock()
|
storeMutex.Unlock()
|
||||||
|
|
||||||
checkVersionMismatch()
|
|
||||||
if autoImportEnabled {
|
if autoImportEnabled {
|
||||||
autoImportIfNewer()
|
autoImportIfNewer()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ With --no-db: creates .beads/ directory and issues.jsonl file instead of SQLite
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create metadata.json for --no-db mode
|
// Create metadata.json for --no-db mode
|
||||||
cfg := configfile.DefaultConfig(Version)
|
cfg := configfile.DefaultConfig()
|
||||||
if err := cfg.Save(localBeadsDir); err != nil {
|
if err := cfg.Save(localBeadsDir); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Warning: failed to create metadata.json: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Warning: failed to create metadata.json: %v\n", err)
|
||||||
// Non-fatal - continue anyway
|
// Non-fatal - continue anyway
|
||||||
@@ -256,7 +256,7 @@ bd.db
|
|||||||
|
|
||||||
// Create metadata.json for database metadata
|
// Create metadata.json for database metadata
|
||||||
if useLocalBeads {
|
if useLocalBeads {
|
||||||
cfg := configfile.DefaultConfig(Version)
|
cfg := configfile.DefaultConfig()
|
||||||
if err := cfg.Save(localBeadsDir); err != nil {
|
if err := cfg.Save(localBeadsDir); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Warning: failed to create metadata.json: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Warning: failed to create metadata.json: %v\n", err)
|
||||||
// Non-fatal - continue anyway
|
// Non-fatal - continue anyway
|
||||||
|
|||||||
@@ -410,9 +410,6 @@ var rootCmd = &cobra.Command{
|
|||||||
// Warn if multiple databases detected in directory hierarchy
|
// Warn if multiple databases detected in directory hierarchy
|
||||||
warnMultipleDatabases(dbPath)
|
warnMultipleDatabases(dbPath)
|
||||||
|
|
||||||
// Check for version mismatch (warn if binary is older than DB)
|
|
||||||
checkVersionMismatch()
|
|
||||||
|
|
||||||
// Auto-import if JSONL is newer than DB (e.g., after git pull)
|
// Auto-import if JSONL is newer than DB (e.g., after git pull)
|
||||||
// Skip for import command itself to avoid recursion
|
// Skip for import command itself to avoid recursion
|
||||||
// Skip for delete command to prevent resurrection of deleted issues (bd-8kde)
|
// Skip for delete command to prevent resurrection of deleted issues (bd-8kde)
|
||||||
|
|||||||
@@ -456,11 +456,11 @@ This command:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save updated config with current version (fixes GH #193)
|
// Save updated config
|
||||||
if !dryRun {
|
if !dryRun {
|
||||||
if err := cfg.Save(beadsDir); err != nil {
|
if err := cfg.Save(beadsDir); err != nil {
|
||||||
if !jsonOutput {
|
if !jsonOutput {
|
||||||
color.Yellow("Warning: failed to update metadata.json version: %v\n", err)
|
color.Yellow("Warning: failed to save metadata.json: %v\n", err)
|
||||||
}
|
}
|
||||||
// Don't fail migration if config save fails
|
// Don't fail migration if config save fails
|
||||||
}
|
}
|
||||||
@@ -704,10 +704,7 @@ func loadOrCreateConfig(beadsDir string) (*configfile.Config, error) {
|
|||||||
|
|
||||||
// Create default if no config exists
|
// Create default if no config exists
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = configfile.DefaultConfig(Version)
|
cfg = configfile.DefaultConfig()
|
||||||
} else {
|
|
||||||
// Update version field in existing config (fixes GH #193)
|
|
||||||
cfg.Version = Version
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
|
|||||||
@@ -11,14 +11,12 @@ const ConfigFileName = "metadata.json"
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Database string `json:"database"`
|
Database string `json:"database"`
|
||||||
Version string `json:"version"`
|
|
||||||
JSONLExport string `json:"jsonl_export,omitempty"`
|
JSONLExport string `json:"jsonl_export,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig(version string) *Config {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Database: "beads.db",
|
Database: "beads.db",
|
||||||
Version: version,
|
|
||||||
JSONLExport: "beads.jsonl",
|
JSONLExport: "beads.jsonl",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultConfig(t *testing.T) {
|
func TestDefaultConfig(t *testing.T) {
|
||||||
cfg := DefaultConfig("0.17.5")
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if cfg.Database != "beads.db" {
|
if cfg.Database != "beads.db" {
|
||||||
t.Errorf("Database = %q, want beads.db", cfg.Database)
|
t.Errorf("Database = %q, want beads.db", cfg.Database)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Version != "0.17.5" {
|
|
||||||
t.Errorf("Version = %q, want 0.17.5", cfg.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.JSONLExport != "beads.jsonl" {
|
if cfg.JSONLExport != "beads.jsonl" {
|
||||||
t.Errorf("JSONLExport = %q, want beads.jsonl", cfg.JSONLExport)
|
t.Errorf("JSONLExport = %q, want beads.jsonl", cfg.JSONLExport)
|
||||||
}
|
}
|
||||||
@@ -29,7 +25,7 @@ func TestLoadSaveRoundtrip(t *testing.T) {
|
|||||||
t.Fatalf("failed to create .beads directory: %v", err)
|
t.Fatalf("failed to create .beads directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := DefaultConfig("0.17.5")
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if err := cfg.Save(beadsDir); err != nil {
|
if err := cfg.Save(beadsDir); err != nil {
|
||||||
t.Fatalf("Save() failed: %v", err)
|
t.Fatalf("Save() failed: %v", err)
|
||||||
@@ -48,10 +44,6 @@ func TestLoadSaveRoundtrip(t *testing.T) {
|
|||||||
t.Errorf("Database = %q, want %q", loaded.Database, cfg.Database)
|
t.Errorf("Database = %q, want %q", loaded.Database, cfg.Database)
|
||||||
}
|
}
|
||||||
|
|
||||||
if loaded.Version != cfg.Version {
|
|
||||||
t.Errorf("Version = %q, want %q", loaded.Version, cfg.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if loaded.JSONLExport != cfg.JSONLExport {
|
if loaded.JSONLExport != cfg.JSONLExport {
|
||||||
t.Errorf("JSONLExport = %q, want %q", loaded.JSONLExport, cfg.JSONLExport)
|
t.Errorf("JSONLExport = %q, want %q", loaded.JSONLExport, cfg.JSONLExport)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user