Skip to content

Commit 024b726

Browse files
Added message for branch deletion error due to worktrees (#660)
testing ss : AV sync <img width="942" height="605" alt="image" src="https://github.com/user-attachments/assets/b5f0e594-2182-4881-8add-b612a2303b85" /> ------------- AV reorder: ![Uploading image.png…]()
1 parent 38c5608 commit 024b726

File tree

2 files changed

+72
-13
lines changed

2 files changed

+72
-13
lines changed

internal/git/gitui/prune.go

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package gitui
33
import (
44
"context"
55
"fmt"
6+
"os/exec"
67
"strings"
78

89
"emperror.dev/errors"
910
"github.com/aviator-co/av/internal/git"
1011
"github.com/aviator-co/av/internal/meta"
1112
"github.com/aviator-co/av/internal/utils/colors"
13+
"github.com/aviator-co/av/internal/utils/errutils"
1214
"github.com/aviator-co/av/internal/utils/uiutils"
1315
"github.com/charmbracelet/bubbles/help"
1416
"github.com/charmbracelet/bubbles/spinner"
@@ -215,24 +217,71 @@ func (vm *PruneBranchModel) runDelete() tea.Msg {
215217
return err
216218
}
217219

220+
// Always restore the checked out state, even if deletion fails.
221+
// Use a variable to capture any deletion error and restore HEAD before returning.
222+
var deletionErr error
223+
var worktreeBranches []string
224+
218225
// Delete in the reverse order just in case. The targetBranches are sorted in the parent ->
219226
// child order.
220227
for i := len(vm.deleteCandidates) - 1; i >= 0; i-- {
221228
branch := vm.deleteCandidates[i]
222-
if _, err := vm.repo.Git(context.Background(), "branch", "-D", branch.branch.Short()); err != nil {
223-
return errors.Errorf("cannot delete merged branch %q: %v", branch.branch.Short(), err)
229+
if err := vm.repo.BranchDelete(context.Background(), branch.branch.Short()); err != nil {
230+
// Check if the error is due to the branch being checked out in a worktree
231+
if exiterr, ok := errutils.As[*exec.ExitError](err); ok &&
232+
strings.Contains(string(exiterr.Stderr), "used by worktree") {
233+
// Collect worktree branches but continue deleting others
234+
worktreeBranches = append(worktreeBranches, branch.branch.Short())
235+
continue
236+
} else {
237+
// Other errors are fatal
238+
deletionErr = errors.Errorf("cannot delete merged branch %q: %v", branch.branch.Short(), err)
239+
break
240+
}
224241
}
225242
tx := vm.db.WriteTx()
226243
tx.DeleteBranch(branch.branch.Short())
227244
if err := tx.Commit(); err != nil {
228-
return err
245+
deletionErr = err
246+
break
229247
}
230248
}
231249

232-
// Restore the checked out state.
250+
// Restore the checked out state before returning any error.
233251
if err := vm.CheckoutInitialState(); err != nil {
234-
return err
252+
// If we have both a deletion error and a checkout error, prioritize the checkout error
253+
// as it's more critical (leaves user in a bad state).
254+
if deletionErr != nil {
255+
return errors.Errorf("failed to restore branch after deletion error: %v (original error: %v)", err, deletionErr)
256+
}
257+
return errors.Errorf("failed to restore branch: %v", err)
258+
}
259+
260+
// Build worktree error message if any branches couldn't be deleted
261+
var worktreeErr error
262+
if len(worktreeBranches) > 0 {
263+
var sb strings.Builder
264+
sb.WriteString("Could not delete the following branches (checked out in worktrees):\n")
265+
for _, br := range worktreeBranches {
266+
fmt.Fprintf(&sb, "- %s\n", br)
267+
}
268+
sb.WriteString("Use 'git worktree list' to see all worktrees\n")
269+
sb.WriteString("Remove worktrees with 'git worktree remove <path>' or checkout a different branch in those worktrees\n")
270+
worktreeErr = errors.New(sb.String())
271+
}
272+
273+
// Return both fatal deletion errors and worktree warnings
274+
if deletionErr != nil {
275+
if worktreeErr != nil {
276+
return errors.Errorf("fatal error during branch deletion: %v\n\n%v", deletionErr, worktreeErr)
277+
}
278+
return deletionErr
235279
}
280+
281+
if worktreeErr != nil {
282+
return worktreeErr
283+
}
284+
236285
return &PruneBranchProgress{deletionDone: true}
237286
}
238287

internal/reorder/deletebranch.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,24 @@ func (d DeleteBranchCmd) Execute(ctx *Context) error {
4444
Args: []string{"branch", "--delete", "--force", d.Name},
4545
ExitError: true,
4646
})
47-
if exiterr, ok := errutils.As[*exec.ExitError](err); ok &&
48-
strings.Contains(string(exiterr.Stderr), "not found") {
49-
_, _ = fmt.Fprint(os.Stderr,
50-
colors.Warning("Branch "), colors.UserInput(d.Name),
51-
colors.Warning(" was already deleted.\n"),
52-
)
53-
return nil
54-
} else if err != nil {
47+
if exiterr, ok := errutils.As[*exec.ExitError](err); ok {
48+
stderr := string(exiterr.Stderr)
49+
if strings.Contains(stderr, "not found") {
50+
_, _ = fmt.Fprint(os.Stderr,
51+
colors.Warning("Branch "), colors.UserInput(d.Name),
52+
colors.Warning(" was already deleted.\n"),
53+
)
54+
return nil
55+
}
56+
if strings.Contains(stderr, "used by worktree") {
57+
msg := fmt.Sprintf("cannot delete branch %q: it is checked out in a worktree\n", d.Name)
58+
msg += "Use 'git worktree list' to see all worktrees\n"
59+
msg += "Remove the worktree with 'git worktree remove <path>' or checkout a different branch in that worktree\n"
60+
_, _ = fmt.Fprint(os.Stderr, msg)
61+
return nil
62+
}
63+
}
64+
if err != nil {
5565
return err
5666
}
5767

0 commit comments

Comments
 (0)