2016-01-28 22:49:05 +03:00
// Copyright 2016 The Gogs Authors. All rights reserved.
2019-04-19 15:17:27 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2016-01-15 21:24:03 +03:00
package repo
import (
2021-06-07 17:52:59 +03:00
"errors"
2020-04-19 05:38:09 +03:00
"fmt"
2019-12-20 20:07:12 +03:00
"net/http"
2020-02-13 02:19:35 +03:00
"code.gitea.io/gitea/models"
2022-06-12 18:51:54 +03:00
git_model "code.gitea.io/gitea/models/git"
2022-03-29 09:29:02 +03:00
"code.gitea.io/gitea/models/organization"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/context"
2019-04-19 15:17:27 +03:00
"code.gitea.io/gitea/modules/git"
2023-06-29 13:03:20 +03:00
repo_module "code.gitea.io/gitea/modules/repository"
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2023-06-29 13:03:20 +03:00
"code.gitea.io/gitea/modules/util"
2021-01-26 18:36:53 +03:00
"code.gitea.io/gitea/modules/web"
2021-02-03 22:06:13 +03:00
"code.gitea.io/gitea/routers/api/v1/utils"
2022-12-29 05:57:15 +03:00
"code.gitea.io/gitea/services/convert"
2020-10-13 21:50:57 +03:00
pull_service "code.gitea.io/gitea/services/pull"
2020-09-11 17:14:48 +03:00
repo_service "code.gitea.io/gitea/services/repository"
2016-01-15 21:24:03 +03:00
)
2016-11-24 10:04:31 +03:00
// GetBranch get a branch of a repository
2016-03-14 01:49:16 +03:00
func GetBranch ( ctx * context . APIContext ) {
2017-11-13 10:02:25 +03:00
// swagger:operation GET /repos/{owner}/{repo}/branches/{branch} repository repoGetBranch
// ---
2019-11-16 22:39:18 +03:00
// summary: Retrieve a specific branch from a repository, including its effective branch protection
2017-11-13 10:02:25 +03:00
// 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 get
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Branch"
2020-11-14 19:13:55 +03:00
// "404":
// "$ref": "#/responses/notFound"
2019-12-20 20:07:12 +03:00
2020-11-14 19:13:55 +03:00
branchName := ctx . Params ( "*" )
2022-01-20 02:26:57 +03:00
branch , err := ctx . Repo . GitRepo . GetBranch ( branchName )
2016-01-15 21:24:03 +03:00
if err != nil {
2019-04-19 15:17:27 +03:00
if git . IsErrBranchNotExist ( err ) {
2019-03-19 05:29:43 +03:00
ctx . NotFound ( err )
2017-06-11 05:57:28 +03:00
} else {
2019-12-20 20:07:12 +03:00
ctx . Error ( http . StatusInternalServerError , "GetBranch" , err )
2017-06-11 05:57:28 +03:00
}
2016-01-15 21:24:03 +03:00
return
}
2016-02-03 01:07:40 +03:00
2016-01-15 21:24:03 +03:00
c , err := branch . GetCommit ( )
if err != nil {
2019-12-20 20:07:12 +03:00
ctx . Error ( http . StatusInternalServerError , "GetCommit" , err )
2016-01-15 21:24:03 +03:00
return
}
2016-02-03 01:07:40 +03:00
2023-01-16 11:00:22 +03:00
branchProtection , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , ctx . Repo . Repository . ID , branchName )
2019-11-16 22:39:18 +03:00
if err != nil {
2019-12-20 20:07:12 +03:00
ctx . Error ( http . StatusInternalServerError , "GetBranchProtection" , err )
2019-11-16 22:39:18 +03:00
return
}
2023-06-29 13:03:20 +03:00
br , err := convert . ToBranch ( ctx , ctx . Repo . Repository , branch . Name , c , branchProtection , ctx . Doer , ctx . Repo . IsAdmin ( ) )
2020-03-21 06:41:33 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "convert.ToBranch" , err )
return
}
ctx . JSON ( http . StatusOK , br )
2016-01-15 21:24:03 +03:00
}
2020-04-19 05:38:09 +03:00
// 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"
2020-11-14 19:13:55 +03:00
// "404":
// "$ref": "#/responses/notFound"
2020-04-19 05:38:09 +03:00
2023-07-01 05:52:52 +03:00
if ctx . Repo . Repository . IsEmpty {
ctx . Error ( http . StatusNotFound , "" , "Git Repository is empty." )
return
}
if ctx . Repo . Repository . IsArchived {
ctx . Error ( http . StatusForbidden , "" , "Git Repository is archived." )
return
}
if ctx . Repo . Repository . IsMirror {
ctx . Error ( http . StatusForbidden , "" , "Git Repository is a mirror." )
return
}
2020-11-14 19:13:55 +03:00
branchName := ctx . Params ( "*" )
2020-04-19 05:38:09 +03:00
2023-06-29 13:03:20 +03:00
if ctx . Repo . Repository . IsEmpty {
ctx . Error ( http . StatusForbidden , "" , "Git Repository is empty." )
return
}
// check whether branches of this repository has been synced
totalNumOfBranches , err := git_model . CountBranches ( ctx , git_model . FindBranchOptions {
RepoID : ctx . Repo . Repository . ID ,
IsDeletedBranch : util . OptionalBoolFalse ,
} )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "CountBranches" , err )
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
_ , err = repo_module . SyncRepoBranches ( ctx , ctx . Repo . Repository . ID , 0 )
if err != nil {
ctx . ServerError ( "SyncRepoBranches" , err )
return
}
}
if ctx . Repo . Repository . IsArchived {
ctx . Error ( http . StatusForbidden , "IsArchived" , fmt . Errorf ( "can not delete branch of an archived repository" ) )
return
}
if ctx . Repo . Repository . IsMirror {
ctx . Error ( http . StatusForbidden , "IsMirrored" , fmt . Errorf ( "can not delete branch of an mirror repository" ) )
return
}
2023-03-01 01:17:51 +03:00
if err := repo_service . DeleteBranch ( ctx , ctx . Doer , ctx . Repo . Repository , ctx . Repo . GitRepo , branchName ) ; err != nil {
2021-06-07 17:52:59 +03:00
switch {
case git . IsErrBranchNotExist ( err ) :
2020-04-19 05:38:09 +03:00
ctx . NotFound ( err )
2021-06-07 17:52:59 +03:00
case errors . Is ( err , repo_service . ErrBranchIsDefault ) :
ctx . Error ( http . StatusForbidden , "DefaultBranch" , fmt . Errorf ( "can not delete default branch" ) )
2023-01-16 11:00:22 +03:00
case errors . Is ( err , git_model . ErrBranchIsProtected ) :
2021-06-07 17:52:59 +03:00
ctx . Error ( http . StatusForbidden , "IsProtectedBranch" , fmt . Errorf ( "branch protected" ) )
default :
ctx . Error ( http . StatusInternalServerError , "DeleteBranch" , err )
2020-04-19 05:38:09 +03:00
}
return
}
ctx . Status ( http . StatusNoContent )
}
2020-05-29 21:16:20 +03:00
// CreateBranch creates a branch for a user's repository
2021-01-26 18:36:53 +03:00
func CreateBranch ( ctx * context . APIContext ) {
2020-05-29 21:16:20 +03:00
// swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
// ---
// summary: Create a branch
// consumes:
// - application/json
// 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: body
// in: body
// schema:
// "$ref": "#/definitions/CreateBranchRepoOption"
// responses:
// "201":
// "$ref": "#/responses/Branch"
2023-07-01 05:52:52 +03:00
// "403":
// description: The branch is archived or a mirror.
2020-05-29 21:16:20 +03:00
// "404":
// description: The old branch does not exist.
// "409":
// description: The branch with the same name already exists.
if ctx . Repo . Repository . IsEmpty {
ctx . Error ( http . StatusNotFound , "" , "Git Repository is empty." )
return
}
2023-07-01 05:52:52 +03:00
if ctx . Repo . Repository . IsArchived {
ctx . Error ( http . StatusForbidden , "" , "Git Repository is archived." )
return
}
if ctx . Repo . Repository . IsMirror {
ctx . Error ( http . StatusForbidden , "" , "Git Repository is a mirror." )
return
}
opt := web . GetForm ( ctx ) . ( * api . CreateBranchRepoOption )
2023-05-09 13:22:32 +03:00
var oldCommit * git . Commit
var err error
if len ( opt . OldRefName ) > 0 {
oldCommit , err = ctx . Repo . GitRepo . GetCommit ( opt . OldRefName )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetCommit" , err )
return
}
} else if len ( opt . OldBranchName ) > 0 { //nolint
if ctx . Repo . GitRepo . IsBranchExist ( opt . OldBranchName ) { //nolint
oldCommit , err = ctx . Repo . GitRepo . GetBranchCommit ( opt . OldBranchName ) //nolint
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetBranchCommit" , err )
return
}
} else {
ctx . Error ( http . StatusNotFound , "" , "The old branch does not exist" )
return
}
} else {
oldCommit , err = ctx . Repo . GitRepo . GetBranchCommit ( ctx . Repo . Repository . DefaultBranch )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetBranchCommit" , err )
return
}
2020-05-29 21:16:20 +03:00
}
2023-05-09 13:22:32 +03:00
err = repo_service . CreateNewBranchFromCommit ( ctx , ctx . Doer , ctx . Repo . Repository , oldCommit . ID . String ( ) , opt . BranchName )
2020-05-29 21:16:20 +03:00
if err != nil {
2023-06-29 13:03:20 +03:00
if git_model . IsErrBranchNotExist ( err ) {
2020-05-29 21:16:20 +03:00
ctx . Error ( http . StatusNotFound , "" , "The old branch does not exist" )
}
if models . IsErrTagAlreadyExists ( err ) {
ctx . Error ( http . StatusConflict , "" , "The branch with the same tag already exists." )
2023-06-29 13:03:20 +03:00
} else if git_model . IsErrBranchAlreadyExists ( err ) || git . IsErrPushOutOfDate ( err ) {
2020-05-29 21:16:20 +03:00
ctx . Error ( http . StatusConflict , "" , "The branch already exists." )
2023-06-29 13:03:20 +03:00
} else if git_model . IsErrBranchNameConflict ( err ) {
2020-05-29 21:16:20 +03:00
ctx . Error ( http . StatusConflict , "" , "The branch with the same name already exists." )
} else {
2023-05-09 13:22:32 +03:00
ctx . Error ( http . StatusInternalServerError , "CreateNewBranchFromCommit" , err )
2020-05-29 21:16:20 +03:00
}
return
}
2022-01-20 02:26:57 +03:00
branch , err := ctx . Repo . GitRepo . GetBranch ( opt . BranchName )
2020-05-29 21:16:20 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetBranch" , err )
return
}
commit , err := branch . GetCommit ( )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetCommit" , err )
return
}
2023-01-16 11:00:22 +03:00
branchProtection , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , ctx . Repo . Repository . ID , branch . Name )
2020-05-29 21:16:20 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetBranchProtection" , err )
return
}
2023-06-29 13:03:20 +03:00
br , err := convert . ToBranch ( ctx , ctx . Repo . Repository , branch . Name , commit , branchProtection , ctx . Doer , ctx . Repo . IsAdmin ( ) )
2020-05-29 21:16:20 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "convert.ToBranch" , err )
return
}
ctx . JSON ( http . StatusCreated , br )
}
2016-11-24 10:04:31 +03:00
// ListBranches list all the branches of a repository
2016-03-14 01:49:16 +03:00
func ListBranches ( ctx * context . APIContext ) {
2017-11-13 10:02:25 +03:00
// swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches
// ---
// summary: List a repository's branches
// 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
2021-02-03 22:06:13 +03:00
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
2017-11-13 10:02:25 +03:00
// responses:
// "200":
// "$ref": "#/responses/BranchList"
2019-12-20 20:07:12 +03:00
2023-06-29 13:03:20 +03:00
var totalNumOfBranches int64
2022-12-04 11:57:17 +03:00
var apiBranches [ ] * api . Branch
2021-02-03 22:06:13 +03:00
listOptions := utils . GetListOptions ( ctx )
2016-02-03 01:07:40 +03:00
2023-07-01 05:52:52 +03:00
if ! ctx . Repo . Repository . IsEmpty {
if ctx . Repo . GitRepo == nil {
ctx . Error ( http . StatusInternalServerError , "Load git repository failed" , nil )
return
}
2023-06-29 13:03:20 +03:00
branchOpts := git_model . FindBranchOptions {
ListOptions : listOptions ,
RepoID : ctx . Repo . Repository . ID ,
IsDeletedBranch : util . OptionalBoolFalse ,
}
var err error
totalNumOfBranches , err = git_model . CountBranches ( ctx , branchOpts )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "CountBranches" , err )
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
totalNumOfBranches , err = repo_module . SyncRepoBranches ( ctx , ctx . Repo . Repository . ID , 0 )
if err != nil {
ctx . ServerError ( "SyncRepoBranches" , err )
return
}
}
2023-01-16 11:00:22 +03:00
rules , err := git_model . FindRepoProtectedBranchRules ( ctx , ctx . Repo . Repository . ID )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "FindMatchedProtectedBranchRules" , err )
return
}
2023-06-29 13:03:20 +03:00
branches , err := git_model . FindBranches ( ctx , branchOpts )
2019-11-16 22:39:18 +03:00
if err != nil {
2022-12-04 11:57:17 +03:00
ctx . Error ( http . StatusInternalServerError , "GetBranches" , err )
2019-11-16 22:39:18 +03:00
return
}
2022-12-04 11:57:17 +03:00
apiBranches = make ( [ ] * api . Branch , 0 , len ( branches ) )
for i := range branches {
2023-06-29 13:03:20 +03:00
c , err := ctx . Repo . GitRepo . GetBranchCommit ( branches [ i ] . Name )
2022-12-04 11:57:17 +03:00
if err != nil {
// Skip if this branch doesn't exist anymore.
if git . IsErrNotExist ( err ) {
2023-06-29 13:03:20 +03:00
totalNumOfBranches --
2022-12-04 11:57:17 +03:00
continue
}
ctx . Error ( http . StatusInternalServerError , "GetCommit" , err )
return
}
2023-01-16 11:00:22 +03:00
branchProtection := rules . GetFirstMatched ( branches [ i ] . Name )
2023-06-29 13:03:20 +03:00
apiBranch , err := convert . ToBranch ( ctx , ctx . Repo . Repository , branches [ i ] . Name , c , branchProtection , ctx . Doer , ctx . Repo . IsAdmin ( ) )
2022-12-04 11:57:17 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "convert.ToBranch" , err )
return
}
apiBranches = append ( apiBranches , apiBranch )
2020-03-21 06:41:33 +03:00
}
2016-01-15 21:24:03 +03:00
}
2016-02-03 01:07:40 +03:00
2023-06-29 13:03:20 +03:00
ctx . SetLinkHeader ( int ( totalNumOfBranches ) , listOptions . PageSize )
ctx . SetTotalCountHeader ( totalNumOfBranches )
2022-12-04 11:57:17 +03:00
ctx . JSON ( http . StatusOK , apiBranches )
2016-01-15 21:24:03 +03:00
}
2020-02-13 02:19:35 +03:00
// GetBranchProtection gets a branch protection
func GetBranchProtection ( ctx * context . APIContext ) {
// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
// ---
// summary: Get a specific branch protection for the 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: name
// in: path
// description: name of protected branch
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/BranchProtection"
// "404":
// "$ref": "#/responses/notFound"
repo := ctx . Repo . Repository
bpName := ctx . Params ( ":name" )
2023-01-16 11:00:22 +03:00
bp , err := git_model . GetProtectedBranchRuleByName ( ctx , repo . ID , bpName )
2020-02-13 02:19:35 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetProtectedBranchByID" , err )
return
}
if bp == nil || bp . RepoID != repo . ID {
ctx . NotFound ( )
return
}
2023-09-14 20:09:32 +03:00
ctx . JSON ( http . StatusOK , convert . ToBranchProtection ( ctx , bp ) )
2020-02-13 02:19:35 +03:00
}
// ListBranchProtections list branch protections for a repo
func ListBranchProtections ( ctx * context . APIContext ) {
// swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection
// ---
// summary: List branch protections for 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
// responses:
// "200":
// "$ref": "#/responses/BranchProtectionList"
repo := ctx . Repo . Repository
2023-01-16 11:00:22 +03:00
bps , err := git_model . FindRepoProtectedBranchRules ( ctx , repo . ID )
2020-02-13 02:19:35 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetProtectedBranches" , err )
return
}
apiBps := make ( [ ] * api . BranchProtection , len ( bps ) )
for i := range bps {
2023-09-14 20:09:32 +03:00
apiBps [ i ] = convert . ToBranchProtection ( ctx , bps [ i ] )
2020-02-13 02:19:35 +03:00
}
ctx . JSON ( http . StatusOK , apiBps )
}
// CreateBranchProtection creates a branch protection for a repo
2021-01-26 18:36:53 +03:00
func CreateBranchProtection ( ctx * context . APIContext ) {
2020-02-13 02:19:35 +03:00
// swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
// ---
// summary: Create a branch protections for a repository
// consumes:
// - application/json
// 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: body
// in: body
// schema:
// "$ref": "#/definitions/CreateBranchProtectionOption"
// responses:
// "201":
// "$ref": "#/responses/BranchProtection"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
2021-01-26 18:36:53 +03:00
form := web . GetForm ( ctx ) . ( * api . CreateBranchProtectionOption )
2020-02-13 02:19:35 +03:00
repo := ctx . Repo . Repository
2023-01-16 11:00:22 +03:00
ruleName := form . RuleName
if ruleName == "" {
ruleName = form . BranchName //nolint
}
2023-04-10 05:52:16 +03:00
if len ( ruleName ) == 0 {
ctx . Error ( http . StatusBadRequest , "both rule_name and branch_name are empty" , "both rule_name and branch_name are empty" )
return
}
2023-01-16 11:00:22 +03:00
isPlainRule := ! git_model . IsRuleNameSpecial ( ruleName )
var isBranchExist bool
if isPlainRule {
isBranchExist = git . IsBranchExist ( ctx . Req . Context ( ) , ctx . Repo . Repository . RepoPath ( ) , ruleName )
2020-02-13 02:19:35 +03:00
}
2023-01-16 11:00:22 +03:00
protectBranch , err := git_model . GetProtectedBranchRuleByName ( ctx , repo . ID , ruleName )
2020-02-13 02:19:35 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetProtectBranchOfRepoByName" , err )
return
} else if protectBranch != nil {
ctx . Error ( http . StatusForbidden , "Create branch protection" , "Branch protection already exist" )
return
}
var requiredApprovals int64
if form . RequiredApprovals > 0 {
requiredApprovals = form . RequiredApprovals
}
2022-11-19 11:12:33 +03:00
whitelistUsers , err := user_model . GetUserIDsByNames ( ctx , form . PushWhitelistUsernames , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2021-11-24 12:49:20 +03:00
if user_model . IsErrUserNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "User does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetUserIDsByNames" , err )
return
}
2022-11-19 11:12:33 +03:00
mergeWhitelistUsers , err := user_model . GetUserIDsByNames ( ctx , form . MergeWhitelistUsernames , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2021-11-24 12:49:20 +03:00
if user_model . IsErrUserNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "User does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetUserIDsByNames" , err )
return
}
2022-11-19 11:12:33 +03:00
approvalsWhitelistUsers , err := user_model . GetUserIDsByNames ( ctx , form . ApprovalsWhitelistUsernames , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2021-11-24 12:49:20 +03:00
if user_model . IsErrUserNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "User does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetUserIDsByNames" , err )
return
}
var whitelistTeams , mergeWhitelistTeams , approvalsWhitelistTeams [ ] int64
if repo . Owner . IsOrganization ( ) {
2022-03-29 09:29:02 +03:00
whitelistTeams , err = organization . GetTeamIDsByNames ( repo . OwnerID , form . PushWhitelistTeams , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2022-03-29 09:29:02 +03:00
if organization . IsErrTeamNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "Team does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetTeamIDsByNames" , err )
return
}
2022-03-29 09:29:02 +03:00
mergeWhitelistTeams , err = organization . GetTeamIDsByNames ( repo . OwnerID , form . MergeWhitelistTeams , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2022-03-29 09:29:02 +03:00
if organization . IsErrTeamNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "Team does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetTeamIDsByNames" , err )
return
}
2022-03-29 09:29:02 +03:00
approvalsWhitelistTeams , err = organization . GetTeamIDsByNames ( repo . OwnerID , form . ApprovalsWhitelistTeams , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2022-03-29 09:29:02 +03:00
if organization . IsErrTeamNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "Team does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetTeamIDsByNames" , err )
return
}
}
2022-06-12 18:51:54 +03:00
protectBranch = & git_model . ProtectedBranch {
2020-11-28 22:30:46 +03:00
RepoID : ctx . Repo . Repository . ID ,
2023-04-10 05:52:16 +03:00
RuleName : ruleName ,
2020-11-28 22:30:46 +03:00
CanPush : form . EnablePush ,
EnableWhitelist : form . EnablePush && form . EnablePushWhitelist ,
EnableMergeWhitelist : form . EnableMergeWhitelist ,
WhitelistDeployKeys : form . EnablePush && form . EnablePushWhitelist && form . PushWhitelistDeployKeys ,
EnableStatusCheck : form . EnableStatusCheck ,
StatusCheckContexts : form . StatusCheckContexts ,
EnableApprovalsWhitelist : form . EnableApprovalsWhitelist ,
RequiredApprovals : requiredApprovals ,
BlockOnRejectedReviews : form . BlockOnRejectedReviews ,
BlockOnOfficialReviewRequests : form . BlockOnOfficialReviewRequests ,
DismissStaleApprovals : form . DismissStaleApprovals ,
RequireSignedCommits : form . RequireSignedCommits ,
ProtectedFilePatterns : form . ProtectedFilePatterns ,
2021-09-11 17:21:17 +03:00
UnprotectedFilePatterns : form . UnprotectedFilePatterns ,
2020-11-28 22:30:46 +03:00
BlockOnOutdatedBranch : form . BlockOnOutdatedBranch ,
2020-02-13 02:19:35 +03:00
}
2022-06-12 18:51:54 +03:00
err = git_model . UpdateProtectBranch ( ctx , ctx . Repo . Repository , protectBranch , git_model . WhitelistOptions {
2020-02-13 02:19:35 +03:00
UserIDs : whitelistUsers ,
TeamIDs : whitelistTeams ,
MergeUserIDs : mergeWhitelistUsers ,
MergeTeamIDs : mergeWhitelistTeams ,
ApprovalsUserIDs : approvalsWhitelistUsers ,
ApprovalsTeamIDs : approvalsWhitelistTeams ,
} )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "UpdateProtectBranch" , err )
return
}
2023-01-16 11:00:22 +03:00
if isBranchExist {
2023-07-22 17:14:27 +03:00
if err = pull_service . CheckPRsForBaseBranch ( ctx , ctx . Repo . Repository , ruleName ) ; err != nil {
2023-01-16 11:00:22 +03:00
ctx . Error ( http . StatusInternalServerError , "CheckPRsForBaseBranch" , err )
return
}
} else {
if ! isPlainRule {
if ctx . Repo . GitRepo == nil {
ctx . Repo . GitRepo , err = git . OpenRepository ( ctx , ctx . Repo . Repository . RepoPath ( ) )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "OpenRepository" , err )
return
}
defer func ( ) {
ctx . Repo . GitRepo . Close ( )
ctx . Repo . GitRepo = nil
} ( )
}
// FIXME: since we only need to recheck files protected rules, we could improve this
2023-06-29 13:03:20 +03:00
matchedBranches , err := git_model . FindAllMatchedBranches ( ctx , ctx . Repo . Repository . ID , ruleName )
2023-01-16 11:00:22 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "FindAllMatchedBranches" , err )
return
}
for _ , branchName := range matchedBranches {
2023-07-22 17:14:27 +03:00
if err = pull_service . CheckPRsForBaseBranch ( ctx , ctx . Repo . Repository , branchName ) ; err != nil {
2023-01-16 11:00:22 +03:00
ctx . Error ( http . StatusInternalServerError , "CheckPRsForBaseBranch" , err )
return
}
}
}
2020-10-13 21:50:57 +03:00
}
2020-02-13 02:19:35 +03:00
// Reload from db to get all whitelists
2023-04-10 05:52:16 +03:00
bp , err := git_model . GetProtectedBranchRuleByName ( ctx , ctx . Repo . Repository . ID , ruleName )
2020-02-13 02:19:35 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetProtectedBranchByID" , err )
return
}
if bp == nil || bp . RepoID != ctx . Repo . Repository . ID {
ctx . Error ( http . StatusInternalServerError , "New branch protection not found" , err )
return
}
2023-09-14 20:09:32 +03:00
ctx . JSON ( http . StatusCreated , convert . ToBranchProtection ( ctx , bp ) )
2020-02-13 02:19:35 +03:00
}
// EditBranchProtection edits a branch protection for a repo
2021-01-26 18:36:53 +03:00
func EditBranchProtection ( ctx * context . APIContext ) {
2020-02-13 02:19:35 +03:00
// swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
// ---
// summary: Edit a branch protections for a repository. Only fields that are set will be changed
// consumes:
// - application/json
// 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: name
// in: path
// description: name of protected branch
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditBranchProtectionOption"
// responses:
// "200":
// "$ref": "#/responses/BranchProtection"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
2021-01-26 18:36:53 +03:00
form := web . GetForm ( ctx ) . ( * api . EditBranchProtectionOption )
2020-02-13 02:19:35 +03:00
repo := ctx . Repo . Repository
bpName := ctx . Params ( ":name" )
2023-01-16 11:00:22 +03:00
protectBranch , err := git_model . GetProtectedBranchRuleByName ( ctx , repo . ID , bpName )
2020-02-13 02:19:35 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetProtectedBranchByID" , err )
return
}
if protectBranch == nil || protectBranch . RepoID != repo . ID {
ctx . NotFound ( )
return
}
if form . EnablePush != nil {
if ! * form . EnablePush {
protectBranch . CanPush = false
protectBranch . EnableWhitelist = false
protectBranch . WhitelistDeployKeys = false
} else {
protectBranch . CanPush = true
if form . EnablePushWhitelist != nil {
if ! * form . EnablePushWhitelist {
protectBranch . EnableWhitelist = false
protectBranch . WhitelistDeployKeys = false
} else {
protectBranch . EnableWhitelist = true
if form . PushWhitelistDeployKeys != nil {
protectBranch . WhitelistDeployKeys = * form . PushWhitelistDeployKeys
}
}
}
}
}
if form . EnableMergeWhitelist != nil {
protectBranch . EnableMergeWhitelist = * form . EnableMergeWhitelist
}
if form . EnableStatusCheck != nil {
protectBranch . EnableStatusCheck = * form . EnableStatusCheck
}
2023-08-24 08:36:04 +03:00
if form . StatusCheckContexts != nil {
2020-02-13 02:19:35 +03:00
protectBranch . StatusCheckContexts = form . StatusCheckContexts
}
if form . RequiredApprovals != nil && * form . RequiredApprovals >= 0 {
protectBranch . RequiredApprovals = * form . RequiredApprovals
}
if form . EnableApprovalsWhitelist != nil {
protectBranch . EnableApprovalsWhitelist = * form . EnableApprovalsWhitelist
}
if form . BlockOnRejectedReviews != nil {
protectBranch . BlockOnRejectedReviews = * form . BlockOnRejectedReviews
}
2020-11-28 22:30:46 +03:00
if form . BlockOnOfficialReviewRequests != nil {
protectBranch . BlockOnOfficialReviewRequests = * form . BlockOnOfficialReviewRequests
}
2020-02-13 02:19:35 +03:00
if form . DismissStaleApprovals != nil {
protectBranch . DismissStaleApprovals = * form . DismissStaleApprovals
}
if form . RequireSignedCommits != nil {
protectBranch . RequireSignedCommits = * form . RequireSignedCommits
}
2020-03-27 01:26:34 +03:00
if form . ProtectedFilePatterns != nil {
protectBranch . ProtectedFilePatterns = * form . ProtectedFilePatterns
}
2021-09-11 17:21:17 +03:00
if form . UnprotectedFilePatterns != nil {
protectBranch . UnprotectedFilePatterns = * form . UnprotectedFilePatterns
}
2020-04-17 04:00:36 +03:00
if form . BlockOnOutdatedBranch != nil {
protectBranch . BlockOnOutdatedBranch = * form . BlockOnOutdatedBranch
}
2020-02-13 02:19:35 +03:00
var whitelistUsers [ ] int64
if form . PushWhitelistUsernames != nil {
2022-11-19 11:12:33 +03:00
whitelistUsers , err = user_model . GetUserIDsByNames ( ctx , form . PushWhitelistUsernames , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2021-11-24 12:49:20 +03:00
if user_model . IsErrUserNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "User does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetUserIDsByNames" , err )
return
}
} else {
whitelistUsers = protectBranch . WhitelistUserIDs
}
var mergeWhitelistUsers [ ] int64
if form . MergeWhitelistUsernames != nil {
2022-11-19 11:12:33 +03:00
mergeWhitelistUsers , err = user_model . GetUserIDsByNames ( ctx , form . MergeWhitelistUsernames , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2021-11-24 12:49:20 +03:00
if user_model . IsErrUserNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "User does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetUserIDsByNames" , err )
return
}
} else {
mergeWhitelistUsers = protectBranch . MergeWhitelistUserIDs
}
var approvalsWhitelistUsers [ ] int64
if form . ApprovalsWhitelistUsernames != nil {
2022-11-19 11:12:33 +03:00
approvalsWhitelistUsers , err = user_model . GetUserIDsByNames ( ctx , form . ApprovalsWhitelistUsernames , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2021-11-24 12:49:20 +03:00
if user_model . IsErrUserNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "User does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetUserIDsByNames" , err )
return
}
} else {
approvalsWhitelistUsers = protectBranch . ApprovalsWhitelistUserIDs
}
var whitelistTeams , mergeWhitelistTeams , approvalsWhitelistTeams [ ] int64
if repo . Owner . IsOrganization ( ) {
if form . PushWhitelistTeams != nil {
2022-03-29 09:29:02 +03:00
whitelistTeams , err = organization . GetTeamIDsByNames ( repo . OwnerID , form . PushWhitelistTeams , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2022-03-29 09:29:02 +03:00
if organization . IsErrTeamNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "Team does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetTeamIDsByNames" , err )
return
}
} else {
whitelistTeams = protectBranch . WhitelistTeamIDs
}
if form . MergeWhitelistTeams != nil {
2022-03-29 09:29:02 +03:00
mergeWhitelistTeams , err = organization . GetTeamIDsByNames ( repo . OwnerID , form . MergeWhitelistTeams , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2022-03-29 09:29:02 +03:00
if organization . IsErrTeamNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "Team does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetTeamIDsByNames" , err )
return
}
} else {
mergeWhitelistTeams = protectBranch . MergeWhitelistTeamIDs
}
if form . ApprovalsWhitelistTeams != nil {
2022-03-29 09:29:02 +03:00
approvalsWhitelistTeams , err = organization . GetTeamIDsByNames ( repo . OwnerID , form . ApprovalsWhitelistTeams , false )
2020-02-13 02:19:35 +03:00
if err != nil {
2022-03-29 09:29:02 +03:00
if organization . IsErrTeamNotExist ( err ) {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "Team does not exist" , err )
return
}
ctx . Error ( http . StatusInternalServerError , "GetTeamIDsByNames" , err )
return
}
} else {
approvalsWhitelistTeams = protectBranch . ApprovalsWhitelistTeamIDs
}
}
2022-06-12 18:51:54 +03:00
err = git_model . UpdateProtectBranch ( ctx , ctx . Repo . Repository , protectBranch , git_model . WhitelistOptions {
2020-02-13 02:19:35 +03:00
UserIDs : whitelistUsers ,
TeamIDs : whitelistTeams ,
MergeUserIDs : mergeWhitelistUsers ,
MergeTeamIDs : mergeWhitelistTeams ,
ApprovalsUserIDs : approvalsWhitelistUsers ,
ApprovalsTeamIDs : approvalsWhitelistTeams ,
} )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "UpdateProtectBranch" , err )
return
}
2023-01-16 11:00:22 +03:00
isPlainRule := ! git_model . IsRuleNameSpecial ( bpName )
var isBranchExist bool
if isPlainRule {
isBranchExist = git . IsBranchExist ( ctx . Req . Context ( ) , ctx . Repo . Repository . RepoPath ( ) , bpName )
}
if isBranchExist {
2023-07-22 17:14:27 +03:00
if err = pull_service . CheckPRsForBaseBranch ( ctx , ctx . Repo . Repository , bpName ) ; err != nil {
2023-01-16 11:00:22 +03:00
ctx . Error ( http . StatusInternalServerError , "CheckPrsForBaseBranch" , err )
return
}
} else {
if ! isPlainRule {
if ctx . Repo . GitRepo == nil {
ctx . Repo . GitRepo , err = git . OpenRepository ( ctx , ctx . Repo . Repository . RepoPath ( ) )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "OpenRepository" , err )
return
}
defer func ( ) {
ctx . Repo . GitRepo . Close ( )
ctx . Repo . GitRepo = nil
} ( )
}
// FIXME: since we only need to recheck files protected rules, we could improve this
2023-06-29 13:03:20 +03:00
matchedBranches , err := git_model . FindAllMatchedBranches ( ctx , ctx . Repo . Repository . ID , protectBranch . RuleName )
2023-01-16 11:00:22 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "FindAllMatchedBranches" , err )
return
}
for _ , branchName := range matchedBranches {
2023-07-22 17:14:27 +03:00
if err = pull_service . CheckPRsForBaseBranch ( ctx , ctx . Repo . Repository , branchName ) ; err != nil {
2023-01-16 11:00:22 +03:00
ctx . Error ( http . StatusInternalServerError , "CheckPrsForBaseBranch" , err )
return
}
}
}
2020-10-13 21:50:57 +03:00
}
2020-02-13 02:19:35 +03:00
// Reload from db to ensure get all whitelists
2023-01-16 11:00:22 +03:00
bp , err := git_model . GetProtectedBranchRuleByName ( ctx , repo . ID , bpName )
2020-02-13 02:19:35 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetProtectedBranchBy" , err )
return
}
if bp == nil || bp . RepoID != ctx . Repo . Repository . ID {
ctx . Error ( http . StatusInternalServerError , "New branch protection not found" , err )
return
}
2023-09-14 20:09:32 +03:00
ctx . JSON ( http . StatusOK , convert . ToBranchProtection ( ctx , bp ) )
2020-02-13 02:19:35 +03:00
}
// DeleteBranchProtection deletes a branch protection for a repo
func DeleteBranchProtection ( ctx * context . APIContext ) {
// swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
// ---
// summary: Delete a specific branch protection for the 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: name
// in: path
// description: name of protected branch
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
repo := ctx . Repo . Repository
bpName := ctx . Params ( ":name" )
2023-01-16 11:00:22 +03:00
bp , err := git_model . GetProtectedBranchRuleByName ( ctx , repo . ID , bpName )
2020-02-13 02:19:35 +03:00
if err != nil {
ctx . Error ( http . StatusInternalServerError , "GetProtectedBranchByID" , err )
return
}
if bp == nil || bp . RepoID != repo . ID {
ctx . NotFound ( )
return
}
2023-01-09 06:50:54 +03:00
if err := git_model . DeleteProtectedBranch ( ctx , ctx . Repo . Repository . ID , bp . ID ) ; err != nil {
2020-02-13 02:19:35 +03:00
ctx . Error ( http . StatusInternalServerError , "DeleteProtectedBranch" , err )
return
}
ctx . Status ( http . StatusNoContent )
}