mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Blocking keyboard input and hiding the working tree state mode are two separate concerns; they were fused into one helper because every caller so far wanted both. A caller that blocks input for something other than a rebase would then hide the "Rebasing" indicator for the duration of its operation, which has nothing to do with it. Make it an explicit option instead, so blocking input on its own doesn't imply anything about the modes on display.
176 lines
4.9 KiB
Go
176 lines
4.9 KiB
Go
package helpers
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type CherryPickHelper struct {
|
|
c *HelperCommon
|
|
|
|
rebaseHelper *MergeAndRebaseHelper
|
|
}
|
|
|
|
// I'm using the analogy of copy+paste in the terminology here because it's intuitively what's going on,
|
|
// even if in truth we're running git cherry-pick
|
|
|
|
func NewCherryPickHelper(
|
|
c *HelperCommon,
|
|
rebaseHelper *MergeAndRebaseHelper,
|
|
) *CherryPickHelper {
|
|
return &CherryPickHelper{
|
|
c: c,
|
|
rebaseHelper: rebaseHelper,
|
|
}
|
|
}
|
|
|
|
func (self *CherryPickHelper) getData() *cherrypicking.CherryPicking {
|
|
return self.c.Modes().CherryPicking
|
|
}
|
|
|
|
func (self *CherryPickHelper) CopyRange(commitsList []*models.Commit, context types.IListContext) error {
|
|
startIdx, endIdx := context.GetList().GetSelectionRange()
|
|
|
|
if err := self.resetIfNecessary(context); err != nil {
|
|
return err
|
|
}
|
|
|
|
// After a paste the buffer is hidden but not cleared, so the user
|
|
// thinks they're starting fresh. Clear it before adding so the new
|
|
// copy replaces the old one.
|
|
if self.getData().DidPaste {
|
|
self.getData().CherryPickedCommits = nil
|
|
self.getData().DidPaste = false
|
|
}
|
|
|
|
commitSet := self.getData().SelectedHashSet()
|
|
|
|
allCommitsCopied := lo.EveryBy(commitsList[startIdx:endIdx+1], func(commit *models.Commit) bool {
|
|
return commitSet.Includes(commit.Hash())
|
|
})
|
|
|
|
// if all selected commits are already copied, we'll uncopy them
|
|
if allCommitsCopied {
|
|
for index := startIdx; index <= endIdx; index++ {
|
|
commit := commitsList[index]
|
|
self.getData().Remove(commit, commitsList)
|
|
}
|
|
} else {
|
|
for index := startIdx; index <= endIdx; index++ {
|
|
commit := commitsList[index]
|
|
self.getData().Add(commit, commitsList)
|
|
}
|
|
}
|
|
|
|
self.rerender()
|
|
return nil
|
|
}
|
|
|
|
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied.
|
|
// Only to be called from the branch commits controller
|
|
func (self *CherryPickHelper) Paste() error {
|
|
self.c.Confirm(types.ConfirmOpts{
|
|
Title: self.c.Tr.CherryPick,
|
|
Prompt: utils.ResolvePlaceholderString(
|
|
self.c.Tr.SureCherryPick,
|
|
map[string]string{
|
|
"numCommits": strconv.Itoa(len(self.getData().CherryPickedCommits)),
|
|
}),
|
|
HandleConfirm: func() error {
|
|
mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
|
|
cherryPickedCommits := self.getData().CherryPickedCommits
|
|
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
|
|
Message: self.c.Tr.CherryPickingStatus,
|
|
HideWorkingTreeState: true,
|
|
}, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.CherryPick)
|
|
|
|
if mustStash {
|
|
if err := self.c.Git().Stash.Push(self.c.Tr.AutoStashForCherryPicking); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
result := self.c.Git().Rebase.CherryPickCommits(cherryPickedCommits)
|
|
err := self.rebaseHelper.CheckMergeOrRebaseWithRefreshOptions(result,
|
|
types.RefreshOptions{BatchUIUpdates: true})
|
|
if err != nil {
|
|
return result
|
|
}
|
|
|
|
// If we're in the cherry-picking state at this point, it must
|
|
// be because there were conflicts. Don't clear the copied
|
|
// commits in this case, since we might want to abort and try
|
|
// pasting them again.
|
|
isInCherryPick, result := self.c.Git().Status.IsInCherryPick()
|
|
if result != nil {
|
|
return result
|
|
}
|
|
if !isInCherryPick {
|
|
// DidPaste and the re-render touch mode state and contexts,
|
|
// so run them on the UI thread.
|
|
self.c.OnUIThread(func() error {
|
|
self.getData().DidPaste = true
|
|
self.rerender()
|
|
return nil
|
|
})
|
|
|
|
if mustStash {
|
|
if err := self.c.Git().Stash.Pop(0); err != nil {
|
|
return err
|
|
}
|
|
self.c.RefreshFromWorker(types.RefreshOptions{
|
|
Scope: []types.RefreshableView{types.STASH, types.FILES},
|
|
})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CherryPickHelper) CanPaste() bool {
|
|
return self.getData().CanPaste()
|
|
}
|
|
|
|
func (self *CherryPickHelper) Reset() error {
|
|
self.getData().ContextKey = ""
|
|
self.getData().CherryPickedCommits = nil
|
|
|
|
self.rerender()
|
|
return nil
|
|
}
|
|
|
|
// you can only copy from one context at a time, because the order and position of commits matter
|
|
func (self *CherryPickHelper) resetIfNecessary(context types.Context) error {
|
|
oldContextKey := types.ContextKey(self.getData().ContextKey)
|
|
|
|
if oldContextKey != context.GetKey() {
|
|
// need to reset the cherry picking mode
|
|
self.getData().ContextKey = string(context.GetKey())
|
|
self.getData().CherryPickedCommits = make([]*models.Commit, 0)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CherryPickHelper) rerender() {
|
|
for _, context := range []types.Context{
|
|
self.c.Contexts().LocalCommits,
|
|
self.c.Contexts().ReflogCommits,
|
|
self.c.Contexts().SubCommits,
|
|
} {
|
|
self.c.PostRefreshUpdate(context)
|
|
}
|
|
}
|