mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-31 23:19:40 +02:00
Add a menu item to delete both local and remote branch at once
This commit is contained in:
parent
e181de1180
commit
1ab70ec645
@ -528,6 +528,10 @@ func (self *BranchesController) remoteDelete(branch *models.Branch) error {
|
|||||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch)
|
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *BranchesController) localAndRemoteDelete(branch *models.Branch) error {
|
||||||
|
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branch)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||||
|
|
||||||
@ -553,6 +557,19 @@ func (self *BranchesController) delete(branch *models.Branch) error {
|
|||||||
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteBothItem := &types.MenuItem{
|
||||||
|
Label: self.c.Tr.DeleteLocalAndRemoteBranch,
|
||||||
|
Key: 'b',
|
||||||
|
OnPress: func() error {
|
||||||
|
return self.localAndRemoteDelete(branch)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if checkedOutBranch.Name == branch.Name {
|
||||||
|
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
|
||||||
|
} else if !branch.IsTrackingRemote() || branch.UpstreamGone {
|
||||||
|
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||||
|
}
|
||||||
|
|
||||||
menuTitle := utils.ResolvePlaceholderString(
|
menuTitle := utils.ResolvePlaceholderString(
|
||||||
self.c.Tr.DeleteBranchTitle,
|
self.c.Tr.DeleteBranchTitle,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
@ -562,7 +579,7 @@ func (self *BranchesController) delete(branch *models.Branch) error {
|
|||||||
|
|
||||||
return self.c.Menu(types.CreateMenuOptions{
|
return self.c.Menu(types.CreateMenuOptions{
|
||||||
Title: menuTitle,
|
Title: menuTitle,
|
||||||
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem},
|
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem, deleteBothItem},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +97,59 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName st
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branch *models.Branch) error {
|
||||||
|
if self.checkedOutByOtherWorktree(branch) {
|
||||||
|
return self.promptWorktreeBranchDelete(branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := utils.ResolvePlaceholderString(
|
||||||
|
self.c.Tr.DeleteLocalAndRemoteBranchPrompt,
|
||||||
|
map[string]string{
|
||||||
|
"localBranchName": branch.Name,
|
||||||
|
"remoteBranchName": branch.UpstreamBranch,
|
||||||
|
"remoteName": branch.UpstreamRemote,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if !isMerged {
|
||||||
|
prompt += "\n\n" + utils.ResolvePlaceholderString(
|
||||||
|
self.c.Tr.ForceDeleteBranchMessage,
|
||||||
|
map[string]string{
|
||||||
|
"selectedBranchName": branch.Name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.c.Confirm(types.ConfirmOpts{
|
||||||
|
Title: self.c.Tr.DeleteLocalAndRemoteBranch,
|
||||||
|
Prompt: prompt,
|
||||||
|
HandleConfirm: func() error {
|
||||||
|
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||||
|
// Delete the remote branch first so that we keep the local one
|
||||||
|
// in case of failure
|
||||||
|
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||||
|
if err := self.c.Git().Remote.DeleteRemoteBranch(task, branch.UpstreamRemote, branch.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||||
|
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func ShortBranchName(fullBranchName string) string {
|
func ShortBranchName(fullBranchName string) string {
|
||||||
return strings.TrimPrefix(strings.TrimPrefix(fullBranchName, "refs/heads/"), "refs/remotes/")
|
return strings.TrimPrefix(strings.TrimPrefix(fullBranchName, "refs/heads/"), "refs/remotes/")
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,7 @@ type TranslationSet struct {
|
|||||||
DeleteLocalBranch string
|
DeleteLocalBranch string
|
||||||
DeleteRemoteBranchOption string
|
DeleteRemoteBranchOption string
|
||||||
DeleteRemoteBranchPrompt string
|
DeleteRemoteBranchPrompt string
|
||||||
|
DeleteLocalAndRemoteBranchPrompt string
|
||||||
ForceDeleteBranchTitle string
|
ForceDeleteBranchTitle string
|
||||||
ForceDeleteBranchMessage string
|
ForceDeleteBranchMessage string
|
||||||
RebaseBranch string
|
RebaseBranch string
|
||||||
@ -473,6 +474,7 @@ type TranslationSet struct {
|
|||||||
RemoveRemotePrompt string
|
RemoveRemotePrompt string
|
||||||
DeleteRemoteBranch string
|
DeleteRemoteBranch string
|
||||||
DeleteRemoteBranchTooltip string
|
DeleteRemoteBranchTooltip string
|
||||||
|
DeleteLocalAndRemoteBranch string
|
||||||
SetAsUpstream string
|
SetAsUpstream string
|
||||||
SetAsUpstreamTooltip string
|
SetAsUpstreamTooltip string
|
||||||
SetUpstream string
|
SetUpstream string
|
||||||
@ -1086,6 +1088,7 @@ func EnglishTranslationSet() *TranslationSet {
|
|||||||
DeleteLocalBranch: "Delete local branch",
|
DeleteLocalBranch: "Delete local branch",
|
||||||
DeleteRemoteBranchOption: "Delete remote branch",
|
DeleteRemoteBranchOption: "Delete remote branch",
|
||||||
DeleteRemoteBranchPrompt: "Are you sure you want to delete the remote branch '{{.selectedBranchName}}' from '{{.upstream}}'?",
|
DeleteRemoteBranchPrompt: "Are you sure you want to delete the remote branch '{{.selectedBranchName}}' from '{{.upstream}}'?",
|
||||||
|
DeleteLocalAndRemoteBranchPrompt: "Are you sure you want to delete both '{{.localBranchName}}' from your machine, and '{{.remoteBranchName}}' from '{{.remoteName}}'?",
|
||||||
ForceDeleteBranchTitle: "Force delete branch",
|
ForceDeleteBranchTitle: "Force delete branch",
|
||||||
ForceDeleteBranchMessage: "'{{.selectedBranchName}}' is not fully merged. Are you sure you want to delete it?",
|
ForceDeleteBranchMessage: "'{{.selectedBranchName}}' is not fully merged. Are you sure you want to delete it?",
|
||||||
RebaseBranch: "Rebase",
|
RebaseBranch: "Rebase",
|
||||||
@ -1462,6 +1465,7 @@ func EnglishTranslationSet() *TranslationSet {
|
|||||||
RemoveRemotePrompt: "Are you sure you want to remove remote?",
|
RemoveRemotePrompt: "Are you sure you want to remove remote?",
|
||||||
DeleteRemoteBranch: "Delete remote branch",
|
DeleteRemoteBranch: "Delete remote branch",
|
||||||
DeleteRemoteBranchTooltip: "Delete the remote branch from the remote.",
|
DeleteRemoteBranchTooltip: "Delete the remote branch from the remote.",
|
||||||
|
DeleteLocalAndRemoteBranch: "Delete local and remote branch",
|
||||||
SetAsUpstream: "Set as upstream",
|
SetAsUpstream: "Set as upstream",
|
||||||
SetAsUpstreamTooltip: "Set the selected remote branch as the upstream of the checked-out branch.",
|
SetAsUpstreamTooltip: "Set the selected remote branch as the upstream of the checked-out branch.",
|
||||||
SetUpstream: "Set upstream of selected branch",
|
SetUpstream: "Set upstream of selected branch",
|
||||||
|
@ -31,6 +31,13 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
EmptyCommit("on branch-four 01").
|
EmptyCommit("on branch-four 01").
|
||||||
PushBranchAndSetUpstream("origin", "branch-four").
|
PushBranchAndSetUpstream("origin", "branch-four").
|
||||||
EmptyCommit("on branch-four 02"). // branch-four is not contained in any of these, so we get a delete confirmation
|
EmptyCommit("on branch-four 02"). // branch-four is not contained in any of these, so we get a delete confirmation
|
||||||
|
NewBranchFrom("branch-five", "master").
|
||||||
|
EmptyCommit("on branch-five 01").
|
||||||
|
PushBranchAndSetUpstream("origin", "branch-five"). // branch-five is contained in its own upstream
|
||||||
|
NewBranchFrom("branch-six", "master").
|
||||||
|
EmptyCommit("on branch-six 01").
|
||||||
|
PushBranchAndSetUpstream("origin", "branch-six").
|
||||||
|
EmptyCommit("on branch-six 02"). // branch-six is not contained in any of these, so we get a delete confirmation
|
||||||
Checkout("current-head")
|
Checkout("current-head")
|
||||||
},
|
},
|
||||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
@ -38,6 +45,8 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
Focus().
|
Focus().
|
||||||
Lines(
|
Lines(
|
||||||
Contains("current-head").IsSelected(),
|
Contains("current-head").IsSelected(),
|
||||||
|
Contains("branch-six ↑1"),
|
||||||
|
Contains("branch-five ✓"),
|
||||||
Contains("branch-four ↑1"),
|
Contains("branch-four ↑1"),
|
||||||
Contains("branch-three"),
|
Contains("branch-three"),
|
||||||
Contains("branch-two ✓"),
|
Contains("branch-two ✓"),
|
||||||
@ -62,7 +71,7 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
|
|
||||||
// Delete branch-four. This is the only branch that is not fully merged, so we get
|
// Delete branch-four. This is the only branch that is not fully merged, so we get
|
||||||
// a confirmation popup.
|
// a confirmation popup.
|
||||||
SelectNextItem().
|
NavigateToLine(Contains("branch-four")).
|
||||||
Press(keys.Universal.Remove).
|
Press(keys.Universal.Remove).
|
||||||
Tap(func() {
|
Tap(func() {
|
||||||
t.ExpectPopup().
|
t.ExpectPopup().
|
||||||
@ -78,6 +87,8 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
}).
|
}).
|
||||||
Lines(
|
Lines(
|
||||||
Contains("current-head"),
|
Contains("current-head"),
|
||||||
|
Contains("branch-six ↑1"),
|
||||||
|
Contains("branch-five ✓"),
|
||||||
Contains("branch-three").IsSelected(),
|
Contains("branch-three").IsSelected(),
|
||||||
Contains("branch-two ✓"),
|
Contains("branch-two ✓"),
|
||||||
Contains("master"),
|
Contains("master"),
|
||||||
@ -96,6 +107,8 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
}).
|
}).
|
||||||
Lines(
|
Lines(
|
||||||
Contains("current-head"),
|
Contains("current-head"),
|
||||||
|
Contains("branch-six ↑1"),
|
||||||
|
Contains("branch-five ✓"),
|
||||||
Contains("branch-two ✓").IsSelected(),
|
Contains("branch-two ✓").IsSelected(),
|
||||||
Contains("master"),
|
Contains("master"),
|
||||||
Contains("branch-one ↑1"),
|
Contains("branch-one ↑1"),
|
||||||
@ -113,6 +126,8 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
}).
|
}).
|
||||||
Lines(
|
Lines(
|
||||||
Contains("current-head"),
|
Contains("current-head"),
|
||||||
|
Contains("branch-six ↑1"),
|
||||||
|
Contains("branch-five ✓"),
|
||||||
Contains("master").IsSelected(),
|
Contains("master").IsSelected(),
|
||||||
Contains("branch-one ↑1"),
|
Contains("branch-one ↑1"),
|
||||||
).
|
).
|
||||||
@ -143,7 +158,9 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
t.Views().
|
t.Views().
|
||||||
RemoteBranches().
|
RemoteBranches().
|
||||||
Lines(
|
Lines(
|
||||||
|
Equals("branch-five"),
|
||||||
Equals("branch-four"),
|
Equals("branch-four"),
|
||||||
|
Equals("branch-six"),
|
||||||
Equals("branch-two"),
|
Equals("branch-two"),
|
||||||
).
|
).
|
||||||
Press(keys.Universal.Return)
|
Press(keys.Universal.Return)
|
||||||
@ -154,6 +171,8 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
}).
|
}).
|
||||||
Lines(
|
Lines(
|
||||||
Contains("current-head"),
|
Contains("current-head"),
|
||||||
|
Contains("branch-six ↑1"),
|
||||||
|
Contains("branch-five ✓"),
|
||||||
Contains("master"),
|
Contains("master"),
|
||||||
Contains("branch-one (upstream gone)").IsSelected(),
|
Contains("branch-one (upstream gone)").IsSelected(),
|
||||||
).
|
).
|
||||||
@ -168,6 +187,51 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
Select(Contains("Delete local branch")).
|
Select(Contains("Delete local branch")).
|
||||||
Confirm()
|
Confirm()
|
||||||
}).
|
}).
|
||||||
|
Lines(
|
||||||
|
Contains("current-head"),
|
||||||
|
Contains("branch-six ↑1"),
|
||||||
|
Contains("branch-five ✓"),
|
||||||
|
Contains("master").IsSelected(),
|
||||||
|
).
|
||||||
|
|
||||||
|
// Delete both local and remote branch of branch-six. We get the force-delete warning because it is not fully merged.
|
||||||
|
NavigateToLine(Contains("branch-six")).
|
||||||
|
Press(keys.Universal.Remove).
|
||||||
|
Tap(func() {
|
||||||
|
t.ExpectPopup().
|
||||||
|
Menu().
|
||||||
|
Title(Equals("Delete branch 'branch-six'?")).
|
||||||
|
Select(Contains("Delete local and remote branch")).
|
||||||
|
Confirm()
|
||||||
|
t.ExpectPopup().
|
||||||
|
Confirmation().
|
||||||
|
Title(Equals("Delete local and remote branch")).
|
||||||
|
Content(Contains("Are you sure you want to delete both 'branch-six' from your machine, and 'branch-six' from 'origin'?").
|
||||||
|
Contains("'branch-six' is not fully merged. Are you sure you want to delete it?")).
|
||||||
|
Confirm()
|
||||||
|
}).
|
||||||
|
Lines(
|
||||||
|
Contains("current-head"),
|
||||||
|
Contains("branch-five ✓").IsSelected(),
|
||||||
|
Contains("master"),
|
||||||
|
).
|
||||||
|
|
||||||
|
// Delete both local and remote branch of branch-five. We get the same popups, but the confirmation
|
||||||
|
// doesn't contain the force-delete warning.
|
||||||
|
Press(keys.Universal.Remove).
|
||||||
|
Tap(func() {
|
||||||
|
t.ExpectPopup().
|
||||||
|
Menu().
|
||||||
|
Title(Equals("Delete branch 'branch-five'?")).
|
||||||
|
Select(Contains("Delete local and remote branch")).
|
||||||
|
Confirm()
|
||||||
|
t.ExpectPopup().
|
||||||
|
Confirmation().
|
||||||
|
Title(Equals("Delete local and remote branch")).
|
||||||
|
Content(Equals("Are you sure you want to delete both 'branch-five' from your machine, and 'branch-five' from 'origin'?").
|
||||||
|
DoesNotContain("not fully merged")).
|
||||||
|
Confirm()
|
||||||
|
}).
|
||||||
Lines(
|
Lines(
|
||||||
Contains("current-head"),
|
Contains("current-head"),
|
||||||
Contains("master").IsSelected(),
|
Contains("master").IsSelected(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user