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),
|
||||
// otherwise default to `.beads/beads.db` for SQLite.
|
||||
// If there's a redirect file, use the redirect target (GH#bd-0qel)
|
||||
initDBPath := dbPath
|
||||
if backend == configfile.BackendDolt {
|
||||
initDBPath = filepath.Join(".beads", "dolt")
|
||||
} 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).
|
||||
@@ -192,7 +196,9 @@ With --stealth: configures per-repository git settings for invisible beads usage
|
||||
|
||||
var beadsDir string
|
||||
// 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
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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
|
||||
// in daemon sync-branch code path. Use CanonicalizePath for OS-agnostic
|
||||
// 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