fix(git): fetch origin after configuring refspec for bare clones (#384)
Bare clones don't have refs/remotes/origin/* populated by default.
The configureRefspec fix (a91e6cd6) set up the fetch config but didn't
actually run a fetch, leaving origin/main unavailable.
This caused polecat worktree creation to fail with:
fatal: invalid reference: origin/main
Fixes:
1. Add git fetch after configureRefspec in bare clone setup
2. Add fetch before polecat worktree creation (ensures latest code)
The second fix matches RepairWorktreeWithOptions which already had a fetch.
Related: #286
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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/<default-branch> to ensure we start from the rig's configured branch
|
||||
defaultBranch := "main"
|
||||
|
||||
Reference in New Issue
Block a user