mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	* 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
This commit is contained in:
		@@ -146,8 +146,8 @@ func TestAPICreateFile(t *testing.T) {
 | 
				
			|||||||
		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)
 | 
							expectedHTMLURL := fmt.Sprintf(setting.AppURL+"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)
 | 
							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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -136,8 +136,8 @@ func TestAPIUpdateFile(t *testing.T) {
 | 
				
			|||||||
		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)
 | 
							expectedHTMLURL := fmt.Sprintf(setting.AppURL+"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)
 | 
							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)
 | 
				
			||||||
@@ -155,8 +155,8 @@ func TestAPIUpdateFile(t *testing.T) {
 | 
				
			|||||||
		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)
 | 
							expectedHTMLURL = fmt.Sprintf(setting.AppURL+"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)
 | 
							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)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										59
									
								
								integrations/api_repo_git_tags_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								integrations/api_repo_git_tags_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -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 (
 | 
					import (
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
@@ -32,7 +31,7 @@ func TestAPIReposGetTags(t *testing.T) {
 | 
				
			|||||||
	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, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.URL)
 | 
				
			||||||
	assert.Equal(t, path.Join(setting.AppSubURL, "/user2/repo1/archive/v1.1.zip"), tags[0].ZipballURL)
 | 
						assert.Equal(t, setting.AppURL+"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+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ import (
 | 
				
			|||||||
	"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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,15 +31,19 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
 | 
				
			|||||||
	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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@
 | 
				
			|||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mcuadros/go-version"
 | 
						"github.com/mcuadros/go-version"
 | 
				
			||||||
@@ -35,34 +36,78 @@ func (repo *Repository) CreateTag(name, revision string) error {
 | 
				
			|||||||
	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
 | 
						// Get tag name
 | 
				
			||||||
	tp, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
 | 
						name, err := repo.GetTagNameBySHA(id.String())
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tp = strings.TrimSpace(tp)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Tag is a commit.
 | 
						tp, err := repo.GetTagType(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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,
 | 
								Name:    name,
 | 
				
			||||||
			Object: id,
 | 
								ID:      tagID,
 | 
				
			||||||
			Type:   string(ObjectCommit),
 | 
								Object:  commitID,
 | 
				
			||||||
			repo:   repo,
 | 
								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
 | 
				
			||||||
@@ -73,16 +118,57 @@ func (repo *Repository) getTag(id SHA1) (*Tag, error) {
 | 
				
			|||||||
		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
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -96,7 +182,6 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tag.Name = name
 | 
					 | 
				
			||||||
	return tag, nil
 | 
						return tag, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,7 +193,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
 | 
				
			|||||||
		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)
 | 
				
			||||||
@@ -120,6 +205,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
 | 
				
			|||||||
		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)
 | 
				
			||||||
@@ -150,3 +236,38 @@ func (repo *Repository) GetTags() ([]string, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,8 +21,8 @@ func TestRepository_GetTags(t *testing.T) {
 | 
				
			|||||||
	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, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String())
 | 
				
			||||||
	assert.EqualValues(t, "commit", tags[0].Type)
 | 
						assert.EqualValues(t, "tag", tags[0].Type)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestRepository_GetTag(t *testing.T) {
 | 
					func TestRepository_GetTag(t *testing.T) {
 | 
				
			||||||
@@ -35,10 +35,78 @@ 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, aTagName, tag.Name)
 | 
				
			||||||
	assert.EqualValues(t, "37991dec2c8e592043f47155ce4808d4580f9123", tag.ID.String())
 | 
						assert.EqualValues(t, aTagID, tag.ID.String())
 | 
				
			||||||
	assert.EqualValues(t, "commit", tag.Type)
 | 
						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 (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tag represents a Git tag.
 | 
					// Tag represents a Git tag.
 | 
				
			||||||
@@ -59,7 +60,7 @@ l:
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,11 +6,27 @@ package structs
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Tag represents a repository tag
 | 
					// Tag represents a repository tag
 | 
				
			||||||
type Tag struct {
 | 
					type Tag struct {
 | 
				
			||||||
	Name   string `json:"name"`
 | 
						Name       string      `json:"name"`
 | 
				
			||||||
	Commit struct {
 | 
						ID         string      `json:"id"`
 | 
				
			||||||
		SHA string `json:"sha"`
 | 
						Commit     *CommitMeta `json:"commit"`
 | 
				
			||||||
		URL string `json:"url"`
 | 
						ZipballURL string      `json:"zipball_url"`
 | 
				
			||||||
	} `json:"commit"`
 | 
						TarballURL string      `json:"tarball_url"`
 | 
				
			||||||
	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)
 | 
											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))
 | 
				
			||||||
@@ -762,6 +763,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
					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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ package convert
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
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"
 | 
				
			||||||
@@ -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 {
 | 
					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,
 | 
				
			||||||
@@ -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 {
 | 
					func ToTag(repo *models.Repository, t *git.Tag) *api.Tag {
 | 
				
			||||||
	return &api.Tag{
 | 
						return &api.Tag{
 | 
				
			||||||
		Name: t.Name,
 | 
							Name:       t.Name,
 | 
				
			||||||
		Commit: struct {
 | 
							ID:         t.ID.String(),
 | 
				
			||||||
			SHA string `json:"sha"`
 | 
							Commit:     ToCommitMeta(repo, t),
 | 
				
			||||||
			URL string `json:"url"`
 | 
							ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"),
 | 
				
			||||||
		}{
 | 
							TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"),
 | 
				
			||||||
			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"),
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 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 {
 | 
				
			||||||
@@ -66,17 +62,10 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
 | 
				
			|||||||
		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,
 | 
				
			||||||
@@ -87,13 +76,24 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
 | 
				
			|||||||
			Email:    c.Committer.Email,
 | 
								Email:    c.Committer.Email,
 | 
				
			||||||
			UserName: committerUsername,
 | 
								UserName: committerUsername,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		Timestamp: c.Author.When,
 | 
							Timestamp:    c.Author.When,
 | 
				
			||||||
		Verification: &api.PayloadCommitVerification{
 | 
							Verification: ToVerification(c),
 | 
				
			||||||
			Verified:  verif.Verified,
 | 
						}
 | 
				
			||||||
			Reason:    verif.Reason,
 | 
					}
 | 
				
			||||||
			Signature: signature,
 | 
					
 | 
				
			||||||
			Payload:   payload,
 | 
					// 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
 | 
						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{
 | 
								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(),
 | 
					 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package repo
 | 
				
			|||||||
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"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -45,3 +46,47 @@ func ListTags(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	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))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,11 +38,25 @@ type swaggerResponseBranchList struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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}": {
 | 
					    "/repos/{owner}/{repo}/git/trees/{sha}": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
@@ -6762,6 +6802,57 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      "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",
 | 
				
			||||||
@@ -9458,18 +9549,11 @@
 | 
				
			|||||||
      "type": "object",
 | 
					      "type": "object",
 | 
				
			||||||
      "properties": {
 | 
					      "properties": {
 | 
				
			||||||
        "commit": {
 | 
					        "commit": {
 | 
				
			||||||
          "type": "object",
 | 
					          "$ref": "#/definitions/CommitMeta"
 | 
				
			||||||
          "properties": {
 | 
					        },
 | 
				
			||||||
            "sha": {
 | 
					        "id": {
 | 
				
			||||||
              "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
              "x-go-name": "SHA"
 | 
					          "x-go-name": "ID"
 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            "url": {
 | 
					 | 
				
			||||||
              "type": "string",
 | 
					 | 
				
			||||||
              "x-go-name": "URL"
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          "x-go-name": "Commit"
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "name": {
 | 
					        "name": {
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
@@ -9735,6 +9819,12 @@
 | 
				
			|||||||
    "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": {
 | 
				
			||||||
@@ -10056,6 +10146,12 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Tag": {
 | 
				
			||||||
 | 
					      "description": "Tag",
 | 
				
			||||||
 | 
					      "schema": {
 | 
				
			||||||
 | 
					        "$ref": "#/definitions/Tag"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "TagList": {
 | 
					    "TagList": {
 | 
				
			||||||
      "description": "TagList",
 | 
					      "description": "TagList",
 | 
				
			||||||
      "schema": {
 | 
					      "schema": {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user