fix(redirect): follow redirect when creating database (GH#bd-0qel)
When bd runs with --no-daemon or during init in a directory that has a .beads/redirect file, it now correctly follows the redirect to create the database in the target location instead of locally. The bug occurred because: 1. init.go hardcoded .beads/beads.db without checking for redirects 2. main.go's fallback path for auto-bootstrap also used local .beads Both code paths now call beads.FollowRedirect() to resolve the correct .beads directory before constructing the database path. Added TestInitWithRedirect to verify the fix. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -139,11 +139,15 @@ With --stealth: configures per-repository git settings for invisible beads usage
|
|||||||
//
|
//
|
||||||
// Use global dbPath if set via --db flag or BEADS_DB env var (SQLite-only),
|
// Use global dbPath if set via --db flag or BEADS_DB env var (SQLite-only),
|
||||||
// otherwise default to `.beads/beads.db` for SQLite.
|
// otherwise default to `.beads/beads.db` for SQLite.
|
||||||
|
// If there's a redirect file, use the redirect target (GH#bd-0qel)
|
||||||
initDBPath := dbPath
|
initDBPath := dbPath
|
||||||
if backend == configfile.BackendDolt {
|
if backend == configfile.BackendDolt {
|
||||||
initDBPath = filepath.Join(".beads", "dolt")
|
initDBPath = filepath.Join(".beads", "dolt")
|
||||||
} else if initDBPath == "" {
|
} else if initDBPath == "" {
|
||||||
initDBPath = filepath.Join(".beads", beads.CanonicalDatabaseName)
|
// Check for redirect in local .beads
|
||||||
|
localBeadsDir := filepath.Join(".", ".beads")
|
||||||
|
targetBeadsDir := beads.FollowRedirect(localBeadsDir)
|
||||||
|
initDBPath = filepath.Join(targetBeadsDir, beads.CanonicalDatabaseName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate old SQLite database files if they exist (SQLite backend only).
|
// Migrate old SQLite database files if they exist (SQLite backend only).
|
||||||
@@ -192,7 +196,9 @@ With --stealth: configures per-repository git settings for invisible beads usage
|
|||||||
|
|
||||||
var beadsDir string
|
var beadsDir string
|
||||||
// For regular repos, use current directory
|
// For regular repos, use current directory
|
||||||
beadsDir = filepath.Join(cwd, ".beads")
|
// But first check if there's a redirect file - if so, use the redirect target (GH#bd-0qel)
|
||||||
|
localBeadsDir := filepath.Join(cwd, ".beads")
|
||||||
|
beadsDir = beads.FollowRedirect(localBeadsDir)
|
||||||
|
|
||||||
// Prevent nested .beads directories
|
// Prevent nested .beads directories
|
||||||
// Check if current working directory is inside a .beads directory
|
// Check if current working directory is inside a .beads directory
|
||||||
|
|||||||
@@ -1465,3 +1465,89 @@ func captureStdout(t *testing.T, fn func() error) string {
|
|||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestInitWithRedirect verifies that bd init creates the database in the redirect target,
|
||||||
|
// not in the local .beads directory. (GH#bd-0qel)
|
||||||
|
func TestInitWithRedirect(t *testing.T) {
|
||||||
|
// Reset global state
|
||||||
|
origDBPath := dbPath
|
||||||
|
defer func() { dbPath = origDBPath }()
|
||||||
|
dbPath = ""
|
||||||
|
|
||||||
|
// Clear BEADS_DIR to ensure we test the tree search path
|
||||||
|
origBeadsDir := os.Getenv("BEADS_DIR")
|
||||||
|
os.Unsetenv("BEADS_DIR")
|
||||||
|
defer func() {
|
||||||
|
if origBeadsDir != "" {
|
||||||
|
os.Setenv("BEADS_DIR", origBeadsDir)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Reset Cobra flags
|
||||||
|
initCmd.Flags().Set("prefix", "")
|
||||||
|
initCmd.Flags().Set("quiet", "false")
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// Create project directory (where we'll run from)
|
||||||
|
projectDir := filepath.Join(tmpDir, "project")
|
||||||
|
if err := os.MkdirAll(projectDir, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create local .beads with redirect file pointing to target
|
||||||
|
localBeadsDir := filepath.Join(projectDir, ".beads")
|
||||||
|
if err := os.MkdirAll(localBeadsDir, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create target .beads directory (the redirect destination)
|
||||||
|
targetBeadsDir := filepath.Join(tmpDir, "canonical", ".beads")
|
||||||
|
if err := os.MkdirAll(targetBeadsDir, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write redirect file - use relative path
|
||||||
|
redirectPath := filepath.Join(localBeadsDir, beads.RedirectFileName)
|
||||||
|
// Relative path from project/.beads to canonical/.beads is ../canonical/.beads
|
||||||
|
if err := os.WriteFile(redirectPath, []byte("../canonical/.beads\n"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to project directory
|
||||||
|
t.Chdir(projectDir)
|
||||||
|
|
||||||
|
// Run bd init
|
||||||
|
rootCmd.SetArgs([]string{"init", "--prefix", "redirect-test", "--quiet"})
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
t.Fatalf("Init with redirect failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify database was created in TARGET directory, not local
|
||||||
|
targetDBPath := filepath.Join(targetBeadsDir, "beads.db")
|
||||||
|
if _, err := os.Stat(targetDBPath); os.IsNotExist(err) {
|
||||||
|
t.Errorf("Database was NOT created in redirect target: %s", targetDBPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify database was NOT created in local directory
|
||||||
|
localDBPath := filepath.Join(localBeadsDir, "beads.db")
|
||||||
|
if _, err := os.Stat(localDBPath); err == nil {
|
||||||
|
t.Errorf("Database was incorrectly created in local .beads: %s (should be in redirect target)", localDBPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the database is functional
|
||||||
|
store, err := openExistingTestDB(t, targetDBPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to open database in redirect target: %v", err)
|
||||||
|
}
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
prefix, err := store.GetConfig(ctx, "issue_prefix")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get issue prefix from database: %v", err)
|
||||||
|
}
|
||||||
|
if prefix != "redirect-test" {
|
||||||
|
t.Errorf("Expected prefix 'redirect-test', got %q", prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -532,7 +532,16 @@ var rootCmd = &cobra.Command{
|
|||||||
// Invariant: dbPath must always be absolute for filepath.Rel() compatibility
|
// Invariant: dbPath must always be absolute for filepath.Rel() compatibility
|
||||||
// in daemon sync-branch code path. Use CanonicalizePath for OS-agnostic
|
// in daemon sync-branch code path. Use CanonicalizePath for OS-agnostic
|
||||||
// handling (symlinks, case normalization on macOS).
|
// handling (symlinks, case normalization on macOS).
|
||||||
dbPath = utils.CanonicalizePath(filepath.Join(".beads", beads.CanonicalDatabaseName))
|
//
|
||||||
|
// IMPORTANT: Use FindBeadsDir() to get the correct .beads directory,
|
||||||
|
// which follows redirect files. Without this, a redirected .beads
|
||||||
|
// would create a local database instead of using the redirect target.
|
||||||
|
// (GH#bd-0qel)
|
||||||
|
targetBeadsDir := beads.FindBeadsDir()
|
||||||
|
if targetBeadsDir == "" {
|
||||||
|
targetBeadsDir = ".beads"
|
||||||
|
}
|
||||||
|
dbPath = utils.CanonicalizePath(filepath.Join(targetBeadsDir, beads.CanonicalDatabaseName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user