Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions pkg/commands/git_commands/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <tagger email>
// 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()
}
32 changes: 18 additions & 14 deletions pkg/commands/git_commands/tag_loader.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package git_commands

import (
"regexp"
"strings"

"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
Expand All @@ -26,30 +26,34 @@ 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
}

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
Expand Down
17 changes: 8 additions & 9 deletions pkg/commands/git_commands/tag_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
},
Expand Down
4 changes: 4 additions & 0 deletions pkg/commands/models/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 36 additions & 1 deletion pkg/gui/controllers/tags_controller.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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{
Expand All @@ -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 {
Expand Down