1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-08-06 22:33:07 +02:00

Fix performance regression on startup in repos with many tags (#4777)

- **PR Description**

In #4663 we added information in the tags panel about the selected tag
(whether it's annotated etc). Unfortunately this introduced a
performance regression in repositories with many tags, so revert this
and implement the feature in a slightly different way to avoid the
performance hit.

Fixes #4770.
This commit is contained in:
Stefan Haller
2025-07-28 12:26:53 +02:00
committed by GitHub
7 changed files with 60 additions and 31 deletions

View File

@ -1,6 +1,8 @@
package git_commands package git_commands
import ( import (
"strings"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "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() 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
}

View File

@ -1,7 +1,7 @@
package git_commands package git_commands
import ( import (
"strings" "regexp"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@ -26,13 +26,9 @@ func NewTagLoader(
} }
func (self *TagLoader) GetTags() ([]*models.Tag, error) { 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 // see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
cmdArgs := NewGitCmd("for-each-ref"). cmdArgs := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToArgv()
Arg("--sort=-creatordate").
Arg("--format=%(refname)%00%(objecttype)%00%(contents:subject)").
Arg("refs/tags").
ToArgv()
tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil { if err != nil {
return nil, err return nil, err
@ -40,20 +36,20 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) {
split := utils.SplitLines(tagsOutput) split := utils.SplitLines(tagsOutput)
tags := lo.FilterMap(split, func(line string, _ int) (*models.Tag, bool) { lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`)
fields := strings.SplitN(line, "\x00", 3)
if len(fields) != 3 { tags := lo.Map(split, func(line string, _ int) *models.Tag {
return nil, false 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{ return &models.Tag{
Name: strings.TrimPrefix(tagName, "refs/tags/"), Name: tagName,
Message: message, Message: message,
IsAnnotated: objectType == "tag", }
}, true
}) })
return tags, nil return tags, nil

View File

@ -9,9 +9,10 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
const tagsOutput = "refs/tags/tag1\x00tag\x00this is my message\n" + const tagsOutput = `tag1 this is my message
"refs/tags/tag2\x00commit\x00\n" + tag2
"refs/tags/tag3\x00tag\x00this is my other message\n" tag3 this is my other message
`
func TestGetTags(t *testing.T) { func TestGetTags(t *testing.T) {
type scenario struct { type scenario struct {
@ -25,18 +26,18 @@ func TestGetTags(t *testing.T) {
{ {
testName: "should return no tags if there are none", testName: "should return no tags if there are none",
runner: oscommands.NewFakeRunner(t). 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{}, expectedTags: []*models.Tag{},
expectedError: nil, expectedError: nil,
}, },
{ {
testName: "should return tags if present", testName: "should return tags if present",
runner: oscommands.NewFakeRunner(t). 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{ expectedTags: []*models.Tag{
{Name: "tag1", Message: "this is my message", IsAnnotated: true}, {Name: "tag1", Message: "this is my message"},
{Name: "tag2", Message: "", IsAnnotated: false}, {Name: "tag2", Message: ""},
{Name: "tag3", Message: "this is my other message", IsAnnotated: true}, {Name: "tag3", Message: "this is my other message"},
}, },
expectedError: nil, expectedError: nil,
}, },

View File

@ -3,13 +3,9 @@ package models
// Tag : A git tag // Tag : A git tag
type Tag struct { type Tag struct {
Name string Name string
// this is either the first line of the message of an annotated tag, or the // 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 // first line of a commit message for a lightweight tag
Message string Message string
// true if this is an annotated tag, false if it's a lightweight tag
IsAnnotated bool
} }
func (t *Tag) FullRefName() string { func (t *Tag) FullRefName() string {

View File

@ -117,7 +117,12 @@ func (self *TagsController) GetOnRenderToMain() func() {
} }
func (self *TagsController) getTagInfo(tag *models.Tag) string { 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))) 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) output, err := self.c.Git().Tag.ShowAnnotationInfo(tag.Name)
if err == nil { if err == nil {

View File

@ -32,6 +32,18 @@ var CrudAnnotated = NewIntegrationTest(NewIntegrationTestArgs{
Lines( Lines(
MatchesRegexp(`new-tag.*message`).IsSelected(), 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). Press(keys.Universal.Push).
Tap(func() { Tap(func() {
t.ExpectPopup().Prompt(). t.ExpectPopup().Prompt().

View File

@ -28,6 +28,13 @@ var CrudLightweight = NewIntegrationTest(NewIntegrationTestArgs{
Lines( Lines(
MatchesRegexp(`new-tag.*initial commit`).IsSelected(), MatchesRegexp(`new-tag.*initial commit`).IsSelected(),
). ).
Tap(func() {
t.Views().Main().ContainsLines(
Equals("Lightweight tag: new-tag"),
Equals(""),
Equals("---"),
)
}).
PressEnter(). PressEnter().
Tap(func() { Tap(func() {
// view the commits of the tag // view the commits of the tag