From 3c5a4d094a572b446435ccf7ebf75836c2d98c57 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 19 Apr 2020 04:38:09 +0200 Subject: [PATCH] [API] Add branch delete (#11112) * use same process as in routers/repo/branch.go/deleteBranch * make sure default branch can not be deleted * remove IsDefaultBranch from UI process - it is worth its own pull * permissions --- integrations/api_branch_test.go | 14 +++++ routers/api/v1/api.go | 1 + routers/api/v1/repo/branch.go | 101 ++++++++++++++++++++++++++++++++ templates/swagger/v1_json.tmpl | 41 +++++++++++++ 4 files changed, 157 insertions(+) diff --git a/integrations/api_branch_test.go b/integrations/api_branch_test.go index b6452a6ab..8417ab36c 100644 --- a/integrations/api_branch_test.go +++ b/integrations/api_branch_test.go @@ -80,6 +80,13 @@ func testAPIDeleteBranchProtection(t *testing.T, branchName string, expectedHTTP session.MakeRequest(t, req, expectedHTTPStatus) } +func testAPIDeleteBranch(t *testing.T, branchName string, expectedHTTPStatus int) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "DELETE", "/api/v1/repos/user2/repo1/branches/%s?token=%s", branchName, token) + session.MakeRequest(t, req, expectedHTTPStatus) +} + func TestAPIGetBranch(t *testing.T) { for _, test := range []struct { BranchName string @@ -106,10 +113,17 @@ func TestAPIBranchProtection(t *testing.T) { // Can only create once testAPICreateBranchProtection(t, "master", http.StatusForbidden) + // Can't delete a protected branch + testAPIDeleteBranch(t, "master", http.StatusForbidden) + testAPIGetBranchProtection(t, "master", http.StatusOK) testAPIEditBranchProtection(t, "master", &api.BranchProtection{ EnablePush: true, }, http.StatusOK) testAPIDeleteBranchProtection(t, "master", http.StatusNoContent) + + // Test branch deletion + testAPIDeleteBranch(t, "master", http.StatusForbidden) + testAPIDeleteBranch(t, "branch2", http.StatusNoContent) } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index bce3bf245..225f6a532 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -664,6 +664,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/branches", func() { m.Get("", repo.ListBranches) m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch) + m.Delete("/*", reqRepoWriter(models.UnitTypeCode), context.RepoRefByType(context.RepoRefBranch), repo.DeleteBranch) }, reqRepoReader(models.UnitTypeCode)) m.Group("/branch_protections", func() { m.Get("", repo.ListBranchProtections) diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 07c615950..57c74d7da 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -6,12 +6,15 @@ package repo import ( + "fmt" "net/http" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/repofiles" repo_module "code.gitea.io/gitea/modules/repository" api "code.gitea.io/gitea/modules/structs" ) @@ -81,6 +84,104 @@ func GetBranch(ctx *context.APIContext) { ctx.JSON(http.StatusOK, br) } +// DeleteBranch get a branch of a repository +func DeleteBranch(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch} repository repoDeleteBranch + // --- + // summary: Delete a specific branch from a repository + // 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: branch + // in: path + // description: branch to delete + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/error" + + if ctx.Repo.TreePath != "" { + // if TreePath != "", then URL contained extra slashes + // (i.e. "master/subbranch" instead of "master"), so branch does + // not exist + ctx.NotFound() + return + } + + if ctx.Repo.Repository.DefaultBranch == ctx.Repo.BranchName { + ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch")) + return + } + + isProtected, err := ctx.Repo.Repository.IsProtectedBranch(ctx.Repo.BranchName, ctx.User) + if err != nil { + ctx.InternalServerError(err) + return + } + if isProtected { + ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected")) + return + } + + branch, err := repo_module.GetBranch(ctx.Repo.Repository, ctx.Repo.BranchName) + if err != nil { + if git.IsErrBranchNotExist(err) { + ctx.NotFound(err) + } else { + ctx.Error(http.StatusInternalServerError, "GetBranch", err) + } + return + } + + c, err := branch.GetCommit() + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetCommit", err) + return + } + + if err := ctx.Repo.GitRepo.DeleteBranch(ctx.Repo.BranchName, git.DeleteBranchOptions{ + Force: true, + }); err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteBranch", err) + return + } + + // Don't return error below this + if err := repofiles.PushUpdate( + ctx.Repo.Repository, + ctx.Repo.BranchName, + repofiles.PushUpdateOptions{ + RefFullName: git.BranchPrefix + ctx.Repo.BranchName, + OldCommitID: c.ID.String(), + NewCommitID: git.EmptySHA, + PusherID: ctx.User.ID, + PusherName: ctx.User.Name, + RepoUserName: ctx.Repo.Owner.Name, + RepoName: ctx.Repo.Repository.Name, + }); err != nil { + log.Error("Update: %v", err) + } + + if err := ctx.Repo.Repository.AddDeletedBranch(ctx.Repo.BranchName, c.ID.String(), ctx.User.ID); err != nil { + log.Warn("AddDeletedBranch: %v", err) + } + + ctx.Status(http.StatusNoContent) +} + // ListBranches list all the branches of a repository func ListBranches(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index e87af4f5c..24a6330a0 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -2269,6 +2269,47 @@ "$ref": "#/responses/Branch" } } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Delete a specific branch from a repository", + "operationId": "repoDeleteBranch", + "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": "branch to delete", + "name": "branch", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "403": { + "$ref": "#/responses/error" + } + } } }, "/repos/{owner}/{repo}/collaborators": {