[API] Add repoCreateTag (#16165)
* Add API CreateTag * Add Test * API: expose Tag Message
This commit is contained in:
		
							parent
							
								
									19dedc3fa5
								
							
						
					
					
						commit
						f7cd394680
					
				
					 7 changed files with 180 additions and 3 deletions
				
			
		|  | @ -5,6 +5,7 @@ | ||||||
| package integrations | package integrations | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | @ -15,14 +16,16 @@ import ( | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestAPIReposGetTags(t *testing.T) { | func TestAPIRepoTags(t *testing.T) { | ||||||
| 	defer prepareTestEnv(t)() | 	defer prepareTestEnv(t)() | ||||||
| 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) | 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) | ||||||
| 	// Login as User2.
 | 	// Login as User2.
 | ||||||
| 	session := loginUser(t, user.Name) | 	session := loginUser(t, user.Name) | ||||||
| 	token := getTokenForLoggedInUser(t, session) | 	token := getTokenForLoggedInUser(t, session) | ||||||
| 
 | 
 | ||||||
| 	req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/tags?token="+token, user.Name) | 	repoName := "repo1" | ||||||
|  | 
 | ||||||
|  | 	req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/tags?token=%s", user.Name, repoName, token) | ||||||
| 	resp := session.MakeRequest(t, req, http.StatusOK) | 	resp := session.MakeRequest(t, req, http.StatusOK) | ||||||
| 
 | 
 | ||||||
| 	var tags []*api.Tag | 	var tags []*api.Tag | ||||||
|  | @ -30,8 +33,36 @@ func TestAPIReposGetTags(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	assert.Len(t, tags, 1) | 	assert.Len(t, tags, 1) | ||||||
| 	assert.Equal(t, "v1.1", tags[0].Name) | 	assert.Equal(t, "v1.1", tags[0].Name) | ||||||
|  | 	assert.Equal(t, "Initial commit", tags[0].Message) | ||||||
| 	assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA) | 	assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA) | ||||||
| 	assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/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, setting.AppURL+"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, setting.AppURL+"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) | ||||||
|  | 
 | ||||||
|  | 	newTag := createNewTagUsingAPI(t, session, token, user.Name, repoName, "awesome-tag", "", "nice!\nand some text") | ||||||
|  | 	resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
|  | 	DecodeJSON(t, resp, &tags) | ||||||
|  | 	assert.Len(t, tags, 2) | ||||||
|  | 	for _, tag := range tags { | ||||||
|  | 		if tag.Name != "v1.1" { | ||||||
|  | 			assert.EqualValues(t, newTag.Name, tag.Name) | ||||||
|  | 			assert.EqualValues(t, newTag.Message, tag.Message) | ||||||
|  | 			assert.EqualValues(t, "nice!\nand some text", tag.Message) | ||||||
|  | 			assert.EqualValues(t, newTag.Commit.SHA, tag.Commit.SHA) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func createNewTagUsingAPI(t *testing.T, session *TestSession, token string, ownerName, repoName, name, target, msg string) *api.Tag { | ||||||
|  | 	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags?token=%s", ownerName, repoName, token) | ||||||
|  | 	req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateTagOption{ | ||||||
|  | 		TagName: name, | ||||||
|  | 		Message: msg, | ||||||
|  | 		Target:  target, | ||||||
|  | 	}) | ||||||
|  | 	resp := session.MakeRequest(t, req, http.StatusCreated) | ||||||
|  | 
 | ||||||
|  | 	var respObj api.Tag | ||||||
|  | 	DecodeJSON(t, resp, &respObj) | ||||||
|  | 	return &respObj | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ package convert | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
|  | @ -135,6 +136,7 @@ func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection { | ||||||
| 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, | ||||||
|  | 		Message:    strings.TrimSpace(t.Message), | ||||||
| 		ID:         t.ID.String(), | 		ID:         t.ID.String(), | ||||||
| 		Commit:     ToCommitMeta(repo, t), | 		Commit:     ToCommitMeta(repo, t), | ||||||
| 		ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), | 		ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ 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"` | ||||||
|  | 	Message    string      `json:"message"` | ||||||
| 	ID         string      `json:"id"` | 	ID         string      `json:"id"` | ||||||
| 	Commit     *CommitMeta `json:"commit"` | 	Commit     *CommitMeta `json:"commit"` | ||||||
| 	ZipballURL string      `json:"zipball_url"` | 	ZipballURL string      `json:"zipball_url"` | ||||||
|  | @ -30,3 +31,11 @@ type AnnotatedTagObject struct { | ||||||
| 	URL  string `json:"url"` | 	URL  string `json:"url"` | ||||||
| 	SHA  string `json:"sha"` | 	SHA  string `json:"sha"` | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // CreateTagOption options when creating a tag
 | ||||||
|  | type CreateTagOption struct { | ||||||
|  | 	// required: true
 | ||||||
|  | 	TagName string `json:"tag_name" binding:"Required"` | ||||||
|  | 	Message string `json:"message"` | ||||||
|  | 	Target  string `json:"target"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -775,6 +775,7 @@ func Routes() *web.Route { | ||||||
| 				}, reqToken(), reqAdmin()) | 				}, reqToken(), reqAdmin()) | ||||||
| 				m.Group("/tags", func() { | 				m.Group("/tags", func() { | ||||||
| 					m.Get("", repo.ListTags) | 					m.Get("", repo.ListTags) | ||||||
|  | 					m.Post("", reqRepoWriter(models.UnitTypeCode), bind(api.CreateTagOption{}), repo.CreateTag) | ||||||
| 					m.Delete("/{tag}", repo.DeleteTag) | 					m.Delete("/{tag}", repo.DeleteTag) | ||||||
| 				}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true)) | 				}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true)) | ||||||
| 				m.Group("/keys", func() { | 				m.Group("/keys", func() { | ||||||
|  |  | ||||||
|  | @ -6,12 +6,14 @@ package repo | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/convert" | 	"code.gitea.io/gitea/modules/convert" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
|  | 	"code.gitea.io/gitea/modules/web" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/utils" | 	"code.gitea.io/gitea/routers/api/v1/utils" | ||||||
| 	releaseservice "code.gitea.io/gitea/services/release" | 	releaseservice "code.gitea.io/gitea/services/release" | ||||||
| ) | ) | ||||||
|  | @ -160,3 +162,62 @@ func DeleteTag(ctx *context.APIContext) { | ||||||
| 
 | 
 | ||||||
| 	ctx.Status(http.StatusNoContent) | 	ctx.Status(http.StatusNoContent) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // CreateTag create a new git tag in a repository
 | ||||||
|  | func CreateTag(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation POST /repos/{owner}/{repo}/tags repository repoCreateTag
 | ||||||
|  | 	// ---
 | ||||||
|  | 	// summary: Create a new git tag in 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: body
 | ||||||
|  | 	//   in: body
 | ||||||
|  | 	//   schema:
 | ||||||
|  | 	//     "$ref": "#/definitions/CreateTagOption"
 | ||||||
|  | 	// responses:
 | ||||||
|  | 	//   "200":
 | ||||||
|  | 	//     "$ref": "#/responses/AnnotatedTag"
 | ||||||
|  | 	//   "404":
 | ||||||
|  | 	//     "$ref": "#/responses/notFound"
 | ||||||
|  | 	//   "409":
 | ||||||
|  | 	//     "$ref": "#/responses/conflict"
 | ||||||
|  | 	form := web.GetForm(ctx).(*api.CreateTagOption) | ||||||
|  | 
 | ||||||
|  | 	// If target is not provided use default branch
 | ||||||
|  | 	if len(form.Target) == 0 { | ||||||
|  | 		form.Target = ctx.Repo.Repository.DefaultBranch | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	commit, err := ctx.Repo.GitRepo.GetCommit(form.Target) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(http.StatusNotFound, "target not found", fmt.Errorf("target not found: %v", err)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := releaseservice.CreateNewTag(ctx.User, ctx.Repo.Repository, commit.ID.String(), form.TagName, form.Message); err != nil { | ||||||
|  | 		if models.IsErrTagAlreadyExists(err) { | ||||||
|  | 			ctx.Error(http.StatusConflict, "tag exist", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ctx.InternalServerError(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tag, err := ctx.Repo.GitRepo.GetTag(form.TagName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.InternalServerError(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.JSON(http.StatusCreated, convert.ToTag(ctx.Repo.Repository, tag)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -158,4 +158,7 @@ type swaggerParameterBodies struct { | ||||||
| 
 | 
 | ||||||
| 	// in:body
 | 	// in:body
 | ||||||
| 	PullReviewRequestOptions api.PullReviewRequestOptions | 	PullReviewRequestOptions api.PullReviewRequestOptions | ||||||
|  | 
 | ||||||
|  | 	// in:body
 | ||||||
|  | 	CreateTagOption api.CreateTagOption | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9082,6 +9082,50 @@ | ||||||
|             "$ref": "#/responses/TagList" |             "$ref": "#/responses/TagList" | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  |       }, | ||||||
|  |       "post": { | ||||||
|  |         "produces": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "tags": [ | ||||||
|  |           "repository" | ||||||
|  |         ], | ||||||
|  |         "summary": "Create a new git tag in a repository", | ||||||
|  |         "operationId": "repoCreateTag", | ||||||
|  |         "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 | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "name": "body", | ||||||
|  |             "in": "body", | ||||||
|  |             "schema": { | ||||||
|  |               "$ref": "#/definitions/CreateTagOption" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         ], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "$ref": "#/responses/AnnotatedTag" | ||||||
|  |           }, | ||||||
|  |           "404": { | ||||||
|  |             "$ref": "#/responses/notFound" | ||||||
|  |           }, | ||||||
|  |           "409": { | ||||||
|  |             "$ref": "#/responses/conflict" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "/repos/{owner}/{repo}/tags/{tag}": { |     "/repos/{owner}/{repo}/tags/{tag}": { | ||||||
|  | @ -13092,6 +13136,28 @@ | ||||||
|       }, |       }, | ||||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" |       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||||
|     }, |     }, | ||||||
|  |     "CreateTagOption": { | ||||||
|  |       "description": "CreateTagOption options when creating a tag", | ||||||
|  |       "type": "object", | ||||||
|  |       "required": [ | ||||||
|  |         "tag_name" | ||||||
|  |       ], | ||||||
|  |       "properties": { | ||||||
|  |         "message": { | ||||||
|  |           "type": "string", | ||||||
|  |           "x-go-name": "Message" | ||||||
|  |         }, | ||||||
|  |         "tag_name": { | ||||||
|  |           "type": "string", | ||||||
|  |           "x-go-name": "TagName" | ||||||
|  |         }, | ||||||
|  |         "target": { | ||||||
|  |           "type": "string", | ||||||
|  |           "x-go-name": "Target" | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||||
|  |     }, | ||||||
|     "CreateTeamOption": { |     "CreateTeamOption": { | ||||||
|       "description": "CreateTeamOption options for creating a team", |       "description": "CreateTeamOption options for creating a team", | ||||||
|       "type": "object", |       "type": "object", | ||||||
|  | @ -16149,6 +16215,10 @@ | ||||||
|           "type": "string", |           "type": "string", | ||||||
|           "x-go-name": "ID" |           "x-go-name": "ID" | ||||||
|         }, |         }, | ||||||
|  |         "message": { | ||||||
|  |           "type": "string", | ||||||
|  |           "x-go-name": "Message" | ||||||
|  |         }, | ||||||
|         "name": { |         "name": { | ||||||
|           "type": "string", |           "type": "string", | ||||||
|           "x-go-name": "Name" |           "x-go-name": "Name" | ||||||
|  | @ -17265,7 +17335,7 @@ | ||||||
|     "parameterBodies": { |     "parameterBodies": { | ||||||
|       "description": "parameterBodies", |       "description": "parameterBodies", | ||||||
|       "schema": { |       "schema": { | ||||||
|         "$ref": "#/definitions/PullReviewRequestOptions" |         "$ref": "#/definitions/CreateTagOption" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "redirect": { |     "redirect": { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue