From fbb668ef1126b3a9d2b5743e24ad92e31fed402b Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Thu, 17 Aug 2023 16:11:42 +0530 Subject: [PATCH] git: add support for lightweight tags Add support for lightweight tags by checking the presence of a tag reference and a tag object. Modify the cloning logic to always attach a tag object to a commit object if checking out via a tag. Signed-off-by: Sanskar Jaiswal --- git/git.go | 27 +++++++++++++++++++++------ git/gogit/clone.go | 12 +++++++----- git/gogit/clone_test.go | 30 +++++++++++++++++------------- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/git/git.go b/git/git.go index aa2ae60e..7d551480 100644 --- a/git/git.go +++ b/git/git.go @@ -88,7 +88,7 @@ type Commit struct { // Message is the commit message, containing arbitrary text. Message string // ReferencingTag is the parent tag, that points to this commit. - ReferencingTag *AnnotatedTag + ReferencingTag *Tag } // String returns a string representation of the Commit, composed @@ -136,8 +136,8 @@ func (c *Commit) ShortMessage() string { return subject } -// AnnotatedTag represents an annotated Git tag. -type AnnotatedTag struct { +// Tag represents a Git tag. +type Tag struct { // Hash is the hash of the tag. Hash Hash // Name is the name of the tag. @@ -155,7 +155,7 @@ type AnnotatedTag struct { // Verify the Signature of the tag with the given key rings. // It returns the fingerprint of the key the signature was verified // with, or an error. -func (t *AnnotatedTag) Verify(keyRings ...string) (string, error) { +func (t *Tag) Verify(keyRings ...string) (string, error) { fingerprint, err := verifySignature(t.Signature, t.Encoded, keyRings...) if err != nil { return "", fmt.Errorf("unable to verify Git tag: %w", err) @@ -164,8 +164,13 @@ func (t *AnnotatedTag) Verify(keyRings ...string) (string, error) { } // String returns a short string representation of the tag in the format -// of , for eg: <1.0.0@a0c14dc8580a23f79bc654faa79c4f62b46c2c22> -func (t *AnnotatedTag) String() string { +// of , for eg: "1.0.0@a0c14dc8580a23f79bc654faa79c4f62b46c2c22" +// If the tag is lightweight, it won't have a hash, so it'll simply return +// the tag name, i.e. "1.0.0". +func (t *Tag) String() string { + if len(t.Hash) == 0 { + return t.Name + } return fmt.Sprintf("%s@%s", t.Name, t.Hash.String()) } @@ -195,6 +200,16 @@ func IsConcreteCommit(c Commit) bool { return false } +// IsAnnotatedTag returns true if the provided tag is annotated. +func IsAnnotatedTag(t Tag) bool { + return len(t.Encoded) > 0 +} + +// IsSignedTag returns true if the provided tag has a signature. +func IsSignedTag(t Tag) bool { + return t.Signature != "" +} + func verifySignature(sig string, payload []byte, keyRings ...string) (string, error) { if sig == "" { return "", fmt.Errorf("unable to verify payload as the provided signature is empty") diff --git a/git/gogit/clone.go b/git/gogit/clone.go index 3c4dc944..7c2db802 100644 --- a/git/gogit/clone.go +++ b/git/gogit/clone.go @@ -535,9 +535,11 @@ func buildSignature(s object.Signature) git.Signature { } } -func buildTag(t *object.Tag) (*git.AnnotatedTag, error) { +func buildTag(t *object.Tag, ref plumbing.ReferenceName) (*git.Tag, error) { if t == nil { - return nil, fmt.Errorf("unable to contruct tag: no object") + return &git.Tag{ + Name: ref.Short(), + }, nil } encoded := &plumbing.MemoryObject{} @@ -553,7 +555,7 @@ func buildTag(t *object.Tag) (*git.AnnotatedTag, error) { return nil, fmt.Errorf("unable to read encoded tag '%s': %w", t.Name, err) } - return &git.AnnotatedTag{ + return &git.Tag{ Hash: []byte(t.Hash.String()), Name: t.Name, Author: buildSignature(t.Tagger), @@ -591,8 +593,8 @@ func buildCommitWithRef(c *object.Commit, t *object.Tag, ref plumbing.ReferenceN Message: c.Message, } - if t != nil { - tt, err := buildTag(t) + if ref.IsTag() { + tt, err := buildTag(t, ref) if err != nil { return nil, err } diff --git a/git/gogit/clone_test.go b/git/gogit/clone_test.go index ac1e644f..bf6d71f2 100644 --- a/git/gogit/clone_test.go +++ b/git/gogit/clone_test.go @@ -290,22 +290,24 @@ func TestClone_cloneTag(t *testing.T) { return } - // Check if commit has a parent if the tag was annotated. - for _, tagInRepo := range tt.tagsInRepo { - if tagInRepo.annotated { - g.Expect(cc.ReferencingTag).ToNot(BeNil()) - g.Expect(cc.ReferencingTag.Message).To(Equal(fmt.Sprintf("Annotated tag for: %s\n", tagInRepo.name))) - } else { - g.Expect(cc.ReferencingTag).To(BeNil()) - } - } - // Check successful checkout results. g.Expect(git.IsConcreteCommit(*cc)).To(Equal(tt.expectConcreteCommit)) targetTagHash := tagCommits[tt.checkoutTag] g.Expect(err).ToNot(HaveOccurred()) g.Expect(cc.String()).To(Equal(tt.checkoutTag + "@" + git.HashTypeSHA1 + ":" + targetTagHash)) + if tt.expectConcreteCommit { + g.Expect(cc.ReferencingTag).ToNot(BeNil()) + for _, tagInRepo := range tt.tagsInRepo { + if tagInRepo.annotated { + g.Expect(git.IsAnnotatedTag(*cc.ReferencingTag)).To(BeTrue()) + g.Expect(cc.ReferencingTag.Message).To(Equal(fmt.Sprintf("Annotated tag for: %s\n", tagInRepo.name))) + } else { + g.Expect(git.IsAnnotatedTag(*cc.ReferencingTag)).To(BeFalse()) + } + } + } + // Check file content only when there's an actual checkout. if tt.lastRevTag != tt.checkoutTag { g.Expect(filepath.Join(tmpDir, "tag")).To(BeARegularFile()) @@ -505,11 +507,12 @@ func TestClone_cloneSemVer(t *testing.T) { g.Expect(cc.String()).To(Equal(tt.expectTag + "@" + git.HashTypeSHA1 + ":" + refs[tt.expectTag])) g.Expect(filepath.Join(tmpDir, "tag")).To(BeARegularFile()) g.Expect(os.ReadFile(filepath.Join(tmpDir, "tag"))).To(BeEquivalentTo(tt.expectTag)) + g.Expect(cc.ReferencingTag).ToNot(BeNil()) if tt.annotated { - g.Expect(cc.ReferencingTag).ToNot(BeNil()) + g.Expect(git.IsAnnotatedTag(*cc.ReferencingTag)).To(BeTrue()) g.Expect(cc.ReferencingTag.Message).To(Equal(fmt.Sprintf("Annotated tag for: %s\n", tt.expectTag))) } else { - g.Expect(cc.ReferencingTag).To(BeNil()) + g.Expect(git.IsAnnotatedTag(*cc.ReferencingTag)).To(BeFalse()) } }) } @@ -653,9 +656,10 @@ func TestClone_cloneRefName(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) g.Expect(cc.AbsoluteReference()).To(Equal(tt.refName + "@" + git.HashTypeSHA1 + ":" + tt.expectedCommit)) g.Expect(git.IsConcreteCommit(*cc)).To(Equal(tt.expectedConcreteCommit)) - if strings.Contains(tt.refName, "tags") && !strings.HasSuffix(tt.refName, tagDereferenceSuffix) { + if tt.expectedConcreteCommit && strings.Contains(tt.refName, "tags") && !strings.HasSuffix(tt.refName, tagDereferenceSuffix) { g.Expect(cc.ReferencingTag).ToNot(BeNil()) g.Expect(cc.ReferencingTag.Message).To(ContainSubstring("Annotated tag for")) + g.Expect(git.IsAnnotatedTag(*cc.ReferencingTag)).To(BeTrue()) } for k, v := range tt.filesCreated {