From 333802fffc2d09eb63f7d0564e49d961750819e3 Mon Sep 17 00:00:00 2001 From: Bruno Jesus Date: Mon, 27 Jan 2025 21:53:13 +0000 Subject: [PATCH 1/5] Copy Tags to clipboard Add an option to copy tag(s) to the clipboard. Works on both the Tags and Commits sections. --- .../controllers/basic_commits_controller.go | 115 +++++++++++------- pkg/gui/keybindings.go | 7 ++ pkg/i18n/english.go | 9 ++ 3 files changed, 88 insertions(+), 43 deletions(-) diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go index 797215746..2a8a7daac 100644 --- a/pkg/gui/controllers/basic_commits_controller.go +++ b/pkg/gui/controllers/basic_commits_controller.go @@ -3,6 +3,7 @@ package controllers import ( "errors" "fmt" + "strings" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" @@ -122,51 +123,67 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [ } func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) error { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.Actions.CopyCommitAttributeToClipboard, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.CommitHash, - OnPress: func() error { - return self.copyCommitHashToClipboard(commit) - }, - }, - { - Label: self.c.Tr.CommitSubject, - OnPress: func() error { - return self.copyCommitSubjectToClipboard(commit) - }, - Key: 's', - }, - { - Label: self.c.Tr.CommitMessage, - OnPress: func() error { - return self.copyCommitMessageToClipboard(commit) - }, - Key: 'm', - }, - { - Label: self.c.Tr.CommitURL, - OnPress: func() error { - return self.copyCommitURLToClipboard(commit) - }, - Key: 'u', - }, - { - Label: self.c.Tr.CommitDiff, - OnPress: func() error { - return self.copyCommitDiffToClipboard(commit) - }, - Key: 'd', - }, - { - Label: self.c.Tr.CommitAuthor, - OnPress: func() error { - return self.copyAuthorToClipboard(commit) - }, - Key: 'a', + items := []*types.MenuItem{ + { + Label: self.c.Tr.CommitHash, + OnPress: func() error { + return self.copyCommitHashToClipboard(commit) }, }, + { + Label: self.c.Tr.CommitSubject, + OnPress: func() error { + return self.copyCommitSubjectToClipboard(commit) + }, + Key: 's', + }, + { + Label: self.c.Tr.CommitMessage, + OnPress: func() error { + return self.copyCommitMessageToClipboard(commit) + }, + Key: 'm', + }, + { + Label: self.c.Tr.CommitURL, + OnPress: func() error { + return self.copyCommitURLToClipboard(commit) + }, + Key: 'u', + }, + { + Label: self.c.Tr.CommitDiff, + OnPress: func() error { + return self.copyCommitDiffToClipboard(commit) + }, + Key: 'd', + }, + { + Label: self.c.Tr.CommitAuthor, + OnPress: func() error { + return self.copyAuthorToClipboard(commit) + }, + Key: 'a', + }, + } + + commitTagsItem := types.MenuItem{ + Label: self.c.Tr.CommitTags, + OnPress: func() error { + return self.copyCommitTagsToClipboard(commit) + }, + Key: 't', + } + + if len(commit.Tags) == 0 { + commitTagsItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoTags} + } + + items = append(items, &commitTagsItem) + + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.Actions.CopyCommitAttributeToClipboard, + Items: items, }) } @@ -257,6 +274,18 @@ func (self *BasicCommitsController) copyCommitSubjectToClipboard(commit *models. return nil } +func (self *BasicCommitsController) copyCommitTagsToClipboard(commit *models.Commit) error { + message := strings.Join(commit.Tags, "\n") + + self.c.LogAction(self.c.Tr.Actions.CopyCommitTagsToClipboard) + if err := self.c.OS().CopyToClipboard(message); err != nil { + return err + } + + self.c.Toast(self.c.Tr.CommitTagsCopiedToClipboard) + return nil +} + func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error { url, err := self.c.Helpers().Host.GetCommitURL(commit.Hash) if err != nil { diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 371d039a6..72af2f9fd 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -145,6 +145,13 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason, Description: self.c.Tr.CopyBranchNameToClipboard, }, + { + ViewName: "tags", + Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), + Handler: self.handleCopySelectedSideContextItemCommitHashToClipboard, + GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason, + Description: self.c.Tr.CopyTagToClipboard, + }, { ViewName: "commits", Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 3efe269b2..8435aaafa 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -612,9 +612,11 @@ type TranslationSet struct { CommitMessage string CommitSubject string CommitAuthor string + CommitTags string CopyCommitAttributeToClipboard string CopyCommitAttributeToClipboardTooltip string CopyBranchNameToClipboard string + CopyTagToClipboard string CopyPathToClipboard string CommitPrefixPatternError string CopySelectedTextToClipboard string @@ -674,6 +676,8 @@ type TranslationSet struct { CommitMessageCopiedToClipboard string CommitSubjectCopiedToClipboard string CommitAuthorCopiedToClipboard string + CommitTagsCopiedToClipboard string + NoTags string PatchCopiedToClipboard string CopiedToClipboard string ErrCannotEditDirectory string @@ -905,6 +909,7 @@ type Actions struct { CopyCommitURLToClipboard string CopyCommitAuthorToClipboard string CopyCommitAttributeToClipboard string + CopyCommitTagsToClipboard string CopyPatchToClipboard string CustomCommand string DiscardAllChangesInDirectory string @@ -1627,9 +1632,11 @@ func EnglishTranslationSet() *TranslationSet { CommitMessage: "Commit message", CommitSubject: "Commit subject", CommitAuthor: "Commit author", + CommitTags: "Commit tags", CopyCommitAttributeToClipboard: "Copy commit attribute to clipboard", CopyCommitAttributeToClipboardTooltip: "Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author).", CopyBranchNameToClipboard: "Copy branch name to clipboard", + CopyTagToClipboard: "Copy tag to clipboard", CopyPathToClipboard: "Copy path to clipboard", CopySelectedTextToClipboard: "Copy selected text to clipboard", CommitPrefixPatternError: "Error in commitPrefix pattern", @@ -1688,6 +1695,8 @@ func EnglishTranslationSet() *TranslationSet { CommitMessageCopiedToClipboard: "Commit message copied to clipboard", CommitSubjectCopiedToClipboard: "Commit subject copied to clipboard", CommitAuthorCopiedToClipboard: "Commit author copied to clipboard", + CommitTagsCopiedToClipboard: "Commit tags copied to clipboard", + NoTags: "No tags", PatchCopiedToClipboard: "Patch copied to clipboard", CopiedToClipboard: "copied to clipboard", ErrCannotEditDirectory: "Cannot edit directories: you can only edit individual files", From 0397ede8a63201e55e3b85351132e3a8ceadd2df Mon Sep 17 00:00:00 2001 From: Bruno Jesus Date: Mon, 27 Jan 2025 22:07:08 +0000 Subject: [PATCH 2/5] Document copy tag keybinding Add the default keybinding for the "Copy tag to clipboard" function on the Tags section. --- docs/keybindings/Keybindings_en.md | 1 + docs/keybindings/Keybindings_ja.md | 1 + docs/keybindings/Keybindings_ko.md | 1 + docs/keybindings/Keybindings_nl.md | 1 + docs/keybindings/Keybindings_pl.md | 1 + docs/keybindings/Keybindings_ru.md | 1 + docs/keybindings/Keybindings_zh-CN.md | 1 + docs/keybindings/Keybindings_zh-TW.md | 1 + 8 files changed, 8 insertions(+) diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index 449c4b6ec..f162014b7 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -352,6 +352,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| +| `` `` | Copy tag to clipboard | | | `` `` | Checkout | Checkout the selected tag as a detached HEAD. | | `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` d `` | Delete | View delete options for local/remote tag. | diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md index 0ccd6e7ab..a1046c8dc 100644 --- a/docs/keybindings/Keybindings_ja.md +++ b/docs/keybindings/Keybindings_ja.md @@ -182,6 +182,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| +| `` `` | Copy tag to clipboard | | | `` `` | チェックアウト | Checkout the selected tag as a detached HEAD. | | `` n `` | タグを作成 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` d `` | Delete | View delete options for local/remote tag. | diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md index e5fb37782..50c719719 100644 --- a/docs/keybindings/Keybindings_ko.md +++ b/docs/keybindings/Keybindings_ko.md @@ -323,6 +323,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| +| `` `` | Copy tag to clipboard | | | `` `` | 체크아웃 | Checkout the selected tag as a detached HEAD. | | `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` d `` | Delete | View delete options for local/remote tag. | diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index 5951320c0..37bacb20f 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -352,6 +352,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| +| `` `` | Copy tag to clipboard | | | `` `` | Uitchecken | Checkout the selected tag as a detached HEAD. | | `` n `` | Creëer tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` d `` | Delete | View delete options for local/remote tag. | diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index c22d151d3..8db7a0e73 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -333,6 +333,7 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita, | Key | Action | Info | |-----|--------|-------------| +| `` `` | Copy tag to clipboard | | | `` `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). | | `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. | | `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. | diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md index a9d977fcb..9c17e4c6b 100644 --- a/docs/keybindings/Keybindings_ru.md +++ b/docs/keybindings/Keybindings_ru.md @@ -288,6 +288,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| +| `` `` | Copy tag to clipboard | | | `` `` | Переключить | Checkout the selected tag as a detached HEAD. | | `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` d `` | Delete | View delete options for local/remote tag. | diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md index 6cabb0a3a..6baea60d0 100644 --- a/docs/keybindings/Keybindings_zh-CN.md +++ b/docs/keybindings/Keybindings_zh-CN.md @@ -250,6 +250,7 @@ _图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ | Key | Action | Info | |-----|--------|-------------| +| `` `` | Copy tag to clipboard | | | `` `` | 检出 | 检出选择的标签作为分离的HEAD | | `` n `` | 创建标签 | 基于当前提交创建一个新标签。你将在弹窗中输入标签名称和描述(可选)。 | | `` d `` | 删除 | 查看本地/远程标签的删除选项 | diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md index c891f6bdc..895a81e79 100644 --- a/docs/keybindings/Keybindings_zh-TW.md +++ b/docs/keybindings/Keybindings_zh-TW.md @@ -284,6 +284,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| +| `` `` | Copy tag to clipboard | | | `` `` | 檢出 | Checkout the selected tag as a detached HEAD. | | `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` d `` | 刪除 | View delete options for local/remote tag. | From ef0d3196864be766057d2abe9eb5cad31e3d86a7 Mon Sep 17 00:00:00 2001 From: Bruno Jesus Date: Mon, 27 Jan 2025 23:43:06 +0000 Subject: [PATCH 3/5] Add copy commit tags to clipboard toast message --- pkg/i18n/english.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 8435aaafa..33a962fc8 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -1881,6 +1881,7 @@ func EnglishTranslationSet() *TranslationSet { 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", From 632695f71c573888d65464fbc8aa8abe681e0398 Mon Sep 17 00:00:00 2001 From: Bruno Jesus Date: Tue, 28 Jan 2025 00:34:57 +0000 Subject: [PATCH 4/5] Integration tests for copy tags to clipboard Adds integration test in order to confirm if tags are being properly sent to the clipboard --- .../tests/commit/copy_tag_to_clipboard.go | 51 +++++++++++++++++++ .../tests/tag/copy_to_clipboard.go | 39 ++++++++++++++ pkg/integration/tests/test_list.go | 2 + 3 files changed, 92 insertions(+) create mode 100644 pkg/integration/tests/commit/copy_tag_to_clipboard.go create mode 100644 pkg/integration/tests/tag/copy_to_clipboard.go diff --git a/pkg/integration/tests/commit/copy_tag_to_clipboard.go b/pkg/integration/tests/commit/copy_tag_to_clipboard.go new file mode 100644 index 000000000..a88148754 --- /dev/null +++ b/pkg/integration/tests/commit/copy_tag_to_clipboard.go @@ -0,0 +1,51 @@ +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 CopyTagToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Copy a commit tag to the clipboard", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + // Include delimiters around the text so that we can assert on the entire content + config.GetUserConfig().OS.CopyToClipboardCmd = "echo _{{text}}_ > clipboard" + }, + + SetupRepo: func(shell *Shell) { + shell.SetAuthor("John Doe", "john@doe.com") + shell.EmptyCommit("commit") + shell.CreateLightweightTag("tag1", "HEAD") + shell.CreateLightweightTag("tag2", "HEAD") + }, + + 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 tags")). + Confirm() + + t.ExpectToast(Equals("Commit tags copied to clipboard")) + + t.Views().Files(). + Focus(). + Press(keys.Files.RefreshFiles). + Lines( + Contains("clipboard").IsSelected(), + ) + + t.Views().Main().Content(Contains("+_tag2")) + t.Views().Main().Content(Contains("+tag1_")) + }, +}) diff --git a/pkg/integration/tests/tag/copy_to_clipboard.go b/pkg/integration/tests/tag/copy_to_clipboard.go new file mode 100644 index 000000000..f0176df9a --- /dev/null +++ b/pkg/integration/tests/tag/copy_to_clipboard.go @@ -0,0 +1,39 @@ +package tag + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var CopyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Copy the tag to the clipboard", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + // Include delimiters around the text so that we can assert on the entire content + config.GetUserConfig().OS.CopyToClipboardCmd = "echo _{{text}}_ > clipboard" + }, + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("one") + shell.CreateLightweightTag("tag1", "HEAD") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Tags(). + Focus(). + Lines( + Contains("tag").IsSelected(), + ). + Press(keys.Universal.CopyToClipboard) + + t.ExpectToast(Equals("'tag1' copied to clipboard")) + + t.Views().Files(). + Focus(). + Press(keys.Files.RefreshFiles). + Lines( + Contains("clipboard").IsSelected(), + ) + + t.Views().Main().Content(Contains("_tag1_")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 0736677a8..8b520e9e4 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -93,6 +93,7 @@ var tests = []*components.IntegrationTest{ commit.CommitWithNonMatchingBranchName, commit.CommitWithPrefix, commit.CopyAuthorToClipboard, + commit.CopyTagToClipboard, commit.CreateAmendCommit, commit.CreateFixupCommitInBranchStack, commit.CreateTag, @@ -351,6 +352,7 @@ var tests = []*components.IntegrationTest{ sync.RenameBranchAndPull, tag.Checkout, tag.CheckoutWhenBranchWithSameNameExists, + tag.CopyToClipboard, tag.CreateWhileCommitting, tag.CrudAnnotated, tag.CrudLightweight, From 698f9287d4695d2c0774e575733ddb9428d52f89 Mon Sep 17 00:00:00 2001 From: Bruno Jesus Date: Tue, 28 Jan 2025 23:11:06 +0000 Subject: [PATCH 5/5] Rename NoTags to CommitHasNoTags --- pkg/gui/controllers/basic_commits_controller.go | 2 +- pkg/i18n/english.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go index 2a8a7daac..fb118b024 100644 --- a/pkg/gui/controllers/basic_commits_controller.go +++ b/pkg/gui/controllers/basic_commits_controller.go @@ -176,7 +176,7 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e } if len(commit.Tags) == 0 { - commitTagsItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoTags} + commitTagsItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CommitHasNoTags} } items = append(items, &commitTagsItem) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 33a962fc8..968aa5718 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -677,7 +677,7 @@ type TranslationSet struct { CommitSubjectCopiedToClipboard string CommitAuthorCopiedToClipboard string CommitTagsCopiedToClipboard string - NoTags string + CommitHasNoTags string PatchCopiedToClipboard string CopiedToClipboard string ErrCannotEditDirectory string @@ -1696,7 +1696,7 @@ func EnglishTranslationSet() *TranslationSet { CommitSubjectCopiedToClipboard: "Commit subject copied to clipboard", CommitAuthorCopiedToClipboard: "Commit author copied to clipboard", CommitTagsCopiedToClipboard: "Commit tags copied to clipboard", - NoTags: "No tags", + CommitHasNoTags: "Commit has no tags", PatchCopiedToClipboard: "Patch copied to clipboard", CopiedToClipboard: "copied to clipboard", ErrCannotEditDirectory: "Cannot edit directories: you can only edit individual files",