Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API] Add delete release by tag & fix unreleased inconsistency #14563

20 changes: 10 additions & 10 deletions integrations/api_releases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,25 +154,25 @@ func TestAPIGetReleaseByTag(t *testing.T) {
assert.EqualValues(t, "Not Found", err.Message)
}

func TestAPIDeleteTagByName(t *testing.T) {
func TestAPIDeleteReleaseByTagName(t *testing.T) {
defer prepareTestEnv(t)()

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

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

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

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

req = NewRequestf(t, http.MethodDelete, urlStr)
_ = session.MakeRequest(t, req, http.StatusConflict)
// delete release tag too
req = NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s", owner.Name, repo.Name, token))
_ = session.MakeRequest(t, req, http.StatusNoContent)
}
24 changes: 24 additions & 0 deletions integrations/api_repo_git_tags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package integrations

import (
"fmt"
"net/http"
"testing"

Expand Down Expand Up @@ -59,3 +60,26 @@ func TestAPIGitTags(t *testing.T) {
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)
}

func TestAPIDeleteTagByName(t *testing.T) {
defer prepareTestEnv(t)()

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

urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/delete-tag?token=%s",
owner.Name, repo.Name, token)

req := NewRequestf(t, http.MethodDelete, urlStr)
_ = session.MakeRequest(t, req, http.StatusNoContent)

// Make sure that actual releases can't be deleted outright
createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s",
owner.Name, repo.Name, token)

req = NewRequestf(t, http.MethodDelete, urlStr)
_ = session.MakeRequest(t, req, http.StatusConflict)
}
5 changes: 3 additions & 2 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,7 @@ func Routes() *web.Route {
}, reqToken(), reqAdmin())
m.Group("/tags", func() {
m.Get("", repo.ListTags)
m.Delete("/{tag}", repo.DeleteTag)
}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true))
m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys).
Expand Down Expand Up @@ -862,8 +863,8 @@ func Routes() *web.Route {
})
m.Group("/tags", func() {
m.Combo("/{tag}").
Get(repo.GetReleaseTag).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseTag)
Get(repo.GetReleaseByTag).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseByTag)
})
}, reqRepoReader(models.UnitTypeReleases))
m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync)
Expand Down
29 changes: 13 additions & 16 deletions routers/api/v1/repo/release_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package repo

import (
"errors"
"net/http"

"code.gitea.io/gitea/models"
Expand All @@ -14,9 +13,9 @@ import (
releaseservice "code.gitea.io/gitea/services/release"
)

// GetReleaseTag get a single release of a repository by its tagname
func GetReleaseTag(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/releases/tags/{tag} repository repoGetReleaseTag
// GetReleaseByTag get a single release of a repository by tag name
func GetReleaseByTag(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/releases/tags/{tag} repository repoGetReleaseByTag
// ---
// summary: Get a release by tag name
// produces:
Expand All @@ -34,7 +33,7 @@ func GetReleaseTag(ctx *context.APIContext) {
// required: true
// - name: tag
// in: path
// description: tagname of the release to get
// description: tag name of the release to get
// type: string
// required: true
// responses:
Expand Down Expand Up @@ -67,11 +66,11 @@ func GetReleaseTag(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, convert.ToRelease(release))
}

// DeleteReleaseTag delete a tag from a repository
func DeleteReleaseTag(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/releases/tags/{tag} repository repoDeleteReleaseTag
// DeleteReleaseByTag delete a release from a repository by tag name
func DeleteReleaseByTag(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/releases/tags/{tag} repository repoDeleteReleaseByTag
// ---
// summary: Delete a release tag
// summary: Delete a release by tag name
// parameters:
// - name: owner
// in: path
Expand All @@ -85,35 +84,33 @@ func DeleteReleaseTag(ctx *context.APIContext) {
// required: true
// - name: tag
// in: path
// description: name of the tag to delete
// description: tag name of the release to delete
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "409":
// "$ref": "#/responses/conflict"

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

release, err := models.GetRelease(ctx.Repo.Repository.ID, tag)
if err != nil {
if models.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusNotFound, "GetRelease", err)
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetRelease", err)
return
}

if !release.IsTag {
ctx.Error(http.StatusConflict, "IsTag", errors.New("a tag attached to a release cannot be deleted directly"))
if release.IsTag {
ctx.NotFound()
return
}

if err := releaseservice.DeleteReleaseByID(release.ID, ctx.User, true); err != nil {
if err = releaseservice.DeleteReleaseByID(release.ID, ctx.User, false); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
}

Expand Down
56 changes: 56 additions & 0 deletions routers/api/v1/repo/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
package repo

import (
"errors"
"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/routers/api/v1/utils"
releaseservice "code.gitea.io/gitea/services/release"
)

// ListTags list all the tags of a repository
Expand Down Expand Up @@ -104,3 +107,56 @@ func GetTag(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx.Repo.Repository, tag, commit))
}
}

// DeleteTag delete a specific tag of in a repository by name
func DeleteTag(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/tags/{tag} repository repoDeleteTag
// ---
// summary: Delete a repository's tag by name
// 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: tag
// in: path
// description: name of tag to delete
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "409":
// "$ref": "#/responses/conflict"

tag, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag"))
if err != nil {
if models.IsErrReleaseNotExist(err) {
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetRelease", err)
return
}

if !tag.IsTag {
ctx.Error(http.StatusConflict, "IsTag", errors.New("a tag attached to a release cannot be deleted directly"))
return
}

if err = releaseservice.DeleteReleaseByID(tag.ID, ctx.User, true); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
}

ctx.Status(http.StatusNoContent)
}
59 changes: 51 additions & 8 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7964,7 +7964,7 @@
"repository"
],
"summary": "Get a release by tag name",
"operationId": "repoGetReleaseTag",
"operationId": "repoGetReleaseByTag",
"parameters": [
{
"type": "string",
Expand All @@ -7982,7 +7982,7 @@
},
{
"type": "string",
"description": "tagname of the release to get",
"description": "tag name of the release to get",
"name": "tag",
"in": "path",
"required": true
Expand All @@ -8001,8 +8001,8 @@
"tags": [
"repository"
],
"summary": "Delete a release tag",
"operationId": "repoDeleteReleaseTag",
"summary": "Delete a release by tag name",
"operationId": "repoDeleteReleaseByTag",
"parameters": [
{
"type": "string",
Expand All @@ -8020,7 +8020,7 @@
},
{
"type": "string",
"description": "name of the tag to delete",
"description": "tag name of the release to delete",
"name": "tag",
"in": "path",
"required": true
Expand All @@ -8032,9 +8032,6 @@
},
"404": {
"$ref": "#/responses/notFound"
},
"409": {
"$ref": "#/responses/conflict"
}
}
}
Expand Down Expand Up @@ -8815,6 +8812,52 @@
}
}
},
"/repos/{owner}/{repo}/tags/{tag}": {
"delete": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Delete a repository's tag by name",
"operationId": "repoDeleteTag",
"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": "name of tag to delete",
"name": "tag",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"$ref": "#/responses/empty"
},
"404": {
"$ref": "#/responses/notFound"
},
"409": {
"$ref": "#/responses/conflict"
}
}
}
},
"/repos/{owner}/{repo}/teams": {
"get": {
"produces": [
Expand Down