@@ -3,12 +3,14 @@ package gitui
33import (
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
0 commit comments