From fc1443dee4b6a1c63e466516f02efce7a0605781 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 11 Nov 2026 10:07:33 +0100 Subject: [PATCH 1/4] Change the commit menu's key for pasting a commit message to `V` We are about to add an entry for copying the commit message to the clipboard. Its obvious key `c` is already taken by "Add co-author", so use `C` for copying and `V` for pasting. This matches the keys for copying and pasting commits in the commits panel. This means changing the key of an existing entry. We change the one for pasting and not the one for adding a co-author, because users are likely to paste a commit message less often than they add a co-author. Co-Authored-By: Claude Opus 5.5 --- pkg/gui/controllers/helpers/commits_helper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 448c451d3..3f0b64036 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -247,7 +247,7 @@ func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.S OnPress: func() error { return self.pasteCommitMessageFromClipboard() }, - Keys: menuKey('p'), + Keys: menuKey('V'), }, } return self.c.Menu(types.CreateMenuOptions{ From f11e1dca4c17634a76bcf22eb32f1a74906d9709 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 11 Nov 2026 09:10:55 +0100 Subject: [PATCH 2/4] Extract a helper that joins summary and description into a message The next commit needs to build a commit message from the commit panel in git's canonical format too. Co-Authored-By: Claude Opus 5.5 --- pkg/gui/controllers/helpers/commits_helper.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 3f0b64036..6a9b0861f 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -9,7 +9,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" ) type CommitsHelper struct { @@ -48,6 +47,15 @@ func (self *CommitsHelper) SplitCommitMessageAndDescription(message string) (str return summary, description } +// joinCommitMessageAndDescription is the inverse of +// SplitCommitMessageAndDescription. +func (self *CommitsHelper) joinCommitMessageAndDescription(summary string, description string) string { + if len(description) == 0 { + return summary + } + return summary + "\n\n" + description +} + // SplitPreservedCommitMessage splits a message in our preservation format // (summary and description joined by a single "\n") into summary and description. // It is lossless: round-tripping through JoinCommitMessageAndUnwrappedDescription @@ -103,9 +111,7 @@ func TryRemoveHardLineBreaks(message string, autoWrapWidth int) string { } func (self *CommitsHelper) SwitchToEditor() error { - message := lo.Ternary(len(self.getCommitDescription()) == 0, - self.getCommitSummary(), - self.getCommitSummary()+"\n\n"+self.getCommitDescription()) + message := self.joinCommitMessageAndDescription(self.getCommitSummary(), self.getCommitDescription()) filepath := filepath.Join(self.c.OS().GetTempDir(), self.c.Git().RepoPaths.RepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".msg") err := self.c.OS().CreateFileWithContent(filepath, message) if err != nil { From 5a5e3ac6961e99f59de69b27cdb4eeaa83f8cdb6 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 11 Nov 2026 09:12:29 +0100 Subject: [PATCH 3/4] Add "Copy commit message to clipboard" to the commit menu The commit menu (ctrl+o in the commit message panel) can paste a message from the clipboard, but it can't copy the message to it. Copying is useful for keeping a message around while committing something else first. For example, if a pre-commit hook fails, users may want to commit a fix for it before they come back to the original change. If they commit the fix through the commit message panel, lazygit deletes the preserved message of the original change. Co-Authored-By: Claude Opus 5.5 --- pkg/gui/controllers/helpers/commits_helper.go | 32 ++++++++++++++ pkg/i18n/english.go | 2 + .../commit/copy_commit_message_from_panel.go | 44 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 4 files changed, 79 insertions(+) create mode 100644 pkg/integration/tests/commit/copy_commit_message_from_panel.go diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 6a9b0861f..601fc45a9 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strings" "time" + "unicode" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/gocui" @@ -248,6 +249,13 @@ func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.S }, Keys: menuKey('c'), }, + { + Label: self.c.Tr.CopyCommitMessageToClipboard, + OnPress: func() error { + return self.copyCommitMessageToClipboard() + }, + Keys: menuKey('C'), + }, { Label: self.c.Tr.PasteCommitMessageFromClipboard, OnPress: func() error { @@ -277,6 +285,30 @@ func (self *CommitsHelper) addCoAuthor(suggestionFunc func(string) []*types.Sugg return nil } +func (self *CommitsHelper) copyCommitMessageToClipboard() error { + // The panel keeps the space at the end of each line that it wraps. Git + // strips trailing whitespace when it commits, so do the same here; this + // makes the copied message look like the committed one. + message := stripTrailingWhitespace( + self.joinCommitMessageAndDescription(self.getCommitSummary(), self.getCommitDescription())) + + self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard) + if err := self.c.OS().CopyToClipboard(message); err != nil { + return err + } + + self.c.Toast(self.c.Tr.CommitMessageCopiedToClipboard) + return nil +} + +func stripTrailingWhitespace(message string) string { + lines := strings.Split(message, "\n") + for i, line := range lines { + lines[i] = strings.TrimRightFunc(line, unicode.IsSpace) + } + return strings.Join(lines, "\n") +} + func (self *CommitsHelper) pasteCommitMessageFromClipboard() error { message, err := self.c.OS().PasteFromClipboard() if err != nil { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 3d4689d8a..d923c6e3b 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -765,6 +765,7 @@ type TranslationSet struct { CopyCommitHashToClipboard string CommitHash string CommitURL string + CopyCommitMessageToClipboard string PasteCommitMessageFromClipboard string SurePasteCommitMessage string CommitMessage string @@ -2002,6 +2003,7 @@ func EnglishTranslationSet() *TranslationSet { CopyCommitHashToClipboard: "Copy abbreviated commit hash to clipboard", CommitHash: "Commit hash", CommitURL: "Commit URL", + CopyCommitMessageToClipboard: "Copy commit message to clipboard", PasteCommitMessageFromClipboard: "Paste commit message from clipboard", SurePasteCommitMessage: "Pasting will overwrite the current commit message, continue?", CommitMessage: "Commit message (subject and body)", diff --git a/pkg/integration/tests/commit/copy_commit_message_from_panel.go b/pkg/integration/tests/commit/copy_commit_message_from_panel.go new file mode 100644 index 000000000..12da37d46 --- /dev/null +++ b/pkg/integration/tests/commit/copy_commit_message_from_panel.go @@ -0,0 +1,44 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var CopyCommitMessageFromPanel = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Copy the commit message from the commit message panel to the clipboard", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > ../clipboard" + }, + 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("subject"). + SwitchToDescription(). + Type("This line is longer than the auto-wrap width, so the clipboard gets it with a line break"). + AddNewline(). + Type("second line"). + SwitchToSummary(). + OpenCommitMenu() + + t.ExpectPopup().Menu().Title(Equals("Commit Menu")). + Select(Contains("Copy commit message to clipboard")). + Confirm() + + t.ExpectToast(Equals("Commit message copied to clipboard")) + + t.FileSystem().FileContent("../clipboard", Equals( + "subject\n\nThis line is longer than the auto-wrap width, so the clipboard gets it\nwith a line break\nsecond line")) + + t.ExpectPopup().CommitMessagePanel(). + Content(Equals("subject")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 6d66a1080..6c8e83b6a 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -156,6 +156,7 @@ var tests = []*components.IntegrationTest{ commit.CommitWithNonMatchingBranchName, commit.CommitWithPrefix, commit.CopyAuthorToClipboard, + commit.CopyCommitMessageFromPanel, commit.CopyMessageBodyToClipboard, commit.CopyTagToClipboard, commit.CreateAmendCommit, From b385a4e164ee585135c89184db33a5f5c0d66bd4 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 11 Nov 2026 09:13:48 +0100 Subject: [PATCH 4/4] Add "Clear commit message" to the commit menu Many users don't know that they can clear the commit message panel quickly by holding down ctrl+u. They hold down backspace instead, and for a long message this takes a long time. Co-Authored-By: Claude Opus 5.5 --- pkg/gui/controllers/helpers/commits_helper.go | 8 ++++ pkg/i18n/english.go | 2 + .../tests/commit/clear_commit_message.go | 39 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 4 files changed, 50 insertions(+) create mode 100644 pkg/integration/tests/commit/clear_commit_message.go diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 601fc45a9..3d58e6aec 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -263,6 +263,14 @@ func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.S }, Keys: menuKey('V'), }, + { + Label: self.c.Tr.ClearCommitMessage, + OnPress: func() error { + self.setSummaryAndDescriptionInView("", "") + return nil + }, + Keys: menuKey('d'), + }, } return self.c.Menu(types.CreateMenuOptions{ Title: self.c.Tr.CommitMenuTitle, diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index d923c6e3b..d8aad8b99 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -768,6 +768,7 @@ type TranslationSet struct { CopyCommitMessageToClipboard string PasteCommitMessageFromClipboard string SurePasteCommitMessage string + ClearCommitMessage string CommitMessage string CommitMessageBody string CommitSubject string @@ -2006,6 +2007,7 @@ func EnglishTranslationSet() *TranslationSet { CopyCommitMessageToClipboard: "Copy commit message to clipboard", PasteCommitMessageFromClipboard: "Paste commit message from clipboard", SurePasteCommitMessage: "Pasting will overwrite the current commit message, continue?", + ClearCommitMessage: "Clear commit message", CommitMessage: "Commit message (subject and body)", CommitMessageBody: "Commit message body", CommitSubject: "Commit subject", diff --git a/pkg/integration/tests/commit/clear_commit_message.go b/pkg/integration/tests/commit/clear_commit_message.go new file mode 100644 index 000000000..1c5909357 --- /dev/null +++ b/pkg/integration/tests/commit/clear_commit_message.go @@ -0,0 +1,39 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ClearCommitMessage = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Clear the commit message in the commit message panel", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + 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("subject"). + SwitchToDescription(). + Type("first line"). + AddNewline(). + Type("second line"). + SwitchToSummary(). + OpenCommitMenu() + + t.ExpectPopup().Menu().Title(Equals("Commit Menu")). + Select(Contains("Clear commit message")). + Confirm() + + t.ExpectPopup().CommitMessagePanel(). + Content(Equals("")). + SwitchToDescription(). + Content(Equals("")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 6c8e83b6a..c6ee9564f 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -142,6 +142,7 @@ var tests = []*components.IntegrationTest{ commit.CheckoutFileFromCommit, commit.CheckoutFileFromRangeSelectionOfCommits, commit.CheckoutFileWithLocalModifications, + commit.ClearCommitMessage, commit.Commit, commit.CommitAllWithNestedRepository, commit.CommitDuringSquashMerge,