mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	[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:
		@@ -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)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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": {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user