Skip to content

Commit d7eda0e

Browse files
committed
Fixes go-gitea#2738 - /git/tags API
1 parent 181b7c9 commit d7eda0e

File tree

10 files changed

+292
-32
lines changed

10 files changed

+292
-32
lines changed

models/repo_tag.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"code.gitea.io/gitea/modules/git"
99
)
1010

11-
// GetTagsByPath returns repo tags by it's path
11+
// GetTagsByPath returns repo tags by its path
1212
func GetTagsByPath(path string) ([]*git.Tag, error) {
1313
gitRepo, err := git.OpenRepository(path)
1414
if err != nil {

modules/git/repo_ref.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
3131
if err = refsIter.ForEach(func(ref *plumbing.Reference) error {
3232
if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() &&
3333
(pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) {
34+
refType := string(ObjectCommit)
35+
if ref.Name().IsTag() {
36+
// tags can be of type `commit` (lightweight) or `tag` (annotated)
37+
if tagType, _ := repo.GetTagType(SHA1(ref.Hash())); err == nil {
38+
refType = tagType
39+
}
40+
}
3441
r := &Reference{
3542
Name: ref.Name().String(),
3643
Object: SHA1(ref.Hash()),
37-
Type: string(ObjectCommit),
44+
Type: refType,
3845
repo: repo,
3946
}
40-
if ref.Name().IsTag() {
41-
r.Type = string(ObjectTag)
42-
}
4347
refs = append(refs, r)
4448
}
4549
return nil

modules/git/repo_tag.go

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package git
77

88
import (
9+
"fmt"
910
"strings"
1011

1112
"github.com/mcuadros/go-version"
@@ -42,27 +43,52 @@ func (repo *Repository) getTag(id SHA1) (*Tag, error) {
4243
return t.(*Tag), nil
4344
}
4445

45-
// Get tag type
46-
tp, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
46+
// Get tag name
47+
name, err := repo.GetTagNameBySHA(id.String())
4748
if err != nil {
4849
return nil, err
4950
}
50-
tp = strings.TrimSpace(tp)
5151

52-
// Tag is a commit.
52+
tp, err := repo.GetTagType(id)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
// The id passed in could be a tag object ID or a commit ID, so getting both
58+
tagID := id
59+
if tagIDStr, _ := repo.GetTagID(name); tagIDStr != "" {
60+
if tID, err := NewIDFromString(tagIDStr); err == nil {
61+
tagID = tID
62+
}
63+
}
64+
commitID := id
65+
if commitIDStr, _ := repo.GetTagCommitID(name); commitIDStr != "" {
66+
if cID, err := NewIDFromString(commitIDStr); err == nil {
67+
commitID = cID
68+
}
69+
}
70+
71+
// If type is "commit, the tag is a lightweight tag
5372
if ObjectType(tp) == ObjectCommit {
73+
commit, err := repo.GetCommit(id.String())
74+
if err != nil {
75+
return nil, err
76+
}
5477
tag := &Tag{
55-
ID: id,
56-
Object: id,
78+
Name: name,
79+
ID: tagID,
80+
Object: commitID,
5781
Type: string(ObjectCommit),
82+
Tagger: commit.Committer,
83+
Message: commit.Message(),
5884
repo: repo,
5985
}
6086

6187
repo.tagCache.Set(id.String(), tag)
6288
return tag, nil
6389
}
6490

65-
// Tag with message.
91+
// The tag is an annotated tag with a message.
6692
data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
6793
if err != nil {
6894
return nil, err
@@ -73,13 +99,53 @@ func (repo *Repository) getTag(id SHA1) (*Tag, error) {
7399
return nil, err
74100
}
75101

102+
tag.Name = name
76103
tag.ID = id
77104
tag.repo = repo
78105

79106
repo.tagCache.Set(id.String(), tag)
80107
return tag, nil
81108
}
82109

110+
// GetTagNameBySHA returns the name of a tag from its tag object SHA or commit SHA
111+
func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
112+
if len(sha) < 5 {
113+
return "", fmt.Errorf("SHA is too short: %s", sha)
114+
}
115+
116+
stdout, err := NewCommand("show-ref", "--tags", "-d").RunInDir(repo.Path)
117+
if err != nil {
118+
return "", err
119+
}
120+
121+
tagRefs := strings.Split(stdout, "\n")
122+
for _, tagRef := range tagRefs {
123+
if len(strings.TrimSpace(tagRef)) > 0 {
124+
fields := strings.Fields(tagRef)
125+
if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], "refs/tags/") {
126+
name := strings.Split(fields[1], "/")[2]
127+
// annotated tags show up twice, their name for commit ID is suffixed with ^{}
128+
name = strings.TrimSuffix(name, "^{}")
129+
return name, nil
130+
}
131+
}
132+
}
133+
return "", ErrNotExist{ID: sha}
134+
}
135+
136+
// GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
137+
func (repo *Repository) GetTagID(name string) (string, error) {
138+
stdout, err := NewCommand("show-ref", name).RunInDir(repo.Path)
139+
if err != nil {
140+
return "", err
141+
}
142+
fields := strings.Fields(stdout)
143+
if len(fields) != 2 {
144+
return "", ErrNotExist{ID: name}
145+
}
146+
return fields[0], nil
147+
}
148+
83149
// GetTag returns a Git tag by given name.
84150
func (repo *Repository) GetTag(name string) (*Tag, error) {
85151
idStr, err := repo.GetTagCommitID(name)
@@ -96,7 +162,6 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
96162
if err != nil {
97163
return nil, err
98164
}
99-
tag.Name = name
100165
return tag, nil
101166
}
102167

@@ -150,3 +215,38 @@ func (repo *Repository) GetTags() ([]string, error) {
150215

151216
return tagNames, nil
152217
}
218+
219+
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
220+
func (repo *Repository) GetTagType(id SHA1) (string, error) {
221+
// Get tag type
222+
stdout, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
223+
if err != nil {
224+
return "", err
225+
}
226+
if len(stdout) == 0 {
227+
return "", ErrNotExist{ID: id.String()}
228+
}
229+
return strings.TrimSpace(stdout), nil
230+
}
231+
232+
// GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
233+
func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
234+
id, err := NewIDFromString(sha)
235+
if err != nil {
236+
return nil, err
237+
}
238+
239+
// Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
240+
if tagType, err := repo.GetTagType(id); err != nil {
241+
return nil, err
242+
} else if ObjectType(tagType) != ObjectTag {
243+
// not an annotated tag
244+
return nil, ErrNotExist{ID: id.String()}
245+
}
246+
247+
tag, err := repo.getTag(id)
248+
if err != nil {
249+
return nil, err
250+
}
251+
return tag, nil
252+
}

modules/structs/repo_tag.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,32 @@
44

55
package structs
66

7-
// Tag represents a repository tag
7+
// Tag represents a repository tag for the /tags API endpoint
88
type Tag struct {
99
Name string `json:"name"`
10+
ID string `json:"id"`
1011
Commit struct {
1112
SHA string `json:"sha"`
1213
URL string `json:"url"`
1314
} `json:"commit"`
1415
ZipballURL string `json:"zipball_url"`
1516
TarballURL string `json:"tarball_url"`
1617
}
18+
19+
// GitTag represents a git tag for the /git/tags API endpoint
20+
type GitTag struct {
21+
Tag string `json:"tag"`
22+
SHA string `json:"sha"`
23+
URL string `json:"url"`
24+
Message string `json:"message"`
25+
Tagger *CommitUser `json:"tagger"`
26+
Object *GitTagObject `json:"object"`
27+
Verification *PayloadCommitVerification `json:"verification"`
28+
}
29+
30+
// GitTagObject contains meta information of the tag object
31+
type GitTagObject struct {
32+
Type string `json:"type"`
33+
URL string `json:"url"`
34+
SHA string `json:"sha"`
35+
}

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ func RegisterRoutes(m *macaron.Macaron) {
761761
m.Get("/refs/*", repo.GetGitRefs)
762762
m.Get("/trees/:sha", context.RepoRef(), repo.GetTree)
763763
m.Get("/blobs/:sha", context.RepoRef(), repo.GetBlob)
764+
m.Get("/tags/:sha", context.RepoRef(), repo.GetTag)
764765
}, reqRepoReader(models.UnitTypeCode))
765766
m.Group("/contents", func() {
766767
m.Get("/*", repo.GetFileContents)

routers/api/v1/convert/convert.go

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package convert
66

77
import (
88
"fmt"
9+
"time"
910

1011
"code.gitea.io/gitea/models"
1112
"code.gitea.io/gitea/modules/git"
@@ -38,6 +39,7 @@ func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch
3839
func ToTag(repo *models.Repository, t *git.Tag) *api.Tag {
3940
return &api.Tag{
4041
Name: t.Name,
42+
ID: t.ID.String(),
4143
Commit: struct {
4244
SHA string `json:"sha"`
4345
URL string `json:"url"`
@@ -66,13 +68,6 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
6668
log.Error("GetUserByEmail: %v", err)
6769
}
6870

69-
verif := models.ParseCommitWithSignature(c)
70-
var signature, payload string
71-
if c.Signature != nil {
72-
signature = c.Signature.Signature
73-
payload = c.Signature.Payload
74-
}
75-
7671
return &api.PayloadCommit{
7772
ID: c.ID.String(),
7873
Message: c.Message(),
@@ -87,13 +82,23 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
8782
Email: c.Committer.Email,
8883
UserName: committerUsername,
8984
},
90-
Timestamp: c.Author.When,
91-
Verification: &api.PayloadCommitVerification{
92-
Verified: verif.Verified,
93-
Reason: verif.Reason,
94-
Signature: signature,
95-
Payload: payload,
96-
},
85+
Timestamp: c.Author.When,
86+
Verification: ToVerification(c),
87+
}
88+
}
89+
90+
func ToVerification(c *git.Commit) *api.PayloadCommitVerification {
91+
verif := models.ParseCommitWithSignature(c)
92+
var signature, payload string
93+
if c.Signature != nil {
94+
signature = c.Signature.Signature
95+
payload = c.Signature.Payload
96+
}
97+
return &api.PayloadCommitVerification{
98+
Verified: verif.Verified,
99+
Reason: verif.Reason,
100+
Signature: signature,
101+
Payload: payload,
97102
}
98103
}
99104

@@ -241,3 +246,33 @@ func ToUser(user *models.User, signed, admin bool) *api.User {
241246
}
242247
return result
243248
}
249+
250+
// ToGitTag convert git.Tag to api.GitTag
251+
func ToGitTag(repo *models.Repository, t *git.Tag, c *git.Commit) *api.GitTag {
252+
return &api.GitTag{
253+
Tag: t.Name,
254+
SHA: t.ID.String(),
255+
Object: ToGitTagObject(repo, c),
256+
Message: t.Message,
257+
Tagger: ToCommitUser(t.Tagger),
258+
Verification: ToVerification(c),
259+
}
260+
}
261+
262+
func ToGitTagObject(repo *models.Repository, commit *git.Commit) *api.GitTagObject {
263+
return &api.GitTagObject{
264+
SHA: commit.ID.String(),
265+
Type: string(git.ObjectCommit),
266+
URL: util.URLJoin(repo.Link(), "git/commits", commit.ID.String()),
267+
}
268+
}
269+
270+
func ToCommitUser(sig *git.Signature) *api.CommitUser {
271+
return &api.CommitUser{
272+
Identity: api.Identity{
273+
Name: sig.Name,
274+
Email: sig.Email,
275+
},
276+
Date: sig.When.UTC().Format(time.RFC3339),
277+
}
278+
}

routers/api/v1/repo/git_ref.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,7 @@ func getGitRefsInternal(ctx *context.APIContext, filter string) {
100100
Object: &api.GitObject{
101101
SHA: refs[i].Object.String(),
102102
Type: refs[i].Type,
103-
// TODO: Add commit/tag info URL
104-
//URL: ctx.Repo.Repository.APIURL() + "/git/" + refs[i].Type + "s/" + refs[i].Object.String(),
103+
URL: ctx.Repo.Repository.APIURL() + "/git/" + refs[i].Type + "s/" + refs[i].Object.String(),
105104
},
106105
}
107106
}

0 commit comments

Comments
 (0)