fix(init): prevent parent hub contamination during bd init (GH#896)
When running bd init in a subdirectory of a hub (e.g., ~/Repos/project where ~/Repos/.beads exists), the new database was incorrectly inheriting issues from the parent hub. Root cause: checkGitForIssues() computed the relative path from gitRoot to beadsDir but did not validate that beadsDir was actually inside the git repository. When beadsDir was outside (e.g., ../.beads), it would still attempt to import, causing contamination. Fix: Add a guard to reject beadsDir paths that start with .. (outside the git repository boundary). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
9d69099803
commit
b3ebedb063
@@ -118,6 +118,13 @@ func checkGitForIssues() (int, string, string) {
|
|||||||
return 0, "", ""
|
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)
|
// 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
|
// 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
|
// We read sync-branch directly from local config file rather than using cached global config
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/steveyegge/beads/internal/beads"
|
"github.com/steveyegge/beads/internal/beads"
|
||||||
|
"github.com/steveyegge/beads/internal/git"
|
||||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
"github.com/steveyegge/beads/internal/storage/sqlite"
|
||||||
"github.com/steveyegge/beads/internal/types"
|
"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) {
|
func TestBoolToFlag(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user