summaryrefslogtreecommitdiffstats
path: root/pkg/gui/controllers/local_commits_controller.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/gui/controllers/local_commits_controller.go')
-rw-r--r--pkg/gui/controllers/local_commits_controller.go783
1 files changed, 783 insertions, 0 deletions
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
new file mode 100644
index 000000000..1d79bd2dd
--- /dev/null
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -0,0 +1,783 @@
+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 {
+ c *ControllerCommon
+ context types.IListContext
+ 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)
+}
+
+var _ types.IController = &LocalCommitsController{}
+
+func NewLocalCommitsController(
+ c *ControllerCommon,
+ context types.IListContext,
+ 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,
+ context: context,
+ 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,
+ Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) },
+ },
+ }
+
+ 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,
+ },
+ }...)
+
+ return append(bindings, self.context.Keybindings(getKey, config, guards)...)
+}
+
+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 {
+ index := self.context.GetPanelState().GetSelectedLineIdx()
+ 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)
+ }
+ self.context.HandleNextLine()
+ 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 {
+ self.context.HandleNextLine()
+ }
+ return self.checkMergeOrRebase(err)
+ })
+}
+
+func (self *LocalCommitsController) handleCommitMoveUp() error {
+ index := self.context.GetPanelState().GetSelectedLineIdx()
+ 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)
+ }
+ self.context.HandlePrevLine()
+ 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 {
+ self.context.HandlePrevLine()
+ }
+ 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 {
+ self.context.HandleNextLine()
+ 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,
+ Context: self.context,
+ 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
+ }
+ }
+
+ self.context.HandleGotoBottom()
+
+ 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 {
+ return self.context
+}