Skip to content

Commit 5f248d0

Browse files
authored
[API] Add delete release by tag & fix unreleased inconsistency (#14563)
* DeleteReleaseByTag delete release not git tags * Add api to delete tag (without release) * fix & extend tests * fix swagger doc
1 parent 240fea8 commit 5f248d0

File tree

6 files changed

+157
-36
lines changed

6 files changed

+157
-36
lines changed

integrations/api_releases_test.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -154,25 +154,25 @@ func TestAPIGetReleaseByTag(t *testing.T) {
154154
assert.EqualValues(t, "Not Found", err.Message)
155155
}
156156

157-
func TestAPIDeleteTagByName(t *testing.T) {
157+
func TestAPIDeleteReleaseByTagName(t *testing.T) {
158158
defer prepareTestEnv(t)()
159159

160160
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
161161
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
162162
session := loginUser(t, owner.LowerName)
163163
token := getTokenForLoggedInUser(t, session)
164164

165-
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/delete-tag?token=%s",
166-
owner.Name, repo.Name, token)
165+
createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
167166

168-
req := NewRequestf(t, http.MethodDelete, urlStr)
167+
// delete release
168+
req := NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag?token=%s", owner.Name, repo.Name, token))
169169
_ = session.MakeRequest(t, req, http.StatusNoContent)
170170

171-
// Make sure that actual releases can't be deleted outright
172-
createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
173-
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag?token=%s",
174-
owner.Name, repo.Name, token)
171+
// make sure release is deleted
172+
req = NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag?token=%s", owner.Name, repo.Name, token))
173+
_ = session.MakeRequest(t, req, http.StatusNotFound)
175174

176-
req = NewRequestf(t, http.MethodDelete, urlStr)
177-
_ = session.MakeRequest(t, req, http.StatusConflict)
175+
// delete release tag too
176+
req = NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s", owner.Name, repo.Name, token))
177+
_ = session.MakeRequest(t, req, http.StatusNoContent)
178178
}

integrations/api_repo_git_tags_test.go

+24
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

@@ -59,3 +60,26 @@ func TestAPIGitTags(t *testing.T) {
5960
badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s?token=%s", user.Name, repo.Name, commit.ID.String(), token)
6061
session.MakeRequest(t, badReq, http.StatusBadRequest)
6162
}
63+
64+
func TestAPIDeleteTagByName(t *testing.T) {
65+
defer prepareTestEnv(t)()
66+
67+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
68+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
69+
session := loginUser(t, owner.LowerName)
70+
token := getTokenForLoggedInUser(t, session)
71+
72+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/delete-tag?token=%s",
73+
owner.Name, repo.Name, token)
74+
75+
req := NewRequestf(t, http.MethodDelete, urlStr)
76+
_ = session.MakeRequest(t, req, http.StatusNoContent)
77+
78+
// Make sure that actual releases can't be deleted outright
79+
createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
80+
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s",
81+
owner.Name, repo.Name, token)
82+
83+
req = NewRequestf(t, http.MethodDelete, urlStr)
84+
_ = session.MakeRequest(t, req, http.StatusConflict)
85+
}

routers/api/v1/api.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ func Routes() *web.Route {
754754
}, reqToken(), reqAdmin())
755755
m.Group("/tags", func() {
756756
m.Get("", repo.ListTags)
757+
m.Delete("/{tag}", repo.DeleteTag)
757758
}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true))
758759
m.Group("/keys", func() {
759760
m.Combo("").Get(repo.ListDeployKeys).
@@ -862,8 +863,8 @@ func Routes() *web.Route {
862863
})
863864
m.Group("/tags", func() {
864865
m.Combo("/{tag}").
865-
Get(repo.GetReleaseTag).
866-
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseTag)
866+
Get(repo.GetReleaseByTag).
867+
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseByTag)
867868
})
868869
}, reqRepoReader(models.UnitTypeReleases))
869870
m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync)

routers/api/v1/repo/release_tags.go

+13-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package repo
66

77
import (
8-
"errors"
98
"net/http"
109

1110
"code.gitea.io/gitea/models"
@@ -14,9 +13,9 @@ import (
1413
releaseservice "code.gitea.io/gitea/services/release"
1514
)
1615

17-
// GetReleaseTag get a single release of a repository by its tagname
18-
func GetReleaseTag(ctx *context.APIContext) {
19-
// swagger:operation GET /repos/{owner}/{repo}/releases/tags/{tag} repository repoGetReleaseTag
16+
// GetReleaseByTag get a single release of a repository by tag name
17+
func GetReleaseByTag(ctx *context.APIContext) {
18+
// swagger:operation GET /repos/{owner}/{repo}/releases/tags/{tag} repository repoGetReleaseByTag
2019
// ---
2120
// summary: Get a release by tag name
2221
// produces:
@@ -34,7 +33,7 @@ func GetReleaseTag(ctx *context.APIContext) {
3433
// required: true
3534
// - name: tag
3635
// in: path
37-
// description: tagname of the release to get
36+
// description: tag name of the release to get
3837
// type: string
3938
// required: true
4039
// responses:
@@ -67,11 +66,11 @@ func GetReleaseTag(ctx *context.APIContext) {
6766
ctx.JSON(http.StatusOK, convert.ToRelease(release))
6867
}
6968

70-
// DeleteReleaseTag delete a tag from a repository
71-
func DeleteReleaseTag(ctx *context.APIContext) {
72-
// swagger:operation DELETE /repos/{owner}/{repo}/releases/tags/{tag} repository repoDeleteReleaseTag
69+
// DeleteReleaseByTag delete a release from a repository by tag name
70+
func DeleteReleaseByTag(ctx *context.APIContext) {
71+
// swagger:operation DELETE /repos/{owner}/{repo}/releases/tags/{tag} repository repoDeleteReleaseByTag
7372
// ---
74-
// summary: Delete a release tag
73+
// summary: Delete a release by tag name
7574
// parameters:
7675
// - name: owner
7776
// in: path
@@ -85,35 +84,33 @@ func DeleteReleaseTag(ctx *context.APIContext) {
8584
// required: true
8685
// - name: tag
8786
// in: path
88-
// description: name of the tag to delete
87+
// description: tag name of the release to delete
8988
// type: string
9089
// required: true
9190
// responses:
9291
// "204":
9392
// "$ref": "#/responses/empty"
9493
// "404":
9594
// "$ref": "#/responses/notFound"
96-
// "409":
97-
// "$ref": "#/responses/conflict"
9895

9996
tag := ctx.Params(":tag")
10097

10198
release, err := models.GetRelease(ctx.Repo.Repository.ID, tag)
10299
if err != nil {
103100
if models.IsErrReleaseNotExist(err) {
104-
ctx.Error(http.StatusNotFound, "GetRelease", err)
101+
ctx.NotFound()
105102
return
106103
}
107104
ctx.Error(http.StatusInternalServerError, "GetRelease", err)
108105
return
109106
}
110107

111-
if !release.IsTag {
112-
ctx.Error(http.StatusConflict, "IsTag", errors.New("a tag attached to a release cannot be deleted directly"))
108+
if release.IsTag {
109+
ctx.NotFound()
113110
return
114111
}
115112

116-
if err := releaseservice.DeleteReleaseByID(release.ID, ctx.User, true); err != nil {
113+
if err = releaseservice.DeleteReleaseByID(release.ID, ctx.User, false); err != nil {
117114
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
118115
}
119116

routers/api/v1/repo/tag.go

+56
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
package repo
66

77
import (
8+
"errors"
89
"net/http"
910

11+
"code.gitea.io/gitea/models"
1012
"code.gitea.io/gitea/modules/context"
1113
"code.gitea.io/gitea/modules/convert"
1214
api "code.gitea.io/gitea/modules/structs"
1315
"code.gitea.io/gitea/routers/api/v1/utils"
16+
releaseservice "code.gitea.io/gitea/services/release"
1417
)
1518

1619
// ListTags list all the tags of a repository
@@ -104,3 +107,56 @@ func GetTag(ctx *context.APIContext) {
104107
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx.Repo.Repository, tag, commit))
105108
}
106109
}
110+
111+
// DeleteTag delete a specific tag of in a repository by name
112+
func DeleteTag(ctx *context.APIContext) {
113+
// swagger:operation DELETE /repos/{owner}/{repo}/tags/{tag} repository repoDeleteTag
114+
// ---
115+
// summary: Delete a repository's tag by name
116+
// produces:
117+
// - application/json
118+
// parameters:
119+
// - name: owner
120+
// in: path
121+
// description: owner of the repo
122+
// type: string
123+
// required: true
124+
// - name: repo
125+
// in: path
126+
// description: name of the repo
127+
// type: string
128+
// required: true
129+
// - name: tag
130+
// in: path
131+
// description: name of tag to delete
132+
// type: string
133+
// required: true
134+
// responses:
135+
// "204":
136+
// "$ref": "#/responses/empty"
137+
// "404":
138+
// "$ref": "#/responses/notFound"
139+
// "409":
140+
// "$ref": "#/responses/conflict"
141+
142+
tag, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag"))
143+
if err != nil {
144+
if models.IsErrReleaseNotExist(err) {
145+
ctx.NotFound()
146+
return
147+
}
148+
ctx.Error(http.StatusInternalServerError, "GetRelease", err)
149+
return
150+
}
151+
152+
if !tag.IsTag {
153+
ctx.Error(http.StatusConflict, "IsTag", errors.New("a tag attached to a release cannot be deleted directly"))
154+
return
155+
}
156+
157+
if err = releaseservice.DeleteReleaseByID(tag.ID, ctx.User, true); err != nil {
158+
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
159+
}
160+
161+
ctx.Status(http.StatusNoContent)
162+
}

templates/swagger/v1_json.tmpl

+51-8
Original file line numberDiff line numberDiff line change
@@ -7964,7 +7964,7 @@
79647964
"repository"
79657965
],
79667966
"summary": "Get a release by tag name",
7967-
"operationId": "repoGetReleaseTag",
7967+
"operationId": "repoGetReleaseByTag",
79687968
"parameters": [
79697969
{
79707970
"type": "string",
@@ -7982,7 +7982,7 @@
79827982
},
79837983
{
79847984
"type": "string",
7985-
"description": "tagname of the release to get",
7985+
"description": "tag name of the release to get",
79867986
"name": "tag",
79877987
"in": "path",
79887988
"required": true
@@ -8001,8 +8001,8 @@
80018001
"tags": [
80028002
"repository"
80038003
],
8004-
"summary": "Delete a release tag",
8005-
"operationId": "repoDeleteReleaseTag",
8004+
"summary": "Delete a release by tag name",
8005+
"operationId": "repoDeleteReleaseByTag",
80068006
"parameters": [
80078007
{
80088008
"type": "string",
@@ -8020,7 +8020,7 @@
80208020
},
80218021
{
80228022
"type": "string",
8023-
"description": "name of the tag to delete",
8023+
"description": "tag name of the release to delete",
80248024
"name": "tag",
80258025
"in": "path",
80268026
"required": true
@@ -8032,9 +8032,6 @@
80328032
},
80338033
"404": {
80348034
"$ref": "#/responses/notFound"
8035-
},
8036-
"409": {
8037-
"$ref": "#/responses/conflict"
80388035
}
80398036
}
80408037
}
@@ -8815,6 +8812,52 @@
88158812
}
88168813
}
88178814
},
8815+
"/repos/{owner}/{repo}/tags/{tag}": {
8816+
"delete": {
8817+
"produces": [
8818+
"application/json"
8819+
],
8820+
"tags": [
8821+
"repository"
8822+
],
8823+
"summary": "Delete a repository's tag by name",
8824+
"operationId": "repoDeleteTag",
8825+
"parameters": [
8826+
{
8827+
"type": "string",
8828+
"description": "owner of the repo",
8829+
"name": "owner",
8830+
"in": "path",
8831+
"required": true
8832+
},
8833+
{
8834+
"type": "string",
8835+
"description": "name of the repo",
8836+
"name": "repo",
8837+
"in": "path",
8838+
"required": true
8839+
},
8840+
{
8841+
"type": "string",
8842+
"description": "name of tag to delete",
8843+
"name": "tag",
8844+
"in": "path",
8845+
"required": true
8846+
}
8847+
],
8848+
"responses": {
8849+
"204": {
8850+
"$ref": "#/responses/empty"
8851+
},
8852+
"404": {
8853+
"$ref": "#/responses/notFound"
8854+
},
8855+
"409": {
8856+
"$ref": "#/responses/conflict"
8857+
}
8858+
}
8859+
}
8860+
},
88188861
"/repos/{owner}/{repo}/teams": {
88198862
"get": {
88208863
"produces": [

0 commit comments

Comments
 (0)