1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-05-31 23:19:40 +02:00

Handle deleting branch attached to worktree

This commit is contained in:
Jesse Duffield 2023-07-16 17:52:07 +10:00
parent 4b3c9f5b35
commit 6b4a638415
6 changed files with 107 additions and 40 deletions

View File

@ -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...)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
})
}

View File

@ -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 {

View File

@ -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",