* 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>tags/v1.15.0-dev
@@ -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) | |||||
} |
@@ -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) | ||||
@@ -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) { | ||||
@@ -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 | |||||
@@ -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{} | ||||
@@ -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) | ||||
@@ -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) | |||||
} |
@@ -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" | ||||
}, | }, | ||||