Delete tag API (#13358)

* Delete tag API

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Wording

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Add conflict response and fix API tests

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix other test

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
John Olheiser 2020-10-30 20:56:34 -05:00 committed by GitHub
parent e16a5bb634
commit b5e974c8a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 149 additions and 5 deletions

View File

@ -154,3 +154,26 @@ func TestAPIGetReleaseByTag(t *testing.T) {
DecodeJSON(t, resp, &err) DecodeJSON(t, resp, &err)
assert.True(t, strings.HasPrefix(err.Message, "release tag does not exist")) assert.True(t, strings.HasPrefix(err.Message, "release tag does not exist"))
} }
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/releases/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/releases/tags/release-tag/?token=%s",
owner.Name, repo.Name, token)
req = NewRequestf(t, http.MethodDelete, urlStr)
_ = session.MakeRequest(t, req, http.StatusConflict)
}

View File

@ -223,7 +223,7 @@ func TestAPIViewRepo(t *testing.T) {
DecodeJSON(t, resp, &repo) DecodeJSON(t, resp, &repo)
assert.EqualValues(t, 1, repo.ID) assert.EqualValues(t, 1, repo.ID)
assert.EqualValues(t, "repo1", repo.Name) assert.EqualValues(t, "repo1", repo.Name)
assert.EqualValues(t, 1, repo.Releases) assert.EqualValues(t, 2, repo.Releases)
assert.EqualValues(t, 1, repo.OpenIssues) assert.EqualValues(t, 1, repo.OpenIssues)
assert.EqualValues(t, 3, repo.OpenPulls) assert.EqualValues(t, 3, repo.OpenPulls)

View File

@ -83,7 +83,7 @@ func TestCreateRelease(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false) createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 2) checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 3)
} }
func TestCreateReleasePreRelease(t *testing.T) { func TestCreateReleasePreRelease(t *testing.T) {
@ -92,7 +92,7 @@ func TestCreateReleasePreRelease(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false) createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 2) checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 3)
} }
func TestCreateReleaseDraft(t *testing.T) { func TestCreateReleaseDraft(t *testing.T) {
@ -101,7 +101,7 @@ func TestCreateReleaseDraft(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true) createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 2) checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 3)
} }
func TestCreateReleasePaging(t *testing.T) { func TestCreateReleasePaging(t *testing.T) {

View File

@ -27,3 +27,19 @@
is_prerelease: false is_prerelease: false
is_tag: false is_tag: false
created_unix: 946684800 created_unix: 946684800
-
id: 3
repo_id: 1
publisher_id: 2
tag_name: "delete-tag"
lower_tag_name: "delete-tag"
target: "master"
title: "delete-tag"
sha1: "65f1bf27bc3bf70f64657658635e66094edbcb4d"
num_commits: 10
is_draft: false
is_prerelease: false
is_tag: true
created_unix: 946684800

View File

@ -61,6 +61,10 @@ type APIForbiddenError struct {
// swagger:response notFound // swagger:response notFound
type APINotFound struct{} type APINotFound struct{}
//APIConflict is a conflict empty response
// swagger:response conflict
type APIConflict struct{}
//APIRedirect is a redirect response //APIRedirect is a redirect response
// swagger:response redirect // swagger:response redirect
type APIRedirect struct{} type APIRedirect struct{}

View File

@ -798,7 +798,9 @@ func RegisterRoutes(m *macaron.Macaron) {
}) })
}) })
m.Group("/tags", func() { m.Group("/tags", func() {
m.Get("/:tag", repo.GetReleaseTag) m.Combo("/:tag").
Get(repo.GetReleaseTag).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseTag)
}) })
}, reqRepoReader(models.UnitTypeReleases)) }, reqRepoReader(models.UnitTypeReleases))
m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync) m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync)

View File

@ -5,11 +5,13 @@
package repo package repo
import ( import (
"errors"
"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"
releaseservice "code.gitea.io/gitea/services/release"
) )
// GetReleaseTag get a single release of a repository by its tagname // GetReleaseTag get a single release of a repository by its tagname
@ -59,3 +61,56 @@ func GetReleaseTag(ctx *context.APIContext) {
} }
ctx.JSON(http.StatusOK, convert.ToRelease(release)) 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
// ---
// summary: Delete a release tag
// 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 the tag 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)
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"))
return
}
if err := releaseservice.DeleteReleaseByID(release.ID, ctx.User, true); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
}
ctx.Status(http.StatusNoContent)
}

View File

@ -7834,6 +7834,47 @@
"$ref": "#/responses/notFound" "$ref": "#/responses/notFound"
} }
} }
},
"delete": {
"tags": [
"repository"
],
"summary": "Delete a release tag",
"operationId": "repoDeleteReleaseTag",
"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 the 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}/releases/{id}": { "/repos/{owner}/{repo}/releases/{id}": {
@ -16249,6 +16290,9 @@
"$ref": "#/definitions/WatchInfo" "$ref": "#/definitions/WatchInfo"
} }
}, },
"conflict": {
"description": "APIConflict is a conflict empty response"
},
"empty": { "empty": {
"description": "APIEmpty is an empty response" "description": "APIEmpty is an empty response"
}, },