mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Allow deleting local+remote of a worktree-checked-out branch at once
Picking "Delete local and remote branch" for a single branch that's checked out in another worktree used to fail with "Some of the selected branches are checked out by other worktrees. Select them one by one to delete them." That message only makes sense for a multi-selection; for a single branch there's no reason we can't remove the worktree and delete both the local and remote branch in one go. Route that case through the same worktree menu as the local-only delete, with labels that spell out that the remote goes too. The multi-select error stays for actual multi-selections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4f078f5463
commit
22914da8e5
|
|
@ -97,8 +97,17 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteB
|
|||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branch) error {
|
||||
if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) {
|
||||
return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError)
|
||||
if len(branches) > 1 {
|
||||
if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) {
|
||||
return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError)
|
||||
}
|
||||
} else if self.checkedOutByOtherWorktree(branches[0]) {
|
||||
return self.promptWorktreeBranchDelete(
|
||||
branches[0],
|
||||
self.c.Tr.RemoveWorktreeAndDeleteBothBranches,
|
||||
self.c.Tr.DetachWorktreeAndDeleteBothBranches,
|
||||
self.deleteLocalAndRemoteBranchesContinuation(branches),
|
||||
)
|
||||
}
|
||||
|
||||
allBranchesMerged, err := self.allBranchesMerged(branches)
|
||||
|
|
@ -281,6 +290,24 @@ func (self *BranchesHelper) deleteLocalBranchesContinuation(branches []*models.B
|
|||
}
|
||||
}
|
||||
|
||||
// deleteLocalAndRemoteBranchesContinuation returns a worktree-removal
|
||||
// continuation that deletes the local and remote branches and refreshes once the
|
||||
// worktree is out of the way.
|
||||
func (self *BranchesHelper) deleteLocalAndRemoteBranchesContinuation(branches []*models.Branch) func(gocui.Task) error {
|
||||
return func(task gocui.Task) error {
|
||||
if err := self.doDeleteLocalAndRemoteBranches(task, branches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.REMOTES, types.FILES},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) allBranchesMerged(branches []*models.Branch) (bool, error) {
|
||||
allBranchesMerged := true
|
||||
for _, branch := range branches {
|
||||
|
|
|
|||
|
|
@ -875,8 +875,10 @@ type TranslationSet struct {
|
|||
RemoveWorktree string
|
||||
RemoveWorktreeTitle string
|
||||
RemoveWorktreeAndDeleteBranch string
|
||||
RemoveWorktreeAndDeleteBothBranches string
|
||||
DetachWorktree string
|
||||
DetachWorktreeAndDeleteBranch string
|
||||
DetachWorktreeAndDeleteBothBranches string
|
||||
DetachingWorktree string
|
||||
WorktreesTitle string
|
||||
WorktreeTitle string
|
||||
|
|
@ -2021,11 +2023,13 @@ func EnglishTranslationSet() *TranslationSet {
|
|||
RemoveWorktree: "Remove worktree",
|
||||
RemoveWorktreeTitle: "Remove worktree",
|
||||
RemoveWorktreeAndDeleteBranch: "Remove worktree and delete branch",
|
||||
RemoveWorktreeAndDeleteBothBranches: "Remove worktree and delete local and remote branch",
|
||||
RemoveWorktreePrompt: "Are you sure you want to remove worktree '{{.worktreeName}}'?",
|
||||
ForceRemoveWorktreePrompt: "'{{.worktreeName}}' contains modified or untracked files, or submodules (or all of these). Are you sure you want to remove it?",
|
||||
RemovingWorktree: "Deleting worktree",
|
||||
DetachWorktree: "Detach worktree",
|
||||
DetachWorktreeAndDeleteBranch: "Detach worktree and delete branch",
|
||||
DetachWorktreeAndDeleteBothBranches: "Detach worktree and delete local and remote branch",
|
||||
DetachingWorktree: "Detaching worktree",
|
||||
AddingWorktree: "Adding worktree",
|
||||
CantDeleteCurrentWorktree: "You cannot remove the current worktree!",
|
||||
|
|
|
|||
|
|
@ -520,6 +520,8 @@ var tests = []*components.IntegrationTest{
|
|||
worktree.LocationCandidates,
|
||||
worktree.NewWorktreePicker,
|
||||
worktree.NewWorktreePickerRemote,
|
||||
worktree.RemoveWorktreeAndBothBranches,
|
||||
worktree.RemoveWorktreeAndDeleteLocalAndRemoteBranch,
|
||||
worktree.RemoveWorktreeFromBranch,
|
||||
worktree.ResetWindowTabs,
|
||||
worktree.SymlinkIntoRepoSubdir,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
package worktree
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var RemoveWorktreeAndDeleteLocalAndRemoteBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Delete the local branch, the remote branch, and the worktree of a single branch checked out in another worktree, all at once",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CloneIntoRemote("origin")
|
||||
shell.EmptyCommit("initial commit")
|
||||
shell.NewBranch("mybranch")
|
||||
shell.EmptyCommit("commit on mybranch")
|
||||
shell.PushBranchAndSetUpstream("origin", "mybranch")
|
||||
shell.EmptyCommit("commit not pushed to the remote") // so mybranch isn't fully merged
|
||||
shell.Checkout("master")
|
||||
shell.AddWorktreeCheckout("mybranch", "../linked-worktree")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("master").IsSelected(),
|
||||
Contains("mybranch (worktree linked-worktree)"),
|
||||
).
|
||||
NavigateToLine(Contains("mybranch")).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Delete branch 'mybranch'?")).
|
||||
Select(Contains("Delete local and remote branch")).
|
||||
Confirm()
|
||||
}).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Branch mybranch is checked out by worktree linked-worktree")).
|
||||
Select(Contains("Remove worktree and delete local and remote branch")).
|
||||
Confirm()
|
||||
|
||||
// mybranch is not contained in master, so we get the force-delete warning
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Force delete branch")).
|
||||
Content(Equals("'mybranch' is not fully merged. Are you sure you want to delete it?")).
|
||||
Confirm()
|
||||
}).
|
||||
// The local branch is gone
|
||||
Lines(
|
||||
Contains("master").IsSelected(),
|
||||
)
|
||||
|
||||
// The remote branch is gone too
|
||||
t.Views().Remotes().
|
||||
Focus().
|
||||
Lines(Contains("origin")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().RemoteBranches().
|
||||
IsEmpty()
|
||||
|
||||
// And so is the worktree
|
||||
t.Views().Worktrees().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("(main worktree)").IsSelected(),
|
||||
)
|
||||
},
|
||||
})
|
||||
Loading…
Reference in a new issue