aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Mahn <richmahn@users.noreply.github.com>2019-06-08 10:31:11 -0400
committerLauris BH <lauris@nix.lv>2019-06-08 17:31:11 +0300
commit8de0b0a3f06fe7879fb014d2624209e0e48455a0 (patch)
tree55a1413aa439c86749ee0af31d484675e3b4b5cc
parent23a2ee3510ad1b1e7e89edc526ed394c71a8ba24 (diff)
downloadgitea-8de0b0a3f06fe7879fb014d2624209e0e48455a0.tar.gz
gitea-8de0b0a3f06fe7879fb014d2624209e0e48455a0.zip
Fixes #2738 - Adds the /git/tags API endpoint (#7138)
* Fixes #2738 - /git/tags API * proper URLs * Adds function comments * Updates swagger * Removes newline from tag message * Removes trailing newline from commit message * Adds integration test * Removed debugging * Adds tests * Fixes bug where multiple tags of same commit show wrong tag name * Fix formatting * Removes unused varaible * Fix to annotated tag function names and response * Update modules/git/repo_tag.go Co-Authored-By: Lauris BH <lauris@nix.lv> * Uses TagPrefix * Changes per review, better error handling for getting tag and commit IDs * Fix to getting commit ID * Fix to getting commit ID * Fix to getting commit ID * Fix to getting commit ID
-rw-r--r--integrations/api_repo_file_create_test.go4
-rw-r--r--integrations/api_repo_file_update_test.go8
-rw-r--r--integrations/api_repo_git_tags_test.go59
-rw-r--r--integrations/api_repo_tags_test.go7
-rw-r--r--models/repo_tag.go2
-rw-r--r--modules/git/repo_ref.go12
-rw-r--r--modules/git/repo_tag.go147
-rw-r--r--modules/git/repo_tag_test.go80
-rw-r--r--modules/git/tag.go3
-rw-r--r--modules/structs/repo_tag.go30
-rw-r--r--routers/api/v1/api.go2
-rw-r--r--routers/api/v1/convert/convert.go98
-rw-r--r--routers/api/v1/repo/git_ref.go3
-rw-r--r--routers/api/v1/repo/tag.go45
-rw-r--r--routers/api/v1/swagger/repo.go16
-rw-r--r--templates/swagger/v1_json.tmpl120
16 files changed, 551 insertions, 85 deletions
diff --git a/integrations/api_repo_file_create_test.go b/integrations/api_repo_file_create_test.go
index 28097179a0..3bb2ecb812 100644
--- a/integrations/api_repo_file_create_test.go
+++ b/integrations/api_repo_file_create_test.go
@@ -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)
+ 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)
diff --git a/integrations/api_repo_file_update_test.go b/integrations/api_repo_file_update_test.go
index 37438339bb..eab7090df6 100644
--- a/integrations/api_repo_file_update_test.go
+++ b/integrations/api_repo_file_update_test.go
@@ -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)
+ 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)
@@ -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)
diff --git a/integrations/api_repo_git_tags_test.go b/integrations/api_repo_git_tags_test.go
new file mode 100644
index 0000000000..ae519249e0
--- /dev/null
+++ b/integrations/api_repo_git_tags_test.go
@@ -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)
+}
diff --git a/integrations/api_repo_tags_test.go b/integrations/api_repo_tags_test.go
index 13b446fb96..252037bd4a 100644
--- a/integrations/api_repo_tags_test.go
+++ b/integrations/api_repo_tags_test.go
@@ -6,7 +6,6 @@ package integrations
import (
"net/http"
- "path"
"testing"
"code.gitea.io/gitea/models"
@@ -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)
}
diff --git a/models/repo_tag.go b/models/repo_tag.go
index fa3f19bb29..3864b7a12a 100644
--- a/models/repo_tag.go
+++ b/models/repo_tag.go
@@ -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 {
diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go
index e1ab46e090..95a6c2ae69 100644
--- a/modules/git/repo_ref.go
+++ b/modules/git/repo_ref.go
@@ -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
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
index 8c72528933..08d66262c1 100644
--- a/modules/git/repo_tag.go
+++ b/modules/git/repo_tag.go
@@ -6,6 +6,7 @@
package git
import (
+ "fmt"
"strings"
"github.com/mcuadros/go-version"
@@ -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)
+ 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
@@ -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
}
@@ -96,7 +182,6 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
if err != nil {
return nil, err
}
- tag.Name = name
return tag, nil
}
@@ -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)
@@ -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)
@@ -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
+}
diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go
index ccb2d57ac2..4f727c6c66 100644
--- a/modules/git/repo_tag_test.go
+++ b/modules/git/repo_tag_test.go
@@ -21,8 +21,8 @@ func TestRepository_GetTags(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, tags, 1)
assert.EqualValues(t, "test", tags[0].Name)
- assert.EqualValues(t, "37991dec2c8e592043f47155ce4808d4580f9123", tags[0].ID.String())
- assert.EqualValues(t, "commit", tags[0].Type)
+ assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String())
+ assert.EqualValues(t, "tag", tags[0].Type)
}
func TestRepository_GetTag(t *testing.T) {
@@ -35,10 +35,78 @@ func TestRepository_GetTag(t *testing.T) {
bareRepo1, err := OpenRepository(clonedPath)
assert.NoError(t, err)
- tag, err := bareRepo1.GetTag("test")
+ lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
+ lTagName := "lightweightTag"
+ bareRepo1.CreateTag(lTagName, lTagCommitID)
+
+ aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
+ aTagName := "annotatedTag"
+ aTagMessage := "my annotated message"
+ bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
+ aTagID, _ := bareRepo1.GetTagID(aTagName)
+
+ lTag, err := bareRepo1.GetTag(lTagName)
+ lTag.repo = nil
+ assert.NoError(t, err)
+ assert.NotNil(t, lTag)
+ assert.EqualValues(t, lTagName, lTag.Name)
+ assert.EqualValues(t, lTagCommitID, lTag.ID.String())
+ assert.EqualValues(t, lTagCommitID, lTag.Object.String())
+ assert.EqualValues(t, "commit", lTag.Type)
+
+ aTag, err := bareRepo1.GetTag(aTagName)
+ assert.NoError(t, err)
+ assert.NotNil(t, aTag)
+ assert.EqualValues(t, aTagName, aTag.Name)
+ assert.EqualValues(t, aTagID, aTag.ID.String())
+ assert.NotEqual(t, aTagID, aTag.Object.String())
+ assert.EqualValues(t, aTagCommitID, aTag.Object.String())
+ assert.EqualValues(t, "tag", aTag.Type)
+}
+
+func TestRepository_GetAnnotatedTag(t *testing.T) {
+ bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
+
+ clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestRepository_GetTag")
+ assert.NoError(t, err)
+ defer os.RemoveAll(clonedPath)
+
+ bareRepo1, err := OpenRepository(clonedPath)
+ assert.NoError(t, err)
+
+ lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
+ lTagName := "lightweightTag"
+ bareRepo1.CreateTag(lTagName, lTagCommitID)
+
+ aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
+ aTagName := "annotatedTag"
+ aTagMessage := "my annotated message"
+ bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
+ aTagID, _ := bareRepo1.GetTagID(aTagName)
+
+ // Try an annotated tag
+ tag, err := bareRepo1.GetAnnotatedTag(aTagID)
assert.NoError(t, err)
assert.NotNil(t, tag)
- assert.EqualValues(t, "test", tag.Name)
- assert.EqualValues(t, "37991dec2c8e592043f47155ce4808d4580f9123", tag.ID.String())
- assert.EqualValues(t, "commit", tag.Type)
+ assert.EqualValues(t, aTagName, tag.Name)
+ assert.EqualValues(t, aTagID, tag.ID.String())
+ assert.EqualValues(t, "tag", tag.Type)
+
+ // Annotated tag's Commit ID should fail
+ tag2, err := bareRepo1.GetAnnotatedTag(aTagCommitID)
+ assert.Error(t, err)
+ assert.True(t, IsErrNotExist(err))
+ assert.Nil(t, tag2)
+
+ // Annotated tag's name should fail
+ tag3, err := bareRepo1.GetAnnotatedTag(aTagName)
+ assert.Error(t, err)
+ assert.Errorf(t, err, "Length must be 40: %d", len(aTagName))
+ assert.Nil(t, tag3)
+
+ // Lightweight Tag should fail
+ tag4, err := bareRepo1.GetAnnotatedTag(lTagCommitID)
+ assert.Error(t, err)
+ assert.True(t, IsErrNotExist(err))
+ assert.Nil(t, tag4)
}
diff --git a/modules/git/tag.go b/modules/git/tag.go
index 500fd27491..c97f574fa6 100644
--- a/modules/git/tag.go
+++ b/modules/git/tag.go
@@ -7,6 +7,7 @@ package git
import (
"bytes"
"sort"
+ "strings"
)
// Tag represents a Git tag.
@@ -59,7 +60,7 @@ l:
}
nextline += eol + 1
case eol == 0:
- tag.Message = string(data[nextline+1:])
+ tag.Message = strings.TrimRight(string(data[nextline+1:]), "\n")
break l
default:
break l
diff --git a/modules/structs/repo_tag.go b/modules/structs/repo_tag.go
index 6294a8099d..b62395cac4 100644
--- a/modules/structs/repo_tag.go
+++ b/modules/structs/repo_tag.go
@@ -6,11 +6,27 @@ package structs
// Tag represents a repository tag
type Tag struct {
- Name string `json:"name"`
- Commit struct {
- SHA string `json:"sha"`
- URL string `json:"url"`
- } `json:"commit"`
- ZipballURL string `json:"zipball_url"`
- TarballURL string `json:"tarball_url"`
+ Name string `json:"name"`
+ ID string `json:"id"`
+ Commit *CommitMeta `json:"commit"`
+ ZipballURL string `json:"zipball_url"`
+ TarballURL string `json:"tarball_url"`
+}
+
+// AnnotatedTag represents an annotated tag
+type AnnotatedTag struct {
+ Tag string `json:"tag"`
+ SHA string `json:"sha"`
+ URL string `json:"url"`
+ Message string `json:"message"`
+ Tagger *CommitUser `json:"tagger"`
+ Object *AnnotatedTagObject `json:"object"`
+ Verification *PayloadCommitVerification `json:"verification"`
+}
+
+// AnnotatedTagObject contains meta information of the tag object
+type AnnotatedTagObject struct {
+ Type string `json:"type"`
+ URL string `json:"url"`
+ SHA string `json:"sha"`
}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index c1561200cd..2268c1be38 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -751,6 +751,7 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/commits/:ref", func() {
+ // TODO: Add m.Get("") for single commit (https://developer.github.com/v3/repos/commits/#get-a-single-commit)
m.Get("/status", repo.GetCombinedCommitStatusByRef)
m.Get("/statuses", repo.GetCommitStatusesByRef)
}, reqRepoReader(models.UnitTypeCode))
@@ -762,6 +763,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/refs/*", repo.GetGitRefs)
m.Get("/trees/:sha", context.RepoRef(), repo.GetTree)
m.Get("/blobs/:sha", context.RepoRef(), repo.GetBlob)
+ m.Get("/tags/:sha", context.RepoRef(), repo.GetTag)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/contents", func() {
m.Get("/*", repo.GetFileContents)
diff --git a/routers/api/v1/convert/convert.go b/routers/api/v1/convert/convert.go
index ba61c7e46c..a982cb8d37 100644
--- a/routers/api/v1/convert/convert.go
+++ b/routers/api/v1/convert/convert.go
@@ -6,6 +6,7 @@ package convert
import (
"fmt"
+ "time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
@@ -26,7 +27,7 @@ func ToEmail(email *models.EmailAddress) *api.Email {
}
}
-// ToBranch convert a commit and branch to an api.Branch
+// ToBranch convert a git.Commit and git.Branch to an api.Branch
func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch {
return &api.Branch{
Name: b.Name,
@@ -34,23 +35,18 @@ func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch
}
}
-// ToTag convert a tag to an api.Tag
+// ToTag convert a git.Tag to an api.Tag
func ToTag(repo *models.Repository, t *git.Tag) *api.Tag {
return &api.Tag{
- Name: t.Name,
- Commit: struct {
- SHA string `json:"sha"`
- URL string `json:"url"`
- }{
- SHA: t.ID.String(),
- URL: util.URLJoin(repo.Link(), "commit", t.ID.String()),
- },
- ZipballURL: util.URLJoin(repo.Link(), "archive", t.Name+".zip"),
- TarballURL: util.URLJoin(repo.Link(), "archive", t.Name+".tar.gz"),
+ Name: t.Name,
+ ID: t.ID.String(),
+ Commit: ToCommitMeta(repo, t),
+ ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"),
+ TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"),
}
}
-// ToCommit convert a commit to api.PayloadCommit
+// ToCommit convert a git.Commit to api.PayloadCommit
func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
authorUsername := ""
if author, err := models.GetUserByEmail(c.Author.Email); err == nil {
@@ -66,17 +62,10 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
log.Error("GetUserByEmail: %v", err)
}
- verif := models.ParseCommitWithSignature(c)
- var signature, payload string
- if c.Signature != nil {
- signature = c.Signature.Signature
- payload = c.Signature.Payload
- }
-
return &api.PayloadCommit{
ID: c.ID.String(),
Message: c.Message(),
- URL: util.URLJoin(repo.Link(), "commit", c.ID.String()),
+ URL: util.URLJoin(repo.HTMLURL(), "commit", c.ID.String()),
Author: &api.PayloadUser{
Name: c.Author.Name,
Email: c.Author.Email,
@@ -87,13 +76,24 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
Email: c.Committer.Email,
UserName: committerUsername,
},
- Timestamp: c.Author.When,
- Verification: &api.PayloadCommitVerification{
- Verified: verif.Verified,
- Reason: verif.Reason,
- Signature: signature,
- Payload: payload,
- },
+ Timestamp: c.Author.When,
+ Verification: ToVerification(c),
+ }
+}
+
+// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
+func ToVerification(c *git.Commit) *api.PayloadCommitVerification {
+ verif := models.ParseCommitWithSignature(c)
+ var signature, payload string
+ if c.Signature != nil {
+ signature = c.Signature.Signature
+ payload = c.Signature.Payload
+ }
+ return &api.PayloadCommitVerification{
+ Verified: verif.Verified,
+ Reason: verif.Reason,
+ Signature: signature,
+ Payload: payload,
}
}
@@ -242,3 +242,45 @@ func ToUser(user *models.User, signed, admin bool) *api.User {
}
return result
}
+
+// ToAnnotatedTag convert git.Tag to api.AnnotatedTag
+func ToAnnotatedTag(repo *models.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag {
+ return &api.AnnotatedTag{
+ Tag: t.Name,
+ SHA: t.ID.String(),
+ Object: ToAnnotatedTagObject(repo, c),
+ Message: t.Message,
+ URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()),
+ Tagger: ToCommitUser(t.Tagger),
+ Verification: ToVerification(c),
+ }
+}
+
+// ToAnnotatedTagObject convert a git.Commit to an api.AnnotatedTagObject
+func ToAnnotatedTagObject(repo *models.Repository, commit *git.Commit) *api.AnnotatedTagObject {
+ return &api.AnnotatedTagObject{
+ SHA: commit.ID.String(),
+ Type: string(git.ObjectCommit),
+ URL: util.URLJoin(repo.APIURL(), "git/commits", commit.ID.String()),
+ }
+}
+
+// ToCommitUser convert a git.Signature to an api.CommitUser
+func ToCommitUser(sig *git.Signature) *api.CommitUser {
+ return &api.CommitUser{
+ Identity: api.Identity{
+ Name: sig.Name,
+ Email: sig.Email,
+ },
+ Date: sig.When.UTC().Format(time.RFC3339),
+ }
+}
+
+// ToCommitMeta convert a git.Tag to an api.CommitMeta
+func ToCommitMeta(repo *models.Repository, tag *git.Tag) *api.CommitMeta {
+ return &api.CommitMeta{
+ SHA: tag.ID.String(),
+ // TODO: Add the /commits API endpoint and use it here (https://developer.github.com/v3/repos/commits/#get-a-single-commit)
+ URL: util.URLJoin(repo.APIURL(), "git/commits", tag.ID.String()),
+ }
+}
diff --git a/routers/api/v1/repo/git_ref.go b/routers/api/v1/repo/git_ref.go
index 2ec8749058..e15f699a1d 100644
--- a/routers/api/v1/repo/git_ref.go
+++ b/routers/api/v1/repo/git_ref.go
@@ -100,8 +100,7 @@ func getGitRefsInternal(ctx *context.APIContext, filter string) {
Object: &api.GitObject{
SHA: refs[i].Object.String(),
Type: refs[i].Type,
- // TODO: Add commit/tag info URL
- //URL: ctx.Repo.Repository.APIURL() + "/git/" + refs[i].Type + "s/" + refs[i].Object.String(),
+ URL: ctx.Repo.Repository.APIURL() + "/git/" + refs[i].Type + "s/" + refs[i].Object.String(),
},
}
}
diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go
index dd1b5aa7c1..ecf580e1b0 100644
--- a/routers/api/v1/repo/tag.go
+++ b/routers/api/v1/repo/tag.go
@@ -7,6 +7,7 @@ package repo
import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/convert"
+ "net/http"
api "code.gitea.io/gitea/modules/structs"
)
@@ -45,3 +46,47 @@ func ListTags(ctx *context.APIContext) {
ctx.JSON(200, &apiTags)
}
+
+// GetTag get the tag of a repository.
+func GetTag(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/git/tags/{sha} repository GetTag
+ // ---
+ // summary: Gets the tag of a repository.
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // - name: sha
+ // in: path
+ // description: sha of the tag
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/AnnotatedTag"
+
+ sha := ctx.Params("sha")
+ if len(sha) == 0 {
+ ctx.Error(http.StatusBadRequest, "", "SHA not provided")
+ return
+ }
+
+ if tag, err := ctx.Repo.GitRepo.GetAnnotatedTag(sha); err != nil {
+ ctx.Error(http.StatusBadRequest, "GetTag", err)
+ } else {
+ commit, err := tag.Commit()
+ if err != nil {
+ ctx.Error(http.StatusBadRequest, "GetTag", err)
+ }
+ ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx.Repo.Repository, tag, commit))
+ }
+}
diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go
index e7df0b8f71..25354b3d66 100644
--- a/routers/api/v1/swagger/repo.go
+++ b/routers/api/v1/swagger/repo.go
@@ -38,11 +38,25 @@ type swaggerResponseBranchList struct {
// TagList
// swagger:response TagList
-type swaggerReponseTagList struct {
+type swaggerResponseTagList struct {
// in:body
Body []api.Tag `json:"body"`
}
+// Tag
+// swagger:response Tag
+type swaggerResponseTag struct {
+ // in:body
+ Body api.Tag `json:"body"`
+}
+
+// AnnotatedTag
+// swagger:response AnnotatedTag
+type swaggerResponseAnnotatedTag struct {
+ // in:body
+ Body api.AnnotatedTag `json:"body"`
+}
+
// Reference
// swagger:response Reference
type swaggerResponseReference struct {
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 0bd85fbb6e..2b40c89791 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -2036,6 +2036,46 @@
}
}
},
+ "/repos/{owner}/{repo}/git/tags/{sha}": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Gets the tag of a repository.",
+ "operationId": "GetTag",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "owner of the repo",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repo",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "sha of the tag",
+ "name": "sha",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/AnnotatedTag"
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/git/trees/{sha}": {
"get": {
"produces": [
@@ -6762,6 +6802,57 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "AnnotatedTag": {
+ "description": "AnnotatedTag represents an annotated tag",
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string",
+ "x-go-name": "Message"
+ },
+ "object": {
+ "$ref": "#/definitions/AnnotatedTagObject"
+ },
+ "sha": {
+ "type": "string",
+ "x-go-name": "SHA"
+ },
+ "tag": {
+ "type": "string",
+ "x-go-name": "Tag"
+ },
+ "tagger": {
+ "$ref": "#/definitions/CommitUser"
+ },
+ "url": {
+ "type": "string",
+ "x-go-name": "URL"
+ },
+ "verification": {
+ "$ref": "#/definitions/PayloadCommitVerification"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
+ "AnnotatedTagObject": {
+ "description": "AnnotatedTagObject contains meta information of the tag object",
+ "type": "object",
+ "properties": {
+ "sha": {
+ "type": "string",
+ "x-go-name": "SHA"
+ },
+ "type": {
+ "type": "string",
+ "x-go-name": "Type"
+ },
+ "url": {
+ "type": "string",
+ "x-go-name": "URL"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"Attachment": {
"description": "Attachment a generic attachment",
"type": "object",
@@ -9458,18 +9549,11 @@
"type": "object",
"properties": {
"commit": {
- "type": "object",
- "properties": {
- "sha": {
- "type": "string",
- "x-go-name": "SHA"
- },
- "url": {
- "type": "string",
- "x-go-name": "URL"
- }
- },
- "x-go-name": "Commit"
+ "$ref": "#/definitions/CommitMeta"
+ },
+ "id": {
+ "type": "string",
+ "x-go-name": "ID"
},
"name": {
"type": "string",
@@ -9735,6 +9819,12 @@
"AccessTokenList": {
"description": "AccessTokenList represents a list of API access token."
},
+ "AnnotatedTag": {
+ "description": "AnnotatedTag",
+ "schema": {
+ "$ref": "#/definitions/AnnotatedTag"
+ }
+ },
"Attachment": {
"description": "Attachment",
"schema": {
@@ -10056,6 +10146,12 @@
}
}
},
+ "Tag": {
+ "description": "Tag",
+ "schema": {
+ "$ref": "#/definitions/Tag"
+ }
+ },
"TagList": {
"description": "TagList",
"schema": {