summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg/commands/git_commands/git_command_builder.go7
-rw-r--r--pkg/commands/git_commands/worktree.go6
-rw-r--r--pkg/gui/controllers/branches_controller.go44
-rw-r--r--pkg/gui/controllers/helpers/worktree_helper.go48
-rw-r--r--pkg/gui/controllers/worktrees_controller.go38
-rw-r--r--pkg/i18n/english.go4
6 files changed, 107 insertions, 40 deletions
diff --git a/pkg/commands/git_commands/git_command_builder.go b/pkg/commands/git_commands/git_command_builder.go
index bb97f2dc6..05d788b4c 100644
--- a/pkg/commands/git_commands/git_command_builder.go
+++ b/pkg/commands/git_commands/git_command_builder.go
@@ -49,6 +49,13 @@ func (self *GitCommandBuilder) RepoPath(value string) *GitCommandBuilder {
return self
}
+func (self *GitCommandBuilder) WorktreePath(path string) *GitCommandBuilder {
+ // worktree path comes before the command
+ self.args = append([]string{"--work-tree", path}, self.args...)
+
+ return self
+}
+
func (self *GitCommandBuilder) ToArgv() []string {
return append([]string{"git"}, self.args...)
}
diff --git a/pkg/commands/git_commands/worktree.go b/pkg/commands/git_commands/worktree.go
index 101601afb..922dbb957 100644
--- a/pkg/commands/git_commands/worktree.go
+++ b/pkg/commands/git_commands/worktree.go
@@ -32,6 +32,12 @@ func (self *WorktreeCommands) Delete(worktreePath string, force bool) error {
return self.cmd.New(cmdArgs).Run()
}
+func (self *WorktreeCommands) Detach(worktreePath string) error {
+ cmdArgs := NewGitCmd("checkout").Arg("--detach").WorktreePath(worktreePath).ToArgv()
+
+ return self.cmd.New(cmdArgs).Run()
+}
+
func (self *WorktreeCommands) IsCurrentWorktree(w *models.Worktree) bool {
return IsCurrentWorktree(w)
}
diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go
index a0092f3c0..62fa786b5 100644
--- a/pkg/gui/controllers/branches_controller.go
+++ b/pkg/gui/controllers/branches_controller.go
@@ -203,7 +203,7 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
}
if selectedBranch.CheckedOutByOtherWorktree {
- worktreeForRef, ok := self.worktreeForRef(selectedBranch.Name)
+ worktreeForRef, ok := self.worktreeForBranch(selectedBranch)
if ok && !self.c.Git().Worktree.IsCurrentWorktree(worktreeForRef) {
return self.promptToCheckoutWorktree(worktreeForRef)
}
@@ -213,9 +213,9 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
}
-func (self *BranchesController) worktreeForRef(ref string) (*models.Worktree, bool) {
+func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
for _, worktree := range self.c.Model().Worktrees {
- if worktree.Branch == ref {
+ if worktree.Branch == branch.Name {
return worktree, true
}
}
@@ -325,9 +325,47 @@ func (self *BranchesController) delete(branch *models.Branch) error {
if checkedOutBranch.Name == branch.Name {
return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
}
+
+ if branch.CheckedOutByOtherWorktree {
+ return self.promptWorktreeBranchDelete(branch)
+ }
+
return self.deleteWithForce(branch, false)
}
+func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *models.Branch) error {
+ worktree, ok := self.worktreeForBranch(selectedBranch)
+ if !ok {
+ self.c.Log.Error("CheckedOutByOtherWorktree out of sync with list of worktrees")
+ return nil
+ }
+
+ return self.c.Menu(types.CreateMenuOptions{
+ Title: fmt.Sprintf("Branch %s is checked out by worktree %s", selectedBranch.Name, worktree.Name()),
+ Items: []*types.MenuItem{
+ {
+ Label: "Switch to worktree " + worktree.Name(),
+ OnPress: func() error {
+ return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
+ },
+ },
+ {
+ Label: "Detach worktree",
+ Tooltip: "This will run `git checkout --detach` on the worktree so that it stops hogging the branch, but the worktree's working tree will be left alone",
+ OnPress: func() error {
+ return self.c.Helpers().Worktree.Detach(worktree)
+ },
+ },
+ {
+ Label: "Remove worktree",
+ OnPress: func() error {
+ return self.c.Helpers().Worktree.Remove(worktree, false)
+ },
+ },
+ },
+ })
+}
+
func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, force bool) error {
title := self.c.Tr.DeleteBranch
var templateStr string
diff --git a/pkg/gui/controllers/helpers/worktree_helper.go b/pkg/gui/controllers/helpers/worktree_helper.go
index 0dce60d60..b00315fbd 100644
--- a/pkg/gui/controllers/helpers/worktree_helper.go
+++ b/pkg/gui/controllers/helpers/worktree_helper.go
@@ -6,10 +6,12 @@ import (
"io/fs"
"log"
"os"
+ "strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
)
type IWorktreeHelper interface {
@@ -87,3 +89,49 @@ func (self *WorktreeHelper) Switch(worktree *models.Worktree, contextKey types.C
return self.reposHelper.DispatchSwitchTo(worktree.Path, true, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey)
}
+
+func (self *WorktreeHelper) Remove(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(),
+ },
+ )
+
+ return 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 {
+ errMessage := err.Error()
+ if !strings.Contains(errMessage, "--force") {
+ return self.c.Error(err)
+ }
+
+ if !force {
+ return self.Remove(worktree, true)
+ }
+ return self.c.ErrorMsg(errMessage)
+ }
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES}})
+ })
+ },
+ })
+}
+
+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)
+
+ return self.c.Git().Worktree.Detach(worktree.Path)
+ })
+}
diff --git a/pkg/gui/controllers/worktrees_controller.go b/pkg/gui/controllers/worktrees_controller.go
index 2f5a35bd5..53dd2b32e 100644
--- a/pkg/gui/controllers/worktrees_controller.go
+++ b/pkg/gui/controllers/worktrees_controller.go
@@ -5,12 +5,10 @@ import (
"strings"
"text/tabwriter"
- "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
- "github.com/jesseduffield/lazygit/pkg/utils"
)
type WorktreesController struct {
@@ -101,41 +99,7 @@ func (self *WorktreesController) remove(worktree *models.Worktree) error {
return self.c.ErrorMsg(self.c.Tr.CantDeleteCurrentWorktree)
}
- return self.removeWithForce(worktree, false)
-}
-
-func (self *WorktreesController) removeWithForce(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(),
- },
- )
-
- return 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.RemovingWorktree)
- if err := self.c.Git().Worktree.Delete(worktree.Path, force); err != nil {
- errMessage := err.Error()
- if !force {
- return self.removeWithForce(worktree, true)
- }
- return self.c.ErrorMsg(errMessage)
- }
- return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES}})
- })
- },
- })
+ return self.c.Helpers().Worktree.Remove(worktree, false)
}
func (self *WorktreesController) GetOnClick() func() error {
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 1e15b22d1..99ebaa918 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -545,6 +545,8 @@ type TranslationSet struct {
SwitchToWorktree string
RemoveWorktree string
RemoveWorktreeTitle string
+ DetachWorktree string
+ DetachingWorktree string
WorktreesTitle string
WorktreeTitle string
RemoveWorktreePrompt string
@@ -1272,6 +1274,8 @@ func EnglishTranslationSet() TranslationSet {
RemoveWorktreePrompt: "Are you sure you want to remove worktree '{{.worktreeName}}'?",
ForceRemoveWorktreePrompt: "'{{.worktreeName}}' contains modified or untracked files (to be honest, it could contain both). Are you sure you want to remove it?",
RemovingWorktree: "Deleting worktree",
+ DetachWorktree: "Detach worktree",
+ DetachingWorktree: "Detaching worktree",
AddingWorktree: "Adding worktree",
CantDeleteCurrentWorktree: "You cannot remove the current worktree!",
AlreadyInWorktree: "You are already in the selected worktree",