diff options
-rw-r--r-- | pkg/commands/git_commands/git_command_builder.go | 7 | ||||
-rw-r--r-- | pkg/commands/git_commands/worktree.go | 6 | ||||
-rw-r--r-- | pkg/gui/controllers/branches_controller.go | 44 | ||||
-rw-r--r-- | pkg/gui/controllers/helpers/worktree_helper.go | 48 | ||||
-rw-r--r-- | pkg/gui/controllers/worktrees_controller.go | 38 | ||||
-rw-r--r-- | pkg/i18n/english.go | 4 |
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", |