Skip to content

Commit 4439531

Browse files
authored
Show annotation information for selected tag (#4663)
- **PR Description** In the Tags panel, show information about the selected tag above the commit log. This is mostly useful for annotated tags. Fixes #4659.
2 parents 718cbdb + e5b09f3 commit 4439531

File tree

5 files changed

+83
-24
lines changed

5 files changed

+83
-24
lines changed

pkg/commands/git_commands/tag.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,20 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
5757

5858
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
5959
}
60+
61+
// Return info about an annotated tag in the format:
62+
//
63+
// Tagger: tagger name <tagger email>
64+
// TaggerDate: tagger date
65+
//
66+
// Tag message
67+
//
68+
// Should only be called for annotated tags.
69+
func (self *TagCommands) ShowAnnotationInfo(tagName string) (string, error) {
70+
cmdArgs := NewGitCmd("for-each-ref").
71+
Arg("--format=Tagger: %(taggername) %(taggeremail)%0aTaggerDate: %(taggerdate)%0a%0a%(contents)").
72+
Arg("refs/tags/" + tagName).
73+
ToArgv()
74+
75+
return self.cmd.New(cmdArgs).RunWithOutput()
76+
}

pkg/commands/git_commands/tag_loader.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package git_commands
22

33
import (
4-
"regexp"
4+
"strings"
55

66
"github.com/jesseduffield/lazygit/pkg/commands/models"
77
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -26,30 +26,34 @@ func NewTagLoader(
2626
}
2727

2828
func (self *TagLoader) GetTags() ([]*models.Tag, error) {
29-
// get remote branches, sorted by creation date (descending)
29+
// get tags, sorted by creation date (descending)
3030
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
31-
cmdArgs := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToArgv()
31+
cmdArgs := NewGitCmd("for-each-ref").
32+
Arg("--sort=-creatordate").
33+
Arg("--format=%(refname)%00%(objecttype)%00%(contents:subject)").
34+
Arg("refs/tags").
35+
ToArgv()
3236
tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
3337
if err != nil {
3438
return nil, err
3539
}
3640

3741
split := utils.SplitLines(tagsOutput)
3842

39-
lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`)
40-
41-
tags := lo.Map(split, func(line string, _ int) *models.Tag {
42-
matches := lineRegex.FindStringSubmatch(line)
43-
tagName := matches[1]
44-
message := ""
45-
if len(matches) > 3 {
46-
message = matches[3]
43+
tags := lo.FilterMap(split, func(line string, _ int) (*models.Tag, bool) {
44+
fields := strings.SplitN(line, "\x00", 3)
45+
if len(fields) != 3 {
46+
return nil, false
4747
}
48+
tagName := fields[0]
49+
objectType := fields[1]
50+
message := fields[2]
4851

4952
return &models.Tag{
50-
Name: tagName,
51-
Message: message,
52-
}
53+
Name: strings.TrimPrefix(tagName, "refs/tags/"),
54+
Message: message,
55+
IsAnnotated: objectType == "tag",
56+
}, true
5357
})
5458

5559
return tags, nil

pkg/commands/git_commands/tag_loader_test.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ import (
99
"github.com/stretchr/testify/assert"
1010
)
1111

12-
const tagsOutput = `tag1 this is my message
13-
tag2
14-
tag3 this is my other message
15-
`
12+
const tagsOutput = "refs/tags/tag1\x00tag\x00this is my message\n" +
13+
"refs/tags/tag2\x00commit\x00\n" +
14+
"refs/tags/tag3\x00tag\x00this is my other message\n"
1615

1716
func TestGetTags(t *testing.T) {
1817
type scenario struct {
@@ -26,18 +25,18 @@ func TestGetTags(t *testing.T) {
2625
{
2726
testName: "should return no tags if there are none",
2827
runner: oscommands.NewFakeRunner(t).
29-
ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, "", nil),
28+
ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, "", nil),
3029
expectedTags: []*models.Tag{},
3130
expectedError: nil,
3231
},
3332
{
3433
testName: "should return tags if present",
3534
runner: oscommands.NewFakeRunner(t).
36-
ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, tagsOutput, nil),
35+
ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, tagsOutput, nil),
3736
expectedTags: []*models.Tag{
38-
{Name: "tag1", Message: "this is my message"},
39-
{Name: "tag2", Message: ""},
40-
{Name: "tag3", Message: "this is my other message"},
37+
{Name: "tag1", Message: "this is my message", IsAnnotated: true},
38+
{Name: "tag2", Message: "", IsAnnotated: false},
39+
{Name: "tag3", Message: "this is my other message", IsAnnotated: true},
4140
},
4241
expectedError: nil,
4342
},

pkg/commands/models/tag.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ package models
33
// Tag : A git tag
44
type Tag struct {
55
Name string
6+
67
// this is either the first line of the message of an annotated tag, or the
78
// first line of a commit message for a lightweight tag
89
Message string
10+
11+
// true if this is an annotated tag, false if it's a lightweight tag
12+
IsAnnotated bool
913
}
1014

1115
func (t *Tag) FullRefName() string {

pkg/gui/controllers/tags_controller.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package controllers
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/jesseduffield/gocui"
58
"github.com/jesseduffield/lazygit/pkg/commands/models"
69
"github.com/jesseduffield/lazygit/pkg/gui/context"
10+
"github.com/jesseduffield/lazygit/pkg/gui/style"
711
"github.com/jesseduffield/lazygit/pkg/gui/types"
812
"github.com/jesseduffield/lazygit/pkg/utils"
13+
"github.com/samber/lo"
914
)
1015

1116
type TagsController struct {
@@ -96,7 +101,8 @@ func (self *TagsController) GetOnRenderToMain() func() {
96101
task = types.NewRenderStringTask("No tags")
97102
} else {
98103
cmdObj := self.c.Git().Branch.GetGraphCmdObj(tag.FullRefName())
99-
task = types.NewRunCommandTask(cmdObj.GetCmd())
104+
prefix := self.getTagInfo(tag) + "\n\n---\n\n"
105+
task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
100106
}
101107

102108
self.c.RenderToMainViews(types.RefreshMainOpts{
@@ -110,6 +116,35 @@ func (self *TagsController) GetOnRenderToMain() func() {
110116
}
111117
}
112118

119+
func (self *TagsController) getTagInfo(tag *models.Tag) string {
120+
if tag.IsAnnotated {
121+
info := fmt.Sprintf("%s: %s", self.c.Tr.AnnotatedTag, style.AttrBold.Sprint(style.FgYellow.Sprint(tag.Name)))
122+
output, err := self.c.Git().Tag.ShowAnnotationInfo(tag.Name)
123+
if err == nil {
124+
info += "\n\n" + strings.TrimRight(filterOutPgpSignature(output), "\n")
125+
}
126+
return info
127+
}
128+
129+
return fmt.Sprintf("%s: %s", self.c.Tr.LightweightTag, style.AttrBold.Sprint(style.FgYellow.Sprint(tag.Name)))
130+
}
131+
132+
func filterOutPgpSignature(output string) string {
133+
lines := strings.Split(output, "\n")
134+
inPgpSignature := false
135+
filteredLines := lo.Filter(lines, func(line string, _ int) bool {
136+
if line == "-----END PGP SIGNATURE-----" {
137+
inPgpSignature = false
138+
return false
139+
}
140+
if line == "-----BEGIN PGP SIGNATURE-----" {
141+
inPgpSignature = true
142+
}
143+
return !inPgpSignature
144+
})
145+
return strings.Join(filteredLines, "\n")
146+
}
147+
113148
func (self *TagsController) checkout(tag *models.Tag) error {
114149
self.c.LogAction(self.c.Tr.Actions.CheckoutTag)
115150
if err := self.c.Helpers().Refs.CheckoutRef(tag.FullRefName(), types.CheckoutRefOptions{}); err != nil {

0 commit comments

Comments
 (0)