fix: prune stale workspace worktree registrations
This commit is contained in:
parent
d42bab79ad
commit
bf6a470903
|
|
@ -611,6 +611,14 @@ func (w *Workspace) addWorktree(ctx context.Context, repo, path, branch, baseBra
|
|||
return err
|
||||
}
|
||||
if _, err := w.run(ctx, w.binary, worktreeAddNewBranchArgs(repo, branch, path, baseRef)...); err != nil {
|
||||
if isMissingRegisteredWorktreeError(err) {
|
||||
if pruneErr := w.pruneWorktrees(ctx, repo); pruneErr != nil {
|
||||
return fmt.Errorf("gitworktree: worktree add branch %q from %q: recover stale registration: %w", branch, baseRef, pruneErr)
|
||||
}
|
||||
if _, retryErr := w.run(ctx, w.binary, worktreeAddNewBranchArgs(repo, branch, path, baseRef)...); retryErr == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("gitworktree: worktree add branch %q from %q: %w", branch, baseRef, err)
|
||||
}
|
||||
return nil
|
||||
|
|
@ -681,6 +689,14 @@ func (w *Workspace) createWorkspaceProjectRepo(ctx context.Context, repo workspa
|
|||
return "", err
|
||||
}
|
||||
if _, err := w.run(ctx, w.binary, worktreeAddNewBranchArgs(repo.repoPath, branch, repo.outputPath, baseRef)...); err != nil {
|
||||
if isMissingRegisteredWorktreeError(err) {
|
||||
if pruneErr := w.pruneWorktrees(ctx, repo.repoPath); pruneErr != nil {
|
||||
return "", fmt.Errorf("gitworktree: workspace repo %q worktree add branch %q from %q: recover stale registration: %w", repo.name, branch, baseRef, pruneErr)
|
||||
}
|
||||
if _, retryErr := w.run(ctx, w.binary, worktreeAddNewBranchArgs(repo.repoPath, branch, repo.outputPath, baseRef)...); retryErr == nil {
|
||||
return baseSHA, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("gitworktree: workspace repo %q worktree add branch %q from %q: %w", repo.name, branch, baseRef, err)
|
||||
}
|
||||
return baseSHA, nil
|
||||
|
|
@ -688,8 +704,8 @@ func (w *Workspace) createWorkspaceProjectRepo(ctx context.Context, repo workspa
|
|||
|
||||
func (w *Workspace) forceDestroyPath(ctx context.Context, repo, path string) error {
|
||||
_, _ = w.run(ctx, w.binary, worktreeForceRemoveArgs(repo, path)...)
|
||||
if _, err := w.run(ctx, w.binary, worktreePruneArgs(repo)...); err != nil {
|
||||
return fmt.Errorf("gitworktree: worktree prune: %w", err)
|
||||
if err := w.pruneWorktrees(ctx, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return fmt.Errorf("gitworktree: force remove path %q: %w", path, err)
|
||||
|
|
@ -697,6 +713,17 @@ func (w *Workspace) forceDestroyPath(ctx context.Context, repo, path string) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (w *Workspace) pruneWorktrees(ctx context.Context, repo string) error {
|
||||
if _, err := w.run(ctx, w.binary, worktreePruneArgs(repo)...); err != nil {
|
||||
return fmt.Errorf("gitworktree: worktree prune: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isMissingRegisteredWorktreeError(err error) bool {
|
||||
return strings.Contains(err.Error(), "is a missing but already registered worktree")
|
||||
}
|
||||
|
||||
func (w *Workspace) revParse(ctx context.Context, repo, ref string) (string, error) {
|
||||
out, err := w.run(ctx, w.binary, "-C", repo, "rev-parse", "--verify", ref)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -211,6 +211,68 @@ func TestCreateReusesRegisteredWorktreeAtExpectedPath(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCreateWorkspaceProjectRepoPrunesStaleRegisteredWorktree(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
repo := t.TempDir()
|
||||
output := filepath.Join(root, "proj", "orchestrator", "proj-orchestrator", "api")
|
||||
ws, err := New(Options{ManagedRoot: root, RepoResolver: StaticRepoResolver{"proj": repo}})
|
||||
if err != nil {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
exitErr := exec.Command("sh", "-c", "exit 1").Run()
|
||||
if exitErr == nil {
|
||||
t.Fatal("expected exit error")
|
||||
}
|
||||
var calls []string
|
||||
addAttempts := 0
|
||||
ws.run = func(_ context.Context, binary string, args ...string) ([]byte, error) {
|
||||
joined := strings.Join(args, " ")
|
||||
calls = append(calls, joined)
|
||||
switch {
|
||||
case strings.Contains(joined, "rev-parse --verify --quiet origin/feature/test"):
|
||||
return nil, commandError{args: append([]string{binary}, args...), err: exitErr}
|
||||
case strings.Contains(joined, "rev-parse --verify --quiet origin/main"):
|
||||
return nil, nil
|
||||
case strings.Contains(joined, "rev-parse --verify origin/main"):
|
||||
return []byte("abc123\n"), nil
|
||||
case strings.Contains(joined, "worktree add -b feature/test "+output+" origin/main"):
|
||||
addAttempts++
|
||||
if addAttempts == 1 {
|
||||
return nil, commandError{
|
||||
args: append([]string{binary}, args...),
|
||||
output: "Preparing worktree (new branch 'feature/test')\nfatal: '" + output + "' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear",
|
||||
err: errors.New("exit status 128"),
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
case strings.Contains(joined, "worktree prune"):
|
||||
return nil, nil
|
||||
default:
|
||||
t.Fatalf("unexpected git invocation: %v", args)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
baseSHA, err := ws.createWorkspaceProjectRepo(context.Background(), workspaceProjectRepo{
|
||||
name: "api",
|
||||
repoPath: repo,
|
||||
outputPath: output,
|
||||
}, "feature/test")
|
||||
if err != nil {
|
||||
t.Fatalf("createWorkspaceProjectRepo: %v", err)
|
||||
}
|
||||
if baseSHA != "abc123" {
|
||||
t.Fatalf("baseSHA = %q, want abc123", baseSHA)
|
||||
}
|
||||
if addAttempts != 2 {
|
||||
t.Fatalf("add attempts = %d, want 2", addAttempts)
|
||||
}
|
||||
got := strings.Join(calls, "\n")
|
||||
if !strings.Contains(got, "worktree prune") {
|
||||
t.Fatalf("calls missing worktree prune:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateConfigRejectsPathEscapingIDs covers review item RB: filepath.Join
|
||||
// in managedPath cleans `..` segments before validateManagedPath sees them, so a
|
||||
// session id of "../other" would stay inside managedRoot while jumping projects.
|
||||
|
|
|
|||
Loading…
Reference in New Issue