diff --git a/docs/Config.md b/docs/Config.md index 92cc9ebd7..837230367 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -171,7 +171,6 @@ keybinding: diffingMenu-alt: '' # deprecated copyToClipboard: '' submitEditorText: '' - appendNewline: '' extrasMenu: '@' toggleWhitespaceInDiffView: '' increaseContextInDiffView: '}' diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go index d68cfbc80..ee2538c1f 100644 --- a/pkg/cheatsheet/generate.go +++ b/pkg/cheatsheet/generate.go @@ -87,33 +87,34 @@ func writeString(file *os.File, str string) { func localisedTitle(tr *i18n.TranslationSet, str string) string { contextTitleMap := map[string]string{ - "global": tr.GlobalTitle, - "navigation": tr.NavigationTitle, - "branches": tr.BranchesTitle, - "localBranches": tr.LocalBranchesTitle, - "files": tr.FilesTitle, - "status": tr.StatusTitle, - "submodules": tr.SubmodulesTitle, - "subCommits": tr.SubCommitsTitle, - "remoteBranches": tr.RemoteBranchesTitle, - "remotes": tr.RemotesTitle, - "reflogCommits": tr.ReflogCommitsTitle, - "tags": tr.TagsTitle, - "commitFiles": tr.CommitFilesTitle, - "commitMessage": tr.CommitMessageTitle, - "commits": tr.CommitsTitle, - "confirmation": tr.ConfirmationTitle, - "information": tr.InformationTitle, - "main": tr.NormalTitle, - "patchBuilding": tr.PatchBuildingTitle, - "mergeConflicts": tr.MergingTitle, - "staging": tr.StagingTitle, - "menu": tr.MenuTitle, - "search": tr.SearchTitle, - "secondary": tr.SecondaryTitle, - "stash": tr.StashTitle, - "suggestions": tr.SuggestionsCheatsheetTitle, - "extras": tr.ExtrasTitle, + "global": tr.GlobalTitle, + "navigation": tr.NavigationTitle, + "branches": tr.BranchesTitle, + "localBranches": tr.LocalBranchesTitle, + "files": tr.FilesTitle, + "status": tr.StatusTitle, + "submodules": tr.SubmodulesTitle, + "subCommits": tr.SubCommitsTitle, + "remoteBranches": tr.RemoteBranchesTitle, + "remotes": tr.RemotesTitle, + "reflogCommits": tr.ReflogCommitsTitle, + "tags": tr.TagsTitle, + "commitFiles": tr.CommitFilesTitle, + "commitMessage": tr.CommitMessageTitle, + "commitDescription": tr.CommitDescriptionTitle, + "commits": tr.CommitsTitle, + "confirmation": tr.ConfirmationTitle, + "information": tr.InformationTitle, + "main": tr.NormalTitle, + "patchBuilding": tr.PatchBuildingTitle, + "mergeConflicts": tr.MergingTitle, + "staging": tr.StagingTitle, + "menu": tr.MenuTitle, + "search": tr.SearchTitle, + "secondary": tr.SecondaryTitle, + "stash": tr.StashTitle, + "suggestions": tr.SuggestionsCheatsheetTitle, + "extras": tr.ExtrasTitle, } title, ok := contextTitleMap[str] diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go index 38459e351..0c5008e38 100644 --- a/pkg/commands/git_commands/commit.go +++ b/pkg/commands/git_commands/commit.go @@ -8,6 +8,8 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/oscommands" ) +var ErrInvalidCommitIndex = errors.New("invalid commit index") + type CommitCommands struct { *GitCommon } @@ -18,11 +20,6 @@ func NewCommitCommands(gitCommon *GitCommon) *CommitCommands { } } -// RewordLastCommit rewords the topmost commit with the given message -func (self *CommitCommands) RewordLastCommit(message string) error { - return self.cmd.New("git commit --allow-empty --amend --only -m " + self.cmd.Quote(message)).Run() -} - // ResetAuthor resets the author of the topmost commit func (self *CommitCommands) ResetAuthor() error { return self.cmd.New("git commit --allow-empty --only --no-edit --amend --reset-author").Run() @@ -45,11 +42,7 @@ func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars [ } func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj { - splitMessage := strings.Split(message, "\n") - lineArgs := "" - for _, line := range splitMessage { - lineArgs += fmt.Sprintf(" -m %s", self.cmd.Quote(line)) - } + messageArgs := self.commitMessageArgs(message) skipHookPrefix := self.UserConfig.Git.SkipHookPrefix noVerifyFlag := "" @@ -57,7 +50,23 @@ func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj { noVerifyFlag = " --no-verify" } - return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), lineArgs)) + return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), messageArgs)) +} + +// RewordLastCommit rewords the topmost commit with the given message +func (self *CommitCommands) RewordLastCommit(message string) error { + messageArgs := self.commitMessageArgs(message) + return self.cmd.New(fmt.Sprintf("git commit --allow-empty --amend --only%s", messageArgs)).Run() +} + +func (self *CommitCommands) commitMessageArgs(message string) string { + msg, description, _ := strings.Cut(message, "\n") + descriptionArgs := "" + if description != "" { + descriptionArgs = fmt.Sprintf(" -m %s", self.cmd.Quote(description)) + } + + return fmt.Sprintf(" -m %s%s", self.cmd.Quote(msg), descriptionArgs) } // runs git commit without the -m argument meaning it will invoke the user's editor @@ -178,3 +187,13 @@ func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error { func (self *CommitCommands) CreateFixupCommit(sha string) error { return self.cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run() } + +// a value of 0 means the head commit, 1 is the parent commit, etc +func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) { + hash, _ := self.cmd.New(fmt.Sprintf("git log -1 --skip=%d --pretty=%%H", value)).DontLog().RunWithOutput() + formattedHash := strings.TrimSpace(hash) + if len(formattedHash) == 0 { + return "", ErrInvalidCommitIndex + } + return self.GetCommitMessage(formattedHash) +} diff --git a/pkg/commands/git_commands/commit_test.go b/pkg/commands/git_commands/commit_test.go index 268e44e46..4cc8a8de2 100644 --- a/pkg/commands/git_commands/commit_test.go +++ b/pkg/commands/git_commands/commit_test.go @@ -9,12 +9,32 @@ import ( ) func TestCommitRewordCommit(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil) - instance := buildCommitCommands(commonDeps{runner: runner}) + type scenario struct { + testName string + runner *oscommands.FakeCmdObjRunner + input string + } + scenarios := []scenario{ + { + "Single line reword", + oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil), + "test", + }, + { + "Multi line reword", + oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test", "-m", "line 2\nline 3"}, "", nil), + "test\nline 2\nline 3", + }, + } + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildCommitCommands(commonDeps{runner: s.runner}) - assert.NoError(t, instance.RewordLastCommit("test")) - runner.CheckForMissingCalls() + assert.NoError(t, instance.RewordLastCommit(s.input)) + s.runner.CheckForMissingCalls() + }) + } } func TestCommitResetToCommit(t *testing.T) { @@ -274,3 +294,40 @@ Merge pull request #1750 from mark2185/fix-issue-template }) } } + +func TestGetCommitMessageFromHistory(t *testing.T) { + type scenario struct { + testName string + runner *oscommands.FakeCmdObjRunner + test func(string, error) + } + scenarios := []scenario{ + { + "Empty message", + oscommands.NewFakeRunner(t).Expect("git log -1 --skip=2 --pretty=%H", "", nil).Expect("git rev-list --format=%B --max-count=1 ", "", nil), + func(output string, err error) { + assert.Error(t, err) + }, + }, + { + "Default case to retrieve a commit in history", + oscommands.NewFakeRunner(t).Expect("git log -1 --skip=2 --pretty=%H", "sha3 \n", nil).Expect("git rev-list --format=%B --max-count=1 sha3", `commit sha3 + use generics to DRY up context code`, nil), + func(output string, err error) { + assert.NoError(t, err) + assert.Equal(t, "use generics to DRY up context code", output) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildCommitCommands(commonDeps{runner: s.runner}) + + output, err := instance.GetCommitMessageFromHistory(2) + + s.test(output, err) + }) + } +} diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index fb0638e43..fb36bb7ea 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -165,6 +165,7 @@ type KeybindingUniversalConfig struct { Select string `yaml:"select"` GoInto string `yaml:"goInto"` Confirm string `yaml:"confirm"` + ConfirmInEditor string `yaml:"confirmInEditor"` Remove string `yaml:"remove"` New string `yaml:"new"` Edit string `yaml:"edit"` @@ -193,7 +194,6 @@ type KeybindingUniversalConfig struct { CopyToClipboard string `yaml:"copyToClipboard"` OpenRecentRepos string `yaml:"openRecentRepos"` SubmitEditorText string `yaml:"submitEditorText"` - AppendNewline string `yaml:"appendNewline"` ExtrasMenu string `yaml:"extrasMenu"` ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"` IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"` @@ -492,6 +492,7 @@ func GetDefaultConfig() *UserConfig { Select: "", GoInto: "", Confirm: "", + ConfirmInEditor: "", Remove: "d", New: "n", Edit: "e", @@ -520,7 +521,6 @@ func GetDefaultConfig() *UserConfig { DiffingMenuAlt: "", CopyToClipboard: "", SubmitEditorText: "", - AppendNewline: "", ExtrasMenu: "@", ToggleWhitespaceInDiffView: "", IncreaseContextInDiffView: "}", diff --git a/pkg/gui/context.go b/pkg/gui/context.go index 28dc5e2b2..252815e18 100644 --- a/pkg/gui/context.go +++ b/pkg/gui/context.go @@ -7,6 +7,7 @@ import ( "github.com/jesseduffield/generics/slices" "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/samber/lo" ) // This file is for the management of contexts. There is a context stack such that @@ -164,6 +165,36 @@ func (self *ContextMgr) Pop() error { return self.activateContext(newContext, types.OnFocusOpts{}) } +func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error { + self.Lock() + + if len(self.ContextStack) == 1 { + self.Unlock() + return nil + } + + rest := lo.Filter(self.ContextStack, func(context types.Context, _ int) bool { + for _, contextToRemove := range contextsToRemove { + if context.GetKey() == contextToRemove.GetKey() { + return false + } + } + return true + }) + self.ContextStack = rest + contextToActivate := rest[len(rest)-1] + self.Unlock() + + for _, context := range contextsToRemove { + if err := self.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil { + return err + } + } + + // activate the item at the top of the stack + return self.activateContext(contextToActivate, types.OnFocusOpts{}) +} + func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error { view, _ := self.gui.c.GocuiGui().View(c.GetViewName()) diff --git a/pkg/gui/context/commit_message_context.go b/pkg/gui/context/commit_message_context.go index 96c6c4f73..4241f859f 100644 --- a/pkg/gui/context/commit_message_context.go +++ b/pkg/gui/context/commit_message_context.go @@ -9,18 +9,45 @@ import ( ) type CommitMessageContext struct { - *SimpleContext c *ContextCommon + types.Context + viewModel *CommitMessageViewModel } var _ types.Context = (*CommitMessageContext)(nil) +// when selectedIndex (see below) is set to this value, it means that we're not +// currently viewing a commit message of an existing commit: instead we're making our own +// new commit message +const NoCommitIndex = -1 + +type CommitMessageViewModel struct { + // index of the commit message, where -1 is 'no commit', 0 is the HEAD commit, 1 + // is the prior commit, and so on + selectedindex int + // if true, then upon escaping from the commit message panel, we will preserve + // the message so that it's still shown next time we open the panel + preserveMessage bool + // the full preserved message (combined summary and description) + preservedMessage string + // invoked when pressing enter in the commit message panel + onConfirm func(string) error + + // The message typed in before cycling through history + // We store this separately to 'preservedMessage' because 'preservedMessage' + // is specifically for committing staged files and we don't want this affected + // by cycling through history in the context of rewording an old commit. + historyMessage string +} + func NewCommitMessageContext( c *ContextCommon, ) *CommitMessageContext { + viewModel := &CommitMessageViewModel{} return &CommitMessageContext{ - c: c, - SimpleContext: NewSimpleContext( + c: c, + viewModel: viewModel, + Context: NewSimpleContext( NewBaseContext(NewBaseContextOpts{ Kind: types.PERSISTENT_POPUP, View: c.Views().CommitMessage, @@ -33,6 +60,45 @@ func NewCommitMessageContext( } } +func (self *CommitMessageContext) SetSelectedIndex(value int) { + self.viewModel.selectedindex = value +} + +func (self *CommitMessageContext) GetSelectedIndex() int { + return self.viewModel.selectedindex +} + +func (self *CommitMessageContext) GetPreserveMessage() bool { + return self.viewModel.preserveMessage +} + +func (self *CommitMessageContext) GetPreservedMessage() string { + return self.viewModel.preservedMessage +} + +func (self *CommitMessageContext) SetPreservedMessage(message string) { + self.viewModel.preservedMessage = message +} + +func (self *CommitMessageContext) GetHistoryMessage() string { + return self.viewModel.historyMessage +} + +func (self *CommitMessageContext) SetHistoryMessage(message string) { + self.viewModel.historyMessage = message +} + +func (self *CommitMessageContext) OnConfirm(message string) error { + return self.viewModel.onConfirm(message) +} + +func (self *CommitMessageContext) SetPanelState(index int, title string, preserveMessage bool, onConfirm func(string) error) { + self.viewModel.selectedindex = index + self.viewModel.preserveMessage = preserveMessage + self.viewModel.onConfirm = onConfirm + self.GetView().Title = title +} + func (self *CommitMessageContext) RenderCommitLength() { if !self.c.UserConfig.Gui.CommitLength.Show { return diff --git a/pkg/gui/context/context.go b/pkg/gui/context/context.go index 7f1439466..ab188d761 100644 --- a/pkg/gui/context/context.go +++ b/pkg/gui/context/context.go @@ -33,13 +33,14 @@ const ( INFORMATION_CONTEXT_KEY types.ContextKey = "information" LIMIT_CONTEXT_KEY types.ContextKey = "limit" - MENU_CONTEXT_KEY types.ContextKey = "menu" - CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation" - SEARCH_CONTEXT_KEY types.ContextKey = "search" - COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage" - SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules" - SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions" - COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog" + MENU_CONTEXT_KEY types.ContextKey = "menu" + CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation" + SEARCH_CONTEXT_KEY types.ContextKey = "search" + COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage" + COMMIT_DESCRIPTION_CONTEXT_KEY types.ContextKey = "commitDescription" + SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules" + SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions" + COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog" ) var AllContextKeys = []types.ContextKey{ @@ -98,6 +99,7 @@ type ContextTree struct { MergeConflicts *MergeConflictsContext Confirmation *ConfirmationContext CommitMessage *CommitMessageContext + CommitDescription types.Context CommandLog types.Context // display contexts @@ -129,6 +131,7 @@ func (self *ContextTree) Flatten() []types.Context { self.Menu, self.Confirmation, self.CommitMessage, + self.CommitDescription, self.MergeConflicts, self.StagingSecondary, diff --git a/pkg/gui/context/setup.go b/pkg/gui/context/setup.go index f6a07ddd2..775803884 100644 --- a/pkg/gui/context/setup.go +++ b/pkg/gui/context/setup.go @@ -100,6 +100,16 @@ func NewContextTree(c *ContextCommon) *ContextTree { ), Confirmation: NewConfirmationContext(c), CommitMessage: NewCommitMessageContext(c), + CommitDescription: NewSimpleContext( + NewBaseContext(NewBaseContextOpts{ + Kind: types.PERSISTENT_POPUP, + View: c.Views().CommitDescription, + WindowName: "commitDescription", + Key: COMMIT_DESCRIPTION_CONTEXT_KEY, + Focusable: true, + HasUncontrolledBounds: true, + }), + ), Search: NewSimpleContext( NewBaseContext(NewBaseContextOpts{ Kind: types.PERSISTENT_POPUP, diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go index 344de939e..78943e798 100644 --- a/pkg/gui/controllers.go +++ b/pkg/gui/controllers.go @@ -22,10 +22,23 @@ func (gui *Gui) resetHelpersAndControllers() { rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, refsHelper) suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon) - setCommitMessage := gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }) - getSavedCommitMessage := func() string { - return gui.State.savedCommitMessage + + setCommitSummary := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }) + setCommitDescription := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitDescription }) + getCommitSummary := func() string { + return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent()) } + + getCommitDescription := func() string { + return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent()) + } + commitsHelper := helpers.NewCommitsHelper(helperCommon, + getCommitSummary, + setCommitSummary, + getCommitDescription, + setCommitDescription, + ) + gpgHelper := helpers.NewGpgHelper(helperCommon) viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts) recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon) @@ -60,7 +73,7 @@ func (gui *Gui) resetHelpersAndControllers() { Bisect: bisectHelper, Suggestions: suggestionsHelper, Files: helpers.NewFilesHelper(helperCommon), - WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, setCommitMessage, getSavedCommitMessage), + WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper), Tags: helpers.NewTagsHelper(helperCommon), GPG: helpers.NewGpgHelper(helperCommon), MergeAndRebase: rebaseHelper, @@ -68,6 +81,7 @@ func (gui *Gui) resetHelpersAndControllers() { CherryPick: cherryPickHelper, Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc), AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper), + Commits: commitsHelper, Snake: helpers.NewSnakeHelper(helperCommon), Diff: diffHelper, Repos: helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo), @@ -102,27 +116,12 @@ func (gui *Gui) resetHelpersAndControllers() { bisectController := controllers.NewBisectController(common) - getCommitMessage := func() string { - return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent()) - } - - onCommitAttempt := func(message string) { - gui.State.savedCommitMessage = message - gui.Views.CommitMessage.ClearTextArea() - } - - onCommitSuccess := func() { - gui.State.savedCommitMessage = "" - _ = gui.c.Refresh(types.RefreshOptions{ - Scope: []types.RefreshableView{types.STAGING}, - }) - } - commitMessageController := controllers.NewCommitMessageController( common, - getCommitMessage, - onCommitAttempt, - onCommitSuccess, + ) + + commitDescriptionController := controllers.NewCommitDescriptionController( + common, ) remoteBranchesController := controllers.NewRemoteBranchesController(common) @@ -132,8 +131,6 @@ func (gui *Gui) resetHelpersAndControllers() { tagsController := controllers.NewTagsController(common) filesController := controllers.NewFilesController( common, - setCommitMessage, - getSavedCommitMessage, ) mergeConflictsController := controllers.NewMergeConflictsController(common) remotesController := controllers.NewRemotesController( @@ -302,6 +299,10 @@ func (gui *Gui) resetHelpersAndControllers() { commitMessageController, ) + controllers.AttachControllers(gui.State.Contexts.CommitDescription, + commitDescriptionController, + ) + controllers.AttachControllers(gui.State.Contexts.RemoteBranches, remoteBranchesController, ) @@ -341,13 +342,13 @@ func (gui *Gui) resetHelpersAndControllers() { } } -func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) { +func (gui *Gui) getCommitMessageSetTextareaTextFn(getView func() *gocui.View) func(string) { return func(text string) { // using a getView function so that we don't need to worry about when the view is created view := getView() view.ClearTextArea() view.TextArea.TypeString(text) - _ = gui.helpers.Confirmation.ResizePopupPanel(view, view.TextArea.GetContent()) + gui.helpers.Confirmation.ResizeCommitMessagePanels() view.RenderTextArea() } } diff --git a/pkg/gui/controllers/commit_description_controller.go b/pkg/gui/controllers/commit_description_controller.go new file mode 100644 index 000000000..78d275184 --- /dev/null +++ b/pkg/gui/controllers/commit_description_controller.go @@ -0,0 +1,60 @@ +package controllers + +import ( + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type CommitDescriptionController struct { + baseController + c *ControllerCommon +} + +var _ types.IController = &CommitMessageController{} + +func NewCommitDescriptionController( + common *ControllerCommon, +) *CommitDescriptionController { + return &CommitDescriptionController{ + baseController: baseController{}, + c: common, + } +} + +func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { + bindings := []*types.Binding{ + { + Key: opts.GetKey(opts.Config.Universal.TogglePanel), + Handler: self.switchToCommitMessage, + }, + { + Key: opts.GetKey(opts.Config.Universal.Return), + Handler: self.close, + }, + { + Key: opts.GetKey(opts.Config.Universal.ConfirmInEditor), + Handler: self.confirm, + }, + } + + return bindings +} + +func (self *CommitDescriptionController) Context() types.Context { + return self.context() +} + +func (self *CommitDescriptionController) context() types.Context { + return self.c.Contexts().CommitMessage +} + +func (self *CommitDescriptionController) switchToCommitMessage() error { + return self.c.PushContext(self.c.Contexts().CommitMessage) +} + +func (self *CommitDescriptionController) close() error { + return self.c.Helpers().Commits.CloseCommitMessagePanel() +} + +func (self *CommitDescriptionController) confirm() error { + return self.c.Helpers().Commits.HandleCommitConfirm() +} diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go index 481d65c4c..b0318e8a4 100644 --- a/pkg/gui/controllers/commit_message_controller.go +++ b/pkg/gui/controllers/commit_message_controller.go @@ -1,6 +1,7 @@ package controllers import ( + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -8,27 +9,16 @@ import ( type CommitMessageController struct { baseController c *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{}, c: common, - - getCommitMessage: getCommitMessage, - onCommitAttempt: onCommitAttempt, - onCommitSuccess: onCommitSuccess, } } @@ -46,6 +36,18 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) Handler: self.close, Description: self.c.Tr.LcClose, }, + { + Key: opts.GetKey(opts.Config.Universal.PrevItem), + Handler: self.handlePreviousCommit, + }, + { + Key: opts.GetKey(opts.Config.Universal.NextItem), + Handler: self.handleNextCommit, + }, + { + Key: opts.GetKey(opts.Config.Universal.TogglePanel), + Handler: self.switchToCommitDescription, + }, } return bindings @@ -62,30 +64,61 @@ 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() *context.CommitMessageContext { return self.c.Contexts().CommitMessage } -func (self *CommitMessageController) confirm() error { - message := self.getCommitMessage() - self.onCommitAttempt(message) +func (self *CommitMessageController) handlePreviousCommit() error { + return self.handleCommitIndexChange(1) +} - if message == "" { - return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr) +func (self *CommitMessageController) handleNextCommit() error { + if self.context().GetSelectedIndex() == context.NoCommitIndex { + return nil + } + return self.handleCommitIndexChange(-1) +} + +func (self *CommitMessageController) switchToCommitDescription() error { + if err := self.c.PushContext(self.c.Contexts().CommitDescription); err != nil { + return err + } + return nil +} + +func (self *CommitMessageController) handleCommitIndexChange(value int) error { + currentIndex := self.context().GetSelectedIndex() + newIndex := currentIndex + value + if newIndex == context.NoCommitIndex { + self.context().SetSelectedIndex(newIndex) + self.c.Helpers().Commits.SetMessageAndDescriptionInView("") + return nil } - cmdObj := self.c.Git().Commit.CommitCmdObj(message) - self.c.LogAction(self.c.Tr.Actions.Commit) + validCommit, err := self.setCommitMessageAtIndex(newIndex) + if validCommit { + self.context().SetSelectedIndex(newIndex) + } + return err +} - _ = self.c.PopContext() - return self.c.Helpers().GPG.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error { - self.onCommitSuccess() - return nil - }) +// returns true if the given index is for a valid commit +func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, error) { + commitMessage, err := self.c.Git().Commit.GetCommitMessageFromHistory(index) + if err != nil { + if err == git_commands.ErrInvalidCommitIndex { + return false, nil + } + return false, self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr) + } + self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage) + return true, nil +} + +func (self *CommitMessageController) confirm() error { + return self.c.Helpers().Commits.HandleCommitConfirm() } func (self *CommitMessageController) close() error { - return self.c.PopContext() + return self.c.Helpers().Commits.CloseCommitMessagePanel() } diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 165878459..65fd16891 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -14,22 +14,15 @@ import ( type FilesController struct { baseController // nolint: unused c *ControllerCommon - - setCommitMessage func(message string) - getSavedCommitMessage func() string } var _ types.IController = &FilesController{} func NewFilesController( common *ControllerCommon, - setCommitMessage func(message string), - getSavedCommitMessage func() string, ) *FilesController { return &FilesController{ - c: common, - setCommitMessage: setCommitMessage, - getSavedCommitMessage: getSavedCommitMessage, + c: common, } } diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go new file mode 100644 index 000000000..ce1b3a9e2 --- /dev/null +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -0,0 +1,153 @@ +package helpers + +import ( + "strings" + + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type ICommitsHelper interface { + UpdateCommitPanelView(message string) +} + +type CommitsHelper struct { + c *HelperCommon + + getCommitSummary func() string + setCommitSummary func(string) + getCommitDescription func() string + setCommitDescription func(string) +} + +var _ ICommitsHelper = &CommitsHelper{} + +func NewCommitsHelper( + c *HelperCommon, + getCommitSummary func() string, + setCommitSummary func(string), + getCommitDescription func() string, + setCommitDescription func(string), +) *CommitsHelper { + return &CommitsHelper{ + c: c, + getCommitSummary: getCommitSummary, + setCommitSummary: setCommitSummary, + getCommitDescription: getCommitDescription, + setCommitDescription: setCommitDescription, + } +} + +func (self *CommitsHelper) SplitCommitMessageAndDescription(message string) (string, string) { + for _, separator := range []string{"\n\n", "\n\r\n\r", "\n", "\n\r"} { + msg, description, found := strings.Cut(message, separator) + if found { + return msg, description + } + } + return message, "" +} + +func (self *CommitsHelper) SetMessageAndDescriptionInView(message string) { + summary, description := self.SplitCommitMessageAndDescription(message) + + self.setCommitSummary(summary) + self.setCommitDescription(description) + self.c.Contexts().CommitMessage.RenderCommitLength() +} + +func (self *CommitsHelper) joinCommitMessageAndDescription() string { + if len(self.getCommitDescription()) == 0 { + return self.getCommitSummary() + } + return self.getCommitSummary() + "\n" + self.getCommitDescription() +} + +func (self *CommitsHelper) UpdateCommitPanelView(message string) { + // first try the passed in message, if not fallback to context -> view in that order + if message != "" { + self.SetMessageAndDescriptionInView(message) + return + } + message = self.c.Contexts().CommitMessage.GetPreservedMessage() + if message != "" { + self.SetMessageAndDescriptionInView(message) + } else { + self.SetMessageAndDescriptionInView(self.getCommitSummary()) + } +} + +type OpenCommitMessagePanelOpts struct { + CommitIndex int + Title string + PreserveMessage bool + OnConfirm func(string) error + InitialMessage string +} + +func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOpts) error { + self.c.Contexts().CommitMessage.SetPanelState( + opts.CommitIndex, + opts.Title, + opts.PreserveMessage, + opts.OnConfirm, + ) + + self.UpdateCommitPanelView(opts.InitialMessage) + + return self.pushCommitMessageContexts() +} + +func (self *CommitsHelper) OnCommitSuccess() { + // if we have a preserved message we want to clear it on success + if self.c.Contexts().CommitMessage.GetPreserveMessage() { + self.c.Contexts().CommitMessage.SetPreservedMessage("") + } + self.SetMessageAndDescriptionInView("") +} + +func (self *CommitsHelper) HandleCommitConfirm() error { + fullMessage := self.joinCommitMessageAndDescription() + + if fullMessage == "" { + return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr) + } + + err := self.c.Contexts().CommitMessage.OnConfirm(fullMessage) + if err != nil { + return err + } + + return nil +} + +func (self *CommitsHelper) CloseCommitMessagePanel() error { + if self.c.Contexts().CommitMessage.GetPreserveMessage() { + message := self.joinCommitMessageAndDescription() + + self.c.Contexts().CommitMessage.SetPreservedMessage(message) + } else { + self.SetMessageAndDescriptionInView("") + } + return self.EscapeCommitsPanel() +} + +func (self *CommitsHelper) EscapeCommitsPanel() error { + return self.c.RemoveContexts(self.commitMessageContexts()) +} + +func (self *CommitsHelper) pushCommitMessageContexts() error { + for _, context := range self.commitMessageContexts() { + if err := self.c.PushContext(context); err != nil { + return err + } + } + + return nil +} + +func (self *CommitsHelper) commitMessageContexts() []types.Context { + return []types.Context{ + self.c.Contexts().CommitDescription, + self.c.Contexts().CommitMessage, + } +} diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index e6e3deeef..7968933fc 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -75,17 +75,17 @@ func getMessageHeight(wrap bool, message string, width int) int { return lineCount } -func (self *ConfirmationHelper) getConfirmationPanelDimensions(wrap bool, prompt string) (int, int, int, int) { - panelWidth := self.getConfirmationPanelWidth() +func (self *ConfirmationHelper) getPopupPanelDimensions(wrap bool, prompt string) (int, int, int, int) { + panelWidth := self.getPopupPanelWidth() panelHeight := getMessageHeight(wrap, prompt, panelWidth) - return self.getConfirmationPanelDimensionsAux(panelWidth, panelHeight) + return self.getPopupPanelDimensionsAux(panelWidth, panelHeight) } -func (self *ConfirmationHelper) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) { - return self.getConfirmationPanelDimensionsAux(panelWidth, contentHeight) +func (self *ConfirmationHelper) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) { + return self.getPopupPanelDimensionsAux(panelWidth, contentHeight) } -func (self *ConfirmationHelper) getConfirmationPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) { +func (self *ConfirmationHelper) getPopupPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) { width, height := self.c.GocuiGui().Size() if panelHeight > height*3/4 { panelHeight = height * 3 / 4 @@ -96,7 +96,7 @@ func (self *ConfirmationHelper) getConfirmationPanelDimensionsAux(panelWidth int height/2 + panelHeight/2 } -func (self *ConfirmationHelper) getConfirmationPanelWidth() int { +func (self *ConfirmationHelper) getPopupPanelWidth() int { width, _ := self.c.GocuiGui().Size() // we want a minimum width up to a point, then we do it based on ratio. panelWidth := 4 * width / 7 @@ -254,7 +254,7 @@ func (self *ConfirmationHelper) ResizeConfirmationPanel() { if self.c.Views().Suggestions.Visible { suggestionsViewHeight = 11 } - panelWidth := self.getConfirmationPanelWidth() + panelWidth := self.getPopupPanelWidth() prompt := self.c.Views().Confirmation.Buffer() wrap := true if self.c.Views().Confirmation.Editable { @@ -262,7 +262,7 @@ func (self *ConfirmationHelper) ResizeConfirmationPanel() { wrap = false } panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight - x0, y0, x1, y1 := self.getConfirmationPanelDimensionsAux(panelWidth, panelHeight) + x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight) confirmationViewBottom := y1 - suggestionsViewHeight _, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0) @@ -271,24 +271,22 @@ func (self *ConfirmationHelper) ResizeConfirmationPanel() { } func (self *ConfirmationHelper) ResizeCurrentPopupPanel() error { - v := self.c.GocuiGui().CurrentView() - if v == nil { - return nil - } + c := self.c.CurrentContext() - if v == self.c.Views().Menu { + switch c { + case self.c.Contexts().Menu: self.resizeMenu() - } else if v == self.c.Views().Confirmation || v == self.c.Views().Suggestions { - self.ResizeConfirmationPanel() - } else if self.IsPopupPanel(v.Name()) { - return self.ResizePopupPanel(v, v.Buffer()) + case self.c.Contexts().Confirmation, self.c.Contexts().Suggestions: + self.resizeConfirmationPanel() + case self.c.Contexts().CommitMessage, self.c.Contexts().CommitDescription: + self.ResizeCommitMessagePanels() } return nil } func (self *ConfirmationHelper) ResizePopupPanel(v *gocui.View, content string) error { - x0, y0, x1, y1 := self.getConfirmationPanelDimensions(v.Wrap, content) + x0, y0, x1, y1 := self.getPopupPanelDimensions(v.Wrap, content) _, err := self.c.GocuiGui().SetView(v.Name(), x0, y0, x1, y1, 0) return err } @@ -296,8 +294,8 @@ func (self *ConfirmationHelper) ResizePopupPanel(v *gocui.View, content string) func (self *ConfirmationHelper) resizeMenu() { itemCount := self.c.Contexts().Menu.GetList().Len() offset := 3 - panelWidth := self.getConfirmationPanelWidth() - x0, y0, x1, y1 := self.getConfirmationPanelDimensionsForContentHeight(panelWidth, itemCount+offset) + panelWidth := self.getPopupPanelWidth() + x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset) menuBottom := y1 - offset _, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0) @@ -306,6 +304,42 @@ func (self *ConfirmationHelper) resizeMenu() { _, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0) } +func (self *ConfirmationHelper) resizeConfirmationPanel() { + suggestionsViewHeight := 0 + if self.c.Views().Suggestions.Visible { + suggestionsViewHeight = 11 + } + panelWidth := self.getPopupPanelWidth() + prompt := self.c.Views().Confirmation.Buffer() + wrap := true + if self.c.Views().Confirmation.Editable { + prompt = self.c.Views().Confirmation.TextArea.GetContent() + wrap = false + } + panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight + x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight) + confirmationViewBottom := y1 - suggestionsViewHeight + _, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0) + + suggestionsViewTop := confirmationViewBottom + 1 + _, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0) +} + +func (self *ConfirmationHelper) ResizeCommitMessagePanels() { + panelWidth := self.getPopupPanelWidth() + content := self.c.Views().CommitDescription.TextArea.GetContent() + summaryViewHeight := 3 + panelHeight := getMessageHeight(false, content, panelWidth) + minHeight := 7 + if panelHeight < minHeight { + panelHeight = minHeight + } + x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight) + + _, _ = self.c.GocuiGui().SetView(self.c.Views().CommitMessage.Name(), x0, y0, x1, y0+summaryViewHeight-1, 0) + _, _ = self.c.GocuiGui().SetView(self.c.Views().CommitDescription.Name(), x0, y0+summaryViewHeight, x1, y1+summaryViewHeight, 0) +} + func (self *ConfirmationHelper) IsPopupPanel(viewName string) bool { return viewName == "commitMessage" || viewName == "confirmation" || viewName == "menu" } diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go index 7e54597e5..faf342f0a 100644 --- a/pkg/gui/controllers/helpers/helpers.go +++ b/pkg/gui/controllers/helpers/helpers.go @@ -32,6 +32,7 @@ type Helpers struct { GPG *GpgHelper Upstream *UpstreamHelper AmendHelper *AmendHelper + Commits *CommitsHelper Snake *SnakeHelper // lives in context package because our contexts need it to render to main Diff *DiffHelper @@ -64,6 +65,7 @@ func NewStubHelpers() *Helpers { GPG: &GpgHelper{}, Upstream: &UpstreamHelper{}, AmendHelper: &AmendHelper{}, + Commits: &CommitsHelper{}, Snake: &SnakeHelper{}, Diff: &DiffHelper{}, Repos: &ReposHelper{}, diff --git a/pkg/gui/controllers/helpers/working_tree_helper.go b/pkg/gui/controllers/helpers/working_tree_helper.go index 5eb67e124..e78c8edf5 100644 --- a/pkg/gui/controllers/helpers/working_tree_helper.go +++ b/pkg/gui/controllers/helpers/working_tree_helper.go @@ -6,6 +6,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -18,23 +19,23 @@ type IWorkingTreeHelper interface { } type WorkingTreeHelper struct { - c *HelperCommon - refHelper *RefsHelper - setCommitMessage func(message string) - getSavedCommitMessage func() string + c *HelperCommon + refHelper *RefsHelper + commitsHelper *CommitsHelper + gpgHelper *GpgHelper } func NewWorkingTreeHelper( c *HelperCommon, refHelper *RefsHelper, - setCommitMessage func(message string), - getSavedCommitMessage func() string, + commitsHelper *CommitsHelper, + gpgHelper *GpgHelper, ) *WorkingTreeHelper { return &WorkingTreeHelper{ - c: c, - refHelper: refHelper, - setCommitMessage: setCommitMessage, - getSavedCommitMessage: getSavedCommitMessage, + c: c, + refHelper: refHelper, + commitsHelper: commitsHelper, + gpgHelper: gpgHelper, } } @@ -83,7 +84,7 @@ func (self *WorkingTreeHelper) OpenMergeTool() error { }) } -func (self *WorkingTreeHelper) HandleCommitPress() error { +func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage string) error { if err := self.prepareFilesForCommit(); err != nil { return self.c.Error(err) } @@ -96,28 +97,25 @@ func (self *WorkingTreeHelper) HandleCommitPress() error { return self.PromptToStageAllAndRetry(self.HandleCommitPress) } - savedCommitMessage := self.getSavedCommitMessage() - if len(savedCommitMessage) > 0 { - self.setCommitMessage(savedCommitMessage) - } else { - commitPrefixConfig := self.commitPrefixConfigForRepo() - if commitPrefixConfig != nil { - prefixPattern := commitPrefixConfig.Pattern - prefixReplace := commitPrefixConfig.Replace - rgx, err := regexp.Compile(prefixPattern) - if err != nil { - return self.c.ErrorMsg(fmt.Sprintf("%s: %s", self.c.Tr.LcCommitPrefixPatternError, err.Error())) - } - prefix := rgx.ReplaceAllString(self.refHelper.GetCheckedOutRef().Name, prefixReplace) - self.setCommitMessage(prefix) - } - } + return self.commitsHelper.OpenCommitMessagePanel( + &OpenCommitMessagePanelOpts{ + CommitIndex: context.NoCommitIndex, + InitialMessage: initialMessage, + Title: self.c.Tr.CommitSummary, + PreserveMessage: true, + OnConfirm: self.handleCommit, + }, + ) +} - if err := self.c.PushContext(self.c.Contexts().CommitMessage); err != nil { - return err - } - - return nil +func (self *WorkingTreeHelper) handleCommit(message string) error { + cmdObj := self.c.Git().Commit.CommitCmdObj(message) + self.c.LogAction(self.c.Tr.Actions.Commit) + _ = self.commitsHelper.EscapeCommitsPanel() + return self.gpgHelper.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error { + self.commitsHelper.OnCommitSuccess() + return nil + }) } // HandleCommitEditorPress - handle when the user wants to commit changes via @@ -143,9 +141,27 @@ func (self *WorkingTreeHelper) HandleWIPCommitPress() error { return self.c.ErrorMsg(self.c.Tr.SkipHookPrefixNotConfigured) } - self.setCommitMessage(skipHookPrefix) + return self.HandleCommitPressWithMessage(skipHookPrefix) +} - return self.HandleCommitPress() +func (self *WorkingTreeHelper) HandleCommitPress() error { + message := self.c.Contexts().CommitMessage.GetPreservedMessage() + + if message != "" { + commitPrefixConfig := self.commitPrefixConfigForRepo() + if commitPrefixConfig != nil { + prefixPattern := commitPrefixConfig.Pattern + prefixReplace := commitPrefixConfig.Replace + rgx, err := regexp.Compile(prefixPattern) + if err != nil { + return self.c.ErrorMsg(fmt.Sprintf("%s: %s", self.c.Tr.LcCommitPrefixPatternError, err.Error())) + } + prefix := rgx.ReplaceAllString(self.refHelper.GetCheckedOutRef().Name, prefixReplace) + message = prefix + } + } + + return self.HandleCommitPressWithMessage(message) } func (self *WorkingTreeHelper) PromptToStageAllAndRetry(retry func() error) error { diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index c7a3491df..7e7a22365 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -7,6 +7,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/gui/context" + "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" @@ -256,24 +257,30 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error { return nil } - message, err := self.c.Git().Commit.GetCommitMessage(commit.Sha) + commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Sha) if err != nil { return self.c.Error(err) } - // TODO: use the commit message panel here - return self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.LcRewordCommit, - InitialContent: message, - HandleConfirm: func(response string) error { - self.c.LogAction(self.c.Tr.Actions.RewordCommit) - if err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx(), response); err != nil { - return self.c.Error(err) - } - - return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) + return self.c.Helpers().Commits.OpenCommitMessagePanel( + &helpers.OpenCommitMessagePanelOpts{ + CommitIndex: self.context().GetSelectedLineIdx(), + InitialMessage: commitMessage, + Title: self.c.Tr.Actions.RewordCommit, + PreserveMessage: false, + OnConfirm: self.handleReword, }, - }) + ) +} + +func (self *LocalCommitsController) handleReword(message string) error { + err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), message) + if err != nil { + return self.c.Error(err) + } + self.c.Helpers().Commits.OnCommitSuccess() + _ = self.c.Helpers().Commits.EscapeCommitsPanel() + return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) } func (self *LocalCommitsController) doRewordEditor() error { diff --git a/pkg/gui/editors.go b/pkg/gui/editors.go index 4325a56f4..1fbba2aad 100644 --- a/pkg/gui/editors.go +++ b/pkg/gui/editors.go @@ -4,15 +4,9 @@ import ( "unicode" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" ) func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch rune, mod gocui.Modifier, allowMultiline bool) bool { - newlineKey, ok := keybindings.GetKey(gui.c.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key) - if !ok { - newlineKey = gocui.KeyAltEnter - } - switch { case key == gocui.KeyBackspace || key == gocui.KeyBackspace2: textArea.BackSpaceChar() @@ -30,7 +24,7 @@ func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch textArea.MoveRightWord() case key == gocui.KeyArrowRight || key == gocui.KeyCtrlF: textArea.MoveCursorRight() - case key == newlineKey: + case key == gocui.KeyEnter: if allowMultiline { textArea.TypeRune('\n') } else { @@ -66,22 +60,20 @@ func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch // we've just copy+pasted the editor from gocui to here so that we can also re- // render the commit message length on each keypress func (gui *Gui) commitMessageEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { - matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, true) - - // This function is called again on refresh as part of the general resize popup call, - // but we need to call it here so that when we go to render the text area it's not - // considered out of bounds to add a newline, meaning we can avoid unnecessary scrolling. - err := gui.helpers.Confirmation.ResizePopupPanel(v, v.TextArea.GetContent()) - if err != nil { - gui.c.Log.Error(err) - } + matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, false) v.RenderTextArea() - gui.State.Contexts.CommitMessage.RenderCommitLength() - + gui.c.Contexts().CommitMessage.RenderCommitLength() return matched } -func (gui *Gui) defaultEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { +func (gui *Gui) commitDescriptionEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { + matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, true) + v.RenderTextArea() + gui.c.Contexts().CommitMessage.RenderCommitLength() + return matched +} + +func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, false) v.RenderTextArea() diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 49dbd556c..6b42da4fc 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -217,10 +217,6 @@ type GuiRepoState struct { // back in sync with the repo state ViewsSetup bool - // we store a commit message in this field if we've escaped the commit message - // panel without committing or if our commit failed - savedCommitMessage string - ScreenMode types.WindowMaximisation CurrentPopupOpts *types.CreatePopupPanelOpts diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go index 32eea569f..f5f977272 100644 --- a/pkg/gui/gui_common.go +++ b/pkg/gui/gui_common.go @@ -52,6 +52,10 @@ func (self *guiCommon) ReplaceContext(context types.Context) error { return self.gui.State.ContextMgr.Replace(context) } +func (self *guiCommon) RemoveContexts(contexts []types.Context) error { + return self.gui.State.ContextMgr.RemoveContexts(contexts) +} + func (self *guiCommon) CurrentContext() types.Context { return self.gui.State.ContextMgr.Current() } diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index c42e3e299..90be93a32 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -56,6 +56,10 @@ type IGuiCommon interface { PushContext(context Context, opts ...OnFocusOpts) error PopContext() error ReplaceContext(context Context) error + // Removes all given contexts from the stack. If a given context is not in the stack, it is ignored. + // This is for when you have a group of contexts that are bundled together e.g. with the commit message panel. + // If you want to remove a single context, you should probably use PopContext instead. + RemoveContexts([]Context) error CurrentContext() Context CurrentStaticContext() Context CurrentSideContext() Context diff --git a/pkg/gui/types/views.go b/pkg/gui/types/views.go index ee45cf7b6..8b8a62e61 100644 --- a/pkg/gui/types/views.go +++ b/pkg/gui/types/views.go @@ -22,20 +22,21 @@ type Views struct { PatchBuildingSecondary *gocui.View MergeConflicts *gocui.View - Options *gocui.View - Confirmation *gocui.View - Menu *gocui.View - CommitMessage *gocui.View - CommitFiles *gocui.View - SubCommits *gocui.View - Information *gocui.View - AppStatus *gocui.View - Search *gocui.View - SearchPrefix *gocui.View - Limit *gocui.View - Suggestions *gocui.View - Tooltip *gocui.View - Extras *gocui.View + Options *gocui.View + Confirmation *gocui.View + Menu *gocui.View + CommitMessage *gocui.View + CommitDescription *gocui.View + CommitFiles *gocui.View + SubCommits *gocui.View + Information *gocui.View + AppStatus *gocui.View + Search *gocui.View + SearchPrefix *gocui.View + Limit *gocui.View + Suggestions *gocui.View + Tooltip *gocui.View + Extras *gocui.View // for playing the easter egg snake game Snake *gocui.View diff --git a/pkg/gui/views.go b/pkg/gui/views.go index 47a2e5093..e761d4cad 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -56,6 +56,7 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping { // popups. {viewPtr: &gui.Views.CommitMessage, name: "commitMessage"}, + {viewPtr: &gui.Views.CommitDescription, name: "commitDescription"}, {viewPtr: &gui.Views.Menu, name: "menu"}, {viewPtr: &gui.Views.Suggestions, name: "suggestions"}, {viewPtr: &gui.Views.Confirmation, name: "confirmation"}, @@ -152,12 +153,19 @@ func (gui *Gui) createAllViews() error { gui.Views.AppStatus.Frame = false gui.Views.CommitMessage.Visible = false - gui.Views.CommitMessage.Title = gui.c.Tr.CommitMessage + gui.Views.CommitMessage.Title = gui.c.Tr.CommitSummary gui.Views.CommitMessage.Editable = true gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor) + gui.Views.CommitDescription.Visible = false + gui.Views.CommitDescription.Title = gui.c.Tr.CommitDescriptionTitle + gui.Views.CommitDescription.Subtitle = gui.Tr.CommitDescriptionSubTitle + gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor + gui.Views.CommitDescription.Editable = true + gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor) + gui.Views.Confirmation.Visible = false - gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.defaultEditor) + gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.promptEditor) gui.Views.Suggestions.Visible = false diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go index 1e41ff961..decbb4ea8 100644 --- a/pkg/i18n/chinese.go +++ b/pkg/i18n/chinese.go @@ -47,7 +47,7 @@ func chineseTranslationSet() TranslationSet { StagingTitle: "正在暂存", MergingTitle: "正在合并", NormalTitle: "正常", - CommitMessage: "提交信息", + CommitSummary: "提交信息", CredentialsUsername: "用户名", CredentialsPassword: "密码", CredentialsPassphrase: "输入 SSH 密钥的密码", diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index c3b42da38..8e8c9d21a 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -13,7 +13,7 @@ func dutchTranslationSet() TranslationSet { MainTitle: "Hoofd", StagingTitle: "Staging", NormalTitle: "Normaal", - CommitMessage: "Commitbericht", + CommitSummary: "Commitbericht", CredentialsUsername: "Gebruikersnaam", CredentialsPassword: "Wachtwoord", CredentialsPassphrase: "Voer een wachtwoordzin in voor de SSH-sleutel", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 34af886a8..5e34022d3 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -27,7 +27,7 @@ type TranslationSet struct { MergeConfirmTitle string NormalTitle string LogTitle string - CommitMessage string + CommitSummary string CredentialsUsername string CredentialsPassword string CredentialsPassphrase string @@ -195,6 +195,8 @@ type TranslationSet struct { MergeOptionsTitle string RebaseOptionsTitle string CommitMessageTitle string + CommitDescriptionTitle string + CommitDescriptionSubTitle string LocalBranchesTitle string SearchTitle string TagsTitle string @@ -696,7 +698,7 @@ func EnglishTranslationSet() TranslationSet { MergingTitle: "Main Panel (Merging)", NormalTitle: "Main Panel (Normal)", LogTitle: "Log", - CommitMessage: "Commit message", + CommitSummary: "Commit summary", CredentialsUsername: "Username", CredentialsPassword: "Password", CredentialsPassphrase: "Enter passphrase for SSH key", @@ -862,7 +864,9 @@ func EnglishTranslationSet() TranslationSet { RecentRepos: "recent repositories", MergeOptionsTitle: "Merge Options", RebaseOptionsTitle: "Rebase Options", - CommitMessageTitle: "Commit Message", + CommitMessageTitle: "Commit Summary", + CommitDescriptionTitle: "Commit description", + CommitDescriptionSubTitle: "Press tab to toggle focus", LocalBranchesTitle: "Local Branches", SearchTitle: "Search", TagsTitle: "Tags", diff --git a/pkg/i18n/japanese.go b/pkg/i18n/japanese.go index 2fd2b850f..1defdf1c9 100644 --- a/pkg/i18n/japanese.go +++ b/pkg/i18n/japanese.go @@ -34,7 +34,7 @@ func japaneseTranslationSet() TranslationSet { MergingTitle: "メインパネル (Merging)", NormalTitle: "メインパネル (Normal)", LogTitle: "ログ", - CommitMessage: "コミットメッセージ", + CommitSummary: "コミットメッセージ", CredentialsUsername: "ユーザ名", CredentialsPassword: "パスワード", CredentialsPassphrase: "SSH鍵のパスフレーズを入力", diff --git a/pkg/i18n/korean.go b/pkg/i18n/korean.go index 3de86121a..ebdcb2ea5 100644 --- a/pkg/i18n/korean.go +++ b/pkg/i18n/korean.go @@ -33,7 +33,7 @@ func koreanTranslationSet() TranslationSet { MergingTitle: "메인 패널 (Merging)", NormalTitle: "메인 패널 (Normal)", LogTitle: "로그", - CommitMessage: "커밋 메시지", + CommitSummary: "커밋 메시지", CredentialsUsername: "사용자 이름", CredentialsPassword: "패스워드", CredentialsPassphrase: "SSH키의 passphrase 입력", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index fb73dbf3d..5fdbbad74 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -10,7 +10,7 @@ func polishTranslationSet() TranslationSet { StashTitle: "Schowek", UnstagedChanges: "Zmiany poza poczekalnią", StagedChanges: "Zmiany w poczekalni", - CommitMessage: "Komunikat commita", + CommitSummary: "Komunikat commita", CredentialsUsername: "Użytkownik", CredentialsPassword: "Hasło", CredentialsPassphrase: "Fraza", diff --git a/pkg/integration/components/commit_description_panel_driver.go b/pkg/integration/components/commit_description_panel_driver.go new file mode 100644 index 000000000..46d36652d --- /dev/null +++ b/pkg/integration/components/commit_description_panel_driver.go @@ -0,0 +1,25 @@ +package components + +type CommitDescriptionPanelDriver struct { + t *TestDriver +} + +func (self *CommitDescriptionPanelDriver) getViewDriver() *ViewDriver { + return self.t.Views().CommitDescription() +} + +func (self *CommitDescriptionPanelDriver) Type(value string) *CommitDescriptionPanelDriver { + self.t.typeContent(value) + + return self +} + +func (self *CommitDescriptionPanelDriver) SwitchToSummary() *CommitMessagePanelDriver { + self.getViewDriver().PressTab() + return &CommitMessagePanelDriver{t: self.t} +} + +func (self *CommitDescriptionPanelDriver) AddNewline() *CommitDescriptionPanelDriver { + self.t.press(self.t.keys.Universal.Confirm) + return self +} diff --git a/pkg/integration/components/commit_message_panel_driver.go b/pkg/integration/components/commit_message_panel_driver.go index e420334bf..d077761fc 100644 --- a/pkg/integration/components/commit_message_panel_driver.go +++ b/pkg/integration/components/commit_message_panel_driver.go @@ -10,19 +10,36 @@ func (self *CommitMessagePanelDriver) getViewDriver() *ViewDriver { // asserts on the text initially present in the prompt func (self *CommitMessagePanelDriver) InitialText(expected *Matcher) *CommitMessagePanelDriver { + return self.Content(expected) +} + +// asserts on the current context in the prompt +func (self *CommitMessagePanelDriver) Content(expected *Matcher) *CommitMessagePanelDriver { self.getViewDriver().Content(expected) return self } +// asserts that the confirmation view has the expected title +func (self *CommitMessagePanelDriver) Title(expected *Matcher) *CommitMessagePanelDriver { + self.getViewDriver().Title(expected) + + return self +} + func (self *CommitMessagePanelDriver) Type(value string) *CommitMessagePanelDriver { self.t.typeContent(value) return self } +func (self *CommitMessagePanelDriver) SwitchToDescription() *CommitDescriptionPanelDriver { + self.getViewDriver().PressTab() + return &CommitDescriptionPanelDriver{t: self.t} +} + func (self *CommitMessagePanelDriver) AddNewline() *CommitMessagePanelDriver { - self.t.press(self.t.keys.Universal.AppendNewline) + self.t.press(self.t.keys.Universal.Confirm) return self } @@ -49,6 +66,20 @@ func (self *CommitMessagePanelDriver) Confirm() { self.getViewDriver().PressEnter() } +func (self *CommitMessagePanelDriver) Close() { + self.getViewDriver().PressEscape() +} + func (self *CommitMessagePanelDriver) Cancel() { self.getViewDriver().PressEscape() } + +func (self *CommitMessagePanelDriver) SelectPreviousMessage() *CommitMessagePanelDriver { + self.getViewDriver().SelectPreviousItem() + return self +} + +func (self *CommitMessagePanelDriver) SelectNextMessage() *CommitMessagePanelDriver { + self.getViewDriver().SelectNextItem() + return self +} diff --git a/pkg/integration/components/popup.go b/pkg/integration/components/popup.go index b342fa03c..46df83e23 100644 --- a/pkg/integration/components/popup.go +++ b/pkg/integration/components/popup.go @@ -62,9 +62,22 @@ func (self *Popup) CommitMessagePanel() *CommitMessagePanelDriver { return &CommitMessagePanelDriver{t: self.t} } +func (self *Popup) CommitDescriptionPanel() *CommitMessagePanelDriver { + self.inCommitDescriptionPanel() + + return &CommitMessagePanelDriver{t: self.t} +} + func (self *Popup) inCommitMessagePanel() { self.t.assertWithRetries(func() (bool, string) { currentView := self.t.gui.CurrentContext().GetView() return currentView.Name() == "commitMessage", "Expected commit message panel to be focused" }) } + +func (self *Popup) inCommitDescriptionPanel() { + self.t.assertWithRetries(func() (bool, string) { + currentView := self.t.gui.CurrentContext().GetView() + return currentView.Name() == "commitDescription", "Expected commit description panel to be focused" + }) +} diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go index 280843cf3..eb9c0d7f7 100644 --- a/pkg/integration/components/view_driver.go +++ b/pkg/integration/components/view_driver.go @@ -361,6 +361,11 @@ func (self *ViewDriver) PressEnter() *ViewDriver { return self.Press(self.t.keys.Universal.Confirm) } +// i.e. pressing tab +func (self *ViewDriver) PressTab() *ViewDriver { + return self.Press(self.t.keys.Universal.TogglePanel) +} + // i.e. pressing escape func (self *ViewDriver) PressEscape() *ViewDriver { return self.Press(self.t.keys.Universal.Return) diff --git a/pkg/integration/components/views.go b/pkg/integration/components/views.go index 667fe9471..1a6e54b7e 100644 --- a/pkg/integration/components/views.go +++ b/pkg/integration/components/views.go @@ -207,6 +207,10 @@ func (self *Views) CommitMessage() *ViewDriver { return self.regularView("commitMessage") } +func (self *Views) CommitDescription() *ViewDriver { + return self.regularView("commitDescription") +} + func (self *Views) Suggestions() *ViewDriver { return self.regularView("suggestions") } diff --git a/pkg/integration/tests/commit/commit_multiline.go b/pkg/integration/tests/commit/commit_multiline.go index 4967ffb77..d36a5fdb4 100644 --- a/pkg/integration/tests/commit/commit_multiline.go +++ b/pkg/integration/tests/commit/commit_multiline.go @@ -22,14 +22,20 @@ var CommitMultiline = NewIntegrationTest(NewIntegrationTestArgs{ PressPrimaryAction(). Press(keys.Files.CommitChanges) - t.ExpectPopup().CommitMessagePanel().Type("first line").AddNewline().AddNewline().Type("third line").Confirm() - + t.ExpectPopup().CommitMessagePanel(). + Type("first line"). + SwitchToDescription(). + AddNewline(). + AddNewline(). + Type("fourth line"). + SwitchToSummary(). + Confirm() t.Views().Commits(). Lines( Contains("first line"), ) t.Views().Commits().Focus() - t.Views().Main().Content(MatchesRegexp("first line\n\\s*\n\\s*third line")) + t.Views().Main().Content(MatchesRegexp("first line\n\\s*\n\\s*fourth line")) }, }) diff --git a/pkg/integration/tests/commit/reword.go b/pkg/integration/tests/commit/reword.go new file mode 100644 index 000000000..f488977ed --- /dev/null +++ b/pkg/integration/tests/commit/reword.go @@ -0,0 +1,66 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var Reword = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Staging a couple files and committing", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateFile("myfile", "myfile content") + shell.CreateFile("myfile2", "myfile2 content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + IsEmpty() + + t.Views().Files(). + IsFocused(). + PressPrimaryAction(). + Press(keys.Files.CommitChanges) + + commitMessage := "my commit message" + + t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm() + t.Views().Commits(). + Lines( + Contains(commitMessage), + ) + + t.Views().Files(). + IsFocused(). + PressPrimaryAction(). + Press(keys.Files.CommitChanges) + + wipCommitMessage := "my commit message wip" + + t.ExpectPopup().CommitMessagePanel().Type(wipCommitMessage).Close() + + t.Views().Commits().Focus(). + Lines( + Contains(commitMessage), + ).Press(keys.Commits.RenameCommit) + + t.ExpectPopup().CommitMessagePanel(). + SwitchToDescription(). + Type("some description"). + SwitchToSummary(). + Confirm() + + t.Views().Main().Content(MatchesRegexp("my commit message\n\\s*some description")) + + t.Views().Files(). + Focus(). + Press(keys.Files.CommitChanges) + + t.ExpectPopup().CommitMessagePanel().Confirm() + t.Views().Commits(). + Lines( + Contains(wipCommitMessage), + ) + }, +}) diff --git a/pkg/integration/tests/interactive_rebase/reword_first_commit.go b/pkg/integration/tests/interactive_rebase/reword_first_commit.go index c85293cec..50ca2fb0e 100644 --- a/pkg/integration/tests/interactive_rebase/reword_first_commit.go +++ b/pkg/integration/tests/interactive_rebase/reword_first_commit.go @@ -27,8 +27,8 @@ var RewordFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ NavigateToLine(Contains("commit 01")). Press(keys.Commits.RenameCommit). Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("reword commit")). + t.ExpectPopup().CommitMessagePanel(). + Title(Equals("Reword commit")). InitialText(Equals("commit 01")). Clear(). Type("renamed 01"). diff --git a/pkg/integration/tests/interactive_rebase/reword_last_commit.go b/pkg/integration/tests/interactive_rebase/reword_last_commit.go index 9a4329219..742b250d4 100644 --- a/pkg/integration/tests/interactive_rebase/reword_last_commit.go +++ b/pkg/integration/tests/interactive_rebase/reword_last_commit.go @@ -23,8 +23,8 @@ var RewordLastCommit = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Commits.RenameCommit). Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("reword commit")). + t.ExpectPopup().CommitMessagePanel(). + Title(Equals("Reword commit")). InitialText(Equals("commit 02")). Clear(). Type("renamed 02"). diff --git a/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit.go b/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit.go index 921e1a016..c7431d059 100644 --- a/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit.go +++ b/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit.go @@ -31,8 +31,8 @@ var RewordYouAreHereCommit = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Commits.RenameCommit). Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("reword commit")). + t.ExpectPopup().CommitMessagePanel(). + Title(Equals("Reword commit")). InitialText(Equals("commit 02")). Clear(). Type("renamed 02"). diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index d4da79732..10838c008 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -53,6 +53,7 @@ var tests = []*components.IntegrationTest{ commit.ResetAuthor, commit.Revert, commit.RevertMerge, + commit.Reword, commit.Search, commit.SetAuthor, commit.StageRangeOfLines,