2022-09-01 23:50:34 -07:00
|
|
|
package helpers
|
|
|
|
|
2022-09-10 22:36:47 -07:00
|
|
|
import (
|
2023-07-16 17:52:07 +10:00
|
|
|
"strings"
|
2022-09-10 22:36:47 -07:00
|
|
|
|
2023-07-16 13:53:59 +10:00
|
|
|
"github.com/jesseduffield/gocui"
|
2023-07-16 19:39:53 +10:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
2022-09-10 22:36:47 -07:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
2023-07-16 19:39:53 +10:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
2022-09-10 22:36:47 -07:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
2023-07-16 17:52:07 +10:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2022-09-10 22:36:47 -07:00
|
|
|
)
|
2022-09-02 21:38:16 -07:00
|
|
|
|
2022-09-01 23:50:34 -07:00
|
|
|
type IWorktreeHelper interface {
|
|
|
|
GetMainWorktreeName() string
|
|
|
|
GetCurrentWorktreeName() string
|
|
|
|
}
|
|
|
|
|
|
|
|
type WorktreeHelper struct {
|
2023-07-16 20:38:22 +10:00
|
|
|
c *HelperCommon
|
|
|
|
reposHelper *ReposHelper
|
|
|
|
refsHelper *RefsHelper
|
|
|
|
suggestionsHelper *SuggestionsHelper
|
2022-09-01 23:50:34 -07:00
|
|
|
}
|
|
|
|
|
2023-07-16 20:38:22 +10:00
|
|
|
func NewWorktreeHelper(c *HelperCommon, reposHelper *ReposHelper, refsHelper *RefsHelper, suggestionsHelper *SuggestionsHelper) *WorktreeHelper {
|
2022-09-01 23:50:34 -07:00
|
|
|
return &WorktreeHelper{
|
2023-07-16 20:38:22 +10:00
|
|
|
c: c,
|
|
|
|
reposHelper: reposHelper,
|
|
|
|
refsHelper: refsHelper,
|
|
|
|
suggestionsHelper: suggestionsHelper,
|
2022-09-01 23:50:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *WorktreeHelper) GetMainWorktreeName() string {
|
|
|
|
for _, worktree := range self.c.Model().Worktrees {
|
2023-07-28 18:53:00 +10:00
|
|
|
if worktree.IsMain {
|
|
|
|
return worktree.Name
|
2022-09-01 23:50:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2023-07-17 14:10:07 +10:00
|
|
|
// If we're on the main worktree, we return an empty string
|
|
|
|
func (self *WorktreeHelper) GetLinkedWorktreeName() string {
|
|
|
|
worktrees := self.c.Model().Worktrees
|
|
|
|
if len(worktrees) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// worktrees always have the current worktree on top
|
|
|
|
currentWorktree := worktrees[0]
|
2023-07-28 18:53:00 +10:00
|
|
|
if currentWorktree.IsMain {
|
2023-07-17 14:10:07 +10:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2023-07-28 18:53:00 +10:00
|
|
|
return currentWorktree.Name
|
2023-07-17 14:10:07 +10:00
|
|
|
}
|
|
|
|
|
2022-09-02 21:38:16 -07:00
|
|
|
func (self *WorktreeHelper) NewWorktree() error {
|
2023-07-16 20:29:53 +10:00
|
|
|
branch := self.refsHelper.GetCheckedOutRef()
|
|
|
|
currentBranchName := branch.RefName()
|
|
|
|
|
|
|
|
f := func(detached bool) error {
|
|
|
|
return self.c.Prompt(types.PromptOpts{
|
2023-07-17 09:29:56 +10:00
|
|
|
Title: self.c.Tr.NewWorktreeBase,
|
2023-07-16 20:38:22 +10:00
|
|
|
InitialContent: currentBranchName,
|
|
|
|
FindSuggestionsFunc: self.suggestionsHelper.GetRefsSuggestionsFunc(),
|
2023-07-16 20:29:53 +10:00
|
|
|
HandleConfirm: func(base string) error {
|
2023-07-17 09:48:37 +10:00
|
|
|
// we assume that the base can be checked out
|
|
|
|
canCheckoutBase := true
|
2023-07-17 22:03:51 +10:00
|
|
|
return self.NewWorktreeCheckout(base, canCheckoutBase, detached, context.WORKTREES_CONTEXT_KEY)
|
2023-07-16 20:29:53 +10:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-07-17 09:29:56 +10:00
|
|
|
placeholders := map[string]string{"ref": "ref"}
|
|
|
|
|
2023-07-16 20:29:53 +10:00
|
|
|
return self.c.Menu(types.CreateMenuOptions{
|
|
|
|
Title: self.c.Tr.WorktreeTitle,
|
|
|
|
Items: []*types.MenuItem{
|
|
|
|
{
|
2023-07-17 09:29:56 +10:00
|
|
|
LabelColumns: []string{utils.ResolvePlaceholderString(self.c.Tr.CreateWorktreeFrom, placeholders)},
|
2023-07-16 20:29:53 +10:00
|
|
|
OnPress: func() error {
|
|
|
|
return f(false)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2023-07-17 09:29:56 +10:00
|
|
|
LabelColumns: []string{utils.ResolvePlaceholderString(self.c.Tr.CreateWorktreeFromDetached, placeholders)},
|
2023-07-16 20:29:53 +10:00
|
|
|
OnPress: func() error {
|
|
|
|
return f(true)
|
2023-07-16 13:43:20 +10:00
|
|
|
},
|
2023-07-16 20:29:53 +10:00
|
|
|
},
|
2022-09-02 21:38:16 -07:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2023-07-16 14:14:09 +10:00
|
|
|
|
2023-07-17 22:03:51 +10:00
|
|
|
func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase bool, detached bool, contextKey types.ContextKey) error {
|
2023-07-16 19:39:53 +10:00
|
|
|
opts := git_commands.NewWorktreeOpts{
|
|
|
|
Base: base,
|
|
|
|
Detach: detached,
|
|
|
|
}
|
|
|
|
|
|
|
|
f := func() error {
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.AddingWorktree, func(gocui.Task) error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.AddWorktree)
|
|
|
|
if err := self.c.Git().Worktree.New(opts); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-28 16:17:15 +10:00
|
|
|
|
|
|
|
return self.reposHelper.DispatchSwitchTo(opts.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey)
|
2023-07-16 19:39:53 +10:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.Prompt(types.PromptOpts{
|
|
|
|
Title: self.c.Tr.NewWorktreePath,
|
|
|
|
HandleConfirm: func(path string) error {
|
|
|
|
opts.Path = path
|
|
|
|
|
|
|
|
if detached {
|
|
|
|
return f()
|
|
|
|
}
|
|
|
|
|
2023-07-16 20:29:53 +10:00
|
|
|
if canCheckoutBase {
|
2023-07-17 09:29:56 +10:00
|
|
|
title := utils.ResolvePlaceholderString(self.c.Tr.NewBranchNameLeaveBlank, map[string]string{"default": base})
|
2023-07-16 19:39:53 +10:00
|
|
|
// prompt for the new branch name where a blank means we just check out the branch
|
|
|
|
return self.c.Prompt(types.PromptOpts{
|
2023-07-17 09:29:56 +10:00
|
|
|
Title: title,
|
2023-07-16 19:39:53 +10:00
|
|
|
HandleConfirm: func(branchName string) error {
|
|
|
|
opts.Branch = branchName
|
|
|
|
|
|
|
|
return f()
|
|
|
|
},
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// prompt for the new branch name where a blank means we just check out the branch
|
|
|
|
return self.c.Prompt(types.PromptOpts{
|
2023-07-17 09:29:56 +10:00
|
|
|
Title: self.c.Tr.NewBranchName,
|
2023-07-16 19:39:53 +10:00
|
|
|
HandleConfirm: func(branchName string) error {
|
|
|
|
if branchName == "" {
|
2023-07-17 09:29:56 +10:00
|
|
|
return self.c.ErrorMsg(self.c.Tr.BranchNameCannotBeBlank)
|
2023-07-16 19:39:53 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
opts.Branch = branchName
|
|
|
|
|
|
|
|
return f()
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-07-28 16:17:15 +10:00
|
|
|
func (self *WorktreeHelper) Switch(worktree *models.Worktree, contextKey types.ContextKey) error {
|
2023-07-28 18:53:00 +10:00
|
|
|
if worktree.IsCurrent {
|
2023-07-16 14:14:09 +10:00
|
|
|
return self.c.ErrorMsg(self.c.Tr.AlreadyInWorktree)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.c.LogAction(self.c.Tr.SwitchToWorktree)
|
|
|
|
|
2023-07-28 16:17:15 +10:00
|
|
|
return self.reposHelper.DispatchSwitchTo(worktree.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey)
|
2023-07-16 14:14:09 +10:00
|
|
|
}
|
2023-07-16 17:52:07 +10:00
|
|
|
|
|
|
|
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{
|
2023-07-28 18:53:00 +10:00
|
|
|
"worktreeName": worktree.Name,
|
2023-07-16 17:52:07 +10:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2023-07-16 18:12:26 +10:00
|
|
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
|
2023-07-16 17:52:07 +10:00
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
2023-07-16 18:12:26 +10:00
|
|
|
err := self.c.Git().Worktree.Detach(worktree.Path)
|
|
|
|
if err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
|
2023-07-16 17:52:07 +10:00
|
|
|
})
|
|
|
|
}
|
2023-07-16 19:39:53 +10:00
|
|
|
|
|
|
|
func (self *WorktreeHelper) ViewWorktreeOptions(context types.IListContext, ref string) error {
|
2023-07-17 09:29:56 +10:00
|
|
|
currentBranch := self.refsHelper.GetCheckedOutRef()
|
|
|
|
canCheckoutBase := context == self.c.Contexts().Branches && ref != currentBranch.RefName()
|
2023-07-16 19:39:53 +10:00
|
|
|
|
2023-07-17 09:29:56 +10:00
|
|
|
return self.ViewBranchWorktreeOptions(ref, canCheckoutBase)
|
2023-07-16 19:39:53 +10:00
|
|
|
}
|
|
|
|
|
2023-07-17 09:29:56 +10:00
|
|
|
func (self *WorktreeHelper) ViewBranchWorktreeOptions(branchName string, canCheckoutBase bool) error {
|
|
|
|
placeholders := map[string]string{"ref": branchName}
|
2023-07-16 19:39:53 +10:00
|
|
|
|
|
|
|
return self.c.Menu(types.CreateMenuOptions{
|
|
|
|
Title: self.c.Tr.WorktreeTitle,
|
|
|
|
Items: []*types.MenuItem{
|
|
|
|
{
|
2023-07-17 09:29:56 +10:00
|
|
|
LabelColumns: []string{utils.ResolvePlaceholderString(self.c.Tr.CreateWorktreeFrom, placeholders)},
|
2023-07-16 19:39:53 +10:00
|
|
|
OnPress: func() error {
|
2023-07-17 22:03:51 +10:00
|
|
|
return self.NewWorktreeCheckout(branchName, canCheckoutBase, false, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
2023-07-16 19:39:53 +10:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2023-07-17 09:29:56 +10:00
|
|
|
LabelColumns: []string{utils.ResolvePlaceholderString(self.c.Tr.CreateWorktreeFromDetached, placeholders)},
|
2023-07-16 19:39:53 +10:00
|
|
|
OnPress: func() error {
|
2023-07-17 22:03:51 +10:00
|
|
|
return self.NewWorktreeCheckout(branchName, canCheckoutBase, true, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
2023-07-16 19:39:53 +10:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|