1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-15 00:15:32 +02:00

add commit message controller

This commit is contained in:
Jesse Duffield
2022-02-22 21:16:00 +11:00
parent 120078f011
commit d991d74b06
11 changed files with 217 additions and 129 deletions

View File

@ -207,6 +207,9 @@ func (self *cmdObjRunner) runAndStreamAux(
cmdObj ICmdObj, cmdObj ICmdObj,
onRun func(*cmdHandler, io.Writer), onRun func(*cmdHandler, io.Writer),
) error { ) error {
// if we're streaming this we don't want any fancy terminal stuff
cmdObj.AddEnvVars("TERM=dumb")
cmdWriter := self.guiIO.newCmdWriterFn() cmdWriter := self.guiIO.newCmdWriterFn()
if cmdObj.ShouldLog() { if cmdObj.ShouldLog() {

View File

@ -8,28 +8,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
func (gui *Gui) handleCommitConfirm() error {
message := strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
gui.State.failedCommitMessage = message
if message == "" {
return gui.c.ErrorMsg(gui.c.Tr.CommitWithoutMessageErr)
}
cmdObj := gui.git.Commit.CommitCmdObj(message)
gui.c.LogAction(gui.c.Tr.Actions.Commit)
_ = gui.c.PopContext()
return gui.withGpgHandling(cmdObj, gui.c.Tr.CommittingStatus, func() error {
gui.Views.CommitMessage.ClearTextArea()
gui.State.failedCommitMessage = ""
return nil
})
}
func (gui *Gui) handleCommitClose() error {
return gui.c.PopContext()
}
func (gui *Gui) handleCommitMessageFocused() error { func (gui *Gui) handleCommitMessageFocused() error {
message := utils.ResolvePlaceholderString( message := utils.ResolvePlaceholderString(
gui.c.Tr.CommitMessageConfirm, gui.c.Tr.CommitMessageConfirm,
@ -45,15 +23,14 @@ func (gui *Gui) handleCommitMessageFocused() error {
return gui.renderString(gui.Views.Options, message) return gui.renderString(gui.Views.Options, message)
} }
func (gui *Gui) getBufferLength(view *gocui.View) string {
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
}
// RenderCommitLength is a function.
func (gui *Gui) RenderCommitLength() { func (gui *Gui) RenderCommitLength() {
if !gui.c.UserConfig.Gui.CommitLength.Show { if !gui.c.UserConfig.Gui.CommitLength.Show {
return return
} }
gui.Views.CommitMessage.Subtitle = gui.getBufferLength(gui.Views.CommitMessage) gui.Views.CommitMessage.Subtitle = getBufferLength(gui.Views.CommitMessage)
}
func getBufferLength(view *gocui.View) string {
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
} }

View File

@ -0,0 +1,79 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type CommitMessageController struct {
baseController
*controllerCommon
getCommitMessage func() string
onCommitAttempt func(message string)
onCommitSuccess func()
}
var _ types.IController = &CommitMessageController{}
func NewCommitMessageController(
common *controllerCommon,
getCommitMessage func() string,
onCommitAttempt func(message string),
onCommitSuccess func(),
) *CommitMessageController {
return &CommitMessageController{
baseController: baseController{},
controllerCommon: common,
getCommitMessage: getCommitMessage,
onCommitAttempt: onCommitAttempt,
onCommitSuccess: onCommitSuccess,
}
}
func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
Handler: self.handleCommitConfirm,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.handleCommitClose,
},
}
return bindings
}
func (self *CommitMessageController) Context() types.Context {
return self.context()
}
// this method is pointless in this context but I'm keeping it consistent
// with other contexts so that when generics arrive it's easier to refactor
func (self *CommitMessageController) context() types.Context {
return self.contexts.CommitMessage
}
func (self *CommitMessageController) handleCommitConfirm() error {
message := self.getCommitMessage()
self.onCommitAttempt(message)
if message == "" {
return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
}
cmdObj := self.git.Commit.CommitCmdObj(message)
self.c.LogAction(self.c.Tr.Actions.Commit)
_ = self.c.PopContext()
return self.helpers.GPG.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error {
self.onCommitSuccess()
return nil
})
}
func (self *CommitMessageController) handleCommitClose() error {
return self.c.PopContext()
}

View File

@ -8,7 +8,6 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "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/config"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/filetree"
@ -22,8 +21,7 @@ type FilesController struct {
enterSubmodule func(submodule *models.SubmoduleConfig) error enterSubmodule func(submodule *models.SubmoduleConfig) error
setCommitMessage func(message string) setCommitMessage func(message string)
withGpgHandling func(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error getSavedCommitMessage func() string
getFailedCommitMessage func() string
switchToMergeFn func(path string) error switchToMergeFn func(path string) error
} }
@ -33,16 +31,14 @@ func NewFilesController(
common *controllerCommon, common *controllerCommon,
enterSubmodule func(submodule *models.SubmoduleConfig) error, enterSubmodule func(submodule *models.SubmoduleConfig) error,
setCommitMessage func(message string), setCommitMessage func(message string),
withGpgHandling func(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error, getSavedCommitMessage func() string,
getFailedCommitMessage func() string,
switchToMergeFn func(path string) error, switchToMergeFn func(path string) error,
) *FilesController { ) *FilesController {
return &FilesController{ return &FilesController{
controllerCommon: common, controllerCommon: common,
enterSubmodule: enterSubmodule, enterSubmodule: enterSubmodule,
setCommitMessage: setCommitMessage, setCommitMessage: setCommitMessage,
withGpgHandling: withGpgHandling, getSavedCommitMessage: getSavedCommitMessage,
getFailedCommitMessage: getFailedCommitMessage,
switchToMergeFn: switchToMergeFn, switchToMergeFn: switchToMergeFn,
} }
} }
@ -409,9 +405,9 @@ func (self *FilesController) HandleCommitPress() error {
return self.promptToStageAllAndRetry(self.HandleCommitPress) return self.promptToStageAllAndRetry(self.HandleCommitPress)
} }
failedCommitMessage := self.getFailedCommitMessage() savedCommitMessage := self.getSavedCommitMessage()
if len(failedCommitMessage) > 0 { if len(savedCommitMessage) > 0 {
self.setCommitMessage(failedCommitMessage) self.setCommitMessage(savedCommitMessage)
} else { } else {
commitPrefixConfig := self.commitPrefixConfigForRepo() commitPrefixConfig := self.commitPrefixConfigForRepo()
if commitPrefixConfig != nil { if commitPrefixConfig != nil {
@ -470,7 +466,7 @@ func (self *FilesController) handleAmendCommitPress() error {
HandleConfirm: func() error { HandleConfirm: func() error {
cmdObj := self.git.Commit.AmendHeadCmdObj() cmdObj := self.git.Commit.AmendHeadCmdObj()
self.c.LogAction(self.c.Tr.Actions.AmendCommit) self.c.LogAction(self.c.Tr.Actions.AmendCommit)
return self.withGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil) return self.helpers.GPG.WithGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil)
}, },
}) })
} }

View File

@ -0,0 +1,74 @@
package helpers
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type GpgHelper struct {
c *types.HelperCommon
os *oscommands.OSCommand
git *commands.GitCommand
}
func NewGpgHelper(
c *types.HelperCommon,
os *oscommands.OSCommand,
git *commands.GitCommand,
) *GpgHelper {
return &GpgHelper{
c: c,
os: os,
git: git,
}
}
// Currently there is a bug where if we switch to a subprocess from within
// WithWaitingStatus we get stuck there and can't return to lazygit. We could
// fix this bug, or just stop running subprocesses from within there, given that
// we don't need to see a loading status if we're in a subprocess.
// TODO: we shouldn't need to use a shell here, but looks like that NewShell function contains some windows specific quoting stuff. We should centralise that.
func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
useSubprocess := self.git.Config.UsingGpg()
if useSubprocess {
success, err := self.c.RunSubprocess(self.os.Cmd.NewShell(cmdObj.ToString()))
if success && onSuccess != nil {
if err := onSuccess(); err != nil {
return err
}
}
if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
return err
} else {
return self.runAndStream(cmdObj, waitingStatus, onSuccess)
}
}
func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
cmdObj = self.os.Cmd.NewShell(cmdObj.ToString())
return self.c.WithWaitingStatus(waitingStatus, func() error {
if err := cmdObj.StreamOutput().Run(); err != nil {
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return self.c.Error(
fmt.Errorf(
self.c.Tr.GitCommandFailed, self.c.UserConfig.Keybinding.Universal.ExtrasMenu,
),
)
}
if onSuccess != nil {
if err := onSuccess(); err != nil {
return err
}
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
})
}

View File

@ -11,6 +11,7 @@ type Helpers struct {
CherryPick *CherryPickHelper CherryPick *CherryPickHelper
Host *HostHelper Host *HostHelper
PatchBuilding *PatchBuildingHelper PatchBuilding *PatchBuildingHelper
GPG *GpgHelper
} }
func NewStubHelpers() *Helpers { func NewStubHelpers() *Helpers {
@ -25,5 +26,6 @@ func NewStubHelpers() *Helpers {
CherryPick: &CherryPickHelper{}, CherryPick: &CherryPickHelper{},
Host: &HostHelper{}, Host: &HostHelper{},
PatchBuilding: &PatchBuildingHelper{}, PatchBuilding: &PatchBuildingHelper{},
GPG: &GpgHelper{},
} }
} }

View File

@ -1,66 +0,0 @@
package gui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// Currently there is a bug where if we switch to a subprocess from within
// WithWaitingStatus we get stuck there and can't return to lazygit. We could
// fix this bug, or just stop running subprocesses from within there, given that
// we don't need to see a loading status if we're in a subprocess.
// TODO: work out if we actually need to use a shell command here
func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
gui.LogCommand(cmdObj.ToString(), true)
useSubprocess := gui.git.Config.UsingGpg()
if useSubprocess {
success, err := gui.runSubprocessWithSuspense(gui.os.Cmd.NewShell(cmdObj.ToString()))
if success && onSuccess != nil {
if err := onSuccess(); err != nil {
return err
}
}
if err := gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
return err
} else {
return gui.RunAndStream(cmdObj, waitingStatus, onSuccess)
}
}
func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
return gui.c.WithWaitingStatus(waitingStatus, func() error {
cmdObj := gui.os.Cmd.NewShell(cmdObj.ToString())
cmdObj.AddEnvVars("TERM=dumb")
cmdWriter := gui.getCmdWriter()
cmd := cmdObj.GetCmd()
cmd.Stdout = cmdWriter
cmd.Stderr = cmdWriter
if err := cmd.Run(); err != nil {
if _, err := cmd.Stdout.Write([]byte(fmt.Sprintf("%s\n", style.FgRed.Sprint(err.Error())))); err != nil {
gui.c.Log.Error(err)
}
_ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return gui.c.Error(
fmt.Errorf(
gui.c.Tr.GitCommandFailed, gui.c.UserConfig.Keybinding.Universal.ExtrasMenu,
),
)
}
if onSuccess != nil {
if err := onSuccess(); err != nil {
return err
}
}
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
})
}

View File

@ -193,8 +193,9 @@ type GuiRepoState struct {
// back in sync with the repo state // back in sync with the repo state
ViewsSetup bool ViewsSetup bool
// this is the message of the last failed commit attempt // we store a commit message in this field if we've escaped the commit message
failedCommitMessage string // panel without committing or if our commit failed
savedCommitMessage string
ScreenMode WindowMaximisation ScreenMode WindowMaximisation
} }
@ -503,6 +504,7 @@ func (gui *Gui) resetControllers() {
Files: helpers.NewFilesHelper(controllerCommon, gui.git, osCommand), Files: helpers.NewFilesHelper(controllerCommon, gui.git, osCommand),
WorkingTree: helpers.NewWorkingTreeHelper(model), WorkingTree: helpers.NewWorkingTreeHelper(model),
Tags: helpers.NewTagsHelper(controllerCommon, gui.git), Tags: helpers.NewTagsHelper(controllerCommon, gui.git),
GPG: helpers.NewGpgHelper(controllerCommon, gui.os, gui.git),
MergeAndRebase: rebaseHelper, MergeAndRebase: rebaseHelper,
CherryPick: helpers.NewCherryPickHelper( CherryPick: helpers.NewCherryPickHelper(
controllerCommon, controllerCommon,
@ -538,15 +540,39 @@ func (gui *Gui) resetControllers() {
reflogController := controllers.NewReflogController(common) reflogController := controllers.NewReflogController(common)
subCommitsController := controllers.NewSubCommitsController(common) subCommitsController := controllers.NewSubCommitsController(common)
getSavedCommitMessage := func() string {
return gui.State.savedCommitMessage
}
getCommitMessage := func() string {
return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
}
setCommitMessage := gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
onCommitAttempt := func(message string) {
gui.Views.CommitMessage.ClearTextArea()
}
onCommitSuccess := func() {
gui.State.savedCommitMessage = ""
}
commitMessageController := controllers.NewCommitMessageController(
common,
getCommitMessage,
onCommitAttempt,
onCommitSuccess,
)
gui.Controllers = Controllers{ gui.Controllers = Controllers{
Submodules: submodulesController, Submodules: submodulesController,
Global: controllers.NewGlobalController(common), Global: controllers.NewGlobalController(common),
Files: controllers.NewFilesController( Files: controllers.NewFilesController(
common, common,
gui.enterSubmodule, gui.enterSubmodule,
gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }), setCommitMessage,
gui.withGpgHandling, getSavedCommitMessage,
func() string { return gui.State.failedCommitMessage },
gui.switchToMerge, gui.switchToMerge,
), ),
Tags: controllers.NewTagsController(common), Tags: controllers.NewTagsController(common),
@ -604,6 +630,7 @@ func (gui *Gui) resetControllers() {
controllers.AttachControllers(gui.State.Contexts.Remotes, gui.Controllers.Remotes) controllers.AttachControllers(gui.State.Contexts.Remotes, gui.Controllers.Remotes)
controllers.AttachControllers(gui.State.Contexts.Stash, stashController) controllers.AttachControllers(gui.State.Contexts.Stash, stashController)
controllers.AttachControllers(gui.State.Contexts.Menu, gui.Controllers.Menu) controllers.AttachControllers(gui.State.Contexts.Menu, gui.Controllers.Menu)
controllers.AttachControllers(gui.State.Contexts.CommitMessage, commitMessageController)
controllers.AttachControllers(gui.State.Contexts.Global, gui.Controllers.Sync, gui.Controllers.Undo, gui.Controllers.Global) controllers.AttachControllers(gui.State.Contexts.Global, gui.Controllers.Sync, gui.Controllers.Undo, gui.Controllers.Global)
listControllerFactory := controllers.NewListControllerFactory(gui.c) listControllerFactory := controllers.NewListControllerFactory(gui.c)

View File

@ -34,6 +34,10 @@ func (self *guiCommon) RunSubprocessAndRefresh(cmdObj oscommands.ICmdObj) error
return self.gui.runSubprocessWithSuspenseAndRefresh(cmdObj) return self.gui.runSubprocessWithSuspenseAndRefresh(cmdObj)
} }
func (self *guiCommon) RunSubprocess(cmdObj oscommands.ICmdObj) (bool, error) {
return self.gui.runSubprocessWithSuspense(cmdObj)
}
func (self *guiCommon) PushContext(context types.Context, opts ...types.OnFocusOpts) error { func (self *guiCommon) PushContext(context types.Context, opts ...types.OnFocusOpts) error {
return self.gui.pushContext(context, opts...) return self.gui.pushContext(context, opts...)
} }

View File

@ -430,18 +430,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Handler: self.handleCopySelectedSideContextItemToClipboard, Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitShaToClipboard, Description: self.c.Tr.LcCopyCommitShaToClipboard,
}, },
{
ViewName: "commitMessage",
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
Modifier: gocui.ModNone,
Handler: self.handleCommitConfirm,
},
{
ViewName: "commitMessage",
Key: opts.GetKey(opts.Config.Universal.Return),
Modifier: gocui.ModNone,
Handler: self.handleCommitClose,
},
{ {
ViewName: "credentials", ViewName: "credentials",
Key: opts.GetKey(opts.Config.Universal.Confirm), Key: opts.GetKey(opts.Config.Universal.Confirm),

View File

@ -27,7 +27,11 @@ type IGuiCommon interface {
PostRefreshUpdate(Context) error PostRefreshUpdate(Context) error
// this just re-renders the screen // this just re-renders the screen
Render() Render()
// returns true if command completed successfully
RunSubprocess(cmdObj oscommands.ICmdObj) (bool, error)
RunSubprocessAndRefresh(oscommands.ICmdObj) error RunSubprocessAndRefresh(oscommands.ICmdObj) error
PushContext(context Context, opts ...OnFocusOpts) error PushContext(context Context, opts ...OnFocusOpts) error
PopContext() error PopContext() error
CurrentContext() Context CurrentContext() Context