[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
This commit is contained in:
6543 2020-04-19 04:38:09 +02:00 committed by GitHub
parent 0ef11ff2c9
commit 3c5a4d094a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 157 additions and 0 deletions

View file

@ -80,6 +80,13 @@ func testAPIDeleteBranchProtection(t *testing.T, branchName string, expectedHTTP
session.MakeRequest(t, req, expectedHTTPStatus) 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) { func TestAPIGetBranch(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
BranchName string BranchName string
@ -106,10 +113,17 @@ func TestAPIBranchProtection(t *testing.T) {
// Can only create once // Can only create once
testAPICreateBranchProtection(t, "master", http.StatusForbidden) testAPICreateBranchProtection(t, "master", http.StatusForbidden)
// Can't delete a protected branch
testAPIDeleteBranch(t, "master", http.StatusForbidden)
testAPIGetBranchProtection(t, "master", http.StatusOK) testAPIGetBranchProtection(t, "master", http.StatusOK)
testAPIEditBranchProtection(t, "master", &api.BranchProtection{ testAPIEditBranchProtection(t, "master", &api.BranchProtection{
EnablePush: true, EnablePush: true,
}, http.StatusOK) }, http.StatusOK)
testAPIDeleteBranchProtection(t, "master", http.StatusNoContent) testAPIDeleteBranchProtection(t, "master", http.StatusNoContent)
// Test branch deletion
testAPIDeleteBranch(t, "master", http.StatusForbidden)
testAPIDeleteBranch(t, "branch2", http.StatusNoContent)
} }

View file

@ -664,6 +664,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/branches", func() { m.Group("/branches", func() {
m.Get("", repo.ListBranches) m.Get("", repo.ListBranches)
m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch) m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
m.Delete("/*", reqRepoWriter(models.UnitTypeCode), context.RepoRefByType(context.RepoRefBranch), repo.DeleteBranch)
}, reqRepoReader(models.UnitTypeCode)) }, reqRepoReader(models.UnitTypeCode))
m.Group("/branch_protections", func() { m.Group("/branch_protections", func() {
m.Get("", repo.ListBranchProtections) m.Get("", repo.ListBranchProtections)

View file

@ -6,12 +6,15 @@
package repo package repo
import ( import (
"fmt"
"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"
"code.gitea.io/gitea/modules/git" "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" repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
@ -81,6 +84,104 @@ func GetBranch(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, br) 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 // ListBranches list all the branches of a repository
func ListBranches(ctx *context.APIContext) { func ListBranches(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches // swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches

View file

@ -2269,6 +2269,47 @@
"$ref": "#/responses/Branch" "$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": { "/repos/{owner}/{repo}/collaborators": {