From 8d97dbe45f8419a587c703491f73cf1fb0ac5028 Mon Sep 17 00:00:00 2001 From: Polo123456789 Date: Tue, 28 Apr 2026 18:56:09 -0600 Subject: [PATCH 1/2] Remove both worktree and branch when selected --- .../controllers/helpers/branches_helper.go | 45 ++++++++++++++++++- .../controllers/helpers/worktree_helper.go | 15 ++++--- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go index 5566dc40c41..4e37e9a0825 100644 --- a/pkg/gui/controllers/helpers/branches_helper.go +++ b/pkg/gui/controllers/helpers/branches_helper.go @@ -235,13 +235,56 @@ func (self *BranchesHelper) promptWorktreeBranchDelete(selectedBranch *models.Br { Label: self.c.Tr.RemoveWorktree, OnPress: func() error { - return self.worktreeHelper.Remove(worktree, false) + return self.removeWorktreeAndBranch(selectedBranch, worktree, false) }, }, }, }) } +func (self *BranchesHelper) removeWorktreeAndBranch(branch *models.Branch, worktree *models.Worktree, force bool) error { + title := self.c.Tr.RemoveWorktreeTitle + var templateStr string + if force { + templateStr = self.c.Tr.ForceRemoveWorktreePrompt + } else { + templateStr = self.c.Tr.RemoveWorktreePrompt + } + 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.RemoveWorktree) + 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) { allBranchesMerged := true for _, branch := range branches { 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) From e333bcf12c6c1f1a01e193b3a297ecfe28d0c021 Mon Sep 17 00:00:00 2001 From: Polo123456789 Date: Tue, 28 Apr 2026 19:27:42 -0600 Subject: [PATCH 2/2] Update integration test --- pkg/integration/tests/test_list.go | 2 +- ...orktree_from_branch.go => remove_worktree_and_branch.go} | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) rename pkg/integration/tests/worktree/{remove_worktree_from_branch.go => remove_worktree_and_branch.go} (92%) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 09d487852a0..61e1b481fb1 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -491,7 +491,7 @@ var tests = []*components.IntegrationTest{ worktree.FastForwardWorktreeBranchShouldNotPolluteCurrentWorktree, worktree.ForceRemoveWorktree, worktree.ForceRemoveWorktreeWithSubmodules, - worktree.RemoveWorktreeFromBranch, + worktree.RemoveWorktreeAndBranch, worktree.ResetWindowTabs, worktree.SymlinkIntoRepoSubdir, worktree.WorktreeInRepo, diff --git a/pkg/integration/tests/worktree/remove_worktree_from_branch.go b/pkg/integration/tests/worktree/remove_worktree_and_branch.go similarity index 92% rename from pkg/integration/tests/worktree/remove_worktree_from_branch.go rename to pkg/integration/tests/worktree/remove_worktree_and_branch.go index 1aa9645f3f0..fc3e5ea4624 100644 --- a/pkg/integration/tests/worktree/remove_worktree_from_branch.go +++ b/pkg/integration/tests/worktree/remove_worktree_and_branch.go @@ -5,7 +5,7 @@ import ( . "github.com/jesseduffield/lazygit/pkg/integration/components" ) -var RemoveWorktreeFromBranch = NewIntegrationTest(NewIntegrationTestArgs{ +var RemoveWorktreeAndBranch = NewIntegrationTest(NewIntegrationTestArgs{ Description: "Remove a worktree from the branches view", ExtraCmdArgs: []string{}, Skip: false, @@ -53,7 +53,9 @@ var RemoveWorktreeFromBranch = NewIntegrationTest(NewIntegrationTestArgs{ }). Lines( Contains("mybranch"), - Contains("newbranch").DoesNotContain("(worktree)").IsSelected(), + ). + Content( + DoesNotContain("newbranch"), ) t.Views().Worktrees().