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:
parent
4b3c9f5b35
commit
6b4a638415
@ -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...)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user