* 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
var fileResponse api.FileResponse | var fileResponse api.FileResponse | ||||
DecodeJSON(t, resp, &fileResponse) | DecodeJSON(t, resp, &fileResponse) | ||||
expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" | 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, expectedSHA, fileResponse.Content.SHA) | ||||
assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | ||||
assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) |
var fileResponse api.FileResponse | var fileResponse api.FileResponse | ||||
DecodeJSON(t, resp, &fileResponse) | DecodeJSON(t, resp, &fileResponse) | ||||
expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" | 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, expectedSHA, fileResponse.Content.SHA) | ||||
assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | ||||
assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | ||||
resp = session.MakeRequest(t, req, http.StatusOK) | resp = session.MakeRequest(t, req, http.StatusOK) | ||||
DecodeJSON(t, resp, &fileResponse) | DecodeJSON(t, resp, &fileResponse) | ||||
expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" | 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, expectedSHA, fileResponse.Content.SHA) | ||||
assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | ||||
assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) |
// 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) | |||||
} |
import ( | import ( | ||||
"net/http" | "net/http" | ||||
"path" | |||||
"testing" | "testing" | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
assert.EqualValues(t, 1, len(tags)) | assert.EqualValues(t, 1, len(tags)) | ||||
assert.Equal(t, "v1.1", tags[0].Name) | assert.Equal(t, "v1.1", tags[0].Name) | ||||
assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA) | 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) | |||||
} | } |
"code.gitea.io/gitea/modules/git" | "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) { | func GetTagsByPath(path string) ([]*git.Tag, error) { | ||||
gitRepo, err := git.OpenRepository(path) | gitRepo, err := git.OpenRepository(path) | ||||
if err != nil { | if err != nil { |
if err = refsIter.ForEach(func(ref *plumbing.Reference) error { | if err = refsIter.ForEach(func(ref *plumbing.Reference) error { | ||||
if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() && | if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() && | ||||
(pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) { | (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{ | r := &Reference{ | ||||
Name: ref.Name().String(), | Name: ref.Name().String(), | ||||
Object: SHA1(ref.Hash()), | Object: SHA1(ref.Hash()), | ||||
Type: string(ObjectCommit), | |||||
Type: refType, | |||||
repo: repo, | repo: repo, | ||||
} | } | ||||
if ref.Name().IsTag() { | |||||
r.Type = string(ObjectTag) | |||||
} | |||||
refs = append(refs, r) | refs = append(refs, r) | ||||
} | } | ||||
return nil | return nil |
package git | package git | ||||
import ( | import ( | ||||
"fmt" | |||||
"strings" | "strings" | ||||
"github.com/mcuadros/go-version" | "github.com/mcuadros/go-version" | ||||
return err | 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) { | func (repo *Repository) getTag(id SHA1) (*Tag, error) { | ||||
t, ok := repo.tagCache.Get(id.String()) | t, ok := repo.tagCache.Get(id.String()) | ||||
if ok { | if ok { | ||||
log("Hit cache: %s", id) | 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 { | if err != nil { | ||||
return nil, err | 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 { | if ObjectType(tp) == ObjectCommit { | ||||
commit, err := repo.GetCommit(id.String()) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
tag := &Tag{ | 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) | repo.tagCache.Set(id.String(), tag) | ||||
return tag, nil | 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) | data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path) | ||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
return nil, err | return nil, err | ||||
} | } | ||||
tag.Name = name | |||||
tag.ID = id | tag.ID = id | ||||
tag.repo = repo | tag.repo = repo | ||||
tag.Type = tp | |||||
repo.tagCache.Set(id.String(), tag) | repo.tagCache.Set(id.String(), tag) | ||||
return tag, nil | 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. | // GetTag returns a Git tag by given name. | ||||
func (repo *Repository) GetTag(name string) (*Tag, error) { | func (repo *Repository) GetTag(name string) (*Tag, error) { | ||||
idStr, err := repo.GetTagCommitID(name) | |||||
idStr, err := repo.GetTagID(name) | |||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
tag.Name = name | |||||
return tag, nil | return tag, nil | ||||
} | } | ||||
return nil, err | return nil, err | ||||
} | } | ||||
tagNames := strings.Split(stdout, "\n") | |||||
tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n") | |||||
var tags = make([]*Tag, 0, len(tagNames)) | var tags = make([]*Tag, 0, len(tagNames)) | ||||
for _, tagName := range tagNames { | for _, tagName := range tagNames { | ||||
tagName = strings.TrimSpace(tagName) | tagName = strings.TrimSpace(tagName) | ||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
tag.Name = tagName | |||||
tags = append(tags, tag) | tags = append(tags, tag) | ||||
} | } | ||||
sortTagsByTime(tags) | sortTagsByTime(tags) | ||||
return tagNames, nil | 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 | |||||
} |
assert.NoError(t, err) | assert.NoError(t, err) | ||||
assert.Len(t, tags, 1) | assert.Len(t, tags, 1) | ||||
assert.EqualValues(t, "test", tags[0].Name) | 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) { | func TestRepository_GetTag(t *testing.T) { | ||||
bareRepo1, err := OpenRepository(clonedPath) | bareRepo1, err := OpenRepository(clonedPath) | ||||
assert.NoError(t, err) | 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.NoError(t, err) | ||||
assert.NotNil(t, tag) | 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) | |||||
} | } |
import ( | import ( | ||||
"bytes" | "bytes" | ||||
"sort" | "sort" | ||||
"strings" | |||||
) | ) | ||||
// Tag represents a Git tag. | // Tag represents a Git tag. | ||||
} | } | ||||
nextline += eol + 1 | nextline += eol + 1 | ||||
case eol == 0: | case eol == 0: | ||||
tag.Message = string(data[nextline+1:]) | |||||
tag.Message = strings.TrimRight(string(data[nextline+1:]), "\n") | |||||
break l | break l | ||||
default: | default: | ||||
break l | break l |
// Tag represents a repository tag | // Tag represents a repository tag | ||||
type Tag struct { | 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"` | |||||
} | } |
Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus) | Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus) | ||||
}, reqRepoReader(models.UnitTypeCode)) | }, reqRepoReader(models.UnitTypeCode)) | ||||
m.Group("/commits/:ref", func() { | 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("/status", repo.GetCombinedCommitStatusByRef) | ||||
m.Get("/statuses", repo.GetCommitStatusesByRef) | m.Get("/statuses", repo.GetCommitStatusesByRef) | ||||
}, reqRepoReader(models.UnitTypeCode)) | }, reqRepoReader(models.UnitTypeCode)) | ||||
m.Get("/refs/*", repo.GetGitRefs) | m.Get("/refs/*", repo.GetGitRefs) | ||||
m.Get("/trees/:sha", context.RepoRef(), repo.GetTree) | m.Get("/trees/:sha", context.RepoRef(), repo.GetTree) | ||||
m.Get("/blobs/:sha", context.RepoRef(), repo.GetBlob) | m.Get("/blobs/:sha", context.RepoRef(), repo.GetBlob) | ||||
m.Get("/tags/:sha", context.RepoRef(), repo.GetTag) | |||||
}, reqRepoReader(models.UnitTypeCode)) | }, reqRepoReader(models.UnitTypeCode)) | ||||
m.Group("/contents", func() { | m.Group("/contents", func() { | ||||
m.Get("/*", repo.GetFileContents) | m.Get("/*", repo.GetFileContents) |
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"time" | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
} | } | ||||
} | } | ||||
// 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 { | func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch { | ||||
return &api.Branch{ | return &api.Branch{ | ||||
Name: b.Name, | Name: b.Name, | ||||
} | } | ||||
} | } | ||||
// 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 { | func ToTag(repo *models.Repository, t *git.Tag) *api.Tag { | ||||
return &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 { | func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit { | ||||
authorUsername := "" | authorUsername := "" | ||||
if author, err := models.GetUserByEmail(c.Author.Email); err == nil { | if author, err := models.GetUserByEmail(c.Author.Email); err == nil { | ||||
log.Error("GetUserByEmail: %v", err) | 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{ | return &api.PayloadCommit{ | ||||
ID: c.ID.String(), | ID: c.ID.String(), | ||||
Message: c.Message(), | Message: c.Message(), | ||||
URL: util.URLJoin(repo.Link(), "commit", c.ID.String()), | |||||
URL: util.URLJoin(repo.HTMLURL(), "commit", c.ID.String()), | |||||
Author: &api.PayloadUser{ | Author: &api.PayloadUser{ | ||||
Name: c.Author.Name, | Name: c.Author.Name, | ||||
Email: c.Author.Email, | Email: c.Author.Email, | ||||
Email: c.Committer.Email, | Email: c.Committer.Email, | ||||
UserName: committerUsername, | 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, | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return result | 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()), | |||||
} | |||||
} |
Object: &api.GitObject{ | Object: &api.GitObject{ | ||||
SHA: refs[i].Object.String(), | SHA: refs[i].Object.String(), | ||||
Type: refs[i].Type, | 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(), | |||||
}, | }, | ||||
} | } | ||||
} | } |
import ( | import ( | ||||
"code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
"code.gitea.io/gitea/routers/api/v1/convert" | "code.gitea.io/gitea/routers/api/v1/convert" | ||||
"net/http" | |||||
api "code.gitea.io/gitea/modules/structs" | api "code.gitea.io/gitea/modules/structs" | ||||
) | ) | ||||
ctx.JSON(200, &apiTags) | 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)) | |||||
} | |||||
} |
// TagList | // TagList | ||||
// swagger:response TagList | // swagger:response TagList | ||||
type swaggerReponseTagList struct { | |||||
type swaggerResponseTagList struct { | |||||
// in:body | // in:body | ||||
Body []api.Tag `json:"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 | // Reference | ||||
// swagger:response Reference | // swagger:response Reference | ||||
type swaggerResponseReference struct { | type swaggerResponseReference struct { |
} | } | ||||
} | } | ||||
}, | }, | ||||
"/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}": { | "/repos/{owner}/{repo}/git/trees/{sha}": { | ||||
"get": { | "get": { | ||||
"produces": [ | "produces": [ | ||||
}, | }, | ||||
"x-go-package": "code.gitea.io/gitea/modules/structs" | "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": { | "Attachment": { | ||||
"description": "Attachment a generic attachment", | "description": "Attachment a generic attachment", | ||||
"type": "object", | "type": "object", | ||||
"type": "object", | "type": "object", | ||||
"properties": { | "properties": { | ||||
"commit": { | "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": { | "name": { | ||||
"type": "string", | "type": "string", | ||||
"AccessTokenList": { | "AccessTokenList": { | ||||
"description": "AccessTokenList represents a list of API access token." | "description": "AccessTokenList represents a list of API access token." | ||||
}, | }, | ||||
"AnnotatedTag": { | |||||
"description": "AnnotatedTag", | |||||
"schema": { | |||||
"$ref": "#/definitions/AnnotatedTag" | |||||
} | |||||
}, | |||||
"Attachment": { | "Attachment": { | ||||
"description": "Attachment", | "description": "Attachment", | ||||
"schema": { | "schema": { | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
"Tag": { | |||||
"description": "Tag", | |||||
"schema": { | |||||
"$ref": "#/definitions/Tag" | |||||
} | |||||
}, | |||||
"TagList": { | "TagList": { | ||||
"description": "TagList", | "description": "TagList", | ||||
"schema": { | "schema": { |