feat(context): centralize RepoContext API for git operations (#1102)

Centralizes repository context resolution via RepoContext API, fixing bugs where git commands run in the wrong repo when BEADS_DIR points elsewhere or in worktree scenarios.
This commit is contained in:
Peter Chanthamynavong
2026-01-15 07:55:08 -08:00
committed by GitHub
parent 159114563b
commit 0a48519561
33 changed files with 3211 additions and 327 deletions

View File

@@ -3,11 +3,11 @@ package daemon
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/steveyegge/beads/internal/beads"
"github.com/steveyegge/beads/internal/lockfile"
"github.com/steveyegge/beads/internal/rpc"
"github.com/steveyegge/beads/internal/utils"
@@ -252,44 +252,37 @@ func FindDaemonByWorkspace(workspacePath string) (*DaemonInfo, error) {
return nil, fmt.Errorf("no daemon found for workspace: %s", workspacePath)
}
// findBeadsDirForWorkspace determines the correct .beads directory for a workspace
// For worktrees, this is the main repository root; for regular repos, it's the workspace itself
// findBeadsDirForWorkspace determines the correct .beads directory for a workspace.
// For worktrees, this is the main repository root; for regular repos, it's the workspace itself.
//
// This function delegates to beads.GetRepoContextForWorkspace() for proper resolution
// including worktree detection and path validation (DMN-001).
func findBeadsDirForWorkspace(workspacePath string) string {
// Change to the workspace directory to check if it's a worktree
originalDir, err := os.Getwd()
// Use the centralized RepoContext API for workspace resolution
rc, err := beads.GetRepoContextForWorkspace(workspacePath)
if err != nil {
return filepath.Join(workspacePath, ".beads") // fallback
// Fallback to simple path join if context resolution fails
// This maintains backward compatibility for edge cases
return filepath.Join(workspacePath, ".beads")
}
defer func() {
_ = os.Chdir(originalDir) // restore original directory
}()
return rc.BeadsDir
}
if err := os.Chdir(workspacePath); err != nil {
return filepath.Join(workspacePath, ".beads") // fallback
}
// Check if we're in a git worktree
cmd := exec.Command("git", "rev-parse", "--git-dir", "--git-common-dir")
output, err := cmd.Output()
// getRepoContextForWorkspace returns the full RepoContext for a workspace.
// This provides access to RepoRoot, BeadsDir, and worktree status for operations
// that need more than just the .beads directory path.
//
// Returns an error if the workspace cannot be resolved or validated.
func getRepoContextForWorkspace(workspacePath string) (*beads.RepoContext, error) {
rc, err := beads.GetRepoContextForWorkspace(workspacePath)
if err != nil {
return filepath.Join(workspacePath, ".beads") // fallback
return nil, fmt.Errorf("cannot resolve workspace context: %w", err)
}
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
if len(lines) >= 2 {
gitDir := strings.TrimSpace(lines[0])
commonDir := strings.TrimSpace(lines[1])
// If git-dir != git-common-dir, we're in a worktree
if gitDir != commonDir {
// Worktree: .beads is in main repo root (parent of git-common-dir)
mainRepoRoot := filepath.Dir(commonDir)
return filepath.Join(mainRepoRoot, ".beads")
}
// Validate the context is still valid (paths exist)
if err := rc.Validate(); err != nil {
return nil, fmt.Errorf("workspace context is stale: %w", err)
}
// Regular repository: .beads is in the workspace
return filepath.Join(workspacePath, ".beads")
return rc, nil
}
// checkDaemonErrorFile checks for a daemon-error file in the .beads directory