* 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 IDtags/v1.9.0-rc1
@@ -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) |
@@ -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) |
@@ -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) | |||
} |
@@ -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) | |||
} |
@@ -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 { |
@@ -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 |
@@ -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 | |||
} |
@@ -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) | |||
} |
@@ -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 |
@@ -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"` | |||
} |
@@ -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) |
@@ -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()), | |||
} | |||
} |
@@ -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(), | |||
}, | |||
} | |||
} |
@@ -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)) | |||
} | |||
} |
@@ -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 { |
@@ -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": { |