Skip to content

Commit f7cd394

Browse files
authored
[API] Add repoCreateTag (#16165)
* Add API CreateTag * Add Test * API: expose Tag Message
1 parent 19dedc3 commit f7cd394

File tree

7 files changed

+180
-3
lines changed

7 files changed

+180
-3
lines changed

integrations/api_repo_tags_test.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package integrations
66

77
import (
8+
"fmt"
89
"net/http"
910
"testing"
1011

@@ -15,23 +16,53 @@ import (
1516
"github.com/stretchr/testify/assert"
1617
)
1718

18-
func TestAPIReposGetTags(t *testing.T) {
19+
func TestAPIRepoTags(t *testing.T) {
1920
defer prepareTestEnv(t)()
2021
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
2122
// Login as User2.
2223
session := loginUser(t, user.Name)
2324
token := getTokenForLoggedInUser(t, session)
2425

25-
req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/tags?token="+token, user.Name)
26+
repoName := "repo1"
27+
28+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/tags?token=%s", user.Name, repoName, token)
2629
resp := session.MakeRequest(t, req, http.StatusOK)
2730

2831
var tags []*api.Tag
2932
DecodeJSON(t, resp, &tags)
3033

3134
assert.Len(t, tags, 1)
3235
assert.Equal(t, "v1.1", tags[0].Name)
36+
assert.Equal(t, "Initial commit", tags[0].Message)
3337
assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA)
3438
assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.URL)
3539
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL)
3640
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL)
41+
42+
newTag := createNewTagUsingAPI(t, session, token, user.Name, repoName, "awesome-tag", "", "nice!\nand some text")
43+
resp = session.MakeRequest(t, req, http.StatusOK)
44+
DecodeJSON(t, resp, &tags)
45+
assert.Len(t, tags, 2)
46+
for _, tag := range tags {
47+
if tag.Name != "v1.1" {
48+
assert.EqualValues(t, newTag.Name, tag.Name)
49+
assert.EqualValues(t, newTag.Message, tag.Message)
50+
assert.EqualValues(t, "nice!\nand some text", tag.Message)
51+
assert.EqualValues(t, newTag.Commit.SHA, tag.Commit.SHA)
52+
}
53+
}
54+
}
55+
56+
func createNewTagUsingAPI(t *testing.T, session *TestSession, token string, ownerName, repoName, name, target, msg string) *api.Tag {
57+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags?token=%s", ownerName, repoName, token)
58+
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateTagOption{
59+
TagName: name,
60+
Message: msg,
61+
Target: target,
62+
})
63+
resp := session.MakeRequest(t, req, http.StatusCreated)
64+
65+
var respObj api.Tag
66+
DecodeJSON(t, resp, &respObj)
67+
return &respObj
3768
}

modules/convert/convert.go

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package convert
88
import (
99
"fmt"
1010
"strconv"
11+
"strings"
1112
"time"
1213

1314
"code.gitea.io/gitea/models"
@@ -135,6 +136,7 @@ func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection {
135136
func ToTag(repo *models.Repository, t *git.Tag) *api.Tag {
136137
return &api.Tag{
137138
Name: t.Name,
139+
Message: strings.TrimSpace(t.Message),
138140
ID: t.ID.String(),
139141
Commit: ToCommitMeta(repo, t),
140142
ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"),

modules/structs/repo_tag.go

+9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package structs
77
// Tag represents a repository tag
88
type Tag struct {
99
Name string `json:"name"`
10+
Message string `json:"message"`
1011
ID string `json:"id"`
1112
Commit *CommitMeta `json:"commit"`
1213
ZipballURL string `json:"zipball_url"`
@@ -30,3 +31,11 @@ type AnnotatedTagObject struct {
3031
URL string `json:"url"`
3132
SHA string `json:"sha"`
3233
}
34+
35+
// CreateTagOption options when creating a tag
36+
type CreateTagOption struct {
37+
// required: true
38+
TagName string `json:"tag_name" binding:"Required"`
39+
Message string `json:"message"`
40+
Target string `json:"target"`
41+
}

routers/api/v1/api.go

+1
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ func Routes() *web.Route {
775775
}, reqToken(), reqAdmin())
776776
m.Group("/tags", func() {
777777
m.Get("", repo.ListTags)
778+
m.Post("", reqRepoWriter(models.UnitTypeCode), bind(api.CreateTagOption{}), repo.CreateTag)
778779
m.Delete("/{tag}", repo.DeleteTag)
779780
}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true))
780781
m.Group("/keys", func() {

routers/api/v1/repo/tag.go

+61
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ package repo
66

77
import (
88
"errors"
9+
"fmt"
910
"net/http"
1011

1112
"code.gitea.io/gitea/models"
1213
"code.gitea.io/gitea/modules/context"
1314
"code.gitea.io/gitea/modules/convert"
1415
api "code.gitea.io/gitea/modules/structs"
16+
"code.gitea.io/gitea/modules/web"
1517
"code.gitea.io/gitea/routers/api/v1/utils"
1618
releaseservice "code.gitea.io/gitea/services/release"
1719
)
@@ -160,3 +162,62 @@ func DeleteTag(ctx *context.APIContext) {
160162

161163
ctx.Status(http.StatusNoContent)
162164
}
165+
166+
// CreateTag create a new git tag in a repository
167+
func CreateTag(ctx *context.APIContext) {
168+
// swagger:operation POST /repos/{owner}/{repo}/tags repository repoCreateTag
169+
// ---
170+
// summary: Create a new git tag in a repository
171+
// produces:
172+
// - application/json
173+
// parameters:
174+
// - name: owner
175+
// in: path
176+
// description: owner of the repo
177+
// type: string
178+
// required: true
179+
// - name: repo
180+
// in: path
181+
// description: name of the repo
182+
// type: string
183+
// required: true
184+
// - name: body
185+
// in: body
186+
// schema:
187+
// "$ref": "#/definitions/CreateTagOption"
188+
// responses:
189+
// "200":
190+
// "$ref": "#/responses/AnnotatedTag"
191+
// "404":
192+
// "$ref": "#/responses/notFound"
193+
// "409":
194+
// "$ref": "#/responses/conflict"
195+
form := web.GetForm(ctx).(*api.CreateTagOption)
196+
197+
// If target is not provided use default branch
198+
if len(form.Target) == 0 {
199+
form.Target = ctx.Repo.Repository.DefaultBranch
200+
}
201+
202+
commit, err := ctx.Repo.GitRepo.GetCommit(form.Target)
203+
if err != nil {
204+
ctx.Error(http.StatusNotFound, "target not found", fmt.Errorf("target not found: %v", err))
205+
return
206+
}
207+
208+
if err := releaseservice.CreateNewTag(ctx.User, ctx.Repo.Repository, commit.ID.String(), form.TagName, form.Message); err != nil {
209+
if models.IsErrTagAlreadyExists(err) {
210+
ctx.Error(http.StatusConflict, "tag exist", err)
211+
return
212+
}
213+
ctx.InternalServerError(err)
214+
return
215+
}
216+
217+
tag, err := ctx.Repo.GitRepo.GetTag(form.TagName)
218+
if err != nil {
219+
ctx.InternalServerError(err)
220+
return
221+
}
222+
ctx.JSON(http.StatusCreated, convert.ToTag(ctx.Repo.Repository, tag))
223+
}

routers/api/v1/swagger/options.go

+3
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,7 @@ type swaggerParameterBodies struct {
158158

159159
// in:body
160160
PullReviewRequestOptions api.PullReviewRequestOptions
161+
162+
// in:body
163+
CreateTagOption api.CreateTagOption
161164
}

templates/swagger/v1_json.tmpl

+71-1
Original file line numberDiff line numberDiff line change
@@ -9082,6 +9082,50 @@
90829082
"$ref": "#/responses/TagList"
90839083
}
90849084
}
9085+
},
9086+
"post": {
9087+
"produces": [
9088+
"application/json"
9089+
],
9090+
"tags": [
9091+
"repository"
9092+
],
9093+
"summary": "Create a new git tag in a repository",
9094+
"operationId": "repoCreateTag",
9095+
"parameters": [
9096+
{
9097+
"type": "string",
9098+
"description": "owner of the repo",
9099+
"name": "owner",
9100+
"in": "path",
9101+
"required": true
9102+
},
9103+
{
9104+
"type": "string",
9105+
"description": "name of the repo",
9106+
"name": "repo",
9107+
"in": "path",
9108+
"required": true
9109+
},
9110+
{
9111+
"name": "body",
9112+
"in": "body",
9113+
"schema": {
9114+
"$ref": "#/definitions/CreateTagOption"
9115+
}
9116+
}
9117+
],
9118+
"responses": {
9119+
"200": {
9120+
"$ref": "#/responses/AnnotatedTag"
9121+
},
9122+
"404": {
9123+
"$ref": "#/responses/notFound"
9124+
},
9125+
"409": {
9126+
"$ref": "#/responses/conflict"
9127+
}
9128+
}
90859129
}
90869130
},
90879131
"/repos/{owner}/{repo}/tags/{tag}": {
@@ -13092,6 +13136,28 @@
1309213136
},
1309313137
"x-go-package": "code.gitea.io/gitea/modules/structs"
1309413138
},
13139+
"CreateTagOption": {
13140+
"description": "CreateTagOption options when creating a tag",
13141+
"type": "object",
13142+
"required": [
13143+
"tag_name"
13144+
],
13145+
"properties": {
13146+
"message": {
13147+
"type": "string",
13148+
"x-go-name": "Message"
13149+
},
13150+
"tag_name": {
13151+
"type": "string",
13152+
"x-go-name": "TagName"
13153+
},
13154+
"target": {
13155+
"type": "string",
13156+
"x-go-name": "Target"
13157+
}
13158+
},
13159+
"x-go-package": "code.gitea.io/gitea/modules/structs"
13160+
},
1309513161
"CreateTeamOption": {
1309613162
"description": "CreateTeamOption options for creating a team",
1309713163
"type": "object",
@@ -16149,6 +16215,10 @@
1614916215
"type": "string",
1615016216
"x-go-name": "ID"
1615116217
},
16218+
"message": {
16219+
"type": "string",
16220+
"x-go-name": "Message"
16221+
},
1615216222
"name": {
1615316223
"type": "string",
1615416224
"x-go-name": "Name"
@@ -17265,7 +17335,7 @@
1726517335
"parameterBodies": {
1726617336
"description": "parameterBodies",
1726717337
"schema": {
17268-
"$ref": "#/definitions/PullReviewRequestOptions"
17338+
"$ref": "#/definitions/CreateTagOption"
1726917339
}
1727017340
},
1727117341
"redirect": {

0 commit comments

Comments
 (0)