From f7cd394680f885061144d236abc3c25f30be3147 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 17 Jun 2021 18:04:10 +0200 Subject: [PATCH] [API] Add repoCreateTag (#16165) * Add API CreateTag * Add Test * API: expose Tag Message --- integrations/api_repo_tags_test.go | 35 ++++++++++++++- modules/convert/convert.go | 2 + modules/structs/repo_tag.go | 9 ++++ routers/api/v1/api.go | 1 + routers/api/v1/repo/tag.go | 61 +++++++++++++++++++++++++ routers/api/v1/swagger/options.go | 3 ++ templates/swagger/v1_json.tmpl | 72 +++++++++++++++++++++++++++++- 7 files changed, 180 insertions(+), 3 deletions(-) diff --git a/integrations/api_repo_tags_test.go b/integrations/api_repo_tags_test.go index 1ffec576d8..1bd9fa6168 100644 --- a/integrations/api_repo_tags_test.go +++ b/integrations/api_repo_tags_test.go @@ -5,6 +5,7 @@ package integrations import ( + "fmt" "net/http" "testing" @@ -15,14 +16,16 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIReposGetTags(t *testing.T) { +func TestAPIRepoTags(t *testing.T) { defer prepareTestEnv(t)() user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // Login as User2. session := loginUser(t, user.Name) 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) var tags []*api.Tag @@ -30,8 +33,36 @@ func TestAPIReposGetTags(t *testing.T) { assert.Len(t, tags, 1) 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, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.URL) assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL) assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL) + + 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 } diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 109931dbc3..0b2135c580 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -8,6 +8,7 @@ package convert import ( "fmt" "strconv" + "strings" "time" "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 { return &api.Tag{ Name: t.Name, + Message: strings.TrimSpace(t.Message), ID: t.ID.String(), Commit: ToCommitMeta(repo, t), ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), diff --git a/modules/structs/repo_tag.go b/modules/structs/repo_tag.go index b62395cac4..80ee1ccf17 100644 --- a/modules/structs/repo_tag.go +++ b/modules/structs/repo_tag.go @@ -7,6 +7,7 @@ package structs // Tag represents a repository tag type Tag struct { Name string `json:"name"` + Message string `json:"message"` ID string `json:"id"` Commit *CommitMeta `json:"commit"` ZipballURL string `json:"zipball_url"` @@ -30,3 +31,11 @@ type AnnotatedTagObject struct { URL string `json:"url"` 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"` +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 0b47953e58..34cf80e072 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -775,6 +775,7 @@ func Routes() *web.Route { }, reqToken(), reqAdmin()) m.Group("/tags", func() { m.Get("", repo.ListTags) + m.Post("", reqRepoWriter(models.UnitTypeCode), bind(api.CreateTagOption{}), repo.CreateTag) m.Delete("/{tag}", repo.DeleteTag) }, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true)) m.Group("/keys", func() { diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index ec9b541bd4..51ba43ea89 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -6,12 +6,14 @@ package repo import ( "errors" + "fmt" "net/http" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" releaseservice "code.gitea.io/gitea/services/release" ) @@ -160,3 +162,62 @@ func DeleteTag(ctx *context.APIContext) { 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)) +} diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index dad025710d..11158fb86d 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -158,4 +158,7 @@ type swaggerParameterBodies struct { // in:body PullReviewRequestOptions api.PullReviewRequestOptions + + // in:body + CreateTagOption api.CreateTagOption } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 9b0d07ebfc..017dc824d7 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -9082,6 +9082,50 @@ "$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}": { @@ -13092,6 +13136,28 @@ }, "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": { "description": "CreateTeamOption options for creating a team", "type": "object", @@ -16149,6 +16215,10 @@ "type": "string", "x-go-name": "ID" }, + "message": { + "type": "string", + "x-go-name": "Message" + }, "name": { "type": "string", "x-go-name": "Name" @@ -17265,7 +17335,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/PullReviewRequestOptions" + "$ref": "#/definitions/CreateTagOption" } }, "redirect": {