code.gitea.io/gitea@v1.22.3/routers/api/v1/repo/branch.go (about)

     1  // Copyright 2016 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  
    12  	"code.gitea.io/gitea/models"
    13  	"code.gitea.io/gitea/models/db"
    14  	git_model "code.gitea.io/gitea/models/git"
    15  	"code.gitea.io/gitea/models/organization"
    16  	user_model "code.gitea.io/gitea/models/user"
    17  	"code.gitea.io/gitea/modules/git"
    18  	"code.gitea.io/gitea/modules/gitrepo"
    19  	"code.gitea.io/gitea/modules/optional"
    20  	repo_module "code.gitea.io/gitea/modules/repository"
    21  	api "code.gitea.io/gitea/modules/structs"
    22  	"code.gitea.io/gitea/modules/web"
    23  	"code.gitea.io/gitea/routers/api/v1/utils"
    24  	"code.gitea.io/gitea/services/context"
    25  	"code.gitea.io/gitea/services/convert"
    26  	pull_service "code.gitea.io/gitea/services/pull"
    27  	repo_service "code.gitea.io/gitea/services/repository"
    28  )
    29  
    30  // GetBranch get a branch of a repository
    31  func GetBranch(ctx *context.APIContext) {
    32  	// swagger:operation GET /repos/{owner}/{repo}/branches/{branch} repository repoGetBranch
    33  	// ---
    34  	// summary: Retrieve a specific branch from a repository, including its effective branch protection
    35  	// produces:
    36  	// - application/json
    37  	// parameters:
    38  	// - name: owner
    39  	//   in: path
    40  	//   description: owner of the repo
    41  	//   type: string
    42  	//   required: true
    43  	// - name: repo
    44  	//   in: path
    45  	//   description: name of the repo
    46  	//   type: string
    47  	//   required: true
    48  	// - name: branch
    49  	//   in: path
    50  	//   description: branch to get
    51  	//   type: string
    52  	//   required: true
    53  	// responses:
    54  	//   "200":
    55  	//     "$ref": "#/responses/Branch"
    56  	//   "404":
    57  	//     "$ref": "#/responses/notFound"
    58  
    59  	branchName := ctx.Params("*")
    60  
    61  	branch, err := ctx.Repo.GitRepo.GetBranch(branchName)
    62  	if err != nil {
    63  		if git.IsErrBranchNotExist(err) {
    64  			ctx.NotFound(err)
    65  		} else {
    66  			ctx.Error(http.StatusInternalServerError, "GetBranch", err)
    67  		}
    68  		return
    69  	}
    70  
    71  	c, err := branch.GetCommit()
    72  	if err != nil {
    73  		ctx.Error(http.StatusInternalServerError, "GetCommit", err)
    74  		return
    75  	}
    76  
    77  	branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName)
    78  	if err != nil {
    79  		ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
    80  		return
    81  	}
    82  
    83  	br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
    84  	if err != nil {
    85  		ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
    86  		return
    87  	}
    88  
    89  	ctx.JSON(http.StatusOK, br)
    90  }
    91  
    92  // DeleteBranch get a branch of a repository
    93  func DeleteBranch(ctx *context.APIContext) {
    94  	// swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch} repository repoDeleteBranch
    95  	// ---
    96  	// summary: Delete a specific branch from a repository
    97  	// produces:
    98  	// - application/json
    99  	// parameters:
   100  	// - name: owner
   101  	//   in: path
   102  	//   description: owner of the repo
   103  	//   type: string
   104  	//   required: true
   105  	// - name: repo
   106  	//   in: path
   107  	//   description: name of the repo
   108  	//   type: string
   109  	//   required: true
   110  	// - name: branch
   111  	//   in: path
   112  	//   description: branch to delete
   113  	//   type: string
   114  	//   required: true
   115  	// responses:
   116  	//   "204":
   117  	//     "$ref": "#/responses/empty"
   118  	//   "403":
   119  	//     "$ref": "#/responses/error"
   120  	//   "404":
   121  	//     "$ref": "#/responses/notFound"
   122  	//   "423":
   123  	//     "$ref": "#/responses/repoArchivedError"
   124  	if ctx.Repo.Repository.IsEmpty {
   125  		ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
   126  		return
   127  	}
   128  
   129  	if ctx.Repo.Repository.IsMirror {
   130  		ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
   131  		return
   132  	}
   133  
   134  	branchName := ctx.Params("*")
   135  
   136  	if ctx.Repo.Repository.IsEmpty {
   137  		ctx.Error(http.StatusForbidden, "", "Git Repository is empty.")
   138  		return
   139  	}
   140  
   141  	// check whether branches of this repository has been synced
   142  	totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
   143  		RepoID:          ctx.Repo.Repository.ID,
   144  		IsDeletedBranch: optional.Some(false),
   145  	})
   146  	if err != nil {
   147  		ctx.Error(http.StatusInternalServerError, "CountBranches", err)
   148  		return
   149  	}
   150  	if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
   151  		_, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
   152  		if err != nil {
   153  			ctx.ServerError("SyncRepoBranches", err)
   154  			return
   155  		}
   156  	}
   157  
   158  	if ctx.Repo.Repository.IsMirror {
   159  		ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository"))
   160  		return
   161  	}
   162  
   163  	if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
   164  		switch {
   165  		case git.IsErrBranchNotExist(err):
   166  			ctx.NotFound(err)
   167  		case errors.Is(err, repo_service.ErrBranchIsDefault):
   168  			ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
   169  		case errors.Is(err, git_model.ErrBranchIsProtected):
   170  			ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
   171  		default:
   172  			ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
   173  		}
   174  		return
   175  	}
   176  
   177  	ctx.Status(http.StatusNoContent)
   178  }
   179  
   180  // CreateBranch creates a branch for a user's repository
   181  func CreateBranch(ctx *context.APIContext) {
   182  	// swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
   183  	// ---
   184  	// summary: Create a branch
   185  	// consumes:
   186  	// - application/json
   187  	// produces:
   188  	// - application/json
   189  	// parameters:
   190  	// - name: owner
   191  	//   in: path
   192  	//   description: owner of the repo
   193  	//   type: string
   194  	//   required: true
   195  	// - name: repo
   196  	//   in: path
   197  	//   description: name of the repo
   198  	//   type: string
   199  	//   required: true
   200  	// - name: body
   201  	//   in: body
   202  	//   schema:
   203  	//     "$ref": "#/definitions/CreateBranchRepoOption"
   204  	// responses:
   205  	//   "201":
   206  	//     "$ref": "#/responses/Branch"
   207  	//   "403":
   208  	//     description: The branch is archived or a mirror.
   209  	//   "404":
   210  	//     description: The old branch does not exist.
   211  	//   "409":
   212  	//     description: The branch with the same name already exists.
   213  	//   "423":
   214  	//     "$ref": "#/responses/repoArchivedError"
   215  
   216  	if ctx.Repo.Repository.IsEmpty {
   217  		ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
   218  		return
   219  	}
   220  
   221  	if ctx.Repo.Repository.IsMirror {
   222  		ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
   223  		return
   224  	}
   225  
   226  	opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
   227  
   228  	var oldCommit *git.Commit
   229  	var err error
   230  
   231  	if len(opt.OldRefName) > 0 {
   232  		oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName)
   233  		if err != nil {
   234  			ctx.Error(http.StatusInternalServerError, "GetCommit", err)
   235  			return
   236  		}
   237  	} else if len(opt.OldBranchName) > 0 { //nolint
   238  		if ctx.Repo.GitRepo.IsBranchExist(opt.OldBranchName) { //nolint
   239  			oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint
   240  			if err != nil {
   241  				ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
   242  				return
   243  			}
   244  		} else {
   245  			ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
   246  			return
   247  		}
   248  	} else {
   249  		oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
   250  		if err != nil {
   251  			ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
   252  			return
   253  		}
   254  	}
   255  
   256  	err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
   257  	if err != nil {
   258  		if git_model.IsErrBranchNotExist(err) {
   259  			ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
   260  		} else if models.IsErrTagAlreadyExists(err) {
   261  			ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
   262  		} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
   263  			ctx.Error(http.StatusConflict, "", "The branch already exists.")
   264  		} else if git_model.IsErrBranchNameConflict(err) {
   265  			ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
   266  		} else {
   267  			ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
   268  		}
   269  		return
   270  	}
   271  
   272  	branch, err := ctx.Repo.GitRepo.GetBranch(opt.BranchName)
   273  	if err != nil {
   274  		ctx.Error(http.StatusInternalServerError, "GetBranch", err)
   275  		return
   276  	}
   277  
   278  	commit, err := branch.GetCommit()
   279  	if err != nil {
   280  		ctx.Error(http.StatusInternalServerError, "GetCommit", err)
   281  		return
   282  	}
   283  
   284  	branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branch.Name)
   285  	if err != nil {
   286  		ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
   287  		return
   288  	}
   289  
   290  	br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
   291  	if err != nil {
   292  		ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
   293  		return
   294  	}
   295  
   296  	ctx.JSON(http.StatusCreated, br)
   297  }
   298  
   299  // ListBranches list all the branches of a repository
   300  func ListBranches(ctx *context.APIContext) {
   301  	// swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches
   302  	// ---
   303  	// summary: List a repository's branches
   304  	// produces:
   305  	// - application/json
   306  	// parameters:
   307  	// - name: owner
   308  	//   in: path
   309  	//   description: owner of the repo
   310  	//   type: string
   311  	//   required: true
   312  	// - name: repo
   313  	//   in: path
   314  	//   description: name of the repo
   315  	//   type: string
   316  	//   required: true
   317  	// - name: page
   318  	//   in: query
   319  	//   description: page number of results to return (1-based)
   320  	//   type: integer
   321  	// - name: limit
   322  	//   in: query
   323  	//   description: page size of results
   324  	//   type: integer
   325  	// responses:
   326  	//   "200":
   327  	//     "$ref": "#/responses/BranchList"
   328  
   329  	var totalNumOfBranches int64
   330  	var apiBranches []*api.Branch
   331  
   332  	listOptions := utils.GetListOptions(ctx)
   333  
   334  	if !ctx.Repo.Repository.IsEmpty {
   335  		if ctx.Repo.GitRepo == nil {
   336  			ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil)
   337  			return
   338  		}
   339  
   340  		branchOpts := git_model.FindBranchOptions{
   341  			ListOptions:     listOptions,
   342  			RepoID:          ctx.Repo.Repository.ID,
   343  			IsDeletedBranch: optional.Some(false),
   344  		}
   345  		var err error
   346  		totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
   347  		if err != nil {
   348  			ctx.Error(http.StatusInternalServerError, "CountBranches", err)
   349  			return
   350  		}
   351  		if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
   352  			totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
   353  			if err != nil {
   354  				ctx.ServerError("SyncRepoBranches", err)
   355  				return
   356  			}
   357  		}
   358  
   359  		rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
   360  		if err != nil {
   361  			ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
   362  			return
   363  		}
   364  
   365  		branches, err := db.Find[git_model.Branch](ctx, branchOpts)
   366  		if err != nil {
   367  			ctx.Error(http.StatusInternalServerError, "GetBranches", err)
   368  			return
   369  		}
   370  
   371  		apiBranches = make([]*api.Branch, 0, len(branches))
   372  		for i := range branches {
   373  			c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
   374  			if err != nil {
   375  				// Skip if this branch doesn't exist anymore.
   376  				if git.IsErrNotExist(err) {
   377  					totalNumOfBranches--
   378  					continue
   379  				}
   380  				ctx.Error(http.StatusInternalServerError, "GetCommit", err)
   381  				return
   382  			}
   383  
   384  			branchProtection := rules.GetFirstMatched(branches[i].Name)
   385  			apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
   386  			if err != nil {
   387  				ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
   388  				return
   389  			}
   390  			apiBranches = append(apiBranches, apiBranch)
   391  		}
   392  	}
   393  
   394  	ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
   395  	ctx.SetTotalCountHeader(totalNumOfBranches)
   396  	ctx.JSON(http.StatusOK, apiBranches)
   397  }
   398  
   399  // GetBranchProtection gets a branch protection
   400  func GetBranchProtection(ctx *context.APIContext) {
   401  	// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
   402  	// ---
   403  	// summary: Get a specific branch protection for the repository
   404  	// produces:
   405  	// - application/json
   406  	// parameters:
   407  	// - name: owner
   408  	//   in: path
   409  	//   description: owner of the repo
   410  	//   type: string
   411  	//   required: true
   412  	// - name: repo
   413  	//   in: path
   414  	//   description: name of the repo
   415  	//   type: string
   416  	//   required: true
   417  	// - name: name
   418  	//   in: path
   419  	//   description: name of protected branch
   420  	//   type: string
   421  	//   required: true
   422  	// responses:
   423  	//   "200":
   424  	//     "$ref": "#/responses/BranchProtection"
   425  	//   "404":
   426  	//     "$ref": "#/responses/notFound"
   427  
   428  	repo := ctx.Repo.Repository
   429  	bpName := ctx.Params(":name")
   430  	bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
   431  	if err != nil {
   432  		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
   433  		return
   434  	}
   435  	if bp == nil || bp.RepoID != repo.ID {
   436  		ctx.NotFound()
   437  		return
   438  	}
   439  
   440  	ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
   441  }
   442  
   443  // ListBranchProtections list branch protections for a repo
   444  func ListBranchProtections(ctx *context.APIContext) {
   445  	// swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection
   446  	// ---
   447  	// summary: List branch protections for a repository
   448  	// produces:
   449  	// - application/json
   450  	// parameters:
   451  	// - name: owner
   452  	//   in: path
   453  	//   description: owner of the repo
   454  	//   type: string
   455  	//   required: true
   456  	// - name: repo
   457  	//   in: path
   458  	//   description: name of the repo
   459  	//   type: string
   460  	//   required: true
   461  	// responses:
   462  	//   "200":
   463  	//     "$ref": "#/responses/BranchProtectionList"
   464  
   465  	repo := ctx.Repo.Repository
   466  	bps, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
   467  	if err != nil {
   468  		ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
   469  		return
   470  	}
   471  	apiBps := make([]*api.BranchProtection, len(bps))
   472  	for i := range bps {
   473  		apiBps[i] = convert.ToBranchProtection(ctx, bps[i], repo)
   474  	}
   475  
   476  	ctx.JSON(http.StatusOK, apiBps)
   477  }
   478  
   479  // CreateBranchProtection creates a branch protection for a repo
   480  func CreateBranchProtection(ctx *context.APIContext) {
   481  	// swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
   482  	// ---
   483  	// summary: Create a branch protections for a repository
   484  	// consumes:
   485  	// - application/json
   486  	// produces:
   487  	// - application/json
   488  	// parameters:
   489  	// - name: owner
   490  	//   in: path
   491  	//   description: owner of the repo
   492  	//   type: string
   493  	//   required: true
   494  	// - name: repo
   495  	//   in: path
   496  	//   description: name of the repo
   497  	//   type: string
   498  	//   required: true
   499  	// - name: body
   500  	//   in: body
   501  	//   schema:
   502  	//     "$ref": "#/definitions/CreateBranchProtectionOption"
   503  	// responses:
   504  	//   "201":
   505  	//     "$ref": "#/responses/BranchProtection"
   506  	//   "403":
   507  	//     "$ref": "#/responses/forbidden"
   508  	//   "404":
   509  	//     "$ref": "#/responses/notFound"
   510  	//   "422":
   511  	//     "$ref": "#/responses/validationError"
   512  	//   "423":
   513  	//     "$ref": "#/responses/repoArchivedError"
   514  
   515  	form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
   516  	repo := ctx.Repo.Repository
   517  
   518  	ruleName := form.RuleName
   519  	if ruleName == "" {
   520  		ruleName = form.BranchName //nolint
   521  	}
   522  	if len(ruleName) == 0 {
   523  		ctx.Error(http.StatusBadRequest, "both rule_name and branch_name are empty", "both rule_name and branch_name are empty")
   524  		return
   525  	}
   526  
   527  	isPlainRule := !git_model.IsRuleNameSpecial(ruleName)
   528  	var isBranchExist bool
   529  	if isPlainRule {
   530  		isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), ruleName)
   531  	}
   532  
   533  	protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, ruleName)
   534  	if err != nil {
   535  		ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
   536  		return
   537  	} else if protectBranch != nil {
   538  		ctx.Error(http.StatusForbidden, "Create branch protection", "Branch protection already exist")
   539  		return
   540  	}
   541  
   542  	var requiredApprovals int64
   543  	if form.RequiredApprovals > 0 {
   544  		requiredApprovals = form.RequiredApprovals
   545  	}
   546  
   547  	whitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
   548  	if err != nil {
   549  		if user_model.IsErrUserNotExist(err) {
   550  			ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
   551  			return
   552  		}
   553  		ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
   554  		return
   555  	}
   556  	mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
   557  	if err != nil {
   558  		if user_model.IsErrUserNotExist(err) {
   559  			ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
   560  			return
   561  		}
   562  		ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
   563  		return
   564  	}
   565  	approvalsWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
   566  	if err != nil {
   567  		if user_model.IsErrUserNotExist(err) {
   568  			ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
   569  			return
   570  		}
   571  		ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
   572  		return
   573  	}
   574  	var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
   575  	if repo.Owner.IsOrganization() {
   576  		whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
   577  		if err != nil {
   578  			if organization.IsErrTeamNotExist(err) {
   579  				ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
   580  				return
   581  			}
   582  			ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
   583  			return
   584  		}
   585  		mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
   586  		if err != nil {
   587  			if organization.IsErrTeamNotExist(err) {
   588  				ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
   589  				return
   590  			}
   591  			ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
   592  			return
   593  		}
   594  		approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
   595  		if err != nil {
   596  			if organization.IsErrTeamNotExist(err) {
   597  				ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
   598  				return
   599  			}
   600  			ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
   601  			return
   602  		}
   603  	}
   604  
   605  	protectBranch = &git_model.ProtectedBranch{
   606  		RepoID:                        ctx.Repo.Repository.ID,
   607  		RuleName:                      ruleName,
   608  		CanPush:                       form.EnablePush,
   609  		EnableWhitelist:               form.EnablePush && form.EnablePushWhitelist,
   610  		EnableMergeWhitelist:          form.EnableMergeWhitelist,
   611  		WhitelistDeployKeys:           form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
   612  		EnableStatusCheck:             form.EnableStatusCheck,
   613  		StatusCheckContexts:           form.StatusCheckContexts,
   614  		EnableApprovalsWhitelist:      form.EnableApprovalsWhitelist,
   615  		RequiredApprovals:             requiredApprovals,
   616  		BlockOnRejectedReviews:        form.BlockOnRejectedReviews,
   617  		BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
   618  		DismissStaleApprovals:         form.DismissStaleApprovals,
   619  		IgnoreStaleApprovals:          form.IgnoreStaleApprovals,
   620  		RequireSignedCommits:          form.RequireSignedCommits,
   621  		ProtectedFilePatterns:         form.ProtectedFilePatterns,
   622  		UnprotectedFilePatterns:       form.UnprotectedFilePatterns,
   623  		BlockOnOutdatedBranch:         form.BlockOnOutdatedBranch,
   624  	}
   625  
   626  	err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
   627  		UserIDs:          whitelistUsers,
   628  		TeamIDs:          whitelistTeams,
   629  		MergeUserIDs:     mergeWhitelistUsers,
   630  		MergeTeamIDs:     mergeWhitelistTeams,
   631  		ApprovalsUserIDs: approvalsWhitelistUsers,
   632  		ApprovalsTeamIDs: approvalsWhitelistTeams,
   633  	})
   634  	if err != nil {
   635  		ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
   636  		return
   637  	}
   638  
   639  	if isBranchExist {
   640  		if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, ruleName); err != nil {
   641  			ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
   642  			return
   643  		}
   644  	} else {
   645  		if !isPlainRule {
   646  			if ctx.Repo.GitRepo == nil {
   647  				ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
   648  				if err != nil {
   649  					ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
   650  					return
   651  				}
   652  				defer func() {
   653  					ctx.Repo.GitRepo.Close()
   654  					ctx.Repo.GitRepo = nil
   655  				}()
   656  			}
   657  			// FIXME: since we only need to recheck files protected rules, we could improve this
   658  			matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
   659  			if err != nil {
   660  				ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
   661  				return
   662  			}
   663  
   664  			for _, branchName := range matchedBranches {
   665  				if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
   666  					ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
   667  					return
   668  				}
   669  			}
   670  		}
   671  	}
   672  
   673  	// Reload from db to get all whitelists
   674  	bp, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, ruleName)
   675  	if err != nil {
   676  		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
   677  		return
   678  	}
   679  	if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
   680  		ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
   681  		return
   682  	}
   683  
   684  	ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp, repo))
   685  }
   686  
   687  // EditBranchProtection edits a branch protection for a repo
   688  func EditBranchProtection(ctx *context.APIContext) {
   689  	// swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
   690  	// ---
   691  	// summary: Edit a branch protections for a repository. Only fields that are set will be changed
   692  	// consumes:
   693  	// - application/json
   694  	// produces:
   695  	// - application/json
   696  	// parameters:
   697  	// - name: owner
   698  	//   in: path
   699  	//   description: owner of the repo
   700  	//   type: string
   701  	//   required: true
   702  	// - name: repo
   703  	//   in: path
   704  	//   description: name of the repo
   705  	//   type: string
   706  	//   required: true
   707  	// - name: name
   708  	//   in: path
   709  	//   description: name of protected branch
   710  	//   type: string
   711  	//   required: true
   712  	// - name: body
   713  	//   in: body
   714  	//   schema:
   715  	//     "$ref": "#/definitions/EditBranchProtectionOption"
   716  	// responses:
   717  	//   "200":
   718  	//     "$ref": "#/responses/BranchProtection"
   719  	//   "404":
   720  	//     "$ref": "#/responses/notFound"
   721  	//   "422":
   722  	//     "$ref": "#/responses/validationError"
   723  	//   "423":
   724  	//     "$ref": "#/responses/repoArchivedError"
   725  	form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
   726  	repo := ctx.Repo.Repository
   727  	bpName := ctx.Params(":name")
   728  	protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
   729  	if err != nil {
   730  		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
   731  		return
   732  	}
   733  	if protectBranch == nil || protectBranch.RepoID != repo.ID {
   734  		ctx.NotFound()
   735  		return
   736  	}
   737  
   738  	if form.EnablePush != nil {
   739  		if !*form.EnablePush {
   740  			protectBranch.CanPush = false
   741  			protectBranch.EnableWhitelist = false
   742  			protectBranch.WhitelistDeployKeys = false
   743  		} else {
   744  			protectBranch.CanPush = true
   745  			if form.EnablePushWhitelist != nil {
   746  				if !*form.EnablePushWhitelist {
   747  					protectBranch.EnableWhitelist = false
   748  					protectBranch.WhitelistDeployKeys = false
   749  				} else {
   750  					protectBranch.EnableWhitelist = true
   751  					if form.PushWhitelistDeployKeys != nil {
   752  						protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys
   753  					}
   754  				}
   755  			}
   756  		}
   757  	}
   758  
   759  	if form.EnableMergeWhitelist != nil {
   760  		protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
   761  	}
   762  
   763  	if form.EnableStatusCheck != nil {
   764  		protectBranch.EnableStatusCheck = *form.EnableStatusCheck
   765  	}
   766  
   767  	if form.StatusCheckContexts != nil {
   768  		protectBranch.StatusCheckContexts = form.StatusCheckContexts
   769  	}
   770  
   771  	if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 {
   772  		protectBranch.RequiredApprovals = *form.RequiredApprovals
   773  	}
   774  
   775  	if form.EnableApprovalsWhitelist != nil {
   776  		protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
   777  	}
   778  
   779  	if form.BlockOnRejectedReviews != nil {
   780  		protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
   781  	}
   782  
   783  	if form.BlockOnOfficialReviewRequests != nil {
   784  		protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
   785  	}
   786  
   787  	if form.DismissStaleApprovals != nil {
   788  		protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
   789  	}
   790  
   791  	if form.IgnoreStaleApprovals != nil {
   792  		protectBranch.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
   793  	}
   794  
   795  	if form.RequireSignedCommits != nil {
   796  		protectBranch.RequireSignedCommits = *form.RequireSignedCommits
   797  	}
   798  
   799  	if form.ProtectedFilePatterns != nil {
   800  		protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns
   801  	}
   802  
   803  	if form.UnprotectedFilePatterns != nil {
   804  		protectBranch.UnprotectedFilePatterns = *form.UnprotectedFilePatterns
   805  	}
   806  
   807  	if form.BlockOnOutdatedBranch != nil {
   808  		protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
   809  	}
   810  
   811  	var whitelistUsers []int64
   812  	if form.PushWhitelistUsernames != nil {
   813  		whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
   814  		if err != nil {
   815  			if user_model.IsErrUserNotExist(err) {
   816  				ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
   817  				return
   818  			}
   819  			ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
   820  			return
   821  		}
   822  	} else {
   823  		whitelistUsers = protectBranch.WhitelistUserIDs
   824  	}
   825  	var mergeWhitelistUsers []int64
   826  	if form.MergeWhitelistUsernames != nil {
   827  		mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
   828  		if err != nil {
   829  			if user_model.IsErrUserNotExist(err) {
   830  				ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
   831  				return
   832  			}
   833  			ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
   834  			return
   835  		}
   836  	} else {
   837  		mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
   838  	}
   839  	var approvalsWhitelistUsers []int64
   840  	if form.ApprovalsWhitelistUsernames != nil {
   841  		approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
   842  		if err != nil {
   843  			if user_model.IsErrUserNotExist(err) {
   844  				ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
   845  				return
   846  			}
   847  			ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
   848  			return
   849  		}
   850  	} else {
   851  		approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
   852  	}
   853  
   854  	var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
   855  	if repo.Owner.IsOrganization() {
   856  		if form.PushWhitelistTeams != nil {
   857  			whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
   858  			if err != nil {
   859  				if organization.IsErrTeamNotExist(err) {
   860  					ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
   861  					return
   862  				}
   863  				ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
   864  				return
   865  			}
   866  		} else {
   867  			whitelistTeams = protectBranch.WhitelistTeamIDs
   868  		}
   869  		if form.MergeWhitelistTeams != nil {
   870  			mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
   871  			if err != nil {
   872  				if organization.IsErrTeamNotExist(err) {
   873  					ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
   874  					return
   875  				}
   876  				ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
   877  				return
   878  			}
   879  		} else {
   880  			mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs
   881  		}
   882  		if form.ApprovalsWhitelistTeams != nil {
   883  			approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
   884  			if err != nil {
   885  				if organization.IsErrTeamNotExist(err) {
   886  					ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
   887  					return
   888  				}
   889  				ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
   890  				return
   891  			}
   892  		} else {
   893  			approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
   894  		}
   895  	}
   896  
   897  	err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
   898  		UserIDs:          whitelistUsers,
   899  		TeamIDs:          whitelistTeams,
   900  		MergeUserIDs:     mergeWhitelistUsers,
   901  		MergeTeamIDs:     mergeWhitelistTeams,
   902  		ApprovalsUserIDs: approvalsWhitelistUsers,
   903  		ApprovalsTeamIDs: approvalsWhitelistTeams,
   904  	})
   905  	if err != nil {
   906  		ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
   907  		return
   908  	}
   909  
   910  	isPlainRule := !git_model.IsRuleNameSpecial(bpName)
   911  	var isBranchExist bool
   912  	if isPlainRule {
   913  		isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), bpName)
   914  	}
   915  
   916  	if isBranchExist {
   917  		if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, bpName); err != nil {
   918  			ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
   919  			return
   920  		}
   921  	} else {
   922  		if !isPlainRule {
   923  			if ctx.Repo.GitRepo == nil {
   924  				ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
   925  				if err != nil {
   926  					ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
   927  					return
   928  				}
   929  				defer func() {
   930  					ctx.Repo.GitRepo.Close()
   931  					ctx.Repo.GitRepo = nil
   932  				}()
   933  			}
   934  
   935  			// FIXME: since we only need to recheck files protected rules, we could improve this
   936  			matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
   937  			if err != nil {
   938  				ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
   939  				return
   940  			}
   941  
   942  			for _, branchName := range matchedBranches {
   943  				if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
   944  					ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
   945  					return
   946  				}
   947  			}
   948  		}
   949  	}
   950  
   951  	// Reload from db to ensure get all whitelists
   952  	bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
   953  	if err != nil {
   954  		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
   955  		return
   956  	}
   957  	if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
   958  		ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
   959  		return
   960  	}
   961  
   962  	ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
   963  }
   964  
   965  // DeleteBranchProtection deletes a branch protection for a repo
   966  func DeleteBranchProtection(ctx *context.APIContext) {
   967  	// swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
   968  	// ---
   969  	// summary: Delete a specific branch protection for the repository
   970  	// produces:
   971  	// - application/json
   972  	// parameters:
   973  	// - name: owner
   974  	//   in: path
   975  	//   description: owner of the repo
   976  	//   type: string
   977  	//   required: true
   978  	// - name: repo
   979  	//   in: path
   980  	//   description: name of the repo
   981  	//   type: string
   982  	//   required: true
   983  	// - name: name
   984  	//   in: path
   985  	//   description: name of protected branch
   986  	//   type: string
   987  	//   required: true
   988  	// responses:
   989  	//   "204":
   990  	//     "$ref": "#/responses/empty"
   991  	//   "404":
   992  	//     "$ref": "#/responses/notFound"
   993  
   994  	repo := ctx.Repo.Repository
   995  	bpName := ctx.Params(":name")
   996  	bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
   997  	if err != nil {
   998  		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
   999  		return
  1000  	}
  1001  	if bp == nil || bp.RepoID != repo.ID {
  1002  		ctx.NotFound()
  1003  		return
  1004  	}
  1005  
  1006  	if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, bp.ID); err != nil {
  1007  		ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
  1008  		return
  1009  	}
  1010  
  1011  	ctx.Status(http.StatusNoContent)
  1012  }