feat: add Git worktree compatibility (PR #478)

Adds comprehensive Git worktree support for beads issue tracking:

Core changes:
- New internal/git/gitdir.go package for worktree detection
- GetGitDir() returns proper .git location (main repo, not worktree)
- Updated all hooks to use git.GetGitDir() instead of local helper
- BeadsDir() now prioritizes main repository's .beads directory

Features:
- Hooks auto-install in main repo when run from worktree
- Shared .beads directory across all worktrees
- Config option no-install-hooks to disable auto-install
- New bd worktree subcommand for diagnostics

Documentation:
- New docs/WORKTREES.md with setup instructions
- Updated CHANGELOG.md and AGENT_INSTRUCTIONS.md

Testing:
- Updated tests to use exported git.GetGitDir()
- Added worktree detection tests

Co-authored-by: Claude <noreply@anthropic.com>
Closes: #478
This commit is contained in:
matt wilkie
2025-12-13 10:40:40 -08:00
committed by Steve Yegge
parent de7b511765
commit e01b7412d9
64 changed files with 1895 additions and 3708 deletions

View File

@@ -8,6 +8,7 @@ import (
"strings"
"testing"
"github.com/steveyegge/beads/internal/git"
"github.com/steveyegge/beads/internal/storage/sqlite"
)
@@ -37,34 +38,37 @@ func failIfProductionDatabase(t *testing.T, dbPath string) {
return
}
// Check if database is in a directory that contains .git
dir := filepath.Dir(absPath)
for {
gitPath := filepath.Join(dir, ".git")
if _, err := os.Stat(gitPath); err == nil {
// Found .git directory - check if this is a test or production database
beadsPath := filepath.Join(dir, ".beads")
if strings.HasPrefix(absPath, beadsPath) {
// Database is in .beads/ directory of a git repository
// This is ONLY allowed if we're in a temp directory
if !strings.Contains(absPath, os.TempDir()) {
t.Fatalf("PRODUCTION DATABASE POLLUTION DETECTED (bd-2c5a):\n"+
" Database: %s\n"+
" Git repo: %s\n"+
" Tests MUST use t.TempDir() or tempfile to create isolated databases.\n"+
" This prevents test issues from polluting the production database.",
absPath, dir)
}
}
break
// Use worktree-aware git directory detection
gitDir, err := git.GetGitDir()
if err != nil {
// Not a git repository, no pollution risk
return
}
// Check if database is in .beads/ directory of this git repository
beadsPath := ""
gitDirAbs, err := filepath.Abs(gitDir)
if err != nil {
t.Logf("Warning: Could not get absolute path for git dir %s: %v", gitDir, err)
return
}
// The .beads directory should be at the root of the git repository
// For worktrees, gitDir points to the main repo's .git directory
repoRoot := filepath.Dir(gitDirAbs)
beadsPath = filepath.Join(repoRoot, ".beads")
if strings.HasPrefix(absPath, beadsPath) {
// Database is in .beads/ directory of a git repository
// This is ONLY allowed if we're in a temp directory
if !strings.Contains(absPath, os.TempDir()) {
t.Fatalf("PRODUCTION DATABASE POLLUTION DETECTED (bd-2c5a):\n"+
" Database: %s\n"+
" Git repo: %s\n"+
" Tests MUST use t.TempDir() or tempfile to create isolated databases.\n"+
" This prevents test issues from polluting the production database.",
absPath, repoRoot)
}
parent := filepath.Dir(dir)
if parent == dir {
// Reached filesystem root
break
}
dir = parent
}
}