mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-11 08:06:25 -04:00
We don't add the commands to the menu when we're rebasing, so it's pointless to check this in the handlers. I guess alternatively we could add all commands always, and strike out the ones that are not applicable using DisabledReasons. But we don't usually do this in menus (except the global keybindings menu).
292 lines
9.6 KiB
Go
292 lines
9.6 KiB
Go
package controllers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/jesseduffield/generics/set"
|
|
"github.com/jesseduffield/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type CustomPatchOptionsMenuAction struct {
|
|
c *ControllerCommon
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) Call() error {
|
|
if !self.c.Git().Patch.PatchBuilder.Active() {
|
|
return errors.New(self.c.Tr.NoPatchError)
|
|
}
|
|
|
|
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
|
return errors.New(self.c.Tr.EmptyPatchError)
|
|
}
|
|
|
|
menuItems := []*types.MenuItem{
|
|
{
|
|
Label: self.c.Tr.ResetPatch,
|
|
Tooltip: self.c.Tr.ResetPatchTooltip,
|
|
OnPress: self.c.Helpers().PatchBuilding.Reset,
|
|
Key: 'c',
|
|
},
|
|
{
|
|
Label: self.c.Tr.ApplyPatch,
|
|
Tooltip: self.c.Tr.ApplyPatchTooltip,
|
|
OnPress: func() error { return self.handleApplyPatch(false) },
|
|
Key: 'a',
|
|
},
|
|
{
|
|
Label: self.c.Tr.ApplyPatchInReverse,
|
|
Tooltip: self.c.Tr.ApplyPatchInReverseTooltip,
|
|
OnPress: func() error { return self.handleApplyPatch(true) },
|
|
Key: 'r',
|
|
},
|
|
}
|
|
|
|
if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState().None() {
|
|
menuItems = append(menuItems, []*types.MenuItem{
|
|
{
|
|
Label: fmt.Sprintf(self.c.Tr.RemovePatchFromOriginalCommit, utils.ShortHash(self.c.Git().Patch.PatchBuilder.To)),
|
|
Tooltip: self.c.Tr.RemovePatchFromOriginalCommitTooltip,
|
|
OnPress: self.handleDeletePatchFromCommit,
|
|
Key: 'd',
|
|
},
|
|
{
|
|
Label: self.c.Tr.MovePatchOutIntoIndex,
|
|
Tooltip: self.c.Tr.MovePatchOutIntoIndexTooltip,
|
|
OnPress: self.handleMovePatchIntoWorkingTree,
|
|
Key: 'i',
|
|
},
|
|
{
|
|
Label: self.c.Tr.MovePatchIntoNewCommit,
|
|
Tooltip: self.c.Tr.MovePatchIntoNewCommitTooltip,
|
|
OnPress: self.handlePullPatchIntoNewCommit,
|
|
Key: 'n',
|
|
},
|
|
{
|
|
Label: self.c.Tr.MovePatchIntoNewCommitBefore,
|
|
Tooltip: self.c.Tr.MovePatchIntoNewCommitBeforeTooltip,
|
|
OnPress: self.handlePullPatchIntoNewCommitBefore,
|
|
Key: 'N',
|
|
},
|
|
}...)
|
|
|
|
if self.c.Context().Current().GetKey() == self.c.Contexts().LocalCommits.GetKey() {
|
|
selectedCommit := self.c.Contexts().LocalCommits.GetSelected()
|
|
if selectedCommit != nil && self.c.Git().Patch.PatchBuilder.To != selectedCommit.Hash() {
|
|
|
|
var disabledReason *types.DisabledReason
|
|
if self.c.Contexts().LocalCommits.AreMultipleItemsSelected() {
|
|
disabledReason = &types.DisabledReason{Text: self.c.Tr.RangeSelectNotSupported}
|
|
}
|
|
|
|
// adding this option to index 1
|
|
menuItems = append(
|
|
menuItems[:1],
|
|
append(
|
|
[]*types.MenuItem{
|
|
{
|
|
Label: fmt.Sprintf(self.c.Tr.MovePatchToSelectedCommit, selectedCommit.Hash()),
|
|
Tooltip: self.c.Tr.MovePatchToSelectedCommitTooltip,
|
|
OnPress: self.handleMovePatchToSelectedCommit,
|
|
Key: 'm',
|
|
DisabledReason: disabledReason,
|
|
},
|
|
}, menuItems[1:]...,
|
|
)...,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
menuItems = append(menuItems, []*types.MenuItem{
|
|
{
|
|
Label: self.c.Tr.CopyPatchToClipboard,
|
|
OnPress: func() error { return self.copyPatchToClipboard() },
|
|
Key: 'y',
|
|
},
|
|
}...)
|
|
|
|
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.PatchOptionsTitle, Items: menuItems})
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int {
|
|
for index, commit := range self.c.Model().Commits {
|
|
if commit.Hash() == self.c.Git().Patch.PatchBuilder.To {
|
|
return index
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) returnFocusFromPatchExplorerIfNecessary() {
|
|
if self.c.Context().Current().GetKey() == self.c.Contexts().CustomPatchBuilder.GetKey() {
|
|
self.c.Helpers().PatchBuilding.Escape()
|
|
}
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
|
|
self.returnFocusFromPatchExplorerIfNecessary()
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
|
commitIndex := self.getPatchCommitIndex()
|
|
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
|
|
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
|
|
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
|
})
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() error {
|
|
self.returnFocusFromPatchExplorerIfNecessary()
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
|
commitIndex := self.getPatchCommitIndex()
|
|
self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit)
|
|
err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx())
|
|
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
|
})
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error {
|
|
self.returnFocusFromPatchExplorerIfNecessary()
|
|
|
|
mustStash := self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules()
|
|
return self.c.ConfirmIf(mustStash, types.ConfirmOpts{
|
|
Title: self.c.Tr.MustStashTitle,
|
|
Prompt: self.c.Tr.MustStashWarning,
|
|
HandleConfirm: func() error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
|
commitIndex := self.getPatchCommitIndex()
|
|
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex)
|
|
err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, mustStash)
|
|
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
|
|
self.returnFocusFromPatchExplorerIfNecessary()
|
|
|
|
commitIndex := self.getPatchCommitIndex()
|
|
self.c.Helpers().Commits.OpenCommitMessagePanel(
|
|
&helpers.OpenCommitMessagePanelOpts{
|
|
// Pass a commit index of one less than the moved-from commit, so that
|
|
// you can press up arrow once to recall the original commit message:
|
|
CommitIndex: commitIndex - 1,
|
|
InitialMessage: "",
|
|
SummaryTitle: self.c.Tr.CommitSummaryTitle,
|
|
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
|
PreserveMessage: false,
|
|
OnConfirm: func(summary string, description string) error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
|
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
|
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
|
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex, summary, description)
|
|
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
|
return err
|
|
}
|
|
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
|
return nil
|
|
})
|
|
},
|
|
},
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() error {
|
|
self.returnFocusFromPatchExplorerIfNecessary()
|
|
|
|
commitIndex := self.getPatchCommitIndex()
|
|
self.c.Helpers().Commits.OpenCommitMessagePanel(
|
|
&helpers.OpenCommitMessagePanelOpts{
|
|
// Pass a commit index of one less than the moved-from commit, so that
|
|
// you can press up arrow once to recall the original commit message:
|
|
CommitIndex: commitIndex - 1,
|
|
InitialMessage: "",
|
|
SummaryTitle: self.c.Tr.CommitSummaryTitle,
|
|
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
|
PreserveMessage: false,
|
|
OnConfirm: func(summary string, description string) error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
|
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
|
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
|
err := self.c.Git().Patch.PullPatchIntoNewCommitBefore(self.c.Model().Commits, commitIndex, summary, description)
|
|
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
|
return err
|
|
}
|
|
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
|
return nil
|
|
})
|
|
},
|
|
},
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
|
|
self.returnFocusFromPatchExplorerIfNecessary()
|
|
|
|
affectedUnstagedFiles := self.getAffectedUnstagedFiles()
|
|
|
|
mustStageFiles := len(affectedUnstagedFiles) > 0
|
|
return self.c.ConfirmIf(mustStageFiles, types.ConfirmOpts{
|
|
Title: self.c.Tr.MustStageFilesAffectedByPatchTitle,
|
|
Prompt: self.c.Tr.MustStageFilesAffectedByPatchWarning,
|
|
HandleConfirm: func() error {
|
|
action := self.c.Tr.Actions.ApplyPatch
|
|
if reverse {
|
|
action = "Apply patch in reverse"
|
|
}
|
|
self.c.LogAction(action)
|
|
|
|
if mustStageFiles {
|
|
if err := self.c.Git().WorkingTree.StageFiles(affectedUnstagedFiles, nil); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
|
return nil
|
|
},
|
|
})
|
|
}
|
|
|
|
func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
|
|
patch := self.c.Git().Patch.PatchBuilder.RenderAggregatedPatch(true)
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.CopyPatchToClipboard)
|
|
if err := self.c.OS().CopyToClipboard(patch); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Toast(self.c.Tr.PatchCopiedToClipboard)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Returns a list of files that have unstaged changes and are contained in the patch.
|
|
func (self *CustomPatchOptionsMenuAction) getAffectedUnstagedFiles() []string {
|
|
unstagedFiles := set.NewFromSlice(lo.FilterMap(self.c.Model().Files, func(f *models.File, _ int) (string, bool) {
|
|
if f.GetHasUnstagedChanges() {
|
|
return f.GetPath(), true
|
|
}
|
|
return "", false
|
|
}))
|
|
|
|
return lo.Filter(self.c.Git().Patch.PatchBuilder.AllFilesInPatch(), func(patchFile string, _ int) bool {
|
|
return unstagedFiles.Includes(patchFile)
|
|
})
|
|
}
|