diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go index 8b19409aa..d32f669b3 100644 --- a/pkg/commands/git_commands/tag.go +++ b/pkg/commands/git_commands/tag.go @@ -57,3 +57,20 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() } + +// Return info about an annotated tag in the format: +// +// Tagger: tagger name +// TaggerDate: tagger date +// +// Tag message +// +// Should only be called for annotated tags. +func (self *TagCommands) ShowAnnotationInfo(tagName string) (string, error) { + cmdArgs := NewGitCmd("for-each-ref"). + Arg("--format=Tagger: %(taggername) %(taggeremail)%0aTaggerDate: %(taggerdate)%0a%0a%(contents)"). + Arg("refs/tags/" + tagName). + ToArgv() + + return self.cmd.New(cmdArgs).RunWithOutput() +} diff --git a/pkg/commands/git_commands/tag_loader.go b/pkg/commands/git_commands/tag_loader.go index bd05fe4b4..1d51c5e5c 100644 --- a/pkg/commands/git_commands/tag_loader.go +++ b/pkg/commands/git_commands/tag_loader.go @@ -1,7 +1,7 @@ package git_commands import ( - "regexp" + "strings" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" @@ -26,9 +26,13 @@ func NewTagLoader( } func (self *TagLoader) GetTags() ([]*models.Tag, error) { - // get remote branches, sorted by creation date (descending) + // get tags, sorted by creation date (descending) // see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt - cmdArgs := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToArgv() + cmdArgs := NewGitCmd("for-each-ref"). + Arg("--sort=-creatordate"). + Arg("--format=%(refname)%00%(objecttype)%00%(contents:subject)"). + Arg("refs/tags"). + ToArgv() tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() if err != nil { return nil, err @@ -36,20 +40,20 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) { split := utils.SplitLines(tagsOutput) - lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`) - - tags := lo.Map(split, func(line string, _ int) *models.Tag { - matches := lineRegex.FindStringSubmatch(line) - tagName := matches[1] - message := "" - if len(matches) > 3 { - message = matches[3] + tags := lo.FilterMap(split, func(line string, _ int) (*models.Tag, bool) { + fields := strings.SplitN(line, "\x00", 3) + if len(fields) != 3 { + return nil, false } + tagName := fields[0] + objectType := fields[1] + message := fields[2] return &models.Tag{ - Name: tagName, - Message: message, - } + Name: strings.TrimPrefix(tagName, "refs/tags/"), + Message: message, + IsAnnotated: objectType == "tag", + }, true }) return tags, nil diff --git a/pkg/commands/git_commands/tag_loader_test.go b/pkg/commands/git_commands/tag_loader_test.go index e8f19fcd4..a56aa34c7 100644 --- a/pkg/commands/git_commands/tag_loader_test.go +++ b/pkg/commands/git_commands/tag_loader_test.go @@ -9,10 +9,9 @@ import ( "github.com/stretchr/testify/assert" ) -const tagsOutput = `tag1 this is my message -tag2 -tag3 this is my other message -` +const tagsOutput = "refs/tags/tag1\x00tag\x00this is my message\n" + + "refs/tags/tag2\x00commit\x00\n" + + "refs/tags/tag3\x00tag\x00this is my other message\n" func TestGetTags(t *testing.T) { type scenario struct { @@ -26,18 +25,18 @@ func TestGetTags(t *testing.T) { { testName: "should return no tags if there are none", runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, "", nil), + ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, "", nil), expectedTags: []*models.Tag{}, expectedError: nil, }, { testName: "should return tags if present", runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, tagsOutput, nil), + ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, tagsOutput, nil), expectedTags: []*models.Tag{ - {Name: "tag1", Message: "this is my message"}, - {Name: "tag2", Message: ""}, - {Name: "tag3", Message: "this is my other message"}, + {Name: "tag1", Message: "this is my message", IsAnnotated: true}, + {Name: "tag2", Message: "", IsAnnotated: false}, + {Name: "tag3", Message: "this is my other message", IsAnnotated: true}, }, expectedError: nil, }, diff --git a/pkg/commands/models/tag.go b/pkg/commands/models/tag.go index 876e2cd77..389083c94 100644 --- a/pkg/commands/models/tag.go +++ b/pkg/commands/models/tag.go @@ -3,9 +3,13 @@ package models // Tag : A git tag type Tag struct { Name string + // this is either the first line of the message of an annotated tag, or the // first line of a commit message for a lightweight tag Message string + + // true if this is an annotated tag, false if it's a lightweight tag + IsAnnotated bool } func (t *Tag) FullRefName() string { diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go index 5ce674d91..784d2cc8f 100644 --- a/pkg/gui/controllers/tags_controller.go +++ b/pkg/gui/controllers/tags_controller.go @@ -1,11 +1,16 @@ package controllers import ( + "fmt" + "strings" + "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/context" + "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/samber/lo" ) type TagsController struct { @@ -96,7 +101,8 @@ func (self *TagsController) GetOnRenderToMain() func() { task = types.NewRenderStringTask("No tags") } else { cmdObj := self.c.Git().Branch.GetGraphCmdObj(tag.FullRefName()) - task = types.NewRunCommandTask(cmdObj.GetCmd()) + prefix := self.getTagInfo(tag) + "\n\n---\n\n" + task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix) } self.c.RenderToMainViews(types.RefreshMainOpts{ @@ -110,6 +116,35 @@ func (self *TagsController) GetOnRenderToMain() func() { } } +func (self *TagsController) getTagInfo(tag *models.Tag) string { + if tag.IsAnnotated { + info := fmt.Sprintf("%s: %s", self.c.Tr.AnnotatedTag, style.AttrBold.Sprint(style.FgYellow.Sprint(tag.Name))) + output, err := self.c.Git().Tag.ShowAnnotationInfo(tag.Name) + if err == nil { + info += "\n\n" + strings.TrimRight(filterOutPgpSignature(output), "\n") + } + return info + } + + return fmt.Sprintf("%s: %s", self.c.Tr.LightweightTag, style.AttrBold.Sprint(style.FgYellow.Sprint(tag.Name))) +} + +func filterOutPgpSignature(output string) string { + lines := strings.Split(output, "\n") + inPgpSignature := false + filteredLines := lo.Filter(lines, func(line string, _ int) bool { + if line == "-----END PGP SIGNATURE-----" { + inPgpSignature = false + return false + } + if line == "-----BEGIN PGP SIGNATURE-----" { + inPgpSignature = true + } + return !inPgpSignature + }) + return strings.Join(filteredLines, "\n") +} + func (self *TagsController) checkout(tag *models.Tag) error { self.c.LogAction(self.c.Tr.Actions.CheckoutTag) if err := self.c.Helpers().Refs.CheckoutRef(tag.FullRefName(), types.CheckoutRefOptions{}); err != nil {