diff --git a/demo.gif b/demo.gif new file mode 100644 index 00000000000..e059d965b74 Binary files /dev/null and b/demo.gif differ diff --git a/docs-master/Config.md b/docs-master/Config.md index fa6b3eeacc7..da9d6582079 100644 --- a/docs-master/Config.md +++ b/docs-master/Config.md @@ -378,6 +378,12 @@ git: # If true, pass '--signoff' flag when committing signOff: false + # Command that generates a commit message. Lazygit runs the command from the + # current git project root and uses stdout as the editable commit message. If + # the command fails, stderr is shown to the user. See + # https://github.com/jesseduffield/lazygit/blob/master/docs/Generated_Commit_Messages.md. + messageGeneratorCommand: "" + # Automatic WYSIWYG wrapping of the commit message as you type autoWrapCommitMessage: true diff --git a/docs-master/Generated_Commit_Messages.md b/docs-master/Generated_Commit_Messages.md new file mode 100644 index 00000000000..ca1af400d23 --- /dev/null +++ b/docs-master/Generated_Commit_Messages.md @@ -0,0 +1,175 @@ +# Generated Commit Messages + +Lazygit can run an external command to generate the text for a new commit message. This is useful if you want a local script, an LLM tool, or another formatter to inspect the staged diff and suggest a message. + +Configure the command under `git.commit.messageGeneratorCommand`: + +```yaml +git: + commit: + messageGeneratorCommand: ~/bin/generate-staged-commit-message.sh +``` + +When this option is set, the commit menu shows `Generate Commit Message`. Selecting it runs the configured command and puts the command's stdout into the commit message fields. Lazygit does not commit immediately, so you can review and edit the generated message before submitting. + +## Command Contract + +Lazygit runs the configured command from the Git project root. For example, if your config contains: + +```yaml +git: + commit: + messageGeneratorCommand: ~/bin/generate-staged-commit-message.sh --style conventional +``` + +Lazygit runs it like this: + +```sh +(cd /path/to/repo && ~/bin/generate-staged-commit-message.sh --style conventional) +``` + +The command should write only the commit message to stdout. If stdout contains a blank line, Lazygit treats the first paragraph as the commit summary and the rest as the commit description. + +If the command exits with a non-zero status, Lazygit shows a notification with stderr. The current commit message is left unchanged. + +## Simple Script + +This example uses the staged file summary to build a basic message: + +```sh +#!/usr/bin/env sh +set -eu + +if git diff --cached --quiet --exit-code; then + echo "no staged changes" >&2 + exit 1 +fi + +files=$(git diff --cached --name-only | sed -n '1,3p' | paste -sd ', ' -) +count=$(git diff --cached --name-only | wc -l | tr -d ' ') + +if [ "$count" -eq 1 ]; then + printf 'update %s\n' "$files" +else + printf 'update %s files\n\n%s\n' "$count" "$files" +fi +``` + +Save it somewhere on your `PATH`, make it executable, and point Lazygit at it: + +```sh +chmod +x ~/bin/generate-staged-commit-message.sh +``` + +```yaml +git: + commit: + messageGeneratorCommand: ~/bin/generate-staged-commit-message.sh +``` + +## Codex Example + +This example asks `codex exec` to inspect the staged diff and output only a commit message. It checks that staged changes exist in the current Git repository and lets you override the model or add extra `codex exec` arguments with environment variables. + +```sh +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat >&2 <<'USAGE' +Usage: generate-staged-commit-message.sh + +Calls Codex to generate a commit message for the currently staged changes in +the current Git repository. + +Environment: + CODEX_MODEL Optional model name passed to `codex exec -m`. + CODEX_EXTRA_ARGS Optional additional arguments appended to `codex exec`. +USAGE +} + +die() { + printf 'error: %s\n' "$*" >&2 + exit 1 +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +[[ $# -eq 0 ]] || { + usage + exit 2 +} + +command -v git >/dev/null 2>&1 || die "git is not installed or not on PATH" +command -v codex >/dev/null 2>&1 || die "codex is not installed or not on PATH" + +repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || + die "not a Git repository: $PWD" + +if git -C "$repo_root" diff --cached --quiet --exit-code; then + die "no staged changes found in $repo_root" +fi + +prompt=$(cat <<'PROMPT' +Generate a concise, high-quality Git commit message for the currently staged changes. + +Inspect the staged diff with: + + git diff --cached --stat + git diff --cached + +Requirements: +- Base the message only on staged changes. +- Prefer a single-line subject under 72 characters. +- Add a short body if it's not trivial commit and it materially improves clarity. +- Output only the commit message. Do not wrap it in Markdown, quotes, or commentary. +- Use the conventional commits format: +``` +[(scope)]: + +[optional body] + +Signed-off-by: Your Name +``` +PROMPT +) + +codex_args=( + exec + --cd "$repo_root" + --sandbox read-only + --ephemeral + --color never +) + +if [[ -n "${CODEX_MODEL:-}" ]]; then + codex_args+=(-m "$CODEX_MODEL") +fi + +if [[ -n "${CODEX_EXTRA_ARGS:-}" ]]; then + # shellcheck disable=SC2206 + extra_args=(${CODEX_EXTRA_ARGS}) + codex_args+=("${extra_args[@]}") +fi + +exec codex "${codex_args[@]}" "$prompt" +``` + +Then configure Lazygit: + +```yaml +git: + commit: + messageGeneratorCommand: ~/bin/generate-staged-commit-message.sh +``` + +## Tips + +Keep the command non-interactive because Lazygit captures stdout and stderr. If the command needs authentication or confirmation, handle that outside Lazygit first. + +Make the command fail when it cannot produce a useful message. Lazygit will show stderr and leave the current message intact. + +Quote paths inside scripts. Repository paths can contain spaces. diff --git a/docs-master/README.md b/docs-master/README.md index 1bc0bb6be2e..bbdfa9bc834 100644 --- a/docs-master/README.md +++ b/docs-master/README.md @@ -2,6 +2,7 @@ * [Configuration](./Config.md). * [Custom Commands](./Custom_Command_Keybindings.md) +* [Generated Commit Messages](./Generated_Commit_Messages.md) * [Custom Pagers](./Custom_Pagers.md) * [Dev docs](./dev) * [Keybindings](./keybindings) diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index d1f760ed1d0..b63c4cb0b05 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -370,6 +370,8 @@ type PagingConfig struct { type CommitConfig struct { // If true, pass '--signoff' flag when committing SignOff bool `yaml:"signOff"` + // Command that generates a commit message. Lazygit runs the command from the current git project root and uses stdout as the editable commit message. If the command fails, stderr is shown to the user. See https://github.com/jesseduffield/lazygit/blob/master/docs/Generated_Commit_Messages.md. + MessageGeneratorCommand string `yaml:"messageGeneratorCommand"` // Automatic WYSIWYG wrapping of the commit message as you type AutoWrapCommitMessage bool `yaml:"autoWrapCommitMessage"` // If autoWrapCommitMessage is true, the width to wrap to @@ -902,9 +904,10 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig { }, Git: GitConfig{ Commit: CommitConfig{ - SignOff: false, - AutoWrapCommitMessage: true, - AutoWrapWidth: 72, + SignOff: false, + MessageGeneratorCommand: "", + AutoWrapCommitMessage: true, + AutoWrapWidth: 72, }, Merging: MergingConfig{ ManualCommit: false, diff --git a/pkg/gui/context/commit_message_context.go b/pkg/gui/context/commit_message_context.go index 7db3a085b1d..5968b16c20d 100644 --- a/pkg/gui/context/commit_message_context.go +++ b/pkg/gui/context/commit_message_context.go @@ -5,7 +5,9 @@ import ( "path/filepath" "strconv" "strings" + "time" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" "github.com/spf13/afero" @@ -41,6 +43,10 @@ type CommitMessageViewModel struct { // invoked when pressing the switch-to-editor key binding onSwitchToEditor func(string) error + generatingCommitMessage bool + cancelingCommitMessage bool + cancelGenerateCommitMessageFn func() + // the following two fields are used for the display of the "hooks disabled" subtitle forceSkipHooks bool skipHooksPrefix string @@ -164,13 +170,70 @@ func (self *CommitMessageContext) SetPanelState( self.GetView().Title = summaryTitle self.c.Views().CommitDescription.Title = descriptionTitle + self.RenderCommitDescriptionSubtitle() + + self.c.Views().CommitDescription.Visible = true +} + +func (self *CommitMessageContext) StartGeneratingCommitMessage(cancel func()) { + self.viewModel.generatingCommitMessage = true + self.viewModel.cancelingCommitMessage = false + self.viewModel.cancelGenerateCommitMessageFn = cancel + self.c.Views().CommitMessage.Editable = false + self.c.Views().CommitDescription.Editable = false + self.RenderCommitDescriptionSubtitle() +} + +func (self *CommitMessageContext) StopGeneratingCommitMessage() { + self.viewModel.generatingCommitMessage = false + self.viewModel.cancelingCommitMessage = false + self.viewModel.cancelGenerateCommitMessageFn = nil + self.c.Views().CommitMessage.Editable = true + self.c.Views().CommitDescription.Editable = true + self.RenderCommitDescriptionSubtitle() +} + +func (self *CommitMessageContext) IsGeneratingCommitMessage() bool { + return self.viewModel.generatingCommitMessage +} + +func (self *CommitMessageContext) CancelGenerateCommitMessage() bool { + if !self.viewModel.generatingCommitMessage { + return false + } + + if !self.viewModel.cancelingCommitMessage { + self.viewModel.cancelingCommitMessage = true + if self.viewModel.cancelGenerateCommitMessageFn != nil { + self.viewModel.cancelGenerateCommitMessageFn() + } + } + self.RenderCommitDescriptionSubtitle() + return true +} + +func (self *CommitMessageContext) RenderCommitDescriptionSubtitle() { + if self.viewModel.generatingCommitMessage { + loader := presentation.Loader(time.Now(), self.c.UserConfig().Gui.Spinner) + if self.viewModel.cancelingCommitMessage { + self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.CancelingGenerateCommitMessageSubTitle, + map[string]string{"spinner": loader}) + return + } + + self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.GenerateCommitMessageSubTitle, + map[string]string{ + "spinner": loader, + "cancelKey": self.c.UserConfig().Keybinding.Universal.Return.String(), + }) + return + } + self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionSubTitle, map[string]string{ "togglePanelKeyBinding": self.c.UserConfig().Keybinding.Universal.TogglePanel.String(), "commitMenuKeybinding": self.c.UserConfig().Keybinding.CommitMessage.CommitMenu.String(), }) - - self.c.Views().CommitDescription.Visible = true } func (self *CommitMessageContext) RenderSubtitle() { diff --git a/pkg/gui/context/commit_message_context_test.go b/pkg/gui/context/commit_message_context_test.go new file mode 100644 index 00000000000..39965c72433 --- /dev/null +++ b/pkg/gui/context/commit_message_context_test.go @@ -0,0 +1,68 @@ +package context + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gocui" + guiTypes "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/i18n" + "github.com/sirupsen/logrus" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" +) + +type commitMessageTestGuiCommon struct { + guiTypes.IGuiCommon + views guiTypes.Views +} + +func (self commitMessageTestGuiCommon) Views() guiTypes.Views { + return self.views +} + +func TestCommitMessageGenerationSubtitle(t *testing.T) { + userConfig := config.GetDefaultConfig() + cmn := &common.Common{ + Log: logrus.NewEntry(logrus.New()), + Tr: i18n.EnglishTranslationSet(), + Fs: afero.NewMemMapFs(), + } + cmn.SetUserConfig(userConfig) + + commitMessageView := gocui.NewView("commitMessage", 0, 0, 80, 3, gocui.OutputNormal) + commitDescriptionView := gocui.NewView("commitDescription", 0, 4, 80, 10, gocui.OutputNormal) + commitMessageView.Editable = true + commitDescriptionView.Editable = true + + ctx := NewCommitMessageContext(&ContextCommon{ + Common: cmn, + IGuiCommon: commitMessageTestGuiCommon{views: guiTypes.Views{ + CommitMessage: commitMessageView, + CommitDescription: commitDescriptionView, + }}, + }) + + ctx.SetPanelState(NoCommitIndex, "Commit summary", "Commit description", false, "", nil, nil, false, "") + assert.Contains(t, commitDescriptionView.Subtitle, "Press to toggle focus") + + cancelCalled := false + ctx.StartGeneratingCommitMessage(func() { + cancelCalled = true + }) + + assert.False(t, commitMessageView.Editable) + assert.False(t, commitDescriptionView.Editable) + assert.Contains(t, commitDescriptionView.Subtitle, "Generating commit message") + assert.Contains(t, commitDescriptionView.Subtitle, "") + + assert.True(t, ctx.CancelGenerateCommitMessage()) + assert.True(t, cancelCalled) + assert.Contains(t, commitDescriptionView.Subtitle, "Canceling commit message generation") + + ctx.StopGeneratingCommitMessage() + assert.True(t, commitMessageView.Editable) + assert.True(t, commitDescriptionView.Editable) + assert.Contains(t, commitDescriptionView.Subtitle, "Press to toggle focus") +} diff --git a/pkg/gui/controllers/commit_description_controller.go b/pkg/gui/controllers/commit_description_controller.go index 41ec5410316..d134c0f7b9a 100644 --- a/pkg/gui/controllers/commit_description_controller.go +++ b/pkg/gui/controllers/commit_description_controller.go @@ -87,6 +87,10 @@ func (self *CommitDescriptionController) switchToCommitMessage() error { } func (self *CommitDescriptionController) handleTogglePanel() error { + if self.c.Contexts().CommitMessage.IsGeneratingCommitMessage() { + return nil + } + // The default keybinding for this action is "", which means that we // also get here when pasting multi-line text that contains tabs. In that // case we don't want to toggle the panel, but insert the tab as a character @@ -113,15 +117,27 @@ func (self *CommitDescriptionController) handleTogglePanel() error { } func (self *CommitDescriptionController) close() error { + if self.c.Contexts().CommitMessage.CancelGenerateCommitMessage() { + return nil + } + self.c.Helpers().Commits.CloseCommitMessagePanel() return nil } func (self *CommitDescriptionController) confirm() error { + if self.c.Contexts().CommitMessage.IsGeneratingCommitMessage() { + return nil + } + return self.c.Helpers().Commits.HandleCommitConfirm() } func (self *CommitDescriptionController) openCommitMenu() error { + if self.c.Contexts().CommitMessage.IsGeneratingCommitMessage() { + return nil + } + authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc() return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion) } diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go index 098f42d30f9..457a4b89588 100644 --- a/pkg/gui/controllers/commit_message_controller.go +++ b/pkg/gui/controllers/commit_message_controller.go @@ -98,10 +98,16 @@ func (self *CommitMessageController) context() *context.CommitMessageContext { } func (self *CommitMessageController) handlePreviousCommit() error { + if self.context().IsGeneratingCommitMessage() { + return nil + } return self.handleCommitIndexChange(1) } func (self *CommitMessageController) handleNextCommit() error { + if self.context().IsGeneratingCommitMessage() { + return nil + } if self.context().GetSelectedIndex() == context.NoCommitIndex { return nil } @@ -114,6 +120,10 @@ func (self *CommitMessageController) switchToCommitDescription() error { } func (self *CommitMessageController) handleTogglePanel() error { + if self.context().IsGeneratingCommitMessage() { + return nil + } + // The default keybinding for this action is "", which means that we // also get here when pasting multi-line text that contains tabs. In that // case we don't want to toggle the panel, but insert the tab as a character @@ -174,6 +184,10 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e } func (self *CommitMessageController) confirm() error { + if self.context().IsGeneratingCommitMessage() { + return nil + } + // The default keybinding for this action is "", which means that we // also get here when pasting multi-line text that contains newlines. In // that case we don't want to confirm the commit, but switch to the @@ -192,11 +206,19 @@ func (self *CommitMessageController) confirm() error { } func (self *CommitMessageController) close() error { + if self.context().CancelGenerateCommitMessage() { + return nil + } + self.c.Helpers().Commits.CloseCommitMessagePanel() return nil } func (self *CommitMessageController) openCommitMenu() error { + if self.context().IsGeneratingCommitMessage() { + return nil + } + authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc() return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion) } diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 5e50ba7b26d..215f7262458 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -1,12 +1,16 @@ package helpers import ( + "bytes" "errors" + "os" "path/filepath" "strings" + "sync" "time" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/samber/lo" @@ -231,27 +235,160 @@ func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.S Keys: menuKey('e'), DisabledReason: disabledReasonForOpenInEditor, }, - { + } + + if strings.TrimSpace(self.c.UserConfig().Git.Commit.MessageGeneratorCommand) != "" { + menuItems = append(menuItems, &types.MenuItem{ + Label: self.c.Tr.GenerateCommitMessage, + OnPress: func() error { + return self.generateCommitMessage() + }, + Keys: menuKey('g'), + }) + } + + menuItems = append(menuItems, + &types.MenuItem{ Label: self.c.Tr.AddCoAuthor, OnPress: func() error { return self.addCoAuthor(suggestionFunc) }, Keys: menuKey('c'), }, - { + &types.MenuItem{ Label: self.c.Tr.PasteCommitMessageFromClipboard, OnPress: func() error { return self.pasteCommitMessageFromClipboard() }, Keys: menuKey('p'), }, - } + ) return self.c.Menu(types.CreateMenuOptions{ Title: self.c.Tr.CommitMenuTitle, Items: menuItems, }) } +func (self *CommitsHelper) generateCommitMessage() error { + if self.c.Contexts().CommitMessage.IsGeneratingCommitMessage() { + return nil + } + + command := strings.TrimSpace(self.c.UserConfig().Git.Commit.MessageGeneratorCommand) + repoRoot := self.c.Git().RepoPaths.WorktreePath() + + cmdObj := self.c.OS().Cmd.NewShell(command, self.c.UserConfig().OS.ShellFunctionsFile).SetWd(repoRoot) + cmd := cmdObj.GetCmd() + + var stdoutBuffer, stderrBuffer bytes.Buffer + cmd.Stdout = &stdoutBuffer + cmd.Stderr = &stderrBuffer + + var mutex sync.Mutex + cancelled := false + var process *os.Process + done := make(chan struct{}) + + cancel := func() { + mutex.Lock() + if cancelled { + mutex.Unlock() + return + } + cancelled = true + processToTerminate := process + mutex.Unlock() + + if processToTerminate == nil { + return + } + + if err := oscommands.TerminateProcessGracefully(cmd); err != nil { + self.c.Log.Errorf("error when trying to terminate commit message generator: %v; Command: %v %v", err, cmd.Path, cmd.Args) + } + + go func() { + select { + case <-done: + case <-time.After(2 * time.Second): + _ = processToTerminate.Kill() + } + }() + } + + self.c.Contexts().CommitMessage.StartGeneratingCommitMessage(cancel) + stopRendering := self.renderGeneratingCommitMessageStatus() + + return self.c.WithWaitingStatus(self.c.Tr.GeneratingCommitMessageStatus, func(gocui.Task) error { + defer close(done) + defer close(stopRendering) + + err := cmd.Start() + mutex.Lock() + if err == nil { + process = cmd.Process + } + shouldTerminate := cancelled && process != nil + mutex.Unlock() + + if shouldTerminate { + _ = oscommands.TerminateProcessGracefully(cmd) + } + + if err == nil { + err = cmd.Wait() + } + + mutex.Lock() + wasCancelled := cancelled + mutex.Unlock() + + self.c.OnUIThread(func() error { + self.c.Contexts().CommitMessage.StopGeneratingCommitMessage() + if wasCancelled { + return nil + } + + if err != nil { + message := strings.TrimSpace(stderrBuffer.String()) + if message == "" { + message = err.Error() + } + self.c.Alert(self.c.Tr.GenerateCommitMessageFailed, message) + return nil + } + + self.SetMessageAndDescriptionInView(stdoutBuffer.String()) + return nil + }) + + return nil + }) +} + +func (self *CommitsHelper) renderGeneratingCommitMessageStatus() chan struct{} { + stop := make(chan struct{}) + + self.c.OnWorker(func(gocui.Task) error { + ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig().Gui.Spinner.Rate)) + defer ticker.Stop() + + for { + select { + case <-stop: + return nil + case <-ticker.C: + self.c.OnUIThreadContentOnly(func() error { + self.c.Contexts().CommitMessage.RenderCommitDescriptionSubtitle() + return nil + }) + } + } + }) + + return stop +} + func (self *CommitsHelper) addCoAuthor(suggestionFunc func(string) []*types.Suggestion) error { self.c.Prompt(types.PromptOpts{ Title: self.c.Tr.AddCoAuthorPromptTitle, diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go index 08f3ecf6229..21c3bd84309 100644 --- a/pkg/gui/gui_driver.go +++ b/pkg/gui/gui_driver.go @@ -27,7 +27,16 @@ var _ integrationTypes.GuiDriver = &GuiDriver{} func (self *GuiDriver) PressKey(keyStr string) { self.CheckAllToastsAcknowledged() + self.pressKeyWithoutWaiting(keyStr) + self.waitTillIdle() +} +func (self *GuiDriver) PressKeyWithoutWaiting(keyStr string) { + self.CheckAllToastsAcknowledged() + self.pressKeyWithoutWaiting(keyStr) +} + +func (self *GuiDriver) pressKeyWithoutWaiting(keyStr string) { key, ok := config.KeyFromLabel(keyStr) if !ok { self.Fail("Unrecognized key: " + keyStr) @@ -37,8 +46,6 @@ func (self *GuiDriver) PressKey(keyStr string) { tcell.NewEventKey(tcell.Key(key.KeyName()), key.Str(), tcell.ModMask(key.Mod())), 0, ) - - self.waitTillIdle() } func (self *GuiDriver) Click(x, y int) { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 20d0d5ff64e..c7da1ff5529 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -11,738 +11,743 @@ Todo list when making a new translation package i18n type TranslationSet struct { - NotEnoughSpace string - DiffTitle string - FilesTitle string - BranchesTitle string - CommitsTitle string - StashTitle string - SnakeTitle string - EasterEgg string - UnstagedChanges string - StagedChanges string - StagingTitle string - MergingTitle string - NormalTitle string - LogTitle string - LogXOfYTitle string - CommitSummary string - CredentialsUsername string - CredentialsPassword string - CredentialsPassphrase string - CredentialsPIN string - CredentialsToken string - PassUnameWrong string - Commit string - CommitTooltip string - AmendLastCommit string - AmendLastCommitTitle string - SureToAmend string - NoCommitToAmend string - CommitChangesWithEditor string - FindBaseCommitForFixup string - FindBaseCommitForFixupTooltip string - NoBaseCommitsFound string - MultipleBaseCommitsFoundStaged string - MultipleBaseCommitsFoundUnstaged string - BaseCommitIsAlreadyOnMainBranch string - BaseCommitIsNotInCurrentView string - HunksWithOnlyAddedLinesWarning string - StatusTitle string - GlobalTitle string - Execute string - Stage string - StageTooltip string - ToggleStagedAll string - ToggleStagedAllTooltip string - ToggleTreeView string - ToggleTreeViewTooltip string - OpenDiffTool string - OpenMergeTool string - Refresh string - RefreshTooltip string - Push string - Pull string - PushTooltip string - PullTooltip string - FileFilter string - CopyToClipboardMenu string - CopyFileName string - CopyRelativeFilePath string - CopyAbsoluteFilePath string - CopyFileDiffTooltip string - CopySelectedDiff string - CopyAllFilesDiff string - CopyFileContent string - NoContentToCopyError string - FileNameCopiedToast string - FilePathCopiedToast string - FileDiffCopiedToast string - AllFilesDiffCopiedToast string - FileContentCopiedToast string - FilterStagedFiles string - FilterUnstagedFiles string - FilterTrackedFiles string - FilterUntrackedFiles string - NoFilter string - FilterLabelStagedFiles string - FilterLabelUnstagedFiles string - FilterLabelTrackedFiles string - FilterLabelUntrackedFiles string - FilterLabelConflictingFiles string - MergeConflictsTitle string - MergeConflictDescription_DD string - MergeConflictDescription_AU string - MergeConflictDescription_UA string - MergeConflictDescription_DU string - MergeConflictDescription_UD string - MergeConflictIncomingDiff string - MergeConflictCurrentDiff string - MergeConflictPressEnterToResolve string - MergeConflictKeepFile string - MergeConflictDeleteFile string - Checkout string - CheckoutTooltip string - CantCheckoutBranchWhilePulling string - TagCheckoutTooltip string - RemoteBranchCheckoutTooltip string - CantPullOrPushSameBranchTwice string - NoChangedFiles string - SoftReset string - AlreadyCheckedOutBranch string - SureForceCheckout string - ForceCheckoutBranch string - BranchName string - NewBranchNameBranchOff string - CantDeleteCheckOutBranch string - DeleteBranchTitle string - DeleteBranchesTitle string - DeleteLocalBranch string - DeleteLocalBranches string - DeleteRemoteBranchPrompt string - DeleteRemoteBranchesPrompt string - DeleteLocalAndRemoteBranchPrompt string - DeleteLocalAndRemoteBranchesPrompt string - ForceDeleteBranchTitle string - ForceDeleteBranchMessage string - ForceDeleteBranchesMessage string - RebaseBranch string - RebaseBranchTooltip string - CantRebaseOntoSelf string - CantMergeBranchIntoItself string - ForceCheckout string - ForceCheckoutTooltip string - CheckoutByName string - CheckoutByNameTooltip string - CheckoutPreviousBranch string - RemoteBranchCheckoutTitle string - RemoteBranchCheckoutPrompt string - CheckoutTypeNewBranch string - CheckoutTypeNewBranchTooltip string - CheckoutTypeDetachedHead string - CheckoutTypeDetachedHeadTooltip string - NewBranch string - NewBranchFromStashTooltip string - MoveCommitsToNewBranch string - MoveCommitsToNewBranchTooltip string - MoveCommitsToNewBranchFromMainPrompt string - MoveCommitsToNewBranchMenuPrompt string - MoveCommitsToNewBranchFromBaseItem string - MoveCommitsToNewBranchStackedItem string - CannotMoveCommitsFromDetachedHead string - CannotMoveCommitsNoUpstream string - CannotMoveCommitsBehindUpstream string - CannotMoveCommitsNoUnpushedCommits string - NoBranchesThisRepo string - CommitWithoutMessageErr string - Close string - CloseCancel string - Confirm string - Quit string - SquashTooltip string - CannotSquashOrFixupFirstCommit string - CannotSquashOrFixupMergeCommit string - Fixup string - FixupTooltip string - FixupKeepMessage string - FixupKeepMessageTooltip string - SetFixupMessage string - SetFixupMessageTooltip string - FixupDiscardMessage string - FixupDiscardMessageTooltip string - SureSquashThisCommit string - Squash string - PickCommitTooltip string - Pick string - Edit string - Revert string - RevertCommitTooltip string - Reword string - CommitRewordTooltip string - DropCommit string - DropCommitTooltip string - MoveDownCommit string - MoveUpCommit string - CannotMoveAnyFurther string - CannotMoveMergeCommit string - EditCommit string - EditCommitTooltip string - AmendCommitTooltip string - Amend string - ResetAuthor string - ResetAuthorTooltip string - SetAuthor string - SetAuthorTooltip string - AddCoAuthor string - AmendCommitAttribute string - AmendCommitAttributeTooltip string - SetAuthorPromptTitle string - AddCoAuthorPromptTitle string - AddCoAuthorTooltip string - RewordCommitEditor string - NoCommitsThisBranch string - UpdateRefHere string - ExecCommandHere string - Error string - Undo string - UndoReflog string - RedoReflog string - UndoTooltip string - RedoTooltip string - UndoMergeResolveTooltip string - DiscardAllTooltip string - DiscardUnstagedTooltip string - DiscardUnstagedDisabled string - Pop string - StashPopTooltip string - Drop string - StashDropTooltip string - Apply string - StashApplyTooltip string - NoStashEntries string - StashDrop string - SureDropStashEntry string - StashPop string - SurePopStashEntry string - StashApply string - SureApplyStashEntry string - NoTrackedStagedFilesStash string - NoFilesToStash string - StashChanges string - RenameStash string - RenameStashPrompt string - OpenConfig string - EditConfig string - ForcePush string - ForcePushPrompt string - ForcePushDisabled string - UpdatesRejected string - UpdatesRejectedAndForcePushDisabled string - CheckForUpdate string - CheckingForUpdates string - UpdateAvailableTitle string - UpdateAvailable string - UpdateInProgressWaitingStatus string - UpdateCompletedTitle string - UpdateCompleted string - FailedToRetrieveLatestVersionErr string - OnLatestVersionErr string - MajorVersionErr string - CouldNotFindBinaryErr string - UpdateFailedErr string - ConfirmQuitDuringUpdateTitle string - ConfirmQuitDuringUpdate string - IntroPopupMessage string - NonReloadableConfigWarningTitle string - NonReloadableConfigWarning string - GitconfigParseErr string - EditFile string - EditFileTooltip string - OpenFile string - OpenFileTooltip string - OpenInEditor string - IgnoreFile string - ExcludeFile string - RefreshFiles string - FocusMainView string - Merge string - MergeBranchTooltip string - RegularMergeFastForward string - RegularMergeFastForwardTooltip string - CannotFastForwardMerge string - RegularMergeNonFastForward string - RegularMergeNonFastForwardTooltip string - SquashMergeUncommitted string - SquashMergeUncommittedTooltip string - SquashMergeCommitted string - SquashMergeCommittedTooltip string - ConfirmQuit string - SwitchRepo string - AllBranchesLogGraph string - AllBranchesLogGraphReverse string - UnsupportedGitService string - CopyPullRequestURL string - OpenPullRequestInBrowser string - NoPullRequestForBranch string - NoBranchOnRemote string - Fetch string - FetchTooltip string - CollapseAll string - CollapseAllTooltip string - ExpandAll string - ExpandAllTooltip string - DisabledInFlatView string - FileEnter string - FileEnterTooltip string - StageSelectionTooltip string - DiscardSelection string - DiscardSelectionTooltip string - ToggleSelectHunk string - SelectHunk string - SelectLineByLine string - ToggleSelectHunkTooltip string - HunkStagingHint string - ToggleSelectionForPatch string - RemoveSelectionFromPatch string - RemoveSelectionFromPatchTooltip string - EditHunk string - EditHunkTooltip string - ToggleStagingView string - ToggleStagingViewTooltip string - ReturnToFilesPanel string - FastForward string - FastForwardTooltip string - FastForwarding string - FoundConflictsTitle string - ViewConflictsMenuItem string - AbortMenuItem string - PickHunk string - PickAllHunks string - ViewMergeRebaseOptions string - ViewMergeRebaseOptionsTooltip string - ViewMergeOptions string - ViewRebaseOptions string - ViewCherryPickOptions string - ViewRevertOptions string - NotMergingOrRebasing string - AlreadyRebasing string - NotMidRebase string - MustSelectFixupCommit string - RecentRepos string - MergeOptionsTitle string - RebaseOptionsTitle string - CherryPickOptionsTitle string - RevertOptionsTitle string - CommitSummaryTitle string - CommitDescriptionTitle string - CommitDescriptionSubTitle string - CommitDescriptionFooter string - CommitHooksDisabledSubTitle string - LocalBranchesTitle string - SearchTitle string - TagsTitle string - MenuTitle string - CommitMenuTitle string - RemotesTitle string - RemoteBranchesTitle string - PatchBuildingTitle string - InformationTitle string - SecondaryTitle string - ReflogCommitsTitle string - ConflictsResolved string - Continue string - UnstagedFilesAfterConflictsResolved string - RebasingTitle string - RebasingFromBaseCommitTitle string - SimpleRebase string - InteractiveRebase string - RebaseOntoBaseBranch string - InteractiveRebaseTooltip string - RebaseOntoBaseBranchTooltip string - MustSelectTodoCommits string - FwdNoUpstream string - FwdNoLocalUpstream string - FwdCommitsToPush string - PullRequestNoUpstream string - ErrorOccurred string - ConflictLabel string - PendingRebaseTodosSectionHeader string - PendingCherryPicksSectionHeader string - PendingRevertsSectionHeader string - CommitsSectionHeader string - YouDied string - RewordNotSupported string - ChangingThisActionIsNotAllowed string - NotAllowedMidCherryPickOrRevert string - PickIsOnlyAllowedDuringRebase string - DroppingMergeRequiresSingleSelection string - CherryPickCopy string - CherryPickCopyTooltip string - PasteCommits string - SureCherryPick string - CherryPick string - CannotCherryPickNonCommit string - Donate string - AskQuestion string - PrevHunk string - NextHunk string - PrevConflict string - NextConflict string - SelectPrevHunk string - SelectNextHunk string - ScrollDown string - ScrollUp string - ScrollUpMainWindow string - ScrollDownMainWindow string - SuspendApp string - CannotSuspendApp string - AmendCommitTitle string - AmendCommitPrompt string - AmendCommitWithConflictsMenuPrompt string - AmendCommitWithConflictsContinue string - AmendCommitWithConflictsAmend string - DropCommitTitle string - DropCommitPrompt string - DropUpdateRefPrompt string - DropMergeCommitPrompt string - PullingStatus string - PushingStatus string - FetchingStatus string - SquashingStatus string - FixingStatus string - DeletingStatus string - DroppingStatus string - MovingStatus string - RebasingStatus string - MergingStatus string - LowercaseRebasingStatus string - LowercaseMergingStatus string - LowercaseCherryPickingStatus string - LowercaseRevertingStatus string - AmendingStatus string - CherryPickingStatus string - UndoingStatus string - RedoingStatus string - CheckingOutStatus string - CommittingStatus string - RewordingStatus string - RevertingStatus string - CreatingFixupCommitStatus string - MovingCommitsToNewBranchStatus string - CommitFiles string - SubCommitsDynamicTitle string - CommitFilesDynamicTitle string - RemoteBranchesDynamicTitle string - ViewItemFiles string - CommitFilesTitle string - CheckoutCommitFileTooltip string - CannotCheckoutWithModifiedFilesErr string - CanOnlyDiscardFromLocalCommits string - CannotDiscardFromMultipleCommits string - Remove string - DiscardOldFileChangeTooltip string - DiscardFileChangesTitle string - DiscardFileChangesPrompt string - DiscardFileChangesPromptResetPatch string - DisabledForGPG string - CreateRepo string - BareRepo string - InitialBranch string - NoRecentRepositories string - IncorrectNotARepository string - AutoStashTitle string - AutoStashPrompt string - AutoStashForUndo string - AutoStashForCheckout string - AutoStashForNewBranch string - AutoStashForMovingPatchToIndex string - AutoStashForCherryPicking string - AutoStashForReverting string - Discard string - DiscardChangesTitle string - DiscardFileChangesTooltip string - Cancel string - DiscardAllChanges string - DiscardUnstagedChanges string - DiscardAllChangesToAllFiles string - DiscardAnyUnstagedChanges string - DiscardUntrackedFiles string - DiscardStagedChanges string - HardReset string - BranchDeleteTooltip string - TagDeleteTooltip string - Delete string - Reset string - ResetTooltip string - ViewResetOptions string - FileResetOptionsTooltip string - CreateFixupCommit string - CreateFixupCommitTooltip string - CreateAmendCommit string - FixupMenu_Fixup string - FixupMenu_FixupTooltip string - FixupMenu_AmendWithChanges string - FixupMenu_AmendWithChangesTooltip string - FixupMenu_AmendWithoutChanges string - FixupMenu_AmendWithoutChangesTooltip string - SquashAboveCommitsTooltip string - SquashCommitsAboveSelectedTooltip string - SquashCommitsInCurrentBranchTooltip string - SquashAboveCommits string - SquashCommitsInCurrentBranch string - SquashCommitsAboveSelectedCommit string - CannotSquashCommitsInCurrentBranch string - ExecuteShellCommand string - ExecuteShellCommandTooltip string - ShellCommand string - CommitChangesWithoutHook string - ResetTo string - ResetSoftTooltip string - ResetMixedTooltip string - ResetHardTooltip string - ResetHardConfirmation string - PressEnterToReturn string - ViewStashOptions string - ViewStashOptionsTooltip string - Stash string - StashTooltip string - StashAllChanges string - StashStagedChanges string - StashAllChangesKeepIndex string - StashUnstagedChanges string - StashIncludeUntrackedChanges string - StashOptions string - NotARepository string - WorkingDirectoryDoesNotExist string - ScrollLeft string - ScrollRight string - DiscardPatch string - DiscardPatchConfirm string - CantPatchWhileRebasingError string - ToggleAddToPatch string - ToggleAddToPatchTooltip string - ToggleAllInPatch string - ToggleAllInPatchTooltip string - UpdatingPatch string - ViewPatchOptions string - PatchOptionsTitle string - NoPatchError string - EmptyPatchError string - EnterCommitFile string - EnterCommitFileTooltip string - ExitCustomPatchBuilder string - ExitFocusedMainView string - EnterUpstream string - InvalidUpstream string - NewRemote string - NewRemoteName string - NewRemoteUrl string - AddForkRemote string - AddForkRemoteUsername string - AddForkRemoteTooltip string - IncompatibleForkAlreadyExistsError string - NoOriginRemote string - ViewBranches string - EditRemoteName string - EditRemoteUrl string - RemoveRemote string - RemoveRemoteTooltip string - RemoveRemotePrompt string - DeleteRemoteBranch string - DeleteRemoteBranches string - DeleteRemoteBranchTooltip string - DeleteLocalAndRemoteBranch string - DeleteLocalAndRemoteBranches string - SetAsUpstream string - SetAsUpstreamTooltip string - SetUpstream string - UnsetUpstream string - ViewDivergenceFromUpstream string - ViewDivergenceFromBaseBranch string - CouldNotDetermineBaseBranch string - DivergenceSectionHeaderLocal string - DivergenceSectionHeaderRemote string - ViewUpstreamResetOptions string - ViewUpstreamResetOptionsTooltip string - ViewUpstreamRebaseOptions string - ViewUpstreamRebaseOptionsTooltip string - UpstreamGenericName string - SetUpstreamTitle string - SetUpstreamMessage string - EditRemoteTooltip string - TagCommit string - TagCommitTooltip string - TagNameTitle string - TagMessageTitle string - LightweightTag string - AnnotatedTag string - DeleteTagTitle string - DeleteLocalTag string - DeleteRemoteTag string - DeleteLocalAndRemoteTag string - SelectRemoteTagUpstream string - DeleteRemoteTagPrompt string - DeleteLocalAndRemoteTagPrompt string - RemoteTagDeletedMessage string - PushTagTitle string - PushTag string - PushTagTooltip string - NewTag string - NewTagTooltip string - CreatingTag string - ForceTag string - ForceTagPrompt string - FetchRemoteTooltip string - CheckoutCommitTooltip string - NoBranchesFoundAtCommitTooltip string - GitFlowOptions string - NotAGitFlowBranch string - NewBranchNamePrompt string - IgnoreTracked string - ExcludeTracked string - IgnoreTrackedPrompt string - ExcludeTrackedPrompt string - ViewResetToUpstreamOptions string - NextScreenMode string - PrevScreenMode string - CyclePagers string - CyclePagersTooltip string - CyclePagersReverse string - CyclePagersReverseTooltip string - CyclePagersDisabledReason string - SelectedPager string - DefaultPagerName string - ExternalDiffPagerName string - StartSearch string - StartFilter string - SelectRemoteRepository string - FetchingPullRequests string - Keybindings string - KeybindingsMenuSectionLocal string - KeybindingsMenuSectionGlobal string - KeybindingsMenuSectionNavigation string - KeybindingsTooltip string - RenameBranch string - Upstream string - BranchUpstreamOptionsTitle string - ViewBranchUpstreamOptions string - ViewBranchUpstreamOptionsTooltip string - UpstreamNotSetError string - UpstreamsNotSetError string - NewGitFlowBranchPrompt string - RenameBranchWarning string - OpenKeybindingsMenu string - ResetCherryPick string - ResetCherryPickShort string - NextTab string - PrevTab string - CantUndoWhileRebasing string - CantRedoWhileRebasing string - MustStashWarning string - MustStashTitle string - ConfirmationTitle string - PromptTitle string - PromptInputCannotBeEmptyToast string - PrevPage string - NextPage string - GotoTop string - GotoBottom string - FilteringBy string - ResetInParentheses string - OpenFilteringMenu string - OpenFilteringMenuTooltip string - FilterBy string - ExitFilterMode string - FilterPathOption string - FilterAuthorOption string - EnterFileName string - EnterAuthor string - FilteringMenuTitle string - WillCancelExistingFilterTooltip string - MustExitFilterModeTitle string - MustExitFilterModePrompt string - Diff string - EnterRefToDiff string - EnterRefName string - ExitDiffMode string - DiffingMenuTitle string - SwapDiff string - ViewDiffingOptions string - ViewDiffingOptionsTooltip string - CancelDiffingMode string - OpenCommandLogMenu string - OpenCommandLogMenuTooltip string - ShowingGitDiff string - ShowingDiffForRange string - CommitDiff string - CopyCommitHashToClipboard string - CommitHash string - CommitURL string - PasteCommitMessageFromClipboard string - SurePasteCommitMessage string - CommitMessage string - CommitMessageBody string - CommitSubject string - CommitAuthor string - CommitTags string - CopyCommitAttributeToClipboard string - CopyCommitAttributeToClipboardTooltip string - CopyBranchNameToClipboard string - CopyTagToClipboard string - CopyPathToClipboard string - CommitPrefixPatternError string - CopySelectedTextToClipboard string - NoFilesStagedTitle string - NoFilesStagedPrompt string - BranchNotFoundTitle string - BranchNotFoundPrompt string - BranchUnknown string - DiscardChangeTitle string - DiscardChangePrompt string - DiscardLinesFromCommitTitle string - DiscardLinesFromCommitPrompt string - DiscardLinesFromCommitPromptWithReset string - CreateNewBranchFromCommit string - BuildingPatch string - ViewCommits string - MinGitVersionError string - RunningCustomCommandStatus string - SubmoduleStashAndReset string - AndResetSubmodules string - EnterSubmoduleTooltip string - BackToParentRepo string - Enter string - CopySubmoduleNameToClipboard string - RemoveSubmodule string - RemoveSubmoduleTooltip string - RemoveSubmodulePrompt string - ResettingSubmoduleStatus string - NewSubmoduleName string - NewSubmoduleUrl string - NewSubmodulePath string - NewSubmodule string - AddingSubmoduleStatus string - UpdateSubmoduleUrl string - UpdatingSubmoduleUrlStatus string - EditSubmoduleUrl string - InitializingSubmoduleStatus string - InitSubmoduleTooltip string - Update string - Initialize string - SubmoduleUpdateTooltip string - UpdatingSubmoduleStatus string - BulkInitSubmodules string - BulkUpdateSubmodules string - BulkDeinitSubmodules string - BulkUpdateRecursiveSubmodules string - ViewBulkSubmoduleOptions string - BulkSubmoduleOptions string - RunningCommand string - SubCommitsTitle string - ExitSubview string - SubmodulesTitle string - NavigationTitle string - SuggestionsCheatsheetTitle string + NotEnoughSpace string + DiffTitle string + FilesTitle string + BranchesTitle string + CommitsTitle string + StashTitle string + SnakeTitle string + EasterEgg string + UnstagedChanges string + StagedChanges string + StagingTitle string + MergingTitle string + NormalTitle string + LogTitle string + LogXOfYTitle string + CommitSummary string + CredentialsUsername string + CredentialsPassword string + CredentialsPassphrase string + CredentialsPIN string + CredentialsToken string + PassUnameWrong string + Commit string + CommitTooltip string + AmendLastCommit string + AmendLastCommitTitle string + SureToAmend string + NoCommitToAmend string + CommitChangesWithEditor string + FindBaseCommitForFixup string + FindBaseCommitForFixupTooltip string + NoBaseCommitsFound string + MultipleBaseCommitsFoundStaged string + MultipleBaseCommitsFoundUnstaged string + BaseCommitIsAlreadyOnMainBranch string + BaseCommitIsNotInCurrentView string + HunksWithOnlyAddedLinesWarning string + StatusTitle string + GlobalTitle string + Execute string + Stage string + StageTooltip string + ToggleStagedAll string + ToggleStagedAllTooltip string + ToggleTreeView string + ToggleTreeViewTooltip string + OpenDiffTool string + OpenMergeTool string + Refresh string + RefreshTooltip string + Push string + Pull string + PushTooltip string + PullTooltip string + FileFilter string + CopyToClipboardMenu string + CopyFileName string + CopyRelativeFilePath string + CopyAbsoluteFilePath string + CopyFileDiffTooltip string + CopySelectedDiff string + CopyAllFilesDiff string + CopyFileContent string + NoContentToCopyError string + FileNameCopiedToast string + FilePathCopiedToast string + FileDiffCopiedToast string + AllFilesDiffCopiedToast string + FileContentCopiedToast string + FilterStagedFiles string + FilterUnstagedFiles string + FilterTrackedFiles string + FilterUntrackedFiles string + NoFilter string + FilterLabelStagedFiles string + FilterLabelUnstagedFiles string + FilterLabelTrackedFiles string + FilterLabelUntrackedFiles string + FilterLabelConflictingFiles string + MergeConflictsTitle string + MergeConflictDescription_DD string + MergeConflictDescription_AU string + MergeConflictDescription_UA string + MergeConflictDescription_DU string + MergeConflictDescription_UD string + MergeConflictIncomingDiff string + MergeConflictCurrentDiff string + MergeConflictPressEnterToResolve string + MergeConflictKeepFile string + MergeConflictDeleteFile string + Checkout string + CheckoutTooltip string + CantCheckoutBranchWhilePulling string + TagCheckoutTooltip string + RemoteBranchCheckoutTooltip string + CantPullOrPushSameBranchTwice string + NoChangedFiles string + SoftReset string + AlreadyCheckedOutBranch string + SureForceCheckout string + ForceCheckoutBranch string + BranchName string + NewBranchNameBranchOff string + CantDeleteCheckOutBranch string + DeleteBranchTitle string + DeleteBranchesTitle string + DeleteLocalBranch string + DeleteLocalBranches string + DeleteRemoteBranchPrompt string + DeleteRemoteBranchesPrompt string + DeleteLocalAndRemoteBranchPrompt string + DeleteLocalAndRemoteBranchesPrompt string + ForceDeleteBranchTitle string + ForceDeleteBranchMessage string + ForceDeleteBranchesMessage string + RebaseBranch string + RebaseBranchTooltip string + CantRebaseOntoSelf string + CantMergeBranchIntoItself string + ForceCheckout string + ForceCheckoutTooltip string + CheckoutByName string + CheckoutByNameTooltip string + CheckoutPreviousBranch string + RemoteBranchCheckoutTitle string + RemoteBranchCheckoutPrompt string + CheckoutTypeNewBranch string + CheckoutTypeNewBranchTooltip string + CheckoutTypeDetachedHead string + CheckoutTypeDetachedHeadTooltip string + NewBranch string + NewBranchFromStashTooltip string + MoveCommitsToNewBranch string + MoveCommitsToNewBranchTooltip string + MoveCommitsToNewBranchFromMainPrompt string + MoveCommitsToNewBranchMenuPrompt string + MoveCommitsToNewBranchFromBaseItem string + MoveCommitsToNewBranchStackedItem string + CannotMoveCommitsFromDetachedHead string + CannotMoveCommitsNoUpstream string + CannotMoveCommitsBehindUpstream string + CannotMoveCommitsNoUnpushedCommits string + NoBranchesThisRepo string + CommitWithoutMessageErr string + Close string + CloseCancel string + Confirm string + Quit string + SquashTooltip string + CannotSquashOrFixupFirstCommit string + CannotSquashOrFixupMergeCommit string + Fixup string + FixupTooltip string + FixupKeepMessage string + FixupKeepMessageTooltip string + SetFixupMessage string + SetFixupMessageTooltip string + FixupDiscardMessage string + FixupDiscardMessageTooltip string + SureSquashThisCommit string + Squash string + PickCommitTooltip string + Pick string + Edit string + Revert string + RevertCommitTooltip string + Reword string + CommitRewordTooltip string + DropCommit string + DropCommitTooltip string + MoveDownCommit string + MoveUpCommit string + CannotMoveAnyFurther string + CannotMoveMergeCommit string + EditCommit string + EditCommitTooltip string + AmendCommitTooltip string + Amend string + ResetAuthor string + ResetAuthorTooltip string + SetAuthor string + SetAuthorTooltip string + AddCoAuthor string + AmendCommitAttribute string + AmendCommitAttributeTooltip string + SetAuthorPromptTitle string + AddCoAuthorPromptTitle string + AddCoAuthorTooltip string + RewordCommitEditor string + NoCommitsThisBranch string + UpdateRefHere string + ExecCommandHere string + Error string + Undo string + UndoReflog string + RedoReflog string + UndoTooltip string + RedoTooltip string + UndoMergeResolveTooltip string + DiscardAllTooltip string + DiscardUnstagedTooltip string + DiscardUnstagedDisabled string + Pop string + StashPopTooltip string + Drop string + StashDropTooltip string + Apply string + StashApplyTooltip string + NoStashEntries string + StashDrop string + SureDropStashEntry string + StashPop string + SurePopStashEntry string + StashApply string + SureApplyStashEntry string + NoTrackedStagedFilesStash string + NoFilesToStash string + StashChanges string + RenameStash string + RenameStashPrompt string + OpenConfig string + EditConfig string + ForcePush string + ForcePushPrompt string + ForcePushDisabled string + UpdatesRejected string + UpdatesRejectedAndForcePushDisabled string + CheckForUpdate string + CheckingForUpdates string + UpdateAvailableTitle string + UpdateAvailable string + UpdateInProgressWaitingStatus string + UpdateCompletedTitle string + UpdateCompleted string + FailedToRetrieveLatestVersionErr string + OnLatestVersionErr string + MajorVersionErr string + CouldNotFindBinaryErr string + UpdateFailedErr string + ConfirmQuitDuringUpdateTitle string + ConfirmQuitDuringUpdate string + IntroPopupMessage string + NonReloadableConfigWarningTitle string + NonReloadableConfigWarning string + GitconfigParseErr string + EditFile string + EditFileTooltip string + OpenFile string + OpenFileTooltip string + OpenInEditor string + IgnoreFile string + ExcludeFile string + RefreshFiles string + FocusMainView string + Merge string + MergeBranchTooltip string + RegularMergeFastForward string + RegularMergeFastForwardTooltip string + CannotFastForwardMerge string + RegularMergeNonFastForward string + RegularMergeNonFastForwardTooltip string + SquashMergeUncommitted string + SquashMergeUncommittedTooltip string + SquashMergeCommitted string + SquashMergeCommittedTooltip string + ConfirmQuit string + SwitchRepo string + AllBranchesLogGraph string + AllBranchesLogGraphReverse string + UnsupportedGitService string + CopyPullRequestURL string + OpenPullRequestInBrowser string + NoPullRequestForBranch string + NoBranchOnRemote string + Fetch string + FetchTooltip string + CollapseAll string + CollapseAllTooltip string + ExpandAll string + ExpandAllTooltip string + DisabledInFlatView string + FileEnter string + FileEnterTooltip string + StageSelectionTooltip string + DiscardSelection string + DiscardSelectionTooltip string + ToggleSelectHunk string + SelectHunk string + SelectLineByLine string + ToggleSelectHunkTooltip string + HunkStagingHint string + ToggleSelectionForPatch string + RemoveSelectionFromPatch string + RemoveSelectionFromPatchTooltip string + EditHunk string + EditHunkTooltip string + ToggleStagingView string + ToggleStagingViewTooltip string + ReturnToFilesPanel string + FastForward string + FastForwardTooltip string + FastForwarding string + FoundConflictsTitle string + ViewConflictsMenuItem string + AbortMenuItem string + PickHunk string + PickAllHunks string + ViewMergeRebaseOptions string + ViewMergeRebaseOptionsTooltip string + ViewMergeOptions string + ViewRebaseOptions string + ViewCherryPickOptions string + ViewRevertOptions string + NotMergingOrRebasing string + AlreadyRebasing string + NotMidRebase string + MustSelectFixupCommit string + RecentRepos string + MergeOptionsTitle string + RebaseOptionsTitle string + CherryPickOptionsTitle string + RevertOptionsTitle string + CommitSummaryTitle string + CommitDescriptionTitle string + CommitDescriptionSubTitle string + CommitDescriptionFooter string + CommitHooksDisabledSubTitle string + LocalBranchesTitle string + SearchTitle string + TagsTitle string + MenuTitle string + CommitMenuTitle string + RemotesTitle string + RemoteBranchesTitle string + PatchBuildingTitle string + InformationTitle string + SecondaryTitle string + ReflogCommitsTitle string + ConflictsResolved string + Continue string + UnstagedFilesAfterConflictsResolved string + RebasingTitle string + RebasingFromBaseCommitTitle string + SimpleRebase string + InteractiveRebase string + RebaseOntoBaseBranch string + InteractiveRebaseTooltip string + RebaseOntoBaseBranchTooltip string + MustSelectTodoCommits string + FwdNoUpstream string + FwdNoLocalUpstream string + FwdCommitsToPush string + PullRequestNoUpstream string + ErrorOccurred string + ConflictLabel string + PendingRebaseTodosSectionHeader string + PendingCherryPicksSectionHeader string + PendingRevertsSectionHeader string + CommitsSectionHeader string + YouDied string + RewordNotSupported string + ChangingThisActionIsNotAllowed string + NotAllowedMidCherryPickOrRevert string + PickIsOnlyAllowedDuringRebase string + DroppingMergeRequiresSingleSelection string + CherryPickCopy string + CherryPickCopyTooltip string + PasteCommits string + SureCherryPick string + CherryPick string + CannotCherryPickNonCommit string + Donate string + AskQuestion string + PrevHunk string + NextHunk string + PrevConflict string + NextConflict string + SelectPrevHunk string + SelectNextHunk string + ScrollDown string + ScrollUp string + ScrollUpMainWindow string + ScrollDownMainWindow string + SuspendApp string + CannotSuspendApp string + AmendCommitTitle string + AmendCommitPrompt string + AmendCommitWithConflictsMenuPrompt string + AmendCommitWithConflictsContinue string + AmendCommitWithConflictsAmend string + DropCommitTitle string + DropCommitPrompt string + DropUpdateRefPrompt string + DropMergeCommitPrompt string + PullingStatus string + PushingStatus string + FetchingStatus string + SquashingStatus string + FixingStatus string + DeletingStatus string + DroppingStatus string + MovingStatus string + RebasingStatus string + MergingStatus string + LowercaseRebasingStatus string + LowercaseMergingStatus string + LowercaseCherryPickingStatus string + LowercaseRevertingStatus string + AmendingStatus string + CherryPickingStatus string + UndoingStatus string + RedoingStatus string + CheckingOutStatus string + CommittingStatus string + RewordingStatus string + RevertingStatus string + CreatingFixupCommitStatus string + MovingCommitsToNewBranchStatus string + CommitFiles string + SubCommitsDynamicTitle string + CommitFilesDynamicTitle string + RemoteBranchesDynamicTitle string + ViewItemFiles string + CommitFilesTitle string + CheckoutCommitFileTooltip string + CannotCheckoutWithModifiedFilesErr string + CanOnlyDiscardFromLocalCommits string + CannotDiscardFromMultipleCommits string + Remove string + DiscardOldFileChangeTooltip string + DiscardFileChangesTitle string + DiscardFileChangesPrompt string + DiscardFileChangesPromptResetPatch string + DisabledForGPG string + CreateRepo string + BareRepo string + InitialBranch string + NoRecentRepositories string + IncorrectNotARepository string + AutoStashTitle string + AutoStashPrompt string + AutoStashForUndo string + AutoStashForCheckout string + AutoStashForNewBranch string + AutoStashForMovingPatchToIndex string + AutoStashForCherryPicking string + AutoStashForReverting string + Discard string + DiscardChangesTitle string + DiscardFileChangesTooltip string + Cancel string + DiscardAllChanges string + DiscardUnstagedChanges string + DiscardAllChangesToAllFiles string + DiscardAnyUnstagedChanges string + DiscardUntrackedFiles string + DiscardStagedChanges string + HardReset string + BranchDeleteTooltip string + TagDeleteTooltip string + Delete string + Reset string + ResetTooltip string + ViewResetOptions string + FileResetOptionsTooltip string + CreateFixupCommit string + CreateFixupCommitTooltip string + CreateAmendCommit string + FixupMenu_Fixup string + FixupMenu_FixupTooltip string + FixupMenu_AmendWithChanges string + FixupMenu_AmendWithChangesTooltip string + FixupMenu_AmendWithoutChanges string + FixupMenu_AmendWithoutChangesTooltip string + SquashAboveCommitsTooltip string + SquashCommitsAboveSelectedTooltip string + SquashCommitsInCurrentBranchTooltip string + SquashAboveCommits string + SquashCommitsInCurrentBranch string + SquashCommitsAboveSelectedCommit string + CannotSquashCommitsInCurrentBranch string + ExecuteShellCommand string + ExecuteShellCommandTooltip string + ShellCommand string + CommitChangesWithoutHook string + ResetTo string + ResetSoftTooltip string + ResetMixedTooltip string + ResetHardTooltip string + ResetHardConfirmation string + PressEnterToReturn string + ViewStashOptions string + ViewStashOptionsTooltip string + Stash string + StashTooltip string + StashAllChanges string + StashStagedChanges string + StashAllChangesKeepIndex string + StashUnstagedChanges string + StashIncludeUntrackedChanges string + StashOptions string + NotARepository string + WorkingDirectoryDoesNotExist string + ScrollLeft string + ScrollRight string + DiscardPatch string + DiscardPatchConfirm string + CantPatchWhileRebasingError string + ToggleAddToPatch string + ToggleAddToPatchTooltip string + ToggleAllInPatch string + ToggleAllInPatchTooltip string + UpdatingPatch string + ViewPatchOptions string + PatchOptionsTitle string + NoPatchError string + EmptyPatchError string + EnterCommitFile string + EnterCommitFileTooltip string + ExitCustomPatchBuilder string + ExitFocusedMainView string + EnterUpstream string + InvalidUpstream string + NewRemote string + NewRemoteName string + NewRemoteUrl string + AddForkRemote string + AddForkRemoteUsername string + AddForkRemoteTooltip string + IncompatibleForkAlreadyExistsError string + NoOriginRemote string + ViewBranches string + EditRemoteName string + EditRemoteUrl string + RemoveRemote string + RemoveRemoteTooltip string + RemoveRemotePrompt string + DeleteRemoteBranch string + DeleteRemoteBranches string + DeleteRemoteBranchTooltip string + DeleteLocalAndRemoteBranch string + DeleteLocalAndRemoteBranches string + SetAsUpstream string + SetAsUpstreamTooltip string + SetUpstream string + UnsetUpstream string + ViewDivergenceFromUpstream string + ViewDivergenceFromBaseBranch string + CouldNotDetermineBaseBranch string + DivergenceSectionHeaderLocal string + DivergenceSectionHeaderRemote string + ViewUpstreamResetOptions string + ViewUpstreamResetOptionsTooltip string + ViewUpstreamRebaseOptions string + ViewUpstreamRebaseOptionsTooltip string + UpstreamGenericName string + SetUpstreamTitle string + SetUpstreamMessage string + EditRemoteTooltip string + TagCommit string + TagCommitTooltip string + TagNameTitle string + TagMessageTitle string + LightweightTag string + AnnotatedTag string + DeleteTagTitle string + DeleteLocalTag string + DeleteRemoteTag string + DeleteLocalAndRemoteTag string + SelectRemoteTagUpstream string + DeleteRemoteTagPrompt string + DeleteLocalAndRemoteTagPrompt string + RemoteTagDeletedMessage string + PushTagTitle string + PushTag string + PushTagTooltip string + NewTag string + NewTagTooltip string + CreatingTag string + ForceTag string + ForceTagPrompt string + FetchRemoteTooltip string + CheckoutCommitTooltip string + NoBranchesFoundAtCommitTooltip string + GitFlowOptions string + NotAGitFlowBranch string + NewBranchNamePrompt string + IgnoreTracked string + ExcludeTracked string + IgnoreTrackedPrompt string + ExcludeTrackedPrompt string + ViewResetToUpstreamOptions string + NextScreenMode string + PrevScreenMode string + CyclePagers string + CyclePagersTooltip string + CyclePagersReverse string + CyclePagersReverseTooltip string + CyclePagersDisabledReason string + SelectedPager string + DefaultPagerName string + ExternalDiffPagerName string + StartSearch string + StartFilter string + SelectRemoteRepository string + FetchingPullRequests string + Keybindings string + KeybindingsMenuSectionLocal string + KeybindingsMenuSectionGlobal string + KeybindingsMenuSectionNavigation string + KeybindingsTooltip string + RenameBranch string + Upstream string + BranchUpstreamOptionsTitle string + ViewBranchUpstreamOptions string + ViewBranchUpstreamOptionsTooltip string + UpstreamNotSetError string + UpstreamsNotSetError string + NewGitFlowBranchPrompt string + RenameBranchWarning string + OpenKeybindingsMenu string + ResetCherryPick string + ResetCherryPickShort string + NextTab string + PrevTab string + CantUndoWhileRebasing string + CantRedoWhileRebasing string + MustStashWarning string + MustStashTitle string + ConfirmationTitle string + PromptTitle string + PromptInputCannotBeEmptyToast string + PrevPage string + NextPage string + GotoTop string + GotoBottom string + FilteringBy string + ResetInParentheses string + OpenFilteringMenu string + OpenFilteringMenuTooltip string + FilterBy string + ExitFilterMode string + FilterPathOption string + FilterAuthorOption string + EnterFileName string + EnterAuthor string + FilteringMenuTitle string + WillCancelExistingFilterTooltip string + MustExitFilterModeTitle string + MustExitFilterModePrompt string + Diff string + EnterRefToDiff string + EnterRefName string + ExitDiffMode string + DiffingMenuTitle string + SwapDiff string + ViewDiffingOptions string + ViewDiffingOptionsTooltip string + CancelDiffingMode string + OpenCommandLogMenu string + OpenCommandLogMenuTooltip string + ShowingGitDiff string + ShowingDiffForRange string + CommitDiff string + CopyCommitHashToClipboard string + CommitHash string + CommitURL string + GenerateCommitMessage string + GenerateCommitMessageFailed string + GenerateCommitMessageSubTitle string + CancelingGenerateCommitMessageSubTitle string + PasteCommitMessageFromClipboard string + SurePasteCommitMessage string + CommitMessage string + CommitMessageBody string + CommitSubject string + CommitAuthor string + CommitTags string + CopyCommitAttributeToClipboard string + CopyCommitAttributeToClipboardTooltip string + CopyBranchNameToClipboard string + CopyTagToClipboard string + CopyPathToClipboard string + CommitPrefixPatternError string + CopySelectedTextToClipboard string + NoFilesStagedTitle string + NoFilesStagedPrompt string + BranchNotFoundTitle string + BranchNotFoundPrompt string + BranchUnknown string + DiscardChangeTitle string + DiscardChangePrompt string + DiscardLinesFromCommitTitle string + DiscardLinesFromCommitPrompt string + DiscardLinesFromCommitPromptWithReset string + CreateNewBranchFromCommit string + BuildingPatch string + ViewCommits string + MinGitVersionError string + RunningCustomCommandStatus string + GeneratingCommitMessageStatus string + SubmoduleStashAndReset string + AndResetSubmodules string + EnterSubmoduleTooltip string + BackToParentRepo string + Enter string + CopySubmoduleNameToClipboard string + RemoveSubmodule string + RemoveSubmoduleTooltip string + RemoveSubmodulePrompt string + ResettingSubmoduleStatus string + NewSubmoduleName string + NewSubmoduleUrl string + NewSubmodulePath string + NewSubmodule string + AddingSubmoduleStatus string + UpdateSubmoduleUrl string + UpdatingSubmoduleUrlStatus string + EditSubmoduleUrl string + InitializingSubmoduleStatus string + InitSubmoduleTooltip string + Update string + Initialize string + SubmoduleUpdateTooltip string + UpdatingSubmoduleStatus string + BulkInitSubmodules string + BulkUpdateSubmodules string + BulkDeinitSubmodules string + BulkUpdateRecursiveSubmodules string + ViewBulkSubmoduleOptions string + BulkSubmoduleOptions string + RunningCommand string + SubCommitsTitle string + ExitSubview string + SubmodulesTitle string + NavigationTitle string + SuggestionsCheatsheetTitle string // Unlike the cheatsheet title above, the real suggestions title has a little message saying press tab to focus SuggestionsTitle string SuggestionsSubtitle string @@ -1810,6 +1815,10 @@ func EnglishTranslationSet() *TranslationSet { CopyCommitHashToClipboard: "Copy abbreviated commit hash to clipboard", CommitHash: "Commit hash", CommitURL: "Commit URL", + GenerateCommitMessage: "Generate Commit Message", + GenerateCommitMessageFailed: "Generate commit message command failed", + GenerateCommitMessageSubTitle: "Generating commit message {{.spinner}} Press {{.cancelKey}} to cancel", + CancelingGenerateCommitMessageSubTitle: "Canceling commit message generation {{.spinner}}", PasteCommitMessageFromClipboard: "Paste commit message from clipboard", SurePasteCommitMessage: "Pasting will overwrite the current commit message, continue?", CommitMessage: "Commit message (subject and body)", @@ -1839,6 +1848,7 @@ func EnglishTranslationSet() *TranslationSet { ViewCommits: "View commits", MinGitVersionError: "Git version must be at least %s. Please upgrade your git version.", RunningCustomCommandStatus: "Running custom command", + GeneratingCommitMessageStatus: "Generating commit message", SubmoduleStashAndReset: "Stash uncommitted submodule changes and update", AndResetSubmodules: "And reset submodules", Enter: "Enter", diff --git a/pkg/integration/components/commit_description_panel_driver.go b/pkg/integration/components/commit_description_panel_driver.go index 6281e756bf2..dab57c995b1 100644 --- a/pkg/integration/components/commit_description_panel_driver.go +++ b/pkg/integration/components/commit_description_panel_driver.go @@ -63,6 +63,12 @@ func (self *CommitDescriptionPanelDriver) Title(expected *TextMatcher) *CommitDe return self } +func (self *CommitDescriptionPanelDriver) Subtitle(expected *TextMatcher) *CommitDescriptionPanelDriver { + self.getViewDriver().Subtitle(expected) + + return self +} + func (self *CommitDescriptionPanelDriver) Cancel() { self.getViewDriver().PressEscape() } diff --git a/pkg/integration/components/popup.go b/pkg/integration/components/popup.go index 3cdc4f1f21c..b82010c62c7 100644 --- a/pkg/integration/components/popup.go +++ b/pkg/integration/components/popup.go @@ -67,10 +67,10 @@ func (self *Popup) CommitMessagePanel() *CommitMessagePanelDriver { return &CommitMessagePanelDriver{t: self.t} } -func (self *Popup) CommitDescriptionPanel() *CommitMessagePanelDriver { +func (self *Popup) CommitDescriptionPanel() *CommitDescriptionPanelDriver { self.inCommitDescriptionPanel() - return &CommitMessagePanelDriver{t: self.t} + return &CommitDescriptionPanelDriver{t: self.t} } func (self *Popup) inCommitMessagePanel() { diff --git a/pkg/integration/components/test_driver.go b/pkg/integration/components/test_driver.go index 8294f3b46fa..4eeac18b50c 100644 --- a/pkg/integration/components/test_driver.go +++ b/pkg/integration/components/test_driver.go @@ -56,6 +56,11 @@ func (self *TestDriver) GlobalPress(key config.Keybinding) { self.press(key[0]) } +// For cases where a keypress is expected to cancel an in-flight background task. +func (self *TestDriver) GlobalPressWithoutWaiting(key config.Keybinding) { + self.gui.PressKeyWithoutWaiting(key[0]) +} + func (self *TestDriver) typeContent(content string) { for _, char := range content { self.pressFast(string(char)) diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go index ab32f9f899f..a6fc174923b 100644 --- a/pkg/integration/components/test_test.go +++ b/pkg/integration/components/test_test.go @@ -30,6 +30,10 @@ func (self *fakeGuiDriver) PressKey(key string) { self.pressedKeys = append(self.pressedKeys, key) } +func (self *fakeGuiDriver) PressKeyWithoutWaiting(key string) { + self.pressedKeys = append(self.pressedKeys, key) +} + func (self *fakeGuiDriver) Click(x, y int) { self.clickedCoordinates = append(self.clickedCoordinates, coordinate{x: x, y: y}) } diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go index e9e5fbbc70e..239d1c4d6fc 100644 --- a/pkg/integration/components/view_driver.go +++ b/pkg/integration/components/view_driver.go @@ -41,6 +41,16 @@ func (self *ViewDriver) Title(expected *TextMatcher) *ViewDriver { return self } +// asserts that the view has the expected subtitle +func (self *ViewDriver) Subtitle(expected *TextMatcher) *ViewDriver { + self.t.assertWithRetries(func() (bool, string) { + actual := self.getView().Subtitle + return expected.context(fmt.Sprintf("%s subtitle", self.context)).test(actual) + }) + + return self +} + func (self *ViewDriver) Clear() *ViewDriver { // clearing multiple times in case there's multiple lines // (the clear button only clears a single line at a time) diff --git a/pkg/integration/tests/commit/generate_commit_message.go b/pkg/integration/tests/commit/generate_commit_message.go new file mode 100644 index 00000000000..703c93a099d --- /dev/null +++ b/pkg/integration/tests/commit/generate_commit_message.go @@ -0,0 +1,33 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var GenerateCommitMessage = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Generate a commit message from a configured command", + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.Commit.MessageGeneratorCommand = `sh -c 'printf "generated subject\n\nroot: %s\nargs: %s" "$(basename "$PWD")" "$#"' sh` + }, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file", "file content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + Focus(). + Press(keys.Files.CommitChanges) + + t.ExpectPopup().CommitMessagePanel(). + OpenCommitMenu() + + t.ExpectPopup().Menu().Title(Equals("Commit Menu")). + Select(Contains("Generate Commit Message")). + Confirm() + + t.ExpectPopup().CommitMessagePanel(). + Content(Equals("generated subject")). + SwitchToDescription(). + Content(Equals("root: repo\nargs: 0")) + }, +}) diff --git a/pkg/integration/tests/commit/generate_commit_message_cancel.go b/pkg/integration/tests/commit/generate_commit_message_cancel.go new file mode 100644 index 00000000000..75353a3fa19 --- /dev/null +++ b/pkg/integration/tests/commit/generate_commit_message_cancel.go @@ -0,0 +1,38 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var GenerateCommitMessageCancel = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Cancel a running commit message generator", + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.Commit.MessageGeneratorCommand = `sh -c 'sleep 5; printf "generated subject"' sh` + }, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file", "file content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + Focus(). + Press(keys.Files.CommitChanges) + + t.ExpectPopup().CommitMessagePanel(). + Type("existing message"). + OpenCommitMenu() + + menu := t.ExpectPopup().Menu().Title(Equals("Commit Menu")). + Select(Contains("Generate Commit Message")) + + go func() { + t.Wait(100) + t.GlobalPressWithoutWaiting(keys.Universal.Return) + }() + + menu.Confirm() + + t.ExpectPopup().CommitMessagePanel(). + Content(Equals("existing message")) + }, +}) diff --git a/pkg/integration/tests/commit/generate_commit_message_error.go b/pkg/integration/tests/commit/generate_commit_message_error.go new file mode 100644 index 00000000000..9cc9fd2d2d6 --- /dev/null +++ b/pkg/integration/tests/commit/generate_commit_message_error.go @@ -0,0 +1,37 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var GenerateCommitMessageError = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Show stderr when commit message generation fails", + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.Commit.MessageGeneratorCommand = `sh -c 'echo generator failed >&2; exit 1' sh` + }, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file", "file content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + Focus(). + Press(keys.Files.CommitChanges) + + t.ExpectPopup().CommitMessagePanel(). + Type("existing message"). + OpenCommitMenu() + + t.ExpectPopup().Menu().Title(Equals("Commit Menu")). + Select(Contains("Generate Commit Message")). + Confirm() + + t.ExpectPopup().Alert(). + Title(Equals("Generate commit message command failed")). + Content(Equals("generator failed")). + Confirm() + + t.ExpectPopup().CommitMessagePanel(). + Content(Equals("existing message")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 1b264e50dc9..e4fdf761cfc 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -136,6 +136,9 @@ var tests = []*components.IntegrationTest{ commit.FindBaseCommitForFixupDisregardMainBranch, commit.FindBaseCommitForFixupOnlyAddedLines, commit.FindBaseCommitForFixupWarningForAddedLines, + commit.GenerateCommitMessage, + commit.GenerateCommitMessageCancel, + commit.GenerateCommitMessageError, commit.Highlight, commit.History, commit.HistoryComplex, diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go index 75263905839..c81ec3eb162 100644 --- a/pkg/integration/types/types.go +++ b/pkg/integration/types/types.go @@ -23,6 +23,7 @@ type IntegrationTest interface { // this is the interface through which our integration tests interact with the lazygit gui type GuiDriver interface { PressKey(string) + PressKeyWithoutWaiting(string) Click(int, int) Keys() config.KeybindingConfig CurrentContext() types.Context diff --git a/schema-master/config.json b/schema-master/config.json index b95c5c98053..a44ef188e5f 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -10,6 +10,10 @@ "description": "If true, pass '--signoff' flag when committing", "default": false }, + "messageGeneratorCommand": { + "type": "string", + "description": "Command that generates a commit message. Lazygit runs the command from the current git project root and uses stdout as the editable commit message. If the command fails, stderr is shown to the user. See https://github.com/jesseduffield/lazygit/blob/master/docs/Generated_Commit_Messages.md." + }, "autoWrapCommitMessage": { "type": "boolean", "description": "Automatic WYSIWYG wrapping of the commit message as you type",