fix: polecat workers start from origin/<default-branch> when recycled

When a polecat worker is recycled via RecreateWithOptions, it now starts
from the latest fetched origin/<default-branch> instead of the stale HEAD.

Previously, `WorktreeAdd` created branches from the current HEAD, but after
fetching, HEAD still pointed to old commits. The new `WorktreeAddFromRef`
method allows specifying a start point (e.g., "origin/main").

Fixes #101
This commit is contained in:
Olivier Debeuf De Rijcker
2026-01-04 22:15:30 +01:00
parent 386dbf85fb
commit 569cb182a6
2 changed files with 86 additions and 24 deletions

View File

@@ -40,6 +40,12 @@ func (g *Git) WorkDir() string {
return g.workDir
}
// IsRepo returns true if the workDir is a git repository.
func (g *Git) IsRepo() bool {
_, err := g.run("rev-parse", "--git-dir")
return err == nil
}
// run executes a git command and returns stdout.
func (g *Git) run(args ...string) (string, error) {
// If gitDir is set (bare repo), prepend --git-dir flag
@@ -99,6 +105,18 @@ func (g *Git) Clone(url, dest string) error {
return nil
}
// CloneWithReference clones a repository using a local repo as an object reference.
// This saves disk by sharing objects without changing remotes.
func (g *Git) CloneWithReference(url, dest, reference string) error {
cmd := exec.Command("git", "clone", "--reference-if-able", reference, url, dest)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return g.wrapError(err, stderr.String(), []string{"clone", "--reference-if-able", url})
}
return nil
}
// CloneBare clones a repository as a bare repo (no working directory).
// This is used for the shared repo architecture where all worktrees share a single git database.
func (g *Git) CloneBare(url, dest string) error {
@@ -111,6 +129,17 @@ func (g *Git) CloneBare(url, dest string) error {
return nil
}
// CloneBareWithReference clones a bare repository using a local repo as an object reference.
func (g *Git) CloneBareWithReference(url, dest, reference string) error {
cmd := exec.Command("git", "clone", "--bare", "--reference-if-able", reference, url, dest)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return g.wrapError(err, stderr.String(), []string{"clone", "--bare", "--reference-if-able", url})
}
return nil
}
// Checkout checks out the given ref.
func (g *Git) Checkout(ref string) error {
_, err := g.run("checkout", ref)
@@ -226,6 +255,36 @@ func (g *Git) DefaultBranch() string {
return "main"
}
// RemoteDefaultBranch returns the default branch from the remote (origin).
// This is useful in worktrees where HEAD may not reflect the repo's actual default.
// Checks origin/HEAD first, then falls back to checking if master/main exists.
// Returns "main" as final fallback.
func (g *Git) RemoteDefaultBranch() string {
// Try to get from origin/HEAD symbolic ref
out, err := g.run("symbolic-ref", "refs/remotes/origin/HEAD")
if err == nil && out != "" {
// Returns refs/remotes/origin/main -> extract branch name
parts := strings.Split(out, "/")
if len(parts) > 0 {
return parts[len(parts)-1]
}
}
// Fallback: check if origin/master exists
_, err = g.run("rev-parse", "--verify", "origin/master")
if err == nil {
return "master"
}
// Fallback: check if origin/main exists
_, err = g.run("rev-parse", "--verify", "origin/main")
if err == nil {
return "main"
}
return "main" // final fallback
}
// HasUncommittedChanges returns true if there are uncommitted changes.
func (g *Git) HasUncommittedChanges() (bool, error) {
status, err := g.Status()
@@ -464,6 +523,13 @@ func (g *Git) WorktreeAdd(path, branch string) error {
return err
}
// WorktreeAddFromRef creates a new worktree at the given path with a new branch
// starting from the specified ref (e.g., "origin/main").
func (g *Git) WorktreeAddFromRef(path, branch, startPoint string) error {
_, err := g.run("worktree", "add", "-b", branch, path, startPoint)
return err
}
// WorktreeAddDetached creates a new worktree at the given path with a detached HEAD.
func (g *Git) WorktreeAddDetached(path, ref string) error {
_, err := g.run("worktree", "add", "--detach", path, ref)