2022-01-16 05:46:53 +02:00
|
|
|
package controllers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/jesseduffield/gocui"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/hosting_service"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
CheckoutRefFn func(refName string, opts types.CheckoutRefOptions) error
|
|
|
|
CreateGitResetMenuFn func(refName string) error
|
|
|
|
SwitchToCommitFilesContextFn func(SwitchToCommitFilesContextOpts) error
|
|
|
|
CreateTagMenuFn func(commitSha string) error
|
|
|
|
GetHostingServiceMgrFn func() *hosting_service.HostingServiceMgr
|
|
|
|
PullFilesFn func() error
|
|
|
|
CheckMergeOrRebase func(error) error
|
|
|
|
OpenSearchFn func(viewName string) error
|
|
|
|
)
|
|
|
|
|
|
|
|
type LocalCommitsController struct {
|
2022-01-23 05:40:28 +02:00
|
|
|
c *ControllerCommon
|
|
|
|
getContext func() types.IListContext
|
|
|
|
os *oscommands.OSCommand
|
|
|
|
git *commands.GitCommand
|
|
|
|
refHelper IRefHelper
|
2022-01-16 05:46:53 +02:00
|
|
|
|
|
|
|
getSelectedLocalCommit func() *models.Commit
|
|
|
|
getCommits func() []*models.Commit
|
|
|
|
getSelectedLocalCommitIdx func() int
|
|
|
|
checkMergeOrRebase CheckMergeOrRebase
|
|
|
|
pullFiles PullFilesFn
|
|
|
|
createTagMenu CreateTagMenuFn
|
|
|
|
getHostingServiceMgr GetHostingServiceMgrFn
|
|
|
|
switchToCommitFilesContext SwitchToCommitFilesContextFn
|
|
|
|
openSearch OpenSearchFn
|
|
|
|
getLimitCommits func() bool
|
|
|
|
setLimitCommits func(bool)
|
|
|
|
getShowWholeGitGraph func() bool
|
|
|
|
setShowWholeGitGraph func(bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ types.IController = &LocalCommitsController{}
|
|
|
|
|
|
|
|
func NewLocalCommitsController(
|
|
|
|
c *ControllerCommon,
|
2022-01-23 05:40:28 +02:00
|
|
|
getContext func() types.IListContext,
|
2022-01-16 05:46:53 +02:00
|
|
|
os *oscommands.OSCommand,
|
|
|
|
git *commands.GitCommand,
|
|
|
|
refHelper IRefHelper,
|
|
|
|
getSelectedLocalCommit func() *models.Commit,
|
|
|
|
getCommits func() []*models.Commit,
|
|
|
|
getSelectedLocalCommitIdx func() int,
|
|
|
|
checkMergeOrRebase CheckMergeOrRebase,
|
|
|
|
pullFiles PullFilesFn,
|
|
|
|
createTagMenu CreateTagMenuFn,
|
|
|
|
getHostingServiceMgr GetHostingServiceMgrFn,
|
|
|
|
switchToCommitFilesContext SwitchToCommitFilesContextFn,
|
|
|
|
openSearch OpenSearchFn,
|
|
|
|
getLimitCommits func() bool,
|
|
|
|
setLimitCommits func(bool),
|
|
|
|
getShowWholeGitGraph func() bool,
|
|
|
|
setShowWholeGitGraph func(bool),
|
|
|
|
) *LocalCommitsController {
|
|
|
|
return &LocalCommitsController{
|
|
|
|
c: c,
|
2022-01-23 05:40:28 +02:00
|
|
|
getContext: getContext,
|
2022-01-16 05:46:53 +02:00
|
|
|
os: os,
|
|
|
|
git: git,
|
|
|
|
refHelper: refHelper,
|
|
|
|
getSelectedLocalCommit: getSelectedLocalCommit,
|
|
|
|
getCommits: getCommits,
|
|
|
|
getSelectedLocalCommitIdx: getSelectedLocalCommitIdx,
|
|
|
|
checkMergeOrRebase: checkMergeOrRebase,
|
|
|
|
pullFiles: pullFiles,
|
|
|
|
createTagMenu: createTagMenu,
|
|
|
|
getHostingServiceMgr: getHostingServiceMgr,
|
|
|
|
switchToCommitFilesContext: switchToCommitFilesContext,
|
|
|
|
openSearch: openSearch,
|
|
|
|
getLimitCommits: getLimitCommits,
|
|
|
|
setLimitCommits: setLimitCommits,
|
|
|
|
getShowWholeGitGraph: getShowWholeGitGraph,
|
|
|
|
setShowWholeGitGraph: setShowWholeGitGraph,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) Keybindings(
|
|
|
|
getKey func(key string) interface{},
|
|
|
|
config config.KeybindingConfig,
|
|
|
|
guards types.KeybindingGuards,
|
|
|
|
) []*types.Binding {
|
|
|
|
outsideFilterModeBindings := []*types.Binding{
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.SquashDown),
|
|
|
|
Handler: self.squashDown,
|
|
|
|
Description: self.c.Tr.LcSquashDown,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.MarkCommitAsFixup),
|
|
|
|
Handler: self.fixup,
|
|
|
|
Description: self.c.Tr.LcFixupCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.RenameCommit),
|
|
|
|
Handler: self.checkSelected(self.reword),
|
|
|
|
Description: self.c.Tr.LcRewordCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.RenameCommitWithEditor),
|
|
|
|
Handler: self.rewordEditor,
|
|
|
|
Description: self.c.Tr.LcRenameCommitEditor,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Universal.Remove),
|
|
|
|
Handler: self.drop,
|
|
|
|
Description: self.c.Tr.LcDeleteCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Universal.Edit),
|
|
|
|
Handler: self.edit,
|
|
|
|
Description: self.c.Tr.LcEditCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.PickCommit),
|
|
|
|
Handler: self.pick,
|
|
|
|
Description: self.c.Tr.LcPickCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.CreateFixupCommit),
|
|
|
|
Handler: self.checkSelected(self.handleCreateFixupCommit),
|
|
|
|
Description: self.c.Tr.LcCreateFixupCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.SquashAboveCommits),
|
|
|
|
Handler: self.checkSelected(self.handleSquashAllAboveFixupCommits),
|
|
|
|
Description: self.c.Tr.LcSquashAboveCommits,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.MoveDownCommit),
|
|
|
|
Handler: self.handleCommitMoveDown,
|
|
|
|
Description: self.c.Tr.LcMoveDownCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.MoveUpCommit),
|
|
|
|
Handler: self.handleCommitMoveUp,
|
|
|
|
Description: self.c.Tr.LcMoveUpCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.AmendToCommit),
|
|
|
|
Handler: self.handleCommitAmendTo,
|
|
|
|
Description: self.c.Tr.LcAmendToCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.RevertCommit),
|
|
|
|
Handler: self.checkSelected(self.handleCommitRevert),
|
|
|
|
Description: self.c.Tr.LcRevertCommit,
|
|
|
|
},
|
|
|
|
// overriding these navigation keybindings because we might need to load
|
|
|
|
// more commits on demand
|
|
|
|
{
|
|
|
|
Key: getKey(config.Universal.StartSearch),
|
|
|
|
Handler: func() error { return self.handleOpenSearch("commits") },
|
|
|
|
Description: self.c.Tr.LcStartSearch,
|
|
|
|
Tag: "navigation",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Universal.GotoBottom),
|
|
|
|
Handler: self.gotoBottom,
|
|
|
|
Description: self.c.Tr.LcGotoBottom,
|
|
|
|
Tag: "navigation",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: gocui.MouseLeft,
|
2022-01-23 05:40:28 +02:00
|
|
|
Handler: func() error { return self.getContext().HandleClick(self.checkSelected(self.enter)) },
|
2022-01-16 05:46:53 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, binding := range outsideFilterModeBindings {
|
|
|
|
binding.Handler = guards.OutsideFilterMode(binding.Handler)
|
|
|
|
}
|
|
|
|
|
|
|
|
bindings := append(outsideFilterModeBindings, []*types.Binding{
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.OpenLogMenu),
|
|
|
|
Handler: self.handleOpenLogMenu,
|
|
|
|
Description: self.c.Tr.LcOpenLogMenu,
|
|
|
|
OpensMenu: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.ViewResetOptions),
|
|
|
|
Handler: self.checkSelected(self.handleCreateCommitResetMenu),
|
|
|
|
Description: self.c.Tr.LcResetToThisCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Universal.GoInto),
|
|
|
|
Handler: self.checkSelected(self.enter),
|
|
|
|
Description: self.c.Tr.LcViewCommitFiles,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.CheckoutCommit),
|
|
|
|
Handler: self.checkSelected(self.handleCheckoutCommit),
|
|
|
|
Description: self.c.Tr.LcCheckoutCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.TagCommit),
|
|
|
|
Handler: self.checkSelected(self.handleTagCommit),
|
|
|
|
Description: self.c.Tr.LcTagCommit,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.CopyCommitMessageToClipboard),
|
|
|
|
Handler: self.checkSelected(self.handleCopySelectedCommitMessageToClipboard),
|
|
|
|
Description: self.c.Tr.LcCopyCommitMessageToClipboard,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: getKey(config.Commits.OpenInBrowser),
|
|
|
|
Handler: self.checkSelected(self.handleOpenCommitInBrowser),
|
|
|
|
Description: self.c.Tr.LcOpenCommitInBrowser,
|
|
|
|
},
|
|
|
|
}...)
|
|
|
|
|
2022-01-23 05:40:28 +02:00
|
|
|
return append(bindings, self.getContext().Keybindings(getKey, config, guards)...)
|
2022-01-16 05:46:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) squashDown() error {
|
|
|
|
if len(self.getCommits()) <= 1 {
|
|
|
|
return self.c.ErrorMsg(self.c.Tr.YouNoCommitsToSquash)
|
|
|
|
}
|
|
|
|
|
|
|
|
applied, err := self.handleMidRebaseCommand("squash")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if applied {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.Ask(popup.AskOpts{
|
|
|
|
Title: self.c.Tr.Squash,
|
|
|
|
Prompt: self.c.Tr.SureSquashThisCommit,
|
|
|
|
HandleConfirm: func() error {
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
|
|
|
|
return self.interactiveRebase("squash")
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) fixup() error {
|
|
|
|
if len(self.getCommits()) <= 1 {
|
|
|
|
return self.c.ErrorMsg(self.c.Tr.YouNoCommitsToSquash)
|
|
|
|
}
|
|
|
|
|
|
|
|
applied, err := self.handleMidRebaseCommand("fixup")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if applied {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.Ask(popup.AskOpts{
|
|
|
|
Title: self.c.Tr.Fixup,
|
|
|
|
Prompt: self.c.Tr.SureFixupThisCommit,
|
|
|
|
HandleConfirm: func() error {
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
|
|
|
|
return self.interactiveRebase("fixup")
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
|
|
|
applied, err := self.handleMidRebaseCommand("reword")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if applied {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
message, err := self.git.Commit.GetCommitMessage(commit.Sha)
|
|
|
|
if err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: use the commit message panel here
|
|
|
|
return self.c.Prompt(popup.PromptOpts{
|
|
|
|
Title: self.c.Tr.LcRewordCommit,
|
|
|
|
InitialContent: message,
|
|
|
|
HandleConfirm: func(response string) error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.RewordCommit)
|
|
|
|
if err := self.git.Rebase.RewordCommit(self.getCommits(), self.getSelectedLocalCommitIdx(), response); err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) rewordEditor() error {
|
|
|
|
applied, err := self.handleMidRebaseCommand("reword")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if applied {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.RewordCommit)
|
|
|
|
subProcess, err := self.git.Rebase.RewordCommitInEditor(
|
|
|
|
self.getCommits(), self.getSelectedLocalCommitIdx(),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
if subProcess != nil {
|
|
|
|
return self.c.RunSubprocessAndRefresh(subProcess)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) drop() error {
|
|
|
|
applied, err := self.handleMidRebaseCommand("drop")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if applied {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.Ask(popup.AskOpts{
|
|
|
|
Title: self.c.Tr.DeleteCommitTitle,
|
|
|
|
Prompt: self.c.Tr.DeleteCommitPrompt,
|
|
|
|
HandleConfirm: func() error {
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
|
|
|
return self.interactiveRebase("drop")
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) edit() error {
|
|
|
|
applied, err := self.handleMidRebaseCommand("edit")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if applied {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.EditCommit)
|
|
|
|
return self.interactiveRebase("edit")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) pick() error {
|
|
|
|
applied, err := self.handleMidRebaseCommand("pick")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if applied {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// at this point we aren't actually rebasing so we will interpret this as an
|
|
|
|
// attempt to pull. We might revoke this later after enabling configurable keybindings
|
|
|
|
return self.pullFiles()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) interactiveRebase(action string) error {
|
|
|
|
err := self.git.Rebase.InteractiveRebase(self.getCommits(), self.getSelectedLocalCommitIdx(), action)
|
|
|
|
return self.checkMergeOrRebase(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleMidRebaseCommand sees if the selected commit is in fact a rebasing
|
|
|
|
// commit meaning you are trying to edit the todo file rather than actually
|
|
|
|
// begin a rebase. It then updates the todo file with that action
|
|
|
|
func (self *LocalCommitsController) handleMidRebaseCommand(action string) (bool, error) {
|
|
|
|
selectedCommit := self.getSelectedLocalCommit()
|
|
|
|
if selectedCommit.Status != "rebasing" {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// for now we do not support setting 'reword' because it requires an editor
|
|
|
|
// and that means we either unconditionally wait around for the subprocess to ask for
|
|
|
|
// our input or we set a lazygit client as the EDITOR env variable and have it
|
|
|
|
// request us to edit the commit message when prompted.
|
|
|
|
if action == "reword" {
|
|
|
|
return true, self.c.ErrorMsg(self.c.Tr.LcRewordNotSupported)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.c.LogAction("Update rebase TODO")
|
|
|
|
self.c.LogCommand(
|
|
|
|
fmt.Sprintf("Updating rebase action of commit %s to '%s'", selectedCommit.ShortSha(), action),
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := self.git.Rebase.EditRebaseTodo(
|
|
|
|
self.getSelectedLocalCommitIdx(), action,
|
|
|
|
); err != nil {
|
|
|
|
return false, self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, self.c.Refresh(types.RefreshOptions{
|
|
|
|
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleCommitMoveDown() error {
|
2022-01-23 05:40:28 +02:00
|
|
|
index := self.getContext().GetPanelState().GetSelectedLineIdx()
|
2022-01-16 05:46:53 +02:00
|
|
|
commits := self.getCommits()
|
|
|
|
selectedCommit := self.getCommits()[index]
|
|
|
|
if selectedCommit.Status == "rebasing" {
|
|
|
|
if commits[index+1].Status != "rebasing" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// logging directly here because MoveTodoDown doesn't have enough information
|
|
|
|
// to provide a useful log
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
|
|
|
|
self.c.LogCommand(fmt.Sprintf("Moving commit %s down", selectedCommit.ShortSha()), false)
|
|
|
|
|
|
|
|
if err := self.git.Rebase.MoveTodoDown(index); err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
2022-01-23 05:40:28 +02:00
|
|
|
_ = self.getContext().HandleNextLine()
|
2022-01-16 05:46:53 +02:00
|
|
|
return self.c.Refresh(types.RefreshOptions{
|
|
|
|
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
|
|
|
|
err := self.git.Rebase.MoveCommitDown(self.getCommits(), index)
|
|
|
|
if err == nil {
|
2022-01-23 05:40:28 +02:00
|
|
|
_ = self.getContext().HandleNextLine()
|
2022-01-16 05:46:53 +02:00
|
|
|
}
|
|
|
|
return self.checkMergeOrRebase(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleCommitMoveUp() error {
|
2022-01-23 05:40:28 +02:00
|
|
|
index := self.getContext().GetPanelState().GetSelectedLineIdx()
|
2022-01-16 05:46:53 +02:00
|
|
|
if index == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
selectedCommit := self.getCommits()[index]
|
|
|
|
if selectedCommit.Status == "rebasing" {
|
|
|
|
// logging directly here because MoveTodoDown doesn't have enough information
|
|
|
|
// to provide a useful log
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
|
|
|
|
self.c.LogCommand(
|
|
|
|
fmt.Sprintf("Moving commit %s up", selectedCommit.ShortSha()),
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := self.git.Rebase.MoveTodoDown(index - 1); err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
2022-01-23 05:40:28 +02:00
|
|
|
_ = self.getContext().HandlePrevLine()
|
2022-01-16 05:46:53 +02:00
|
|
|
return self.c.Refresh(types.RefreshOptions{
|
|
|
|
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
|
|
|
|
err := self.git.Rebase.MoveCommitDown(self.getCommits(), index-1)
|
|
|
|
if err == nil {
|
2022-01-23 05:40:28 +02:00
|
|
|
_ = self.getContext().HandlePrevLine()
|
2022-01-16 05:46:53 +02:00
|
|
|
}
|
|
|
|
return self.checkMergeOrRebase(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleCommitAmendTo() error {
|
|
|
|
return self.c.Ask(popup.AskOpts{
|
|
|
|
Title: self.c.Tr.AmendCommitTitle,
|
|
|
|
Prompt: self.c.Tr.AmendCommitPrompt,
|
|
|
|
HandleConfirm: func() error {
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
|
|
|
err := self.git.Rebase.AmendTo(self.getSelectedLocalCommit().Sha)
|
|
|
|
return self.checkMergeOrRebase(err)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleCommitRevert(commit *models.Commit) error {
|
|
|
|
if commit.IsMerge() {
|
|
|
|
return self.createRevertMergeCommitMenu(commit)
|
|
|
|
} else {
|
|
|
|
return self.c.Ask(popup.AskOpts{
|
|
|
|
Title: self.c.Tr.Actions.RevertCommit,
|
|
|
|
Prompt: utils.ResolvePlaceholderString(
|
|
|
|
self.c.Tr.ConfirmRevertCommit,
|
|
|
|
map[string]string{
|
|
|
|
"selectedCommit": commit.ShortSha(),
|
|
|
|
}),
|
|
|
|
HandleConfirm: func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.RevertCommit)
|
|
|
|
if err := self.git.Commit.Revert(commit.Sha); err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
return self.afterRevertCommit()
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) createRevertMergeCommitMenu(commit *models.Commit) error {
|
|
|
|
menuItems := make([]*popup.MenuItem, len(commit.Parents))
|
|
|
|
for i, parentSha := range commit.Parents {
|
|
|
|
i := i
|
|
|
|
message, err := self.git.Commit.GetCommitMessageFirstLine(parentSha)
|
|
|
|
if err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
menuItems[i] = &popup.MenuItem{
|
|
|
|
DisplayString: fmt.Sprintf("%s: %s", utils.SafeTruncate(parentSha, 8), message),
|
|
|
|
OnPress: func() error {
|
|
|
|
parentNumber := i + 1
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.RevertCommit)
|
|
|
|
if err := self.git.Commit.RevertMerge(commit.Sha, parentNumber); err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
return self.afterRevertCommit()
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.Menu(popup.CreateMenuOptions{Title: self.c.Tr.SelectParentCommitForMerge, Items: menuItems})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) afterRevertCommit() error {
|
2022-01-23 05:40:28 +02:00
|
|
|
_ = self.getContext().HandleNextLine()
|
2022-01-16 05:46:53 +02:00
|
|
|
return self.c.Refresh(types.RefreshOptions{
|
|
|
|
Mode: types.BLOCK_UI, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) enter(commit *models.Commit) error {
|
|
|
|
return self.switchToCommitFilesContext(SwitchToCommitFilesContextOpts{
|
|
|
|
RefName: commit.Sha,
|
|
|
|
CanRebase: true,
|
2022-01-23 05:40:28 +02:00
|
|
|
Context: self.getContext(),
|
2022-01-16 05:46:53 +02:00
|
|
|
WindowName: "commits",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleCreateFixupCommit(commit *models.Commit) error {
|
|
|
|
prompt := utils.ResolvePlaceholderString(
|
|
|
|
self.c.Tr.SureCreateFixupCommit,
|
|
|
|
map[string]string{
|
|
|
|
"commit": commit.Sha,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
return self.c.Ask(popup.AskOpts{
|
|
|
|
Title: self.c.Tr.CreateFixupCommit,
|
|
|
|
Prompt: prompt,
|
|
|
|
HandleConfirm: func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
|
|
|
|
if err := self.git.Commit.CreateFixupCommit(commit.Sha); err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleSquashAllAboveFixupCommits(commit *models.Commit) error {
|
|
|
|
prompt := utils.ResolvePlaceholderString(
|
|
|
|
self.c.Tr.SureSquashAboveCommits,
|
|
|
|
map[string]string{
|
|
|
|
"commit": commit.Sha,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
return self.c.Ask(popup.AskOpts{
|
|
|
|
Title: self.c.Tr.SquashAboveCommits,
|
|
|
|
Prompt: prompt,
|
|
|
|
HandleConfirm: func() error {
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
|
|
|
|
err := self.git.Rebase.SquashAllAboveFixupCommits(commit.Sha)
|
|
|
|
return self.checkMergeOrRebase(err)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleTagCommit(commit *models.Commit) error {
|
|
|
|
return self.createTagMenu(commit.Sha)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleCheckoutCommit(commit *models.Commit) error {
|
|
|
|
return self.c.Ask(popup.AskOpts{
|
|
|
|
Title: self.c.Tr.LcCheckoutCommit,
|
|
|
|
Prompt: self.c.Tr.SureCheckoutThisCommit,
|
|
|
|
HandleConfirm: func() error {
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
|
|
|
return self.refHelper.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleCreateCommitResetMenu(commit *models.Commit) error {
|
|
|
|
return self.refHelper.CreateGitResetMenu(commit.Sha)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleOpenSearch(string) error {
|
|
|
|
// we usually lazyload these commits but now that we're searching we need to load them now
|
|
|
|
if self.getLimitCommits() {
|
|
|
|
self.setLimitCommits(false)
|
|
|
|
if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.openSearch("commits")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) gotoBottom() error {
|
|
|
|
// we usually lazyload these commits but now that we're jumping to the bottom we need to load them now
|
|
|
|
if self.getLimitCommits() {
|
|
|
|
self.setLimitCommits(false)
|
|
|
|
if err := self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-23 05:40:28 +02:00
|
|
|
_ = self.getContext().HandleGotoBottom()
|
2022-01-16 05:46:53 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleCopySelectedCommitMessageToClipboard(commit *models.Commit) error {
|
|
|
|
message, err := self.git.Commit.GetCommitMessage(commit.Sha)
|
|
|
|
if err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
|
|
|
|
if err := self.os.CopyToClipboard(message); err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.c.Toast(self.c.Tr.CommitMessageCopiedToClipboard)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleOpenLogMenu() error {
|
|
|
|
return self.c.Menu(popup.CreateMenuOptions{
|
|
|
|
Title: self.c.Tr.LogMenuTitle,
|
|
|
|
Items: []*popup.MenuItem{
|
|
|
|
{
|
|
|
|
DisplayString: self.c.Tr.ToggleShowGitGraphAll,
|
|
|
|
OnPress: func() error {
|
|
|
|
self.setShowWholeGitGraph(!self.getShowWholeGitGraph())
|
|
|
|
|
|
|
|
if self.getShowWholeGitGraph() {
|
|
|
|
self.setLimitCommits(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.LcLoadingCommits, func() error {
|
|
|
|
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DisplayString: self.c.Tr.ShowGitGraph,
|
|
|
|
OpensMenu: true,
|
|
|
|
OnPress: func() error {
|
|
|
|
onPress := func(value string) func() error {
|
|
|
|
return func() error {
|
|
|
|
self.c.UserConfig.Git.Log.ShowGraph = value
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return self.c.Menu(popup.CreateMenuOptions{
|
|
|
|
Title: self.c.Tr.LogMenuTitle,
|
|
|
|
Items: []*popup.MenuItem{
|
|
|
|
{
|
|
|
|
DisplayString: "always",
|
|
|
|
OnPress: onPress("always"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DisplayString: "never",
|
|
|
|
OnPress: onPress("never"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DisplayString: "when maximised",
|
|
|
|
OnPress: onPress("when-maximised"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DisplayString: self.c.Tr.SortCommits,
|
|
|
|
OpensMenu: true,
|
|
|
|
OnPress: func() error {
|
|
|
|
onPress := func(value string) func() error {
|
|
|
|
return func() error {
|
|
|
|
self.c.UserConfig.Git.Log.Order = value
|
|
|
|
return self.c.WithWaitingStatus(self.c.Tr.LcLoadingCommits, func() error {
|
|
|
|
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.c.Menu(popup.CreateMenuOptions{
|
|
|
|
Title: self.c.Tr.LogMenuTitle,
|
|
|
|
Items: []*popup.MenuItem{
|
|
|
|
{
|
|
|
|
DisplayString: "topological (topo-order)",
|
|
|
|
OnPress: onPress("topo-order"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DisplayString: "date-order",
|
|
|
|
OnPress: onPress("date-order"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DisplayString: "author-date-order",
|
|
|
|
OnPress: onPress("author-date-order"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) handleOpenCommitInBrowser(commit *models.Commit) error {
|
|
|
|
hostingServiceMgr := self.getHostingServiceMgr()
|
|
|
|
|
|
|
|
url, err := hostingServiceMgr.GetCommitURL(commit.Sha)
|
|
|
|
if err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
|
|
|
|
if err := self.os.OpenLink(url); err != nil {
|
|
|
|
return self.c.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) checkSelected(callback func(*models.Commit) error) func() error {
|
|
|
|
return func() error {
|
|
|
|
commit := self.getSelectedLocalCommit()
|
|
|
|
if commit == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return callback(commit)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *LocalCommitsController) Context() types.Context {
|
2022-01-23 05:40:28 +02:00
|
|
|
return self.getContext()
|
2022-01-16 05:46:53 +02:00
|
|
|
}
|