diff --git a/cmd/bd/autoimport.go b/cmd/bd/autoimport.go index 4e873f84..c0775d0a 100644 --- a/cmd/bd/autoimport.go +++ b/cmd/bd/autoimport.go @@ -118,6 +118,13 @@ func checkGitForIssues() (int, string, string) { return 0, "", "" } + // GH#896: Reject beadsDir that is outside the git repository. + // This prevents bd init from inheriting issues from a parent hub + // when initializing a new project in a subdirectory. + if strings.HasPrefix(relBeads, "..") { + return 0, "", "" + } + // Determine which branch to read from (bd-0is fix) // If sync-branch is configured in local config.yaml, use it; otherwise fall back to HEAD // We read sync-branch directly from local config file rather than using cached global config diff --git a/cmd/bd/autoimport_test.go b/cmd/bd/autoimport_test.go index d255e98b..8d2b69b5 100644 --- a/cmd/bd/autoimport_test.go +++ b/cmd/bd/autoimport_test.go @@ -4,10 +4,12 @@ import ( "context" "encoding/json" "os" + "os/exec" "path/filepath" "testing" "github.com/steveyegge/beads/internal/beads" + "github.com/steveyegge/beads/internal/git" "github.com/steveyegge/beads/internal/storage/sqlite" "github.com/steveyegge/beads/internal/types" ) @@ -282,6 +284,59 @@ func TestCheckGitForIssues_NoBeadsDir(t *testing.T) { } } +// TestCheckGitForIssues_ParentHubNotInherited tests the GH#896 fix: when running +// bd init in a subdirectory of a hub, the parent hub's .beads should NOT be used +// for importing issues. This prevents contamination from parent hub issues. +func TestCheckGitForIssues_ParentHubNotInherited(t *testing.T) { + // Create a structure simulating the bug scenario: + // tmpDir/ + // .beads/ <- parent hub (NOT a git repo) + // issues.jsonl + // config.yaml + // newproject/ <- new project (IS a git repo) + // .git/ + tmpDir := t.TempDir() + + // Create parent hub's .beads with issues + parentBeadsDir := filepath.Join(tmpDir, ".beads") + if err := os.MkdirAll(parentBeadsDir, 0755); err != nil { + t.Fatalf("Failed to create parent .beads: %v", err) + } + if err := os.WriteFile(filepath.Join(parentBeadsDir, "config.yaml"), []byte("prefix: hub\n"), 0644); err != nil { + t.Fatalf("Failed to create parent config.yaml: %v", err) + } + if err := os.WriteFile(filepath.Join(parentBeadsDir, "issues.jsonl"), []byte(`{"id":"hub-001","title":"Parent Issue"}`+"\n"), 0644); err != nil { + t.Fatalf("Failed to create parent issues.jsonl: %v", err) + } + + // Create newproject as a git repo + newProject := filepath.Join(tmpDir, "newproject") + if err := os.MkdirAll(newProject, 0755); err != nil { + t.Fatalf("Failed to create newproject: %v", err) + } + + // Initialize git repo in newproject + t.Chdir(newProject) + cmd := exec.Command("git", "init") + if err := cmd.Run(); err != nil { + t.Fatalf("Failed to git init: %v", err) + } + + // Reset git context cache (important for test isolation) + git.ResetCaches() + + // Now checkGitForIssues should NOT find the parent hub's issues + // because the parent .beads is outside the git root (newproject) + count, path, _ := checkGitForIssues() + + if count != 0 { + t.Errorf("GH#896: checkGitForIssues found %d issues from parent hub (should be 0)", count) + } + if path != "" { + t.Errorf("GH#896: checkGitForIssues returned path %q (should be empty)", path) + } +} + func TestBoolToFlag(t *testing.T) { t.Parallel() tests := []struct {