mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-15 18:16:25 -04:00
Move, revert, squash-fixups, create-fixup and cherry-pick paste ran their rebase synchronously on the UI thread via WithWaitingStatusSync, which froze the UI for the duration but kept the user from disrupting the operation with a stray keypress. Switch them to WithWaitingStatusBlockingInput so the git work runs on a worker — the UI keeps rendering and the spinner animates — while input stays blocked for the whole operation, as before. discard-patch-from-commit also moves off WithWaitingStatusSync, but as a plain WithWaitingStatus: it's a custom-patch command, and those don't block input. The bodies now follow the worker conventions: model state they need is captured on the UI thread before dispatching, self.c.Refresh becomes RefreshFromWorker, and CheckMergeOrRebase uses the worker variant. An operation that moves the selection does so in the refresh's Then, so it lands in the same frame as the refreshed commit list; squash sets it as an absolute index there, because the shorter list would clamp a relative move.
173 lines
4.8 KiB
Go
173 lines
4.8 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(self.c.Tr.CherryPickingStatus, 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)
|
|
}
|
|
}
|