From 44159ff926c94c25490656a8d67eec6b978dad91 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 27 Jul 2025 15:59:31 +0200 Subject: [PATCH 1/3] Add tests for tag information rendering These should have been added when we started rendering this information in e5b09f34e0; apparently I was too lazy back then. Adding them now to guard against breaking it in the next commit. I'm adding these to the CRUD tests, it doesn't seem worth adding separate tests just for these assertions. --- pkg/integration/tests/tag/crud_annotated.go | 12 ++++++++++++ pkg/integration/tests/tag/crud_lightweight.go | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/pkg/integration/tests/tag/crud_annotated.go b/pkg/integration/tests/tag/crud_annotated.go index 12fa16645..d2c3b98c0 100644 --- a/pkg/integration/tests/tag/crud_annotated.go +++ b/pkg/integration/tests/tag/crud_annotated.go @@ -32,6 +32,18 @@ var CrudAnnotated = NewIntegrationTest(NewIntegrationTestArgs{ Lines( MatchesRegexp(`new-tag.*message`).IsSelected(), ). + Tap(func() { + t.Views().Main().ContainsLines( + Equals("Annotated tag: new-tag"), + Equals(""), + Contains("Tagger:"), + Contains("TaggerDate:"), + Equals(""), + Equals("message"), + Equals(""), + Equals("---"), + ) + }). Press(keys.Universal.Push). Tap(func() { t.ExpectPopup().Prompt(). diff --git a/pkg/integration/tests/tag/crud_lightweight.go b/pkg/integration/tests/tag/crud_lightweight.go index dd6614683..736205ecd 100644 --- a/pkg/integration/tests/tag/crud_lightweight.go +++ b/pkg/integration/tests/tag/crud_lightweight.go @@ -28,6 +28,13 @@ var CrudLightweight = NewIntegrationTest(NewIntegrationTestArgs{ Lines( MatchesRegexp(`new-tag.*initial commit`).IsSelected(), ). + Tap(func() { + t.Views().Main().ContainsLines( + Equals("Lightweight tag: new-tag"), + Equals(""), + Equals("---"), + ) + }). PressEnter(). Tap(func() { // view the commits of the tag From 151e80902ec1be653e3faf9f01338e88d276c756 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 27 Jul 2025 17:57:28 +0200 Subject: [PATCH 2/3] Use a different way to check if a tag is annotated Storing it in the Tag struct makes loading tags a lot slower when there is a very large number of tags; so determine it on the fly instead. On my machine, the additional call takes under 5ms, so it seems we can afford it. --- pkg/commands/git_commands/tag.go | 12 ++++++++++++ pkg/gui/controllers/tags_controller.go | 7 ++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go index d32f669b3..1e9b449b1 100644 --- a/pkg/commands/git_commands/tag.go +++ b/pkg/commands/git_commands/tag.go @@ -1,6 +1,8 @@ package git_commands import ( + "strings" + "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" ) @@ -74,3 +76,13 @@ func (self *TagCommands) ShowAnnotationInfo(tagName string) (string, error) { return self.cmd.New(cmdArgs).RunWithOutput() } + +func (self *TagCommands) IsTagAnnotated(tagName string) (bool, error) { + cmdArgs := NewGitCmd("cat-file"). + Arg("-t"). + Arg("refs/tags/" + tagName). + ToArgv() + + output, err := self.cmd.New(cmdArgs).RunWithOutput() + return strings.TrimSpace(output) == "tag", err +} diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go index 71561ed6e..c6fa60bd2 100644 --- a/pkg/gui/controllers/tags_controller.go +++ b/pkg/gui/controllers/tags_controller.go @@ -117,7 +117,12 @@ func (self *TagsController) GetOnRenderToMain() func() { } func (self *TagsController) getTagInfo(tag *models.Tag) string { - if tag.IsAnnotated { + tagIsAnnotated, err := self.c.Git().Tag.IsTagAnnotated(tag.Name) + if err != nil { + self.c.Log.Warnf("Error checking if tag is annotated: %v", err) + } + + if tagIsAnnotated { 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 { From 1c533dcd55e93dcc479f04d4793084153e50a337 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 27 Jul 2025 18:00:08 +0200 Subject: [PATCH 3/3] Revert "Add IsAnnotated field to models.Tag struct" It seems that `git for-each-ref` is a lot slower than `git tag --list` when there are thousands of tags, so revert back to the previous method, now that we no longer use the IsAnnotated field. This reverts commit b12b1040c3b248959e2a330560d339d617d11a2d. --- pkg/commands/git_commands/tag_loader.go | 32 +++++++++----------- pkg/commands/git_commands/tag_loader_test.go | 17 ++++++----- pkg/commands/models/tag.go | 4 --- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/pkg/commands/git_commands/tag_loader.go b/pkg/commands/git_commands/tag_loader.go index 1d51c5e5c..bd05fe4b4 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 ( - "strings" + "regexp" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" @@ -26,13 +26,9 @@ func NewTagLoader( } func (self *TagLoader) GetTags() ([]*models.Tag, error) { - // get tags, sorted by creation date (descending) + // get remote branches, sorted by creation date (descending) // see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt - cmdArgs := NewGitCmd("for-each-ref"). - Arg("--sort=-creatordate"). - Arg("--format=%(refname)%00%(objecttype)%00%(contents:subject)"). - Arg("refs/tags"). - ToArgv() + cmdArgs := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToArgv() tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() if err != nil { return nil, err @@ -40,20 +36,20 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) { split := utils.SplitLines(tagsOutput) - tags := lo.FilterMap(split, func(line string, _ int) (*models.Tag, bool) { - fields := strings.SplitN(line, "\x00", 3) - if len(fields) != 3 { - return nil, false + 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] } - tagName := fields[0] - objectType := fields[1] - message := fields[2] return &models.Tag{ - Name: strings.TrimPrefix(tagName, "refs/tags/"), - Message: message, - IsAnnotated: objectType == "tag", - }, true + Name: tagName, + Message: message, + } }) return tags, nil diff --git a/pkg/commands/git_commands/tag_loader_test.go b/pkg/commands/git_commands/tag_loader_test.go index a56aa34c7..e8f19fcd4 100644 --- a/pkg/commands/git_commands/tag_loader_test.go +++ b/pkg/commands/git_commands/tag_loader_test.go @@ -9,9 +9,10 @@ import ( "github.com/stretchr/testify/assert" ) -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" +const tagsOutput = `tag1 this is my message +tag2 +tag3 this is my other message +` func TestGetTags(t *testing.T) { type scenario struct { @@ -25,18 +26,18 @@ func TestGetTags(t *testing.T) { { testName: "should return no tags if there are none", runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, "", nil), + ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, "", nil), expectedTags: []*models.Tag{}, expectedError: nil, }, { testName: "should return tags if present", runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, tagsOutput, nil), + ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, tagsOutput, nil), expectedTags: []*models.Tag{ - {Name: "tag1", Message: "this is my message", IsAnnotated: true}, - {Name: "tag2", Message: "", IsAnnotated: false}, - {Name: "tag3", Message: "this is my other message", IsAnnotated: true}, + {Name: "tag1", Message: "this is my message"}, + {Name: "tag2", Message: ""}, + {Name: "tag3", Message: "this is my other message"}, }, expectedError: nil, }, diff --git a/pkg/commands/models/tag.go b/pkg/commands/models/tag.go index 389083c94..876e2cd77 100644 --- a/pkg/commands/models/tag.go +++ b/pkg/commands/models/tag.go @@ -3,13 +3,9 @@ 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 {