From d6016d628651f13ec1bee215c6017a057a645b66 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 30 Jun 2026 18:52:56 +0200 Subject: [PATCH] Extract reusable branch-deletion helpers Pull the merged-check-and-force-warning step and the actual git deletion out of ConfirmLocalDelete and ConfirmLocalAndRemoteDelete into helpers, so that the upcoming worktree-aware delete flows can reuse them instead of duplicating the logic. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../controllers/helpers/branches_helper.go | 106 +++++++++--------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go index ccc9d33ac..3e6c50b58 100644 --- a/pkg/gui/controllers/helpers/branches_helper.go +++ b/pkg/gui/controllers/helpers/branches_helper.go @@ -35,17 +35,9 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error return self.promptWorktreeBranchDelete(branches[0]) } - allBranchesMerged, err := self.allBranchesMerged(branches) - if err != nil { - return err - } - - doDelete := func() error { + return self.confirmForceIfUnmerged(branches, func() error { return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch) - self.logBranchHashes(branches) - branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name }) - if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil { + if err := self.doDeleteLocalBranches(branches); err != nil { return err } @@ -53,34 +45,7 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) return nil }) - } - - if allBranchesMerged { - return doDelete() - } - - title := self.c.Tr.ForceDeleteBranchTitle - var message string - if len(branches) == 1 { - message = utils.ResolvePlaceholderString( - self.c.Tr.ForceDeleteBranchMessage, - map[string]string{ - "selectedBranchName": branches[0].Name, - }, - ) - } else { - message = self.c.Tr.ForceDeleteBranchesMessage - } - - self.c.Confirm(types.ConfirmOpts{ - Title: title, - Prompt: message, - HandleConfirm: func() error { - return doDelete() - }, }) - - return nil } func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteBranch, resetRemoteBranchesSelection bool) error { @@ -169,19 +134,7 @@ func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branc Prompt: prompt, HandleConfirm: func() error { return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error { - // Delete the remote branches first so that we keep the local ones - // in case of failure - remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch { - return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote} - }) - if err := self.deleteRemoteBranches(remoteBranches, task); err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch) - self.logBranchHashes(branches) - branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name }) - if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil { + if err := self.doDeleteLocalAndRemoteBranches(task, branches); err != nil { return err } @@ -244,6 +197,59 @@ func (self *BranchesHelper) promptWorktreeBranchDelete(selectedBranch *models.Br }) } +// confirmForceIfUnmerged runs onConfirm directly if all the branches are fully +// merged, and otherwise shows the force-delete warning first and runs onConfirm +// when the user confirms it. +func (self *BranchesHelper) confirmForceIfUnmerged(branches []*models.Branch, onConfirm func() error) error { + allBranchesMerged, err := self.allBranchesMerged(branches) + if err != nil { + return err + } + if allBranchesMerged { + return onConfirm() + } + + var message string + if len(branches) == 1 { + message = utils.ResolvePlaceholderString( + self.c.Tr.ForceDeleteBranchMessage, + map[string]string{ + "selectedBranchName": branches[0].Name, + }, + ) + } else { + message = self.c.Tr.ForceDeleteBranchesMessage + } + + self.c.Confirm(types.ConfirmOpts{ + Title: self.c.Tr.ForceDeleteBranchTitle, + Prompt: message, + HandleConfirm: onConfirm, + }) + + return nil +} + +func (self *BranchesHelper) doDeleteLocalBranches(branches []*models.Branch) error { + self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch) + self.logBranchHashes(branches) + branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name }) + return self.c.Git().Branch.LocalDelete(branchNames, true) +} + +func (self *BranchesHelper) doDeleteLocalAndRemoteBranches(task gocui.Task, branches []*models.Branch) error { + // Delete the remote branches first so that we keep the local ones + // in case of failure + remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch { + return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote} + }) + if err := self.deleteRemoteBranches(remoteBranches, task); err != nil { + return err + } + + return self.doDeleteLocalBranches(branches) +} + func (self *BranchesHelper) allBranchesMerged(branches []*models.Branch) (bool, error) { allBranchesMerged := true for _, branch := range branches {