diff --git a/internal/git/git.go b/internal/git/git.go index ead1fc1d..4fb0eac5 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -194,6 +194,12 @@ func configureRefspec(repoPath string) error { if err := cmd.Run(); err != nil { return fmt.Errorf("configuring refspec: %s", strings.TrimSpace(stderr.String())) } + // Fetch to populate refs/remotes/origin/* so worktrees can use origin/main + fetchCmd := exec.Command("git", "-C", repoPath, "fetch", "origin") + fetchCmd.Stderr = &stderr + if err := fetchCmd.Run(); err != nil { + return fmt.Errorf("fetching origin: %s", strings.TrimSpace(stderr.String())) + } return nil } diff --git a/internal/git/git_test.go b/internal/git/git_test.go index d7d4bacd..3cc58834 100644 --- a/internal/git/git_test.go +++ b/internal/git/git_test.go @@ -395,3 +395,96 @@ func TestCheckConflicts_WithConflict(t *testing.T) { t.Error("expected clean working directory after CheckConflicts") } } + +// TestCloneBareHasOriginRefs verifies that after CloneBare, origin/* refs +// are available for worktree creation. This was broken before the fix: +// bare clones had refspec configured but no fetch was run, so origin/main +// didn't exist and WorktreeAddFromRef("origin/main") failed. +// +// Related: GitHub issue #286 +func TestCloneBareHasOriginRefs(t *testing.T) { + tmp := t.TempDir() + + // Create a "remote" repo with a commit on main + remoteDir := filepath.Join(tmp, "remote") + if err := os.MkdirAll(remoteDir, 0755); err != nil { + t.Fatalf("mkdir remote: %v", err) + } + cmd := exec.Command("git", "init") + cmd.Dir = remoteDir + if err := cmd.Run(); err != nil { + t.Fatalf("git init: %v", err) + } + cmd = exec.Command("git", "config", "user.email", "test@test.com") + cmd.Dir = remoteDir + _ = cmd.Run() + cmd = exec.Command("git", "config", "user.name", "Test User") + cmd.Dir = remoteDir + _ = cmd.Run() + + // Create initial commit + readmeFile := filepath.Join(remoteDir, "README.md") + if err := os.WriteFile(readmeFile, []byte("# Test\n"), 0644); err != nil { + t.Fatalf("write file: %v", err) + } + cmd = exec.Command("git", "add", ".") + cmd.Dir = remoteDir + _ = cmd.Run() + cmd = exec.Command("git", "commit", "-m", "initial") + cmd.Dir = remoteDir + if err := cmd.Run(); err != nil { + t.Fatalf("git commit: %v", err) + } + + // Get the main branch name (main or master depending on git version) + cmd = exec.Command("git", "branch", "--show-current") + cmd.Dir = remoteDir + out, err := cmd.Output() + if err != nil { + t.Fatalf("git branch --show-current: %v", err) + } + mainBranch := string(out[:len(out)-1]) // trim newline + + // Clone as bare repo using our CloneBare function + bareDir := filepath.Join(tmp, "bare.git") + g := NewGit(tmp) + if err := g.CloneBare(remoteDir, bareDir); err != nil { + t.Fatalf("CloneBare: %v", err) + } + + // Verify origin/main exists (this was the bug - it didn't exist before the fix) + bareGit := NewGitWithDir(bareDir, "") + cmd = exec.Command("git", "branch", "-r") + cmd.Dir = bareDir + out, err = cmd.Output() + if err != nil { + t.Fatalf("git branch -r: %v", err) + } + + originMain := "origin/" + mainBranch + if !stringContains(string(out), originMain) { + t.Errorf("expected %q in remote branches, got: %s", originMain, out) + } + + // Verify WorktreeAddFromRef succeeds with origin/main + // This is what polecat creation does + worktreePath := filepath.Join(tmp, "worktree") + if err := bareGit.WorktreeAddFromRef(worktreePath, "test-branch", originMain); err != nil { + t.Errorf("WorktreeAddFromRef(%q) failed: %v", originMain, err) + } + + // Verify the worktree was created and has the expected file + worktreeReadme := filepath.Join(worktreePath, "README.md") + if _, err := os.Stat(worktreeReadme); err != nil { + t.Errorf("expected README.md in worktree: %v", err) + } +} + +func stringContains(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/internal/polecat/manager.go b/internal/polecat/manager.go index 13b547f5..8490a44f 100644 --- a/internal/polecat/manager.go +++ b/internal/polecat/manager.go @@ -261,6 +261,12 @@ func (m *Manager) AddWithOptions(name string, opts AddOptions) (*Polecat, error) return nil, fmt.Errorf("finding repo base: %w", err) } + // Fetch latest from origin to ensure worktree starts from up-to-date code + if err := repoGit.Fetch("origin"); err != nil { + // Non-fatal - proceed with potentially stale code + fmt.Printf("Warning: could not fetch origin: %v\n", err) + } + // Determine the start point for the new worktree // Use origin/ to ensure we start from the rig's configured branch defaultBranch := "main"