diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go index 5566dc40c41..821573b1beb 100644 --- a/pkg/gui/controllers/helpers/branches_helper.go +++ b/pkg/gui/controllers/helpers/branches_helper.go @@ -238,8 +238,57 @@ func (self *BranchesHelper) promptWorktreeBranchDelete(selectedBranch *models.Br return self.worktreeHelper.Remove(worktree, false) }, }, + { + Label: self.c.Tr.RemoveWorktreeAndBranch, + OnPress: func() error { + return self.removeWorktreeAndBranch(selectedBranch, worktree, false) + }, + }, + }, + }) +} + +func (self *BranchesHelper) removeWorktreeAndBranch(branch *models.Branch, worktree *models.Worktree, force bool) error { + title := self.c.Tr.RemoveWorktreeAndBranchTitle + var templateStr string + if force { + templateStr = self.c.Tr.ForceRemoveWorktreePrompt + } else { + templateStr = self.c.Tr.RemoveWorktreeAndBranchPrompt + } + message := utils.ResolvePlaceholderString( + templateStr, + map[string]string{ + "worktreeName": worktree.Name, + "branchName": branch.Name, + }, + ) + + self.c.Confirm(types.ConfirmOpts{ + Title: title, + Prompt: message, + HandleConfirm: func() error { + return self.c.WithWaitingStatus(self.c.Tr.RemovingWorktree, func(gocui.Task) error { + self.c.LogAction(self.c.Tr.RemoveWorktreeAndBranch) + if err := self.c.Git().Worktree.Delete(worktree.Path, force); err != nil { + if !force && self.worktreeHelper.removeShouldRetryWithForce(err) { + return self.removeWorktreeAndBranch(branch, worktree, true) + } + return err + } + + if err := self.c.Git().Branch.LocalDelete([]string{branch.Name}, true); err != nil { + return err + } + + self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}}) + return nil + }) }, }) + + self.c.Contexts().Branches.CollapseRangeSelectionToTop() + return nil } func (self *BranchesHelper) allBranchesMerged(branches []*models.Branch) (bool, error) { diff --git a/pkg/gui/controllers/helpers/worktree_helper.go b/pkg/gui/controllers/helpers/worktree_helper.go index 671743fe626..1667daec5ca 100644 --- a/pkg/gui/controllers/helpers/worktree_helper.go +++ b/pkg/gui/controllers/helpers/worktree_helper.go @@ -186,17 +186,12 @@ func (self *WorktreeHelper) Remove(worktree *models.Worktree, force bool) error return self.c.WithWaitingStatus(self.c.Tr.RemovingWorktree, func(gocui.Task) error { self.c.LogAction(self.c.Tr.RemoveWorktree) if err := self.c.Git().Worktree.Delete(worktree.Path, force); err != nil { - errMessage := err.Error() - if !strings.Contains(errMessage, "--force") && - !strings.Contains(errMessage, "fatal: working trees containing submodules cannot be moved or removed") { - return err - } - - if !force { + if !force && self.removeShouldRetryWithForce(err) { return self.Remove(worktree, true) } return err } + self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}}) return nil }) @@ -206,6 +201,12 @@ func (self *WorktreeHelper) Remove(worktree *models.Worktree, force bool) error return nil } +func (self *WorktreeHelper) removeShouldRetryWithForce(err error) bool { + errMessage := err.Error() + return strings.Contains(errMessage, "--force") || + strings.Contains(errMessage, "fatal: working trees containing submodules cannot be moved or removed") +} + func (self *WorktreeHelper) Detach(worktree *models.Worktree) error { return self.c.WithWaitingStatus(self.c.Tr.DetachingWorktree, func(gocui.Task) error { self.c.LogAction(self.c.Tr.RemovingWorktree) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 970afd498be..78b0b430589 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -848,12 +848,16 @@ type TranslationSet struct { Switching string RemoveWorktree string RemoveWorktreeTitle string + RemoveWorktreeAndBranch string + RemoveWorktreeAndBranchTitle string DetachWorktree string DetachingWorktree string WorktreesTitle string WorktreeTitle string RemoveWorktreePrompt string + RemoveWorktreeAndBranchPrompt string ForceRemoveWorktreePrompt string + ForceRemoveWorktreeAndBranchPrompt string RemovingWorktree string AddingWorktree string CantDeleteCurrentWorktree string @@ -1948,8 +1952,12 @@ func EnglishTranslationSet() *TranslationSet { Switching: "Switching", RemoveWorktree: "Remove worktree", RemoveWorktreeTitle: "Remove worktree", + RemoveWorktreeAndBranch: "Remove worktree and its branch", + RemoveWorktreeAndBranchTitle: "Remove worktree and branch", RemoveWorktreePrompt: "Are you sure you want to remove worktree '{{.worktreeName}}'?", + RemoveWorktreeAndBranchPrompt: "Are you sure you want to remove worktree '{{.worktreeName}}' and its branch '{{.branchName}}'?", ForceRemoveWorktreePrompt: "'{{.worktreeName}}' contains modified or untracked files, or submodules (or all of these). Are you sure you want to remove it?", + ForceRemoveWorktreeAndBranchPrompt: "'{{.worktreeName}}' contains modified or untracked files, or submodules (or all of these). Are you sure you want to remove the worktree and its branch?", RemovingWorktree: "Deleting worktree", DetachWorktree: "Detach worktree", DetachingWorktree: "Detaching worktree",