mirror of
https://github.com/jesseduffield/lazygit.git
synced 2024-12-12 11:15:00 +02:00
Merge pull request #2390 from seand52/revamp-commit-message
This commit is contained in:
commit
8ed29f2b7d
@ -171,7 +171,6 @@ keybinding:
|
||||
diffingMenu-alt: '<c-e>' # deprecated
|
||||
copyToClipboard: '<c-o>'
|
||||
submitEditorText: '<enter>'
|
||||
appendNewline: '<a-enter>'
|
||||
extrasMenu: '@'
|
||||
toggleWhitespaceInDiffView: '<c-w>'
|
||||
increaseContextInDiffView: '}'
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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: "<space>",
|
||||
GoInto: "<enter>",
|
||||
Confirm: "<enter>",
|
||||
ConfirmInEditor: "<a-enter>",
|
||||
Remove: "d",
|
||||
New: "n",
|
||||
Edit: "e",
|
||||
@ -520,7 +521,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
DiffingMenuAlt: "<c-e>",
|
||||
CopyToClipboard: "<c-o>",
|
||||
SubmitEditorText: "<enter>",
|
||||
AppendNewline: "<a-enter>",
|
||||
ExtrasMenu: "@",
|
||||
ToggleWhitespaceInDiffView: "<c-w>",
|
||||
IncreaseContextInDiffView: "}",
|
||||
|
@ -15,7 +15,6 @@ func (gui *Gui) handleCommitMessageFocused() error {
|
||||
map[string]string{
|
||||
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
|
||||
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
|
||||
"keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -78,17 +78,11 @@ func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
||||
return lineCount
|
||||
}
|
||||
|
||||
func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, int, int, int) {
|
||||
panelWidth := gui.getConfirmationPanelWidth()
|
||||
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
|
||||
return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
|
||||
func (gui *Gui) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
|
||||
return gui.getPopupPanelDimensionsAux(panelWidth, contentHeight)
|
||||
}
|
||||
|
||||
func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
|
||||
return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight)
|
||||
}
|
||||
|
||||
func (gui *Gui) getConfirmationPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
|
||||
func (gui *Gui) getPopupPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
|
||||
width, height := gui.g.Size()
|
||||
if panelHeight > height*3/4 {
|
||||
panelHeight = height * 3 / 4
|
||||
@ -186,7 +180,7 @@ func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPane
|
||||
}
|
||||
confirmationView := gui.Views.Confirmation
|
||||
confirmationView.Editable = opts.Editable
|
||||
confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor)
|
||||
confirmationView.Editor = gocui.EditorFunc(gui.promptEditor)
|
||||
|
||||
if opts.Editable {
|
||||
textArea := confirmationView.TextArea
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"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
|
||||
@ -146,6 +147,36 @@ func (gui *Gui) popContext() error {
|
||||
return gui.activateContext(newContext, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) removeContexts(contextsToRemove []types.Context) error {
|
||||
gui.State.ContextManager.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 1 {
|
||||
gui.State.ContextManager.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
rest := lo.Filter(gui.State.ContextManager.ContextStack, func(context types.Context, _ int) bool {
|
||||
for _, contextToRemove := range contextsToRemove {
|
||||
if context.GetKey() == contextToRemove.GetKey() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
gui.State.ContextManager.ContextStack = rest
|
||||
contextToActivate := rest[len(rest)-1]
|
||||
gui.State.ContextManager.Unlock()
|
||||
|
||||
for _, context := range contextsToRemove {
|
||||
if err := gui.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// activate the item at the top of the stack
|
||||
return gui.activateContext(contextToActivate, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := gui.g.View(c.GetViewName())
|
||||
|
||||
|
95
pkg/gui/context/commit_message_context.go
Normal file
95
pkg/gui/context/commit_message_context.go
Normal file
@ -0,0 +1,95 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CommitMessageContext struct {
|
||||
types.Context
|
||||
viewModel *CommitMessageViewModel
|
||||
}
|
||||
|
||||
// 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(
|
||||
view *gocui.View,
|
||||
opts ContextCallbackOpts,
|
||||
) *CommitMessageContext {
|
||||
viewModel := &CommitMessageViewModel{}
|
||||
return &CommitMessageContext{
|
||||
viewModel: viewModel,
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: view,
|
||||
WindowName: "commitMessage",
|
||||
Key: COMMIT_MESSAGE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
opts,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -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{
|
||||
@ -97,7 +98,8 @@ type ContextTree struct {
|
||||
CustomPatchBuilderSecondary types.Context
|
||||
MergeConflicts *MergeConflictsContext
|
||||
Confirmation types.Context
|
||||
CommitMessage types.Context
|
||||
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,
|
||||
|
@ -217,17 +217,29 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
},
|
||||
},
|
||||
),
|
||||
CommitMessage: context.NewSimpleContext(
|
||||
CommitMessage: context.NewCommitMessageContext(
|
||||
gui.Views.CommitMessage,
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
|
||||
},
|
||||
),
|
||||
CommitDescription: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: gui.Views.CommitMessage,
|
||||
WindowName: "commitMessage",
|
||||
Key: context.COMMIT_MESSAGE_CONTEXT_KEY,
|
||||
View: gui.Views.CommitDescription,
|
||||
WindowName: "commitDescription",
|
||||
Key: context.COMMIT_DESCRIPTION_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
|
||||
OnFocus: func(opts types.OnFocusOpts) error {
|
||||
_, err := gui.g.SetViewBeneath("commitDescription", "commitMessage", 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
Search: context.NewSimpleContext(
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/snake"
|
||||
)
|
||||
|
||||
@ -26,10 +25,24 @@ func (gui *Gui) resetControllers() {
|
||||
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper)
|
||||
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions)
|
||||
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,
|
||||
gui.State.Model,
|
||||
gui.State.Contexts,
|
||||
getCommitSummary,
|
||||
setCommitSummary,
|
||||
getCommitDescription,
|
||||
setCommitDescription,
|
||||
gui.RenderCommitLength,
|
||||
)
|
||||
gpgHelper := helpers.NewGpgHelper(helperCommon, gui.os, gui.git)
|
||||
gui.helpers = &helpers.Helpers{
|
||||
Refs: refsHelper,
|
||||
@ -38,7 +51,7 @@ func (gui *Gui) resetControllers() {
|
||||
Bisect: helpers.NewBisectHelper(helperCommon, gui.git),
|
||||
Suggestions: suggestionsHelper,
|
||||
Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, gui.git, gui.State.Contexts, refsHelper, model, setCommitMessage, getSavedCommitMessage),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, gui.git, gui.State.Contexts, refsHelper, model, setCommitSummary, commitsHelper, gpgHelper),
|
||||
Tags: helpers.NewTagsHelper(helperCommon, gui.git),
|
||||
GPG: gpgHelper,
|
||||
MergeAndRebase: rebaseHelper,
|
||||
@ -52,6 +65,7 @@ func (gui *Gui) resetControllers() {
|
||||
),
|
||||
Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
||||
AmendHelper: helpers.NewAmendHelper(helperCommon, gui.git, gpgHelper),
|
||||
Commits: commitsHelper,
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
@ -84,27 +98,12 @@ func (gui *Gui) resetControllers() {
|
||||
|
||||
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)
|
||||
@ -115,8 +114,7 @@ func (gui *Gui) resetControllers() {
|
||||
filesController := controllers.NewFilesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
setCommitMessage,
|
||||
getSavedCommitMessage,
|
||||
setCommitSummary,
|
||||
)
|
||||
mergeConflictsController := controllers.NewMergeConflictsController(common)
|
||||
remotesController := controllers.NewRemotesController(
|
||||
@ -250,6 +248,10 @@ func (gui *Gui) resetControllers() {
|
||||
commitMessageController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.CommitDescription,
|
||||
commitDescriptionController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.RemoteBranches,
|
||||
remoteBranchesController,
|
||||
)
|
||||
|
60
pkg/gui/controllers/commit_description_controller.go
Normal file
60
pkg/gui/controllers/commit_description_controller.go
Normal file
@ -0,0 +1,60 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CommitDescriptionController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommitMessageController{}
|
||||
|
||||
func NewCommitDescriptionController(
|
||||
common *controllerCommon,
|
||||
) *CommitDescriptionController {
|
||||
return &CommitDescriptionController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: 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.contexts.CommitMessage
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) switchToCommitMessage() error {
|
||||
return self.c.PushContext(self.contexts.CommitMessage)
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) close() error {
|
||||
return self.helpers.Commits.CloseCommitMessagePanel()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) confirm() error {
|
||||
return self.helpers.Commits.HandleCommitConfirm()
|
||||
}
|
@ -1,33 +1,24 @@
|
||||
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"
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +32,18 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.close,
|
||||
},
|
||||
{
|
||||
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
|
||||
@ -50,30 +53,63 @@ 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 {
|
||||
func (self *CommitMessageController) context() *context.CommitMessageContext {
|
||||
return self.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.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.helpers.Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage())
|
||||
return nil
|
||||
} else if currentIndex == context.NoCommitIndex {
|
||||
self.context().SetHistoryMessage(self.helpers.Commits.JoinCommitMessageAndDescription())
|
||||
}
|
||||
|
||||
cmdObj := self.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.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.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.helpers.Commits.UpdateCommitPanelView(commitMessage)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) confirm() error {
|
||||
return self.helpers.Commits.HandleCommitConfirm()
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) close() error {
|
||||
return self.c.PopContext()
|
||||
return self.helpers.Commits.CloseCommitMessagePanel()
|
||||
}
|
||||
|
@ -15,9 +15,8 @@ type FilesController struct {
|
||||
baseController // nolint: unused
|
||||
*controllerCommon
|
||||
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||
setCommitMessage func(message string)
|
||||
getSavedCommitMessage func() string
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||
setCommitMessage func(message string)
|
||||
}
|
||||
|
||||
var _ types.IController = &FilesController{}
|
||||
@ -26,13 +25,11 @@ func NewFilesController(
|
||||
common *controllerCommon,
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
||||
setCommitMessage func(message string),
|
||||
getSavedCommitMessage func() string,
|
||||
) *FilesController {
|
||||
return &FilesController{
|
||||
controllerCommon: common,
|
||||
enterSubmodule: enterSubmodule,
|
||||
setCommitMessage: setCommitMessage,
|
||||
getSavedCommitMessage: getSavedCommitMessage,
|
||||
controllerCommon: common,
|
||||
enterSubmodule: enterSubmodule,
|
||||
setCommitMessage: setCommitMessage,
|
||||
}
|
||||
}
|
||||
|
||||
|
166
pkg/gui/controllers/helpers/commits_helper.go
Normal file
166
pkg/gui/controllers/helpers/commits_helper.go
Normal file
@ -0,0 +1,166 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ICommitsHelper interface {
|
||||
UpdateCommitPanelView(message string)
|
||||
}
|
||||
|
||||
type CommitsHelper struct {
|
||||
c *types.HelperCommon
|
||||
|
||||
model *types.Model
|
||||
contexts *context.ContextTree
|
||||
getCommitSummary func() string
|
||||
setCommitSummary func(string)
|
||||
getCommitDescription func() string
|
||||
setCommitDescription func(string)
|
||||
renderCommitLength func()
|
||||
}
|
||||
|
||||
var _ ICommitsHelper = &CommitsHelper{}
|
||||
|
||||
func NewCommitsHelper(
|
||||
c *types.HelperCommon,
|
||||
model *types.Model,
|
||||
contexts *context.ContextTree,
|
||||
getCommitSummary func() string,
|
||||
setCommitSummary func(string),
|
||||
getCommitDescription func() string,
|
||||
setCommitDescription func(string),
|
||||
renderCommitLength func(),
|
||||
) *CommitsHelper {
|
||||
return &CommitsHelper{
|
||||
c: c,
|
||||
model: model,
|
||||
contexts: contexts,
|
||||
getCommitSummary: getCommitSummary,
|
||||
setCommitSummary: setCommitSummary,
|
||||
getCommitDescription: getCommitDescription,
|
||||
setCommitDescription: setCommitDescription,
|
||||
renderCommitLength: renderCommitLength,
|
||||
}
|
||||
}
|
||||
|
||||
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.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.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.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.contexts.CommitMessage.GetPreserveMessage() {
|
||||
self.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.contexts.CommitMessage.OnConfirm(fullMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitsHelper) CloseCommitMessagePanel() error {
|
||||
if self.contexts.CommitMessage.GetPreserveMessage() {
|
||||
message := self.JoinCommitMessageAndDescription()
|
||||
|
||||
self.contexts.CommitMessage.SetPreservedMessage(message)
|
||||
} else {
|
||||
self.SetMessageAndDescriptionInView("")
|
||||
}
|
||||
|
||||
self.contexts.CommitMessage.SetHistoryMessage("")
|
||||
|
||||
return self.PopCommitMessageContexts()
|
||||
}
|
||||
|
||||
func (self *CommitsHelper) PopCommitMessageContexts() 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.contexts.CommitDescription,
|
||||
self.contexts.CommitMessage,
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ type Helpers struct {
|
||||
GPG *GpgHelper
|
||||
Upstream *UpstreamHelper
|
||||
AmendHelper *AmendHelper
|
||||
Commits *CommitsHelper
|
||||
}
|
||||
|
||||
func NewStubHelpers() *Helpers {
|
||||
@ -33,5 +34,6 @@ func NewStubHelpers() *Helpers {
|
||||
GPG: &GpgHelper{},
|
||||
Upstream: &UpstreamHelper{},
|
||||
AmendHelper: &AmendHelper{},
|
||||
Commits: &CommitsHelper{},
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,14 @@ type IWorkingTreeHelper interface {
|
||||
}
|
||||
|
||||
type WorkingTreeHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
contexts *context.ContextTree
|
||||
refHelper *RefsHelper
|
||||
model *types.Model
|
||||
setCommitMessage func(message string)
|
||||
getSavedCommitMessage func() string
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
contexts *context.ContextTree
|
||||
refHelper *RefsHelper
|
||||
model *types.Model
|
||||
setCommitMessage func(message string)
|
||||
commitsHelper *CommitsHelper
|
||||
gpgHelper *GpgHelper
|
||||
}
|
||||
|
||||
func NewWorkingTreeHelper(
|
||||
@ -36,16 +37,18 @@ func NewWorkingTreeHelper(
|
||||
refHelper *RefsHelper,
|
||||
model *types.Model,
|
||||
setCommitMessage func(message string),
|
||||
getSavedCommitMessage func() string,
|
||||
commitsHelper *CommitsHelper,
|
||||
gpgHelper *GpgHelper,
|
||||
) *WorkingTreeHelper {
|
||||
return &WorkingTreeHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
contexts: contexts,
|
||||
refHelper: refHelper,
|
||||
model: model,
|
||||
setCommitMessage: setCommitMessage,
|
||||
getSavedCommitMessage: getSavedCommitMessage,
|
||||
c: c,
|
||||
git: git,
|
||||
contexts: contexts,
|
||||
refHelper: refHelper,
|
||||
model: model,
|
||||
setCommitMessage: setCommitMessage,
|
||||
commitsHelper: commitsHelper,
|
||||
gpgHelper: gpgHelper,
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +97,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)
|
||||
}
|
||||
@ -107,28 +110,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.contexts.CommitMessage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
func (self *WorkingTreeHelper) handleCommit(message string) error {
|
||||
cmdObj := self.git.Commit.CommitCmdObj(message)
|
||||
self.c.LogAction(self.c.Tr.Actions.Commit)
|
||||
_ = self.commitsHelper.PopCommitMessageContexts()
|
||||
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
|
||||
@ -154,9 +154,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.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 {
|
||||
|
@ -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"
|
||||
@ -19,7 +20,6 @@ type (
|
||||
type LocalCommitsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
|
||||
pullFiles PullFilesFn
|
||||
}
|
||||
|
||||
@ -209,24 +209,30 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
message, err := self.git.Commit.GetCommitMessage(commit.Sha)
|
||||
commitMessage, 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(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.git.Rebase.RewordCommit(self.model.Commits, self.context().GetSelectedLineIdx(), response); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return self.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.git.Rebase.RewordCommit(self.model.Commits, self.contexts.LocalCommits.GetSelectedLineIdx(), message)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.helpers.Commits.OnCommitSuccess()
|
||||
_ = self.helpers.Commits.PopCommitMessageContexts()
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) doRewordEditor() error {
|
||||
|
@ -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.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.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.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()
|
||||
|
@ -83,13 +83,13 @@ func (gui *Gui) filesRenderToMain() error {
|
||||
return gui.c.RenderToMainViews(refreshOpts)
|
||||
}
|
||||
|
||||
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.resizePopupPanel(view, view.TextArea.GetContent())
|
||||
gui.resizeCommitMessagePanels()
|
||||
view.RenderTextArea()
|
||||
}
|
||||
}
|
||||
|
@ -195,10 +195,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 WindowMaximisation
|
||||
|
||||
CurrentPopupOpts *types.CreatePopupPanelOpts
|
||||
|
@ -58,6 +58,10 @@ func (self *guiCommon) PopContext() error {
|
||||
return self.gui.popContext()
|
||||
}
|
||||
|
||||
func (self *guiCommon) RemoveContexts(contexts []types.Context) error {
|
||||
return self.gui.removeContexts(contexts)
|
||||
}
|
||||
|
||||
func (self *guiCommon) CurrentContext() types.Context {
|
||||
return self.gui.currentContext()
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
|
||||
types.REMOTES,
|
||||
types.STATUS,
|
||||
types.BISECT_INFO,
|
||||
types.STAGING,
|
||||
})
|
||||
} else {
|
||||
scopeSet = set.NewFromSlice(options.Scope)
|
||||
@ -563,6 +564,13 @@ func (gui *Gui) refreshStatus() {
|
||||
|
||||
func (gui *Gui) refreshStagingPanel(focusOpts types.OnFocusOpts) error {
|
||||
secondaryFocused := gui.secondaryStagingFocused()
|
||||
mainFocused := gui.mainStagingFocused()
|
||||
|
||||
// this method could be called when the staging panel is not being used,
|
||||
// in which case we don't want to do anything.
|
||||
if !mainFocused && !secondaryFocused {
|
||||
return nil
|
||||
}
|
||||
|
||||
mainSelectedLineIdx := -1
|
||||
secondarySelectedLineIdx := -1
|
||||
@ -647,6 +655,10 @@ func (gui *Gui) secondaryStagingFocused() bool {
|
||||
return gui.currentStaticContext().GetKey() == gui.State.Contexts.StagingSecondary.GetKey()
|
||||
}
|
||||
|
||||
func (gui *Gui) mainStagingFocused() bool {
|
||||
return gui.currentStaticContext().GetKey() == gui.State.Contexts.Staging.GetKey()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshPatchBuildingPanel(opts types.OnFocusOpts) error {
|
||||
selectedLineIdx := -1
|
||||
if opts.ClickedWindowName == "main" {
|
||||
|
@ -42,6 +42,10 @@ type IGuiCommon interface {
|
||||
|
||||
PushContext(context Context, opts ...OnFocusOpts) error
|
||||
PopContext() 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
|
||||
IsCurrentContext(Context) bool
|
||||
|
@ -78,28 +78,24 @@ func (gui *Gui) resizeCurrentPopupPanel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v == gui.Views.Menu {
|
||||
c := gui.c.CurrentContext()
|
||||
|
||||
if c == gui.State.Contexts.Menu {
|
||||
gui.resizeMenu()
|
||||
} else if v == gui.Views.Confirmation || v == gui.Views.Suggestions {
|
||||
} else if c == gui.State.Contexts.Confirmation || c == gui.State.Contexts.Suggestions {
|
||||
gui.resizeConfirmationPanel()
|
||||
} else if gui.isPopupPanel(v.Name()) {
|
||||
return gui.resizePopupPanel(v, v.Buffer())
|
||||
} else if c == gui.State.Contexts.CommitMessage || c == gui.State.Contexts.CommitDescription {
|
||||
gui.resizeCommitMessagePanels()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) resizePopupPanel(v *gocui.View, content string) error {
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(v.Wrap, content)
|
||||
_, err := gui.g.SetView(v.Name(), x0, y0, x1, y1, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) resizeMenu() {
|
||||
itemCount := gui.State.Contexts.Menu.GetList().Len()
|
||||
offset := 3
|
||||
panelWidth := gui.getConfirmationPanelWidth()
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
|
||||
x0, y0, x1, y1 := gui.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
|
||||
menuBottom := y1 - offset
|
||||
_, _ = gui.g.SetView(gui.Views.Menu.Name(), x0, y0, x1, menuBottom, 0)
|
||||
|
||||
@ -121,7 +117,7 @@ func (gui *Gui) resizeConfirmationPanel() {
|
||||
wrap = false
|
||||
}
|
||||
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
|
||||
x0, y0, x1, y1 := gui.getPopupPanelDimensionsAux(panelWidth, panelHeight)
|
||||
confirmationViewBottom := y1 - suggestionsViewHeight
|
||||
_, _ = gui.g.SetView(gui.Views.Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
|
||||
|
||||
@ -129,6 +125,21 @@ func (gui *Gui) resizeConfirmationPanel() {
|
||||
_, _ = gui.g.SetView(gui.Views.Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
|
||||
}
|
||||
|
||||
func (gui *Gui) resizeCommitMessagePanels() {
|
||||
panelWidth := gui.getConfirmationPanelWidth()
|
||||
content := gui.Views.CommitDescription.TextArea.GetContent()
|
||||
summaryViewHeight := 3
|
||||
panelHeight := gui.getMessageHeight(false, content, panelWidth)
|
||||
minHeight := 7
|
||||
if panelHeight < minHeight {
|
||||
panelHeight = minHeight
|
||||
}
|
||||
x0, y0, x1, y1 := gui.getPopupPanelDimensionsAux(panelWidth, panelHeight)
|
||||
|
||||
_, _ = gui.g.SetView(gui.Views.CommitMessage.Name(), x0, y0, x1, y0+summaryViewHeight-1, 0)
|
||||
_, _ = gui.g.SetView(gui.Views.CommitDescription.Name(), x0, y0+summaryViewHeight, x1, y1+summaryViewHeight, 0)
|
||||
}
|
||||
|
||||
func (gui *Gui) globalOptionsMap() map[string]string {
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
|
||||
|
@ -26,20 +26,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
|
||||
@ -94,6 +95,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"},
|
||||
@ -199,10 +201,17 @@ 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.Suggestions.Visible = false
|
||||
|
@ -47,7 +47,7 @@ func chineseTranslationSet() TranslationSet {
|
||||
StagingTitle: "正在暂存",
|
||||
MergingTitle: "正在合并",
|
||||
NormalTitle: "正常",
|
||||
CommitMessage: "提交信息",
|
||||
CommitSummary: "提交信息",
|
||||
CredentialsUsername: "用户名",
|
||||
CredentialsPassword: "密码",
|
||||
CredentialsPassphrase: "输入 SSH 密钥的密码",
|
||||
@ -94,7 +94,7 @@ func chineseTranslationSet() TranslationSet {
|
||||
LcNewBranch: "新分支",
|
||||
LcDeleteBranch: "删除分支",
|
||||
NoBranchesThisRepo: "此仓库中没有分支",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}:关闭,{{.keyBindNewLine}}:新行,{{.keyBindConfirm}}:确认",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}:关闭,{{.keyBindConfirm}}:确认",
|
||||
CommitWithoutMessageErr: "您必须编写提交消息才能进行提交",
|
||||
CloseConfirm: "{{.keyBindClose}}:关闭,{{.keyBindConfirm}}:确认",
|
||||
LcClose: "关闭",
|
||||
|
@ -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",
|
||||
@ -60,7 +60,7 @@ func dutchTranslationSet() TranslationSet {
|
||||
LcNewBranch: "nieuwe branch",
|
||||
LcDeleteBranch: "verwijder branch",
|
||||
NoBranchesThisRepo: "Geen branches voor deze repo",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: Sluiten, {{.keyBindNewLine}}: Nieuwe lijn, {{.keyBindConfirm}}: Bevestig",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: Sluiten, {{.keyBindConfirm}}: Bevestig",
|
||||
CommitWithoutMessageErr: "Je kan geen commit maken zonder commit bericht",
|
||||
CloseConfirm: "{{.keyBindClose}}: Sluiten, {{.keyBindConfirm}}: Bevestig",
|
||||
LcClose: "sluiten",
|
||||
|
@ -27,7 +27,7 @@ type TranslationSet struct {
|
||||
MergeConfirmTitle string
|
||||
NormalTitle string
|
||||
LogTitle string
|
||||
CommitMessage string
|
||||
CommitSummary string
|
||||
CredentialsUsername string
|
||||
CredentialsPassword string
|
||||
CredentialsPassphrase string
|
||||
@ -197,6 +197,8 @@ type TranslationSet struct {
|
||||
MergeOptionsTitle string
|
||||
RebaseOptionsTitle string
|
||||
CommitMessageTitle string
|
||||
CommitDescriptionTitle string
|
||||
CommitDescriptionSubTitle string
|
||||
LocalBranchesTitle string
|
||||
SearchTitle string
|
||||
TagsTitle string
|
||||
@ -698,7 +700,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",
|
||||
@ -750,7 +752,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
LcNewBranch: "new branch",
|
||||
LcDeleteBranch: "delete branch",
|
||||
NoBranchesThisRepo: "No branches for this repo",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: close, {{.keyBindNewLine}}: new line, {{.keyBindConfirm}}: confirm",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: close, {{.keyBindConfirm}}: confirm",
|
||||
CommitWithoutMessageErr: "You cannot commit without a commit message",
|
||||
CloseConfirm: "{{.keyBindClose}}: close/cancel, {{.keyBindConfirm}}: confirm",
|
||||
LcClose: "close",
|
||||
@ -866,7 +868,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",
|
||||
|
@ -34,7 +34,7 @@ func japaneseTranslationSet() TranslationSet {
|
||||
MergingTitle: "メインパネル (Merging)",
|
||||
NormalTitle: "メインパネル (Normal)",
|
||||
LogTitle: "ログ",
|
||||
CommitMessage: "コミットメッセージ",
|
||||
CommitSummary: "コミットメッセージ",
|
||||
CredentialsUsername: "ユーザ名",
|
||||
CredentialsPassword: "パスワード",
|
||||
CredentialsPassphrase: "SSH鍵のパスフレーズを入力",
|
||||
@ -85,7 +85,7 @@ func japaneseTranslationSet() TranslationSet {
|
||||
LcNewBranch: "新しいブランチを作成",
|
||||
LcDeleteBranch: "ブランチを削除",
|
||||
NoBranchesThisRepo: "リポジトリにブランチが存在しません",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: 閉じる, {{.keyBindNewLine}}: 改行, {{.keyBindConfirm}}: 確定",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: 閉じる, {{.keyBindConfirm}}: 確定",
|
||||
CommitWithoutMessageErr: "コミットメッセージを入力してください",
|
||||
CloseConfirm: "{{.keyBindClose}}: 閉じる/キャンセル, {{.keyBindConfirm}}: 確認",
|
||||
LcClose: "閉じる",
|
||||
|
@ -33,7 +33,7 @@ func koreanTranslationSet() TranslationSet {
|
||||
MergingTitle: "메인 패널 (Merging)",
|
||||
NormalTitle: "메인 패널 (Normal)",
|
||||
LogTitle: "로그",
|
||||
CommitMessage: "커밋 메시지",
|
||||
CommitSummary: "커밋 메시지",
|
||||
CredentialsUsername: "사용자 이름",
|
||||
CredentialsPassword: "패스워드",
|
||||
CredentialsPassphrase: "SSH키의 passphrase 입력",
|
||||
@ -84,7 +84,7 @@ func koreanTranslationSet() TranslationSet {
|
||||
LcNewBranch: "새 브랜치 생성",
|
||||
LcDeleteBranch: "브랜치 삭제",
|
||||
NoBranchesThisRepo: "저장소에 브랜치가 존재하지 않습니다.",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: 닫기, {{.keyBindNewLine}}: 개행, {{.keyBindConfirm}}: 확인",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: 닫기, {{.keyBindConfirm}}: 확인",
|
||||
CommitWithoutMessageErr: "커밋 메시지를 입력하세요.",
|
||||
CloseConfirm: "{{.keyBindClose}}: 닫기/취소, {{.keyBindConfirm}}: 확인",
|
||||
LcClose: "닫기",
|
||||
|
@ -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",
|
||||
@ -55,7 +55,7 @@ func polishTranslationSet() TranslationSet {
|
||||
LcNewBranch: "nowa gałąź",
|
||||
LcDeleteBranch: "usuń gałąź",
|
||||
NoBranchesThisRepo: "Brak gałęzi dla tego repozytorium",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: zamknij, {{.keyBindNewLine}}: nowa linia, {{.keyBindConfirm}}: potwierdź",
|
||||
CommitMessageConfirm: "{{.keyBindClose}}: zamknij, {{.keyBindConfirm}}: potwierdź",
|
||||
CommitWithoutMessageErr: "Nie możesz commitować bez komunikatu",
|
||||
CloseConfirm: "{{.keyBindClose}}: zamknij, {{.keyBindConfirm}}: potwierdź",
|
||||
LcClose: "zamknij",
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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"))
|
||||
},
|
||||
})
|
||||
|
53
pkg/integration/tests/commit/history.go
Normal file
53
pkg/integration/tests/commit/history.go
Normal file
@ -0,0 +1,53 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var History = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Cycling through commit message history in the commit message panel",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.EmptyCommit("initial commit")
|
||||
shell.EmptyCommit("commit 2")
|
||||
shell.EmptyCommit("commit 3")
|
||||
|
||||
shell.CreateFile("myfile", "myfile content")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
PressPrimaryAction(). // stage file
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
InitialText(Equals("")).
|
||||
Type("my commit message").
|
||||
SelectPreviousMessage().
|
||||
Content(Equals("commit 3")).
|
||||
SelectPreviousMessage().
|
||||
Content(Equals("commit 2")).
|
||||
SelectPreviousMessage().
|
||||
Content(Equals("initial commit")).
|
||||
SelectPreviousMessage().
|
||||
Content(Equals("initial commit")). // we hit the end
|
||||
SelectNextMessage().
|
||||
Content(Equals("commit 2")).
|
||||
SelectNextMessage().
|
||||
Content(Equals("commit 3")).
|
||||
SelectNextMessage().
|
||||
Content(Equals("my commit message")).
|
||||
SelectNextMessage().
|
||||
Content(Equals("my commit message")). // we hit the beginning
|
||||
Type(" with extra added").
|
||||
Confirm()
|
||||
|
||||
t.Views().Commits().
|
||||
TopLines(
|
||||
Contains("my commit message with extra added").IsSelected(),
|
||||
)
|
||||
},
|
||||
})
|
59
pkg/integration/tests/commit/history_complex.go
Normal file
59
pkg/integration/tests/commit/history_complex.go
Normal file
@ -0,0 +1,59 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var HistoryComplex = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "More complex flow for cycling commit message history",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.EmptyCommit("initial commit")
|
||||
shell.EmptyCommit("commit 2")
|
||||
shell.EmptyCommit("commit 3")
|
||||
|
||||
shell.CreateFileAndAdd("myfile", "myfile content")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
// We're going to start a new commit message,
|
||||
// then leave and try to reword a commit, then
|
||||
// come back to original message and confirm we haven't lost our message.
|
||||
// This shows that we're storing the preserved message for a new commit separately
|
||||
// to the message when cycling history.
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
InitialText(Equals("")).
|
||||
Type("my commit message").
|
||||
Cancel()
|
||||
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
SelectedLine(Contains("commit 3")).
|
||||
Press(keys.Commits.RenameCommit)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
InitialText(Equals("commit 3")).
|
||||
SelectNextMessage().
|
||||
Content(Equals("")).
|
||||
Type("reworded message").
|
||||
SelectPreviousMessage().
|
||||
Content(Equals("commit 3")).
|
||||
SelectNextMessage().
|
||||
Content(Equals("reworded message")).
|
||||
Cancel()
|
||||
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
InitialText(Equals("my commit message"))
|
||||
},
|
||||
})
|
66
pkg/integration/tests/commit/reword.go
Normal file
66
pkg/integration/tests/commit/reword.go
Normal file
@ -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),
|
||||
)
|
||||
},
|
||||
})
|
@ -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").
|
||||
|
@ -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").
|
||||
|
@ -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").
|
||||
|
@ -49,10 +49,13 @@ var tests = []*components.IntegrationTest{
|
||||
commit.CommitMultiline,
|
||||
commit.CreateTag,
|
||||
commit.DiscardOldFileChange,
|
||||
commit.History,
|
||||
commit.HistoryComplex,
|
||||
commit.NewBranch,
|
||||
commit.ResetAuthor,
|
||||
commit.Revert,
|
||||
commit.RevertMerge,
|
||||
commit.Reword,
|
||||
commit.Search,
|
||||
commit.SetAuthor,
|
||||
commit.StageRangeOfLines,
|
||||
|
Loading…
Reference in New Issue
Block a user