mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00: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,26 +36,70 @@ 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, | ||||||
|  | 			Object:  commitID, | ||||||
| 			Type:    string(ObjectCommit), | 			Type:    string(ObjectCommit), | ||||||
|  | 			Tagger:  commit.Committer, | ||||||
|  | 			Message: commit.Message(), | ||||||
| 			repo:    repo, | 			repo:    repo, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -62,7 +107,7 @@ func (repo *Repository) getTag(id SHA1) (*Tag, error) { | |||||||
| 		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 | ||||||
|   | |||||||
| @@ -7,10 +7,26 @@ 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"` |  | ||||||
| 	} `json:"commit"` |  | ||||||
| 	ZipballURL string      `json:"zipball_url"` | 	ZipballURL string      `json:"zipball_url"` | ||||||
| 	TarballURL string      `json:"tarball_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, | ||||||
| @@ -88,12 +77,23 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit { | |||||||
| 			UserName: committerUsername, | 			UserName: committerUsername, | ||||||
| 		}, | 		}, | ||||||
| 		Timestamp:    c.Author.When, | 		Timestamp:    c.Author.When, | ||||||
| 		Verification: &api.PayloadCommitVerification{ | 		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, | 		Verified:  verif.Verified, | ||||||
| 		Reason:    verif.Reason, | 		Reason:    verif.Reason, | ||||||
| 		Signature: signature, | 		Signature: signature, | ||||||
| 		Payload:   payload, | 		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": { |  | ||||||
|               "type": "string", |  | ||||||
|               "x-go-name": "SHA" |  | ||||||
|         }, |         }, | ||||||
|             "url": { |         "id": { | ||||||
|           "type": "string", |           "type": "string", | ||||||
|               "x-go-name": "URL" |           "x-go-name": "ID" | ||||||
|             } |  | ||||||
|           }, |  | ||||||
|           "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