Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2738 - Adds the /git/tags API endpoint #7138

Merged
merged 27 commits into from
Jun 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d7eda0e
Fixes #2738 - /git/tags API
richmahn Jun 5, 2019
0fc1605
Merge remote-tracking branch 'upstream/master' into fix-2738-tag-api
richmahn Jun 5, 2019
4b84785
proper URLs
richmahn Jun 5, 2019
ab588c7
Adds function comments
richmahn Jun 5, 2019
716e76c
Updates swagger
richmahn Jun 5, 2019
861df65
Removes newline from tag message
richmahn Jun 5, 2019
157d6b6
Removes trailing newline from commit message
richmahn Jun 5, 2019
41bb6aa
Adds integration test
richmahn Jun 6, 2019
fae950f
Removed debugging
richmahn Jun 6, 2019
7013cce
Merge remote-tracking branch 'upstream/master' into fix-2738-tag-api
richmahn Jun 6, 2019
a5208ca
Adds tests
richmahn Jun 6, 2019
171ea70
Fixes bug where multiple tags of same commit show wrong tag name
richmahn Jun 6, 2019
ad3f04c
Fix formatting
richmahn Jun 6, 2019
582c6cc
Removes unused varaible
richmahn Jun 6, 2019
9c52751
Fix to annotated tag function names and response
richmahn Jun 6, 2019
3e7633b
Merge remote-tracking branch 'upstream/master' into fix-2738-tag-api
richmahn Jun 6, 2019
1df581d
Update modules/git/repo_tag.go
richmahn Jun 6, 2019
6547bfc
Uses TagPrefix
richmahn Jun 6, 2019
90e5315
Uses TagPrefix
richmahn Jun 6, 2019
1f8c122
Merge remote-tracking branch 'upstream/master' into fix-2738-tag-api
richmahn Jun 8, 2019
dcce6bf
Changes per review, better error handling for getting tag and commit IDs
richmahn Jun 8, 2019
9bc4e19
Fix to getting commit ID
richmahn Jun 8, 2019
49e4349
Fix to getting commit ID
richmahn Jun 8, 2019
8897b75
Fix to getting commit ID
richmahn Jun 8, 2019
0321844
Fix to getting commit ID
richmahn Jun 8, 2019
b23c6d0
Merge remote-tracking branch 'upstream/master' into fix-2738-tag-api
richmahn Jun 8, 2019
0aeec96
Merge branch 'master' into fix-2738-tag-api
lunny Jun 8, 2019
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
4 changes: 2 additions & 2 deletions integrations/api_repo_file_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ func TestAPICreateFile(t *testing.T) {
var fileResponse api.FileResponse
DecodeJSON(t, resp, &fileResponse)
expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/new/file%d.txt", fileID)
expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID)
expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/blob/new_branch/new/file%d.txt", fileID)
richmahn marked this conversation as resolved.
Show resolved Hide resolved
expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID)
assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA)
assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL)
assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL)
Expand Down
8 changes: 4 additions & 4 deletions integrations/api_repo_file_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ func TestAPIUpdateFile(t *testing.T) {
var fileResponse api.FileResponse
DecodeJSON(t, resp, &fileResponse)
expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136"
expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/update/file%d.txt", fileID)
expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID)
expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/blob/new_branch/update/file%d.txt", fileID)
richmahn marked this conversation as resolved.
Show resolved Hide resolved
expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID)
assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA)
assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL)
assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL)
Expand All @@ -155,8 +155,8 @@ func TestAPIUpdateFile(t *testing.T) {
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &fileResponse)
expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136"
expectedHTMLURL = fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/master/rename/update/file%d.txt", fileID)
expectedDownloadURL = fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID)
expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/blob/master/rename/update/file%d.txt", fileID)
expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID)
assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA)
assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL)
assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL)
Expand Down
59 changes: 59 additions & 0 deletions integrations/api_repo_git_tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
"net/http"
"testing"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"

"github.com/stretchr/testify/assert"
)

func TestAPIGitTags(t *testing.T) {
prepareTestEnv(t)
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
// Login as User2.
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session)

// Set up git config for the tagger
git.NewCommand("config", "user.name", user.Name).RunInDir(repo.RepoPath())
git.NewCommand("config", "user.email", user.Email).RunInDir(repo.RepoPath())

gitRepo, _ := git.OpenRepository(repo.RepoPath())
commit, _ := gitRepo.GetBranchCommit("master")
lTagName := "lightweightTag"
gitRepo.CreateTag(lTagName, commit.ID.String())

aTagName := "annotatedTag"
aTagMessage := "my annotated message"
gitRepo.CreateAnnotatedTag(aTagName, aTagMessage, commit.ID.String())
aTag, _ := gitRepo.GetTag(aTagName)

// SHOULD work for annotated tags
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s?token=%s", user.Name, repo.Name, aTag.ID.String(), token)
res := session.MakeRequest(t, req, http.StatusOK)

var tag *api.AnnotatedTag
DecodeJSON(t, res, &tag)

assert.Equal(t, aTagName, tag.Tag)
assert.Equal(t, aTag.ID.String(), tag.SHA)
assert.Equal(t, commit.ID.String(), tag.Object.SHA)
assert.Equal(t, aTagMessage, tag.Message)
assert.Equal(t, user.Name, tag.Tagger.Name)
assert.Equal(t, user.Email, tag.Tagger.Email)
assert.Equal(t, util.URLJoin(repo.APIURL(), "git/tags", aTag.ID.String()), tag.URL)

// Should NOT work for lightweight tags
badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s?token=%s", user.Name, repo.Name, commit.ID.String(), token)
session.MakeRequest(t, badReq, http.StatusBadRequest)
}
7 changes: 3 additions & 4 deletions integrations/api_repo_tags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package integrations

import (
"net/http"
"path"
"testing"

"code.gitea.io/gitea/models"
Expand All @@ -32,7 +31,7 @@ func TestAPIReposGetTags(t *testing.T) {
assert.EqualValues(t, 1, len(tags))
assert.Equal(t, "v1.1", tags[0].Name)
assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA)
assert.Equal(t, path.Join(setting.AppSubURL, "/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d"), tags[0].Commit.URL)
assert.Equal(t, path.Join(setting.AppSubURL, "/user2/repo1/archive/v1.1.zip"), tags[0].ZipballURL)
assert.Equal(t, path.Join(setting.AppSubURL, "/user2/repo1/archive/v1.1.tar.gz"), tags[0].TarballURL)
assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.URL)
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL)
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL)
}
2 changes: 1 addition & 1 deletion models/repo_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"code.gitea.io/gitea/modules/git"
)

// GetTagsByPath returns repo tags by it's path
// GetTagsByPath returns repo tags by its path
func GetTagsByPath(path string) ([]*git.Tag, error) {
gitRepo, err := git.OpenRepository(path)
if err != nil {
Expand Down
12 changes: 8 additions & 4 deletions modules/git/repo_ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,19 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
if err = refsIter.ForEach(func(ref *plumbing.Reference) error {
if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() &&
(pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) {
refType := string(ObjectCommit)
if ref.Name().IsTag() {
// tags can be of type `commit` (lightweight) or `tag` (annotated)
if tagType, _ := repo.GetTagType(SHA1(ref.Hash())); err == nil {
refType = tagType
}
}
r := &Reference{
Name: ref.Name().String(),
Object: SHA1(ref.Hash()),
Type: string(ObjectCommit),
Type: refType,
repo: repo,
}
if ref.Name().IsTag() {
r.Type = string(ObjectTag)
}
refs = append(refs, r)
}
return nil
Expand Down
147 changes: 134 additions & 13 deletions modules/git/repo_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package git

import (
"fmt"
"strings"

"github.com/mcuadros/go-version"
Expand Down Expand Up @@ -35,34 +36,78 @@ func (repo *Repository) CreateTag(name, revision string) error {
return err
}

// CreateAnnotatedTag create one annotated tag in the repository
func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
_, err := NewCommand("tag", "-a", "-m", message, name, revision).RunInDir(repo.Path)
return err
}

func (repo *Repository) getTag(id SHA1) (*Tag, error) {
t, ok := repo.tagCache.Get(id.String())
if ok {
log("Hit cache: %s", id)
return t.(*Tag), nil
tagClone := *t.(*Tag)
richmahn marked this conversation as resolved.
Show resolved Hide resolved
return &tagClone, nil
}

// Get tag type
tp, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
// Get tag name
name, err := repo.GetTagNameBySHA(id.String())
if err != nil {
return nil, err
}

tp, err := repo.GetTagType(id)
if err != nil {
return nil, err
}
tp = strings.TrimSpace(tp)

// Tag is a commit.
// Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object
commitIDStr, err := repo.GetTagCommitID(name)
if err != nil {
// every tag should have a commit ID so return all errors
return nil, err
}
commitID, err := NewIDFromString(commitIDStr)
if err != nil {
return nil, err
}

// tagID defaults to the commit ID as the tag ID and then tries to get a tag ID (only annotated tags)
tagID := commitID
if tagIDStr, err := repo.GetTagID(name); err != nil {
// if the err is NotExist then we can ignore and just keep tagID as ID (is lightweight tag)
// all other errors we return
if !IsErrNotExist(err) {
return nil, err
}
} else {
tagID, err = NewIDFromString(tagIDStr)
if err != nil {
return nil, err
}
}

// If type is "commit, the tag is a lightweight tag
if ObjectType(tp) == ObjectCommit {
commit, err := repo.GetCommit(id.String())
if err != nil {
return nil, err
}
tag := &Tag{
ID: id,
Object: id,
Type: string(ObjectCommit),
repo: repo,
Name: name,
ID: tagID,
Object: commitID,
Type: string(ObjectCommit),
Tagger: commit.Committer,
Message: commit.Message(),
repo: repo,
}

repo.tagCache.Set(id.String(), tag)
return tag, nil
}

// Tag with message.
// The tag is an annotated tag with a message.
data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
if err != nil {
return nil, err
Expand All @@ -73,16 +118,57 @@ func (repo *Repository) getTag(id SHA1) (*Tag, error) {
return nil, err
}

tag.Name = name
tag.ID = id
tag.repo = repo
tag.Type = tp

repo.tagCache.Set(id.String(), tag)
return tag, nil
}

// GetTagNameBySHA returns the name of a tag from its tag object SHA or commit SHA
func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
if len(sha) < 5 {
return "", fmt.Errorf("SHA is too short: %s", sha)
}

stdout, err := NewCommand("show-ref", "--tags", "-d").RunInDir(repo.Path)
if err != nil {
return "", err
}

tagRefs := strings.Split(stdout, "\n")
for _, tagRef := range tagRefs {
if len(strings.TrimSpace(tagRef)) > 0 {
fields := strings.Fields(tagRef)
if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], TagPrefix) {
name := fields[1][len(TagPrefix):]
// annotated tags show up twice, their name for commit ID is suffixed with ^{}
name = strings.TrimSuffix(name, "^{}")
return name, nil
}
}
}
return "", ErrNotExist{ID: sha}
}

// GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
func (repo *Repository) GetTagID(name string) (string, error) {
stdout, err := NewCommand("show-ref", name).RunInDir(repo.Path)
if err != nil {
return "", err
}
fields := strings.Fields(stdout)
if len(fields) != 2 {
return "", ErrNotExist{ID: name}
}
return fields[0], nil
}

// GetTag returns a Git tag by given name.
func (repo *Repository) GetTag(name string) (*Tag, error) {
idStr, err := repo.GetTagCommitID(name)
idStr, err := repo.GetTagID(name)
if err != nil {
return nil, err
}
Expand All @@ -96,7 +182,6 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
if err != nil {
return nil, err
}
tag.Name = name
return tag, nil
}

Expand All @@ -108,7 +193,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
return nil, err
}

tagNames := strings.Split(stdout, "\n")
tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n")
var tags = make([]*Tag, 0, len(tagNames))
for _, tagName := range tagNames {
tagName = strings.TrimSpace(tagName)
Expand All @@ -120,6 +205,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
if err != nil {
return nil, err
}
tag.Name = tagName
tags = append(tags, tag)
}
sortTagsByTime(tags)
Expand Down Expand Up @@ -150,3 +236,38 @@ func (repo *Repository) GetTags() ([]string, error) {

return tagNames, nil
}

// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id SHA1) (string, error) {
// Get tag type
stdout, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
if err != nil {
return "", err
}
if len(stdout) == 0 {
return "", ErrNotExist{ID: id.String()}
}
return strings.TrimSpace(stdout), nil
}

// GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
id, err := NewIDFromString(sha)
if err != nil {
return nil, err
}

// Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
if tagType, err := repo.GetTagType(id); err != nil {
return nil, err
} else if ObjectType(tagType) != ObjectTag {
// not an annotated tag
return nil, ErrNotExist{ID: id.String()}
}

tag, err := repo.getTag(id)
if err != nil {
return nil, err
}
return tag, nil
}
Loading