From ab23539c0c69ea192edd18cb9e6a22930f4ad9e2 Mon Sep 17 00:00:00 2001 From: Chris McDonnell Date: Sat, 15 Feb 2025 19:48:54 -0500 Subject: [PATCH] Add option to copy commit message body --- .../controllers/basic_commits_controller.go | 35 +++++++ pkg/i18n/english.go | 94 ++++++++++--------- pkg/integration/components/shell.go | 4 + .../commit/copy_message_body_to_clipboard.go | 39 ++++++++ .../disable_copy_commit_message_body.go | 33 +++++++ .../tests/commit/paste_commit_message.go | 2 +- .../paste_commit_message_over_existing.go | 2 +- pkg/integration/tests/test_list.go | 2 + 8 files changed, 166 insertions(+), 45 deletions(-) create mode 100644 pkg/integration/tests/commit/copy_message_body_to_clipboard.go create mode 100644 pkg/integration/tests/commit/disable_copy_commit_message_body.go diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go index fb118b024..8aa4c8048 100644 --- a/pkg/gui/controllers/basic_commits_controller.go +++ b/pkg/gui/controllers/basic_commits_controller.go @@ -122,7 +122,24 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [ return bindings } +func (self *BasicCommitsController) getCommitMessageBody(hash string) string { + commitMessageBody, err := self.c.Git().Commit.GetCommitMessage(hash) + if err != nil { + return "" + } + _, body := self.c.Helpers().Commits.SplitCommitMessageAndDescription(commitMessageBody) + return body +} + func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) error { + commitMessageBody := self.getCommitMessageBody(commit.Hash) + var commitMessageBodyDisabled *types.DisabledReason + if commitMessageBody == "" { + commitMessageBodyDisabled = &types.DisabledReason{ + Text: self.c.Tr.CommitHasNoMessageBody, + } + } + items := []*types.MenuItem{ { Label: self.c.Tr.CommitHash, @@ -144,6 +161,14 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e }, Key: 'm', }, + { + Label: self.c.Tr.CommitMessageBody, + DisabledReason: commitMessageBodyDisabled, + OnPress: func() error { + return self.copyCommitMessageBodyToClipboard(commitMessageBody) + }, + Key: 'b', + }, { Label: self.c.Tr.CommitURL, OnPress: func() error { @@ -259,6 +284,16 @@ func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models. return nil } +func (self *BasicCommitsController) copyCommitMessageBodyToClipboard(commitMessageBody string) error { + self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageBodyToClipboard) + if err := self.c.OS().CopyToClipboard(commitMessageBody); err != nil { + return err + } + + self.c.Toast(self.c.Tr.CommitMessageBodyCopiedToClipboard) + return nil +} + func (self *BasicCommitsController) copyCommitSubjectToClipboard(commit *models.Commit) error { message, err := self.c.Git().Commit.GetCommitSubject(commit.Hash) if err != nil { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index a5584667a..3bd2ca528 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -621,6 +621,7 @@ type TranslationSet struct { PasteCommitMessageFromClipboard string SurePasteCommitMessage string CommitMessage string + CommitMessageBody string CommitSubject string CommitAuthor string CommitTags string @@ -685,10 +686,12 @@ type TranslationSet struct { CommitDiffCopiedToClipboard string CommitURLCopiedToClipboard string CommitMessageCopiedToClipboard string + CommitMessageBodyCopiedToClipboard string CommitSubjectCopiedToClipboard string CommitAuthorCopiedToClipboard string CommitTagsCopiedToClipboard string CommitHasNoTags string + CommitHasNoMessageBody string PatchCopiedToClipboard string CopiedToClipboard string ErrCannotEditDirectory string @@ -914,6 +917,7 @@ type Actions struct { MoveCommitUp string MoveCommitDown string CopyCommitMessageToClipboard string + CopyCommitMessageBodyToClipboard string CopyCommitSubjectToClipboard string CopyCommitDiffToClipboard string CopyCommitHashToClipboard string @@ -1653,7 +1657,8 @@ func EnglishTranslationSet() *TranslationSet { CopyCommitMessageToClipboard: "Copy commit message to clipboard", PasteCommitMessageFromClipboard: "Paste commit message from clipboard", SurePasteCommitMessage: "Pasting will overwrite the current commit message, continue?", - CommitMessage: "Commit message", + CommitMessage: "Commit message (subject and body)", + CommitMessageBody: "Commit message body", CommitSubject: "Commit subject", CommitAuthor: "Commit author", CommitTags: "Commit tags", @@ -1717,10 +1722,12 @@ func EnglishTranslationSet() *TranslationSet { CommitDiffCopiedToClipboard: "Commit diff copied to clipboard", CommitURLCopiedToClipboard: "Commit URL copied to clipboard", CommitMessageCopiedToClipboard: "Commit message copied to clipboard", + CommitMessageBodyCopiedToClipboard: "Commit message body copied to clipboard", CommitSubjectCopiedToClipboard: "Commit subject copied to clipboard", CommitAuthorCopiedToClipboard: "Commit author copied to clipboard", CommitTagsCopiedToClipboard: "Commit tags copied to clipboard", CommitHasNoTags: "Commit has no tags", + CommitHasNoMessageBody: "Commit has no message body", PatchCopiedToClipboard: "Patch copied to clipboard", CopiedToClipboard: "copied to clipboard", ErrCannotEditDirectory: "Cannot edit directories: you can only edit individual files", @@ -1873,48 +1880,49 @@ func EnglishTranslationSet() *TranslationSet { Actions: Actions{ // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) - CheckoutCommit: "Checkout commit", - CheckoutBranchAtCommit: "Checkout branch '%s'", - CheckoutCommitAsDetachedHead: "Checkout commit %s as detached head", - CheckoutTag: "Checkout tag", - CheckoutBranch: "Checkout branch", - ForceCheckoutBranch: "Force checkout branch", - CheckoutBranchOrCommit: "Checkout branch or commit", - DeleteLocalBranch: "Delete local branch", - Merge: "Merge", - SquashMerge: "Squash merge", - RebaseBranch: "Rebase branch", - RenameBranch: "Rename branch", - CreateBranch: "Create branch", - CherryPick: "(Cherry-pick) paste commits", - CheckoutFile: "Checkout file", - DiscardOldFileChange: "Discard old file change", - SquashCommitDown: "Squash commit down", - FixupCommit: "Fixup commit", - RewordCommit: "Reword commit", - DropCommit: "Drop commit", - EditCommit: "Edit commit", - AmendCommit: "Amend commit", - ResetCommitAuthor: "Reset commit author", - SetCommitAuthor: "Set commit author", - AddCommitCoAuthor: "Add commit co-author", - RevertCommit: "Revert commit", - CreateFixupCommit: "Create fixup commit", - SquashAllAboveFixupCommits: "Squash all above fixup commits", - CreateLightweightTag: "Create lightweight tag", - CreateAnnotatedTag: "Create annotated tag", - CopyCommitMessageToClipboard: "Copy commit message to clipboard", - CopyCommitSubjectToClipboard: "Copy commit subject to clipboard", - CopyCommitTagsToClipboard: "Copy commit tags to clipboard", - CopyCommitDiffToClipboard: "Copy commit diff to clipboard", - CopyCommitHashToClipboard: "Copy full commit hash to clipboard", - CopyCommitURLToClipboard: "Copy commit URL to clipboard", - CopyCommitAuthorToClipboard: "Copy commit author to clipboard", - CopyCommitAttributeToClipboard: "Copy to clipboard", - CopyPatchToClipboard: "Copy patch to clipboard", - MoveCommitUp: "Move commit up", - MoveCommitDown: "Move commit down", - CustomCommand: "Custom command", + CheckoutCommit: "Checkout commit", + CheckoutBranchAtCommit: "Checkout branch '%s'", + CheckoutCommitAsDetachedHead: "Checkout commit %s as detached head", + CheckoutTag: "Checkout tag", + CheckoutBranch: "Checkout branch", + ForceCheckoutBranch: "Force checkout branch", + CheckoutBranchOrCommit: "Checkout branch or commit", + DeleteLocalBranch: "Delete local branch", + Merge: "Merge", + SquashMerge: "Squash merge", + RebaseBranch: "Rebase branch", + RenameBranch: "Rename branch", + CreateBranch: "Create branch", + CherryPick: "(Cherry-pick) paste commits", + CheckoutFile: "Checkout file", + DiscardOldFileChange: "Discard old file change", + SquashCommitDown: "Squash commit down", + FixupCommit: "Fixup commit", + RewordCommit: "Reword commit", + DropCommit: "Drop commit", + EditCommit: "Edit commit", + AmendCommit: "Amend commit", + ResetCommitAuthor: "Reset commit author", + SetCommitAuthor: "Set commit author", + AddCommitCoAuthor: "Add commit co-author", + RevertCommit: "Revert commit", + CreateFixupCommit: "Create fixup commit", + SquashAllAboveFixupCommits: "Squash all above fixup commits", + CreateLightweightTag: "Create lightweight tag", + CreateAnnotatedTag: "Create annotated tag", + CopyCommitMessageToClipboard: "Copy commit message to clipboard", + CopyCommitMessageBodyToClipboard: "Copy commit message body to clipboard", + CopyCommitSubjectToClipboard: "Copy commit subject to clipboard", + CopyCommitTagsToClipboard: "Copy commit tags to clipboard", + CopyCommitDiffToClipboard: "Copy commit diff to clipboard", + CopyCommitHashToClipboard: "Copy full commit hash to clipboard", + CopyCommitURLToClipboard: "Copy commit URL to clipboard", + CopyCommitAuthorToClipboard: "Copy commit author to clipboard", + CopyCommitAttributeToClipboard: "Copy to clipboard", + CopyPatchToClipboard: "Copy patch to clipboard", + MoveCommitUp: "Move commit up", + MoveCommitDown: "Move commit down", + CustomCommand: "Custom command", // TODO: remove DiscardAllChangesInDirectory: "Discard all changes in directory", diff --git a/pkg/integration/components/shell.go b/pkg/integration/components/shell.go index faf58e64a..cdafb5756 100644 --- a/pkg/integration/components/shell.go +++ b/pkg/integration/components/shell.go @@ -174,6 +174,10 @@ func (self *Shell) EmptyCommit(message string) *Shell { return self.RunCommand([]string{"git", "commit", "--allow-empty", "-m", message}) } +func (self *Shell) EmptyCommitWithBody(subject string, body string) *Shell { + return self.RunCommand([]string{"git", "commit", "--allow-empty", "-m", subject, "-m", body}) +} + func (self *Shell) EmptyCommitDaysAgo(message string, daysAgo int) *Shell { return self.RunCommand([]string{"git", "commit", "--allow-empty", "--date", fmt.Sprintf("%d days ago", daysAgo), "-m", message}) } diff --git a/pkg/integration/tests/commit/copy_message_body_to_clipboard.go b/pkg/integration/tests/commit/copy_message_body_to_clipboard.go new file mode 100644 index 000000000..b0bb72488 --- /dev/null +++ b/pkg/integration/tests/commit/copy_message_body_to_clipboard.go @@ -0,0 +1,39 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +// We're emulating the clipboard by writing to a file called clipboard + +var CopyMessageBodyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Copy a commit message body 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.EmptyCommitWithBody("My Subject", "My awesome commit message body") + }, + + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("My Subject").IsSelected(), + ). + Press(keys.Commits.CopyCommitAttributeToClipboard) + + t.ExpectPopup().Menu(). + Title(Equals("Copy to clipboard")). + Select(Contains("Commit message body")). + Confirm() + + t.ExpectToast(Equals("Commit message body copied to clipboard")) + + t.FileSystem().FileContent("clipboard", Equals("My awesome commit message body")) + }, +}) diff --git a/pkg/integration/tests/commit/disable_copy_commit_message_body.go b/pkg/integration/tests/commit/disable_copy_commit_message_body.go new file mode 100644 index 000000000..d6c03c851 --- /dev/null +++ b/pkg/integration/tests/commit/disable_copy_commit_message_body.go @@ -0,0 +1,33 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var DisableCopyCommitMessageBody = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Disables copy commit message body when there is no body", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("commit") + }, + + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit").IsSelected(), + ). + Press(keys.Commits.CopyCommitAttributeToClipboard) + + t.ExpectPopup().Menu(). + Title(Equals("Copy to clipboard")). + Select(Contains("Commit message body")). + Confirm() + + t.ExpectToast(Equals("Disabled: Commit has no message body")) + }, +}) diff --git a/pkg/integration/tests/commit/paste_commit_message.go b/pkg/integration/tests/commit/paste_commit_message.go index 2e38ae41c..72edf72af 100644 --- a/pkg/integration/tests/commit/paste_commit_message.go +++ b/pkg/integration/tests/commit/paste_commit_message.go @@ -26,7 +26,7 @@ var PasteCommitMessage = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Commits.CopyCommitAttributeToClipboard) t.ExpectPopup().Menu().Title(Equals("Copy to clipboard")). - Select(Contains("Commit message")).Confirm() + Select(Contains("Commit message (subject and body)")).Confirm() t.ExpectToast(Equals("Commit message copied to clipboard")) diff --git a/pkg/integration/tests/commit/paste_commit_message_over_existing.go b/pkg/integration/tests/commit/paste_commit_message_over_existing.go index bb55e5998..049d5acbd 100644 --- a/pkg/integration/tests/commit/paste_commit_message_over_existing.go +++ b/pkg/integration/tests/commit/paste_commit_message_over_existing.go @@ -26,7 +26,7 @@ var PasteCommitMessageOverExisting = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Commits.CopyCommitAttributeToClipboard) t.ExpectPopup().Menu().Title(Equals("Copy to clipboard")). - Select(Contains("Commit message")).Confirm() + Select(Contains("Commit message (subject and body)")).Confirm() t.ExpectToast(Equals("Commit message copied to clipboard")) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index b9b480e91..1ea9b2b03 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -97,10 +97,12 @@ var tests = []*components.IntegrationTest{ commit.CommitWithNonMatchingBranchName, commit.CommitWithPrefix, commit.CopyAuthorToClipboard, + commit.CopyMessageBodyToClipboard, commit.CopyTagToClipboard, commit.CreateAmendCommit, commit.CreateFixupCommitInBranchStack, commit.CreateTag, + commit.DisableCopyCommitMessageBody, commit.DiscardOldFileChanges, commit.FindBaseCommitForFixup, commit.FindBaseCommitForFixupDisregardMainBranch,