Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete range selection of branches #4073

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/commands/git_commands/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ func (self *BranchCommands) CurrentBranchName() (string, error) {
}

// LocalDelete delete branch locally
func (self *BranchCommands) LocalDelete(branch string, force bool) error {
func (self *BranchCommands) LocalDelete(branches []string, force bool) error {
cmdArgs := NewGitCmd("branch").
ArgIfElse(force, "-D", "-d").
Arg(branch).
Arg(branches...).
ToArgv()

return self.cmd.New(cmdArgs).Run()
Expand Down
31 changes: 26 additions & 5 deletions pkg/commands/git_commands/branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,36 +62,57 @@ func TestBranchNewBranch(t *testing.T) {

func TestBranchDeleteBranch(t *testing.T) {
type scenario struct {
testName string
force bool
runner *oscommands.FakeCmdObjRunner
test func(error)
testName string
branchNames []string
force bool
runner *oscommands.FakeCmdObjRunner
test func(error)
}

scenarios := []scenario{
{
"Delete a branch",
[]string{"test"},
false,
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test"}, "", nil),
func(err error) {
assert.NoError(t, err)
},
},
{
"Delete multiple branches",
[]string{"test1", "test2", "test3"},
false,
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test1", "test2", "test3"}, "", nil),
func(err error) {
assert.NoError(t, err)
},
},
{
"Force delete a branch",
[]string{"test"},
true,
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test"}, "", nil),
func(err error) {
assert.NoError(t, err)
},
},
{
"Force delete multiple branches",
[]string{"test1", "test2", "test3"},
true,
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test1", "test2", "test3"}, "", nil),
func(err error) {
assert.NoError(t, err)
},
},
}

for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner})

s.test(instance.LocalDelete("test", s.force))
s.test(instance.LocalDelete(s.branchNames, s.force))
s.runner.CheckForMissingCalls()
})
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/commands/git_commands/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string
return self.cmd.New(cmdArgs).Run()
}

func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchName string) error {
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchNames []string) error {
cmdArgs := NewGitCmd("push").
Arg(remoteName, "--delete", branchName).
Arg(remoteName, "--delete").
Arg(branchNames...).
ToArgv()

return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
Expand Down
82 changes: 55 additions & 27 deletions pkg/gui/controllers/branches_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.delete),
GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)),
Handler: self.withItems(self.delete),
GetDisabledReason: self.require(self.itemRangeSelected(self.branchesAreReal)),
Description: self.c.Tr.Delete,
Tooltip: self.c.Tr.BranchDeleteTooltip,
OpensMenu: true,
Expand Down Expand Up @@ -520,62 +520,80 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
}

func (self *BranchesController) localDelete(branch *models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branch)
func (self *BranchesController) localDelete(branches []*models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branches)
}

func (self *BranchesController) remoteDelete(branch *models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch)
func (self *BranchesController) remoteDelete(branches []*models.Branch) error {
remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch {
return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote}
})
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(remoteBranches)
}

func (self *BranchesController) localAndRemoteDelete(branch *models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branch)
func (self *BranchesController) localAndRemoteDelete(branches []*models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branches)
}

func (self *BranchesController) delete(branch *models.Branch) error {
func (self *BranchesController) delete(branches []*models.Branch) error {
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
isBranchCheckedOut := lo.SomeBy(branches, func(branch *models.Branch) bool {
return checkedOutBranch.Name == branch.Name
})
hasUpstream := lo.EveryBy(branches, func(branch *models.Branch) bool {
return branch.IsTrackingRemote() && !branch.UpstreamGone
})

localDeleteItem := &types.MenuItem{
Label: self.c.Tr.DeleteLocalBranch,
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalBranches, self.c.Tr.DeleteLocalBranch),
Key: 'c',
OnPress: func() error {
return self.localDelete(branch)
return self.localDelete(branches)
},
}
if checkedOutBranch.Name == branch.Name {
if isBranchCheckedOut {
localDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
}

remoteDeleteItem := &types.MenuItem{
Label: self.c.Tr.DeleteRemoteBranch,
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteRemoteBranches, self.c.Tr.DeleteRemoteBranch),
Key: 'r',
OnPress: func() error {
return self.remoteDelete(branch)
return self.remoteDelete(branches)
},
}
if !branch.IsTrackingRemote() || branch.UpstreamGone {
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
if !hasUpstream {
remoteDeleteItem.DisabledReason = &types.DisabledReason{
Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError),
}
}

deleteBothItem := &types.MenuItem{
Label: self.c.Tr.DeleteLocalAndRemoteBranch,
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalAndRemoteBranches, self.c.Tr.DeleteLocalAndRemoteBranch),
Key: 'b',
OnPress: func() error {
return self.localAndRemoteDelete(branch)
return self.localAndRemoteDelete(branches)
},
}
if checkedOutBranch.Name == branch.Name {
if isBranchCheckedOut {
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
} else if !branch.IsTrackingRemote() || branch.UpstreamGone {
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
} else if !hasUpstream {
deleteBothItem.DisabledReason = &types.DisabledReason{
Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError),
}
}

menuTitle := utils.ResolvePlaceholderString(
self.c.Tr.DeleteBranchTitle,
map[string]string{
"selectedBranchName": branch.Name,
},
)
var menuTitle string
if len(branches) == 1 {
menuTitle = utils.ResolvePlaceholderString(
self.c.Tr.DeleteBranchTitle,
map[string]string{
"selectedBranchName": branches[0].Name,
},
)
} else {
menuTitle = self.c.Tr.DeleteBranchesTitle
}

return self.c.Menu(types.CreateMenuOptions{
Title: menuTitle,
Expand Down Expand Up @@ -819,6 +837,16 @@ func (self *BranchesController) branchIsReal(branch *models.Branch) *types.Disab
return nil
}

func (self *BranchesController) branchesAreReal(selectedBranches []*models.Branch, startIdx int, endIdx int) *types.DisabledReason {
if !lo.EveryBy(selectedBranches, func(branch *models.Branch) bool {
return 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
Expand Down
Loading
Loading