fix(init): respect BEADS_DIR environment variable (#1273)
* fix(sync): read sync.mode from yaml first, then database bd config set sync.mode writes to config.yaml (because sync.* is a yaml-only prefix), but GetSyncMode() only read from the database. This caused dolt-native mode to be ignored - JSONL export still happened because the database had no sync.mode value. Now GetSyncMode() checks config.yaml first (via config.GetSyncMode()), falling back to database for backward compatibility. Fixes: oss-5ca279 * fix(init): respect BEADS_DIR environment variable Problem: - `bd init` ignored BEADS_DIR when checking for existing data - `bd init` created database at CWD/.beads instead of BEADS_DIR - Contributor wizard used ~/.beads-planning as default, ignoring BEADS_DIR Solution: - Add BEADS_DIR check in checkExistingBeadsData() (matches FindBeadsDir pattern) - Compute beadsDirForInit early, before initDBPath determination - Use BEADS_DIR as default in contributor wizard when set - Preserve precedence: --db > BEADS_DB > BEADS_DIR > default Impact: - Users with BEADS_DIR set now get consistent behavior across all bd commands - ACF-style fork tracking (external .beads directory) now works correctly Fixes: steveyegge/beads#??? * fix(doctor): respect BEADS_DIR environment variable Also updates documentation to reflect BEADS_DIR support in init and doctor. Changes: - doctor.go: Check BEADS_DIR before falling back to CWD - doctor_test.go: Add tests for BEADS_DIR path resolution - WORKTREES.md: Document simplified BEADS_DIR+init workflow - CONTRIBUTOR_NAMESPACE_ISOLATION.md: Note init/doctor BEADS_DIR support * test(init): add BEADS_DB > BEADS_DIR precedence test Verifies that BEADS_DB env var takes precedence over BEADS_DIR when both are set, ensuring the documented precedence order: --db > BEADS_DB > BEADS_DIR > default * chore: fill in GH#1277 placeholder in sync_mode comment
This commit is contained in:
committed by
GitHub
parent
810192157c
commit
b7d650bd8e
191
cmd/bd/init.go
191
cmd/bd/init.go
@@ -148,23 +148,34 @@ and --server-user. Password should be set via BEADS_DOLT_PASSWORD environment va
|
||||
// The hyphen is added automatically during ID generation
|
||||
prefix = strings.TrimRight(prefix, "-")
|
||||
|
||||
// Determine beadsDir first (used for all storage path calculations).
|
||||
// BEADS_DIR takes precedence, otherwise use CWD/.beads (with redirect support).
|
||||
// This must be computed BEFORE initDBPath to ensure consistent path resolution
|
||||
// (avoiding macOS /var -> /private/var symlink issues when directory creation
|
||||
// happens between path computations).
|
||||
var beadsDirForInit string
|
||||
if envBeadsDir := os.Getenv("BEADS_DIR"); envBeadsDir != "" {
|
||||
beadsDirForInit = utils.CanonicalizePath(envBeadsDir)
|
||||
} else {
|
||||
localBeadsDir := filepath.Join(".", ".beads")
|
||||
beadsDirForInit = beads.FollowRedirect(localBeadsDir)
|
||||
}
|
||||
|
||||
// Determine storage path.
|
||||
//
|
||||
// IMPORTANT: In Dolt mode, we must NOT create a SQLite database file.
|
||||
// `initDBPath` is used for SQLite-specific tasks (migration, import helpers, etc),
|
||||
// so in Dolt mode it should point to the Dolt directory instead.
|
||||
//
|
||||
// Use global dbPath if set via --db flag or BEADS_DB env var (SQLite-only),
|
||||
// otherwise default to `.beads/beads.db` for SQLite.
|
||||
// Precedence: --db > BEADS_DB > BEADS_DIR > default (.beads/beads.db)
|
||||
// If there's a redirect file, use the redirect target (GH#bd-0qel)
|
||||
initDBPath := dbPath
|
||||
if backend == configfile.BackendDolt {
|
||||
initDBPath = filepath.Join(".beads", "dolt")
|
||||
// Dolt backend: use computed beadsDirForInit
|
||||
initDBPath = filepath.Join(beadsDirForInit, "dolt")
|
||||
} else if initDBPath == "" {
|
||||
// Check for redirect in local .beads
|
||||
localBeadsDir := filepath.Join(".", ".beads")
|
||||
targetBeadsDir := beads.FollowRedirect(localBeadsDir)
|
||||
initDBPath = filepath.Join(targetBeadsDir, beads.CanonicalDatabaseName)
|
||||
// SQLite backend: use computed beadsDirForInit
|
||||
initDBPath = filepath.Join(beadsDirForInit, beads.CanonicalDatabaseName)
|
||||
}
|
||||
|
||||
// Migrate old SQLite database files if they exist (SQLite backend only).
|
||||
@@ -211,11 +222,9 @@ and --server-user. Password should be set via BEADS_DOLT_PASSWORD environment va
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var beadsDir string
|
||||
// For regular repos, use current directory
|
||||
// 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)
|
||||
// Use the beadsDir computed earlier (before any directory creation)
|
||||
// to ensure consistent path representation.
|
||||
beadsDir := beadsDirForInit
|
||||
|
||||
// Prevent nested .beads directories
|
||||
// Check if current working directory is inside a .beads directory
|
||||
@@ -850,6 +859,78 @@ func readFirstIssueFromGit(jsonlPath, gitRef string) (*types.Issue, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// checkExistingBeadsDataAt checks for existing database at a specific beadsDir path.
|
||||
// This is extracted to support both BEADS_DIR and CWD-based resolution.
|
||||
func checkExistingBeadsDataAt(beadsDir string, prefix string) error {
|
||||
// Check if .beads directory exists
|
||||
if _, err := os.Stat(beadsDir); os.IsNotExist(err) {
|
||||
return nil // No .beads directory, safe to init
|
||||
}
|
||||
|
||||
// Check for existing database (SQLite or Dolt)
|
||||
if cfg, err := configfile.Load(beadsDir); err == nil && cfg != nil && cfg.GetBackend() == configfile.BackendDolt {
|
||||
doltPath := filepath.Join(beadsDir, "dolt")
|
||||
if info, err := os.Stat(doltPath); err == nil && info.IsDir() {
|
||||
return fmt.Errorf(`
|
||||
%s Found existing Dolt database: %s
|
||||
|
||||
This workspace is already initialized.
|
||||
|
||||
To use the existing database:
|
||||
Just run bd commands normally (e.g., %s)
|
||||
|
||||
To completely reinitialize (data loss warning):
|
||||
rm -rf %s && bd init --backend dolt --prefix %s
|
||||
|
||||
Aborting.`, ui.RenderWarn("⚠"), doltPath, ui.RenderAccent("bd list"), beadsDir, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for redirect file - if present, check the redirect target
|
||||
redirectTarget := beads.FollowRedirect(beadsDir)
|
||||
if redirectTarget != beadsDir {
|
||||
targetDBPath := filepath.Join(redirectTarget, beads.CanonicalDatabaseName)
|
||||
if _, err := os.Stat(targetDBPath); err == nil {
|
||||
return fmt.Errorf(`
|
||||
%s Cannot init: redirect target already has database
|
||||
|
||||
Local .beads redirects to: %s
|
||||
That location already has: %s
|
||||
|
||||
The redirect target is already initialized. Running init here would overwrite it.
|
||||
|
||||
To use the existing database:
|
||||
Just run bd commands normally (e.g., %s)
|
||||
The redirect will route to the canonical database.
|
||||
|
||||
To reinitialize the canonical location (data loss warning):
|
||||
rm %s && bd init --prefix %s
|
||||
|
||||
Aborting.`, ui.RenderWarn("⚠"), redirectTarget, targetDBPath, ui.RenderAccent("bd list"), targetDBPath, prefix)
|
||||
}
|
||||
return nil // Redirect target has no database - safe to init
|
||||
}
|
||||
|
||||
// Check for existing database file (no redirect case)
|
||||
dbPath := filepath.Join(beadsDir, beads.CanonicalDatabaseName)
|
||||
if _, err := os.Stat(dbPath); err == nil {
|
||||
return fmt.Errorf(`
|
||||
%s Found existing database: %s
|
||||
|
||||
This workspace is already initialized.
|
||||
|
||||
To use the existing database:
|
||||
Just run bd commands normally (e.g., %s)
|
||||
|
||||
To completely reinitialize (data loss warning):
|
||||
rm -rf %s && bd init --prefix %s
|
||||
|
||||
Aborting.`, ui.RenderWarn("⚠"), dbPath, ui.RenderAccent("bd list"), beadsDir, prefix)
|
||||
}
|
||||
|
||||
return nil // No database found, safe to init
|
||||
}
|
||||
|
||||
// checkExistingBeadsData checks for existing database files
|
||||
// and returns an error if found (safety guard for bd-emg)
|
||||
//
|
||||
@@ -863,6 +944,13 @@ func readFirstIssueFromGit(jsonlPath, gitRef string) (*types.Issue, error) {
|
||||
// For redirects, checks the redirect target and errors if it already has a database.
|
||||
// This prevents accidentally overwriting an existing canonical database (GH#bd-0qel).
|
||||
func checkExistingBeadsData(prefix string) error {
|
||||
// Check BEADS_DIR environment variable first (matches FindBeadsDir pattern)
|
||||
// When BEADS_DIR is set, it takes precedence over CWD and worktree checks
|
||||
if envBeadsDir := os.Getenv("BEADS_DIR"); envBeadsDir != "" {
|
||||
absBeadsDir := utils.CanonicalizePath(envBeadsDir)
|
||||
return checkExistingBeadsDataAt(absBeadsDir, prefix)
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil // Can't determine CWD, allow init to proceed
|
||||
@@ -884,82 +972,5 @@ func checkExistingBeadsData(prefix string) error {
|
||||
beadsDir = filepath.Join(cwd, ".beads")
|
||||
}
|
||||
|
||||
// Check if .beads directory exists
|
||||
if _, err := os.Stat(beadsDir); os.IsNotExist(err) {
|
||||
return nil // No .beads directory, safe to init
|
||||
}
|
||||
|
||||
// Check for existing database (SQLite or Dolt)
|
||||
//
|
||||
// NOTE: For Dolt backend, the "database" is a directory at `.beads/dolt/`.
|
||||
// We prefer metadata.json as the single source of truth, but we also keep a
|
||||
// conservative fallback for legacy SQLite setups.
|
||||
if cfg, err := configfile.Load(beadsDir); err == nil && cfg != nil && cfg.GetBackend() == configfile.BackendDolt {
|
||||
doltPath := filepath.Join(beadsDir, "dolt")
|
||||
if info, err := os.Stat(doltPath); err == nil && info.IsDir() {
|
||||
return fmt.Errorf(`
|
||||
%s Found existing Dolt database: %s
|
||||
|
||||
This workspace is already initialized.
|
||||
|
||||
To use the existing database:
|
||||
Just run bd commands normally (e.g., %s)
|
||||
|
||||
To completely reinitialize (data loss warning):
|
||||
rm -rf .beads && bd init --backend dolt --prefix %s
|
||||
|
||||
Aborting.`, ui.RenderWarn("⚠"), doltPath, ui.RenderAccent("bd list"), prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for redirect file - if present, we need to check the redirect target (GH#bd-0qel)
|
||||
redirectTarget := beads.FollowRedirect(beadsDir)
|
||||
if redirectTarget != beadsDir {
|
||||
// There's a redirect - check if the target already has a database
|
||||
targetDBPath := filepath.Join(redirectTarget, beads.CanonicalDatabaseName)
|
||||
if _, err := os.Stat(targetDBPath); err == nil {
|
||||
return fmt.Errorf(`
|
||||
%s Cannot init: redirect target already has database
|
||||
|
||||
Local .beads redirects to: %s
|
||||
That location already has: %s
|
||||
|
||||
The redirect target is already initialized. Running init here would overwrite it.
|
||||
|
||||
To use the existing database:
|
||||
Just run bd commands normally (e.g., %s)
|
||||
The redirect will route to the canonical database.
|
||||
|
||||
To reinitialize the canonical location (data loss warning):
|
||||
rm %s && bd init --prefix %s
|
||||
|
||||
Aborting.`, ui.RenderWarn("⚠"), redirectTarget, targetDBPath, ui.RenderAccent("bd list"), targetDBPath, prefix)
|
||||
}
|
||||
// Redirect target has no database - safe to init there
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for existing database file (no redirect case)
|
||||
dbPath := filepath.Join(beadsDir, beads.CanonicalDatabaseName)
|
||||
if _, err := os.Stat(dbPath); err == nil {
|
||||
return fmt.Errorf(`
|
||||
%s Found existing database: %s
|
||||
|
||||
This workspace is already initialized.
|
||||
|
||||
To use the existing database:
|
||||
Just run bd commands normally (e.g., %s)
|
||||
|
||||
To completely reinitialize (data loss warning):
|
||||
rm -rf .beads && bd init --prefix %s
|
||||
|
||||
Aborting.`, ui.RenderWarn("⚠"), dbPath, ui.RenderAccent("bd list"), prefix)
|
||||
}
|
||||
|
||||
// Fresh clones (JSONL exists but no database) are allowed - init will
|
||||
// create the database and import from JSONL automatically.
|
||||
// This fixes the circular dependency where init told users to run
|
||||
// "bd doctor --fix" but doctor couldn't create a database (bd-4h9).
|
||||
|
||||
return nil // No database found, safe to init
|
||||
return checkExistingBeadsDataAt(beadsDir, prefix)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user