diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go index 99229b12b05..d8c170bcb2a 100644 --- a/pkg/commands/git_commands/branch.go +++ b/pkg/commands/git_commands/branch.go @@ -118,6 +118,16 @@ func (self *BranchCommands) LocalDelete(branch string, force bool) error { return self.cmd.New(cmdArgs).Run() } +// LocalDelete delete many branches locally +func (self *BranchCommands) LocalDeleteMany(branches []string, force bool) error { + cmdArgs := NewGitCmd("branch"). + ArgIfElse(force, "-D", "-d"). + Arg(branches...). + ToArgv() + + return self.cmd.New(cmdArgs).Run() +} + // Checkout checks out a branch (or commit), with --force if you set the force arg to true type CheckoutOptions struct { Force bool diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index b02a959f531..a1d260519ce 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -387,6 +387,7 @@ type KeybindingUniversalConfig struct { Confirm string `yaml:"confirm"` ConfirmInEditor string `yaml:"confirmInEditor"` Remove string `yaml:"remove"` + RemoveMany string `yaml:"removeMany"` New string `yaml:"new"` Edit string `yaml:"edit"` OpenFile string `yaml:"openFile"` @@ -829,6 +830,7 @@ func GetDefaultConfig() *UserConfig { Confirm: "", ConfirmInEditor: "", Remove: "d", + RemoveMany: "", New: "n", Edit: "e", OpenFile: "o", diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go index a97168fc15f..e54e6d7bb7b 100644 --- a/pkg/gui/controllers/branches_controller.go +++ b/pkg/gui/controllers/branches_controller.go @@ -98,6 +98,15 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty OpensMenu: true, DisplayOnScreen: true, }, + { + Key: opts.GetKey(opts.Config.Universal.RemoveMany), + Handler: self.withItem(self.deleteMultiple), + GetDisabledReason: self.require(self.itemsSelected(self.branchesAreReal)), + Description: self.c.Tr.Delete, + Tooltip: self.c.Tr.BranchDeleteTooltip, + OpensMenu: true, + DisplayOnScreen: true, + }, { Key: opts.GetKey(opts.Config.Branches.RebaseBranch), Handler: opts.Guards.OutsideFilterMode(self.withItem(self.rebase)), @@ -524,6 +533,10 @@ func (self *BranchesController) localDelete(branch *models.Branch) error { return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branch) } +func (self *BranchesController) localDeleteMany(branches []*models.Branch) error { + return self.c.Helpers().BranchesHelper.ConfirmLocalDeleteMany(branches) +} + func (self *BranchesController) remoteDelete(branch *models.Branch) error { return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch) } @@ -532,6 +545,37 @@ func (self *BranchesController) localAndRemoteDelete(branch *models.Branch) erro return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branch) } +func (self *BranchesController) deleteMultiple(branch *models.Branch) error { + checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef() + selectedBranches, _, _ := self.context().GetSelectedItems() + + localDeleteItems := &types.MenuItem{ + Label: self.c.Tr.DeleteLocalBranch, + Key: 'c', + OnPress: func() error { + return self.localDeleteMany(selectedBranches) + }, + } + + for _, branch := range selectedBranches { + if checkedOutBranch.Name == branch.Name { + localDeleteItems.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch} + } + } + + menuTitle := utils.ResolvePlaceholderString( + self.c.Tr.DeleteBranchTitle, + map[string]string{ + "selectedBranchName": branch.Name, + }, + ) + + return self.c.Menu(types.CreateMenuOptions{ + Title: menuTitle, + Items: []*types.MenuItem{localDeleteItems}, + }) +} + func (self *BranchesController) delete(branch *models.Branch) error { checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef() @@ -787,6 +831,16 @@ func (self *BranchesController) branchIsReal(branch *models.Branch) *types.Disab return nil } +func (self *BranchesController) branchesAreReal(branches []*models.Branch) *types.DisabledReason { + for _, branch := range branches { + if !branch.IsRealBranch() { + return &types.DisabledReason{Text: self.c.Tr.SelectedItemIsNotABranch} + } + } + + return nil +} + func (self *BranchesController) notMergingIntoYourself(branch *models.Branch) *types.DisabledReason { selectedBranchName := branch.Name checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go index fdd72e18870..d413e384f6d 100644 --- a/pkg/gui/controllers/helpers/branches_helper.go +++ b/pkg/gui/controllers/helpers/branches_helper.go @@ -23,6 +23,25 @@ func NewBranchesHelper(c *HelperCommon, worktreeHelper *WorktreeHelper) *Branche } } +func (self *BranchesHelper) ConfirmLocalDeleteMany(branches []*models.Branch) error { + branchNames := make([]string, len(branches)) + + for i, branch := range branches { + branchNames[i] = branch.Name + } + doDelete := func() error { + return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error { + self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch) + if err := self.c.Git().Branch.LocalDeleteMany(branchNames, true); err != nil { + return err + } + return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) + }) + } + + return doDelete() +} + func (self *BranchesHelper) ConfirmLocalDelete(branch *models.Branch) error { if self.checkedOutByOtherWorktree(branch) { return self.promptWorktreeBranchDelete(branch)