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

@@ -738,11 +738,41 @@ func getRemoteForBranch(ctx context.Context, worktreePath, branch string) string
}
// GetRepoRoot returns the git repository root directory
// For worktrees, this returns the main repository root (not the worktree root)
func GetRepoRoot(ctx context.Context) (string, error) {
// Check if .git is a file (worktree) or directory (regular repo)
gitPath := ".git"
if info, err := os.Stat(gitPath); err == nil {
if info.Mode().IsRegular() {
// Worktree: read .git file
content, err := os.ReadFile(gitPath)
if err != nil {
return "", fmt.Errorf("failed to read .git file: %w", err)
}
line := strings.TrimSpace(string(content))
if strings.HasPrefix(line, "gitdir: ") {
gitDir := strings.TrimPrefix(line, "gitdir: ")
// Remove /worktrees/* part
if idx := strings.Index(gitDir, "/worktrees/"); idx > 0 {
gitDir = gitDir[:idx]
}
return filepath.Dir(gitDir), nil
}
} else if info.IsDir() {
// Regular repo: .git is a directory
absGitPath, err := filepath.Abs(gitPath)
if err != nil {
return "", err
}
return filepath.Dir(absGitPath), nil
}
}
// Fallback to git command
cmd := exec.CommandContext(ctx, "git", "rev-parse", "--show-toplevel")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get git root: %w", err)
return "", fmt.Errorf("not a git repository: %w", err)
}
return strings.TrimSpace(string(output)), nil
}