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

@@ -1,13 +1,14 @@
package main
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/beads"
"github.com/steveyegge/beads/internal/merge"
)
@@ -105,6 +106,14 @@ func cleanupMergeArtifacts(outputPath string, debug bool) {
fmt.Fprintf(os.Stderr, "Cleaning up artifacts in: %s\n", beadsDir)
}
// Get RepoContext for git operations (provides security via hook disabling)
// Note: rc may be nil if not in a git repo (e.g., tests) - git ops are skipped
rc, rcErr := beads.GetRepoContext()
if rcErr != nil && debug {
fmt.Fprintf(os.Stderr, "Warning: failed to get repo context (git ops skipped): %v\n", rcErr)
}
ctx := context.Background()
// 1. Find and remove any files with "backup" in the name
entries, err := os.ReadDir(beadsDir)
if err != nil {
@@ -121,15 +130,16 @@ func cleanupMergeArtifacts(outputPath string, debug bool) {
if strings.Contains(strings.ToLower(entry.Name()), "backup") {
fullPath := filepath.Join(beadsDir, entry.Name())
// Try to git rm if tracked
// #nosec G204 -- fullPath is safely constructed via filepath.Join from entry.Name()
// from os.ReadDir. exec.Command does NOT use shell interpretation - arguments
// are passed directly to git binary. See TestCleanupMergeArtifacts_CommandInjectionPrevention
gitRmCmd := exec.Command("git", "rm", "-f", "--quiet", fullPath)
gitRmCmd.Dir = filepath.Dir(beadsDir)
_ = gitRmCmd.Run() // Ignore errors, file may not be tracked
// Try to git rm if tracked (only if RepoContext available)
if rcErr == nil {
// #nosec G204 -- fullPath is safely constructed via filepath.Join from entry.Name()
// from os.ReadDir. exec.Command does NOT use shell interpretation - arguments
// are passed directly to git binary. See TestCleanupMergeArtifacts_CommandInjectionPrevention
gitRmCmd := rc.GitCmd(ctx, "rm", "-f", "--quiet", fullPath)
_ = gitRmCmd.Run() // Ignore errors, file may not be tracked
}
// Also remove from filesystem if git rm didn't work
// Also remove from filesystem if git rm didn't work (or wasn't available)
if err := os.Remove(fullPath); err == nil {
if debug {
fmt.Fprintf(os.Stderr, "Removed backup file: %s\n", entry.Name())
@@ -139,14 +149,17 @@ func cleanupMergeArtifacts(outputPath string, debug bool) {
}
// 2. Run git clean -f in .beads/ directory to remove untracked files
cleanCmd := exec.Command("git", "clean", "-f")
cleanCmd.Dir = beadsDir
if debug {
cleanCmd.Stderr = os.Stderr
cleanCmd.Stdout = os.Stderr
fmt.Fprintf(os.Stderr, "Running: git clean -f in %s\n", beadsDir)
// (only if RepoContext available)
if rcErr == nil {
cleanCmd := rc.GitCmd(ctx, "clean", "-f")
cleanCmd.Dir = beadsDir // Override to target .beads/ subdirectory specifically
if debug {
cleanCmd.Stderr = os.Stderr
cleanCmd.Stdout = os.Stderr
fmt.Fprintf(os.Stderr, "Running: git clean -f in %s\n", beadsDir)
}
_ = cleanCmd.Run() // Ignore errors, git clean may fail in some contexts
}
_ = cleanCmd.Run() // Ignore errors, git clean may fail in some contexts
if debug {
fmt.Fprintf(os.Stderr, "Cleanup complete\n\n")