fix(worktree): add -f flag to handle missing but registered state (#609)
Problem: When the daemon auto-sync runs with --auto-commit --auto-push, the sync branch pull operation consistently fails with: fatal: '.git/beads-worktrees/beads-metadata' is a missing but already registered worktree; use 'add -f' to override, or 'prune' or 'remove' to clear This occurs because: 1. Daemon creates worktree at .git/beads-worktrees/<branch> 2. Git registers it in .git/worktrees/<branch> 3. After the operation, worktree contents are removed 4. Git registration persists, pointing to the now-empty path 5. Subsequent CreateBeadsWorktree calls fail because os.Stat() returns error (path missing), so no cleanup happens, then git worktree add fails because git still has it registered Root cause: The git worktree add commands in CreateBeadsWorktree() did not use the -f (force) flag, which is needed to override the "missing but already registered" state. Solution: Add -f flag to both git worktree add commands (for existing branch and new branch cases). Per git documentation, -f overrides the safeguard that prevents creating a worktree when the path is already registered but missing. The existing git worktree prune call (line 30-32) was intended to handle this, but it runs before the path check and may not always clear the registration in time. The -f flag provides a robust fallback. Testing: - All existing worktree tests pass - Added regression test TestCreateBeadsWorktree_MissingButRegistered that simulates the exact issue #609 scenario Fixes #609 Co-authored-by: Charles P. Cross <cpdata@users.noreply.github.com>
This commit is contained in:
@@ -52,13 +52,15 @@ func (wm *WorktreeManager) CreateBeadsWorktree(branch, worktreePath string) erro
|
|||||||
branchExists := wm.branchExists(branch)
|
branchExists := wm.branchExists(branch)
|
||||||
|
|
||||||
// Create worktree without checking out files initially
|
// Create worktree without checking out files initially
|
||||||
|
// Use -f (force) to handle "missing but already registered" state (issue #609)
|
||||||
|
// This occurs when the worktree directory was deleted but git registration persists
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if branchExists {
|
if branchExists {
|
||||||
// Checkout existing branch
|
// Checkout existing branch
|
||||||
cmd = exec.Command("git", "worktree", "add", "--no-checkout", worktreePath, branch)
|
cmd = exec.Command("git", "worktree", "add", "-f", "--no-checkout", worktreePath, branch)
|
||||||
} else {
|
} else {
|
||||||
// Create new branch
|
// Create new branch
|
||||||
cmd = exec.Command("git", "worktree", "add", "--no-checkout", "-b", branch, worktreePath)
|
cmd = exec.Command("git", "worktree", "add", "-f", "--no-checkout", "-b", branch, worktreePath)
|
||||||
}
|
}
|
||||||
cmd.Dir = wm.repoPath
|
cmd.Dir = wm.repoPath
|
||||||
|
|
||||||
|
|||||||
@@ -1056,3 +1056,65 @@ func TestIsWorktree(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCreateBeadsWorktree_MissingButRegistered tests the issue #609 scenario where
|
||||||
|
// the worktree directory is deleted but git still has it registered in .git/worktrees/.
|
||||||
|
// The -f flag on git worktree add should handle this gracefully.
|
||||||
|
func TestCreateBeadsWorktree_MissingButRegistered(t *testing.T) {
|
||||||
|
repoPath, cleanup := setupTestRepo(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
wm := NewWorktreeManager(repoPath)
|
||||||
|
worktreePath := filepath.Join(t.TempDir(), "beads-worktree-gh609")
|
||||||
|
branch := "beads-metadata-gh609"
|
||||||
|
|
||||||
|
// Step 1: Create a worktree
|
||||||
|
if err := wm.CreateBeadsWorktree(branch, worktreePath); err != nil {
|
||||||
|
t.Fatalf("Initial CreateBeadsWorktree failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it exists and is registered
|
||||||
|
if _, err := os.Stat(worktreePath); os.IsNotExist(err) {
|
||||||
|
t.Fatal("Worktree was not created")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Manually delete the worktree directory (simulating the bug scenario)
|
||||||
|
// but leave the git registration in .git/worktrees/
|
||||||
|
if err := os.RemoveAll(worktreePath); err != nil {
|
||||||
|
t.Fatalf("Failed to remove worktree directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the directory is gone
|
||||||
|
if _, err := os.Stat(worktreePath); !os.IsNotExist(err) {
|
||||||
|
t.Fatal("Worktree directory should not exist after removal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify git still has it registered (this is the "missing but registered" state)
|
||||||
|
cmd := exec.Command("git", "worktree", "list", "--porcelain")
|
||||||
|
cmd.Dir = repoPath
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to list worktrees: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(output), "worktree") {
|
||||||
|
t.Log("Note: git worktree list shows no worktrees - prune may have run")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Try to recreate the worktree - this should succeed with -f flag (issue #609 fix)
|
||||||
|
// Without the fix, this would fail with:
|
||||||
|
// "fatal: '.git/beads-worktrees/...' is a missing but already registered worktree"
|
||||||
|
if err := wm.CreateBeadsWorktree(branch, worktreePath); err != nil {
|
||||||
|
t.Errorf("CreateBeadsWorktree failed for missing-but-registered worktree (issue #609): %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the worktree was recreated successfully
|
||||||
|
if _, err := os.Stat(worktreePath); os.IsNotExist(err) {
|
||||||
|
t.Error("Worktree was not recreated after issue #609 fix")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it's a valid worktree
|
||||||
|
valid, err := wm.isValidWorktree(worktreePath)
|
||||||
|
if err != nil || !valid {
|
||||||
|
t.Errorf("Recreated worktree should be valid: valid=%v, err=%v", valid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user