Add API branch protection endpoint (#9311)
* add API branch protection endpoint * lint * Change to use team names instead of ids. * Status codes. * fix * Fix * Add new branch protection options (BlockOnRejectedReviews, DismissStaleApprovals, RequireSignedCommits) * Do xorm query directly * fix xorm GetUserNamesByIDs * Add some tests * Improved GetTeamNamesByID * http status created for CreateBranchProtection * Correct status code in integration test Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: zeripath <art27@cantab.net>
This commit is contained in:
		
							parent
							
								
									908f8952be
								
							
						
					
					
						commit
						9ff4e1d2d9
					
				
					 10 changed files with 1352 additions and 28 deletions
				
			
		|  | @ -30,6 +30,54 @@ func testAPIGetBranch(t *testing.T, branchName string, exists bool) { | |||
| 	assert.EqualValues(t, branchName, branch.Name) | ||||
| } | ||||
| 
 | ||||
| func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { | ||||
| 	session := loginUser(t, "user2") | ||||
| 	token := getTokenForLoggedInUser(t, session) | ||||
| 	req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/branch_protections/%s?token=%s", branchName, token) | ||||
| 	resp := session.MakeRequest(t, req, expectedHTTPStatus) | ||||
| 
 | ||||
| 	if resp.Code == 200 { | ||||
| 		var branchProtection api.BranchProtection | ||||
| 		DecodeJSON(t, resp, &branchProtection) | ||||
| 		assert.EqualValues(t, branchName, branchProtection.BranchName) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func testAPICreateBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { | ||||
| 	session := loginUser(t, "user2") | ||||
| 	token := getTokenForLoggedInUser(t, session) | ||||
| 	req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections?token="+token, &api.BranchProtection{ | ||||
| 		BranchName: branchName, | ||||
| 	}) | ||||
| 	resp := session.MakeRequest(t, req, expectedHTTPStatus) | ||||
| 
 | ||||
| 	if resp.Code == 201 { | ||||
| 		var branchProtection api.BranchProtection | ||||
| 		DecodeJSON(t, resp, &branchProtection) | ||||
| 		assert.EqualValues(t, branchName, branchProtection.BranchName) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func testAPIEditBranchProtection(t *testing.T, branchName string, body *api.BranchProtection, expectedHTTPStatus int) { | ||||
| 	session := loginUser(t, "user2") | ||||
| 	token := getTokenForLoggedInUser(t, session) | ||||
| 	req := NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user2/repo1/branch_protections/"+branchName+"?token="+token, body) | ||||
| 	resp := session.MakeRequest(t, req, expectedHTTPStatus) | ||||
| 
 | ||||
| 	if resp.Code == 200 { | ||||
| 		var branchProtection api.BranchProtection | ||||
| 		DecodeJSON(t, resp, &branchProtection) | ||||
| 		assert.EqualValues(t, branchName, branchProtection.BranchName) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func testAPIDeleteBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { | ||||
| 	session := loginUser(t, "user2") | ||||
| 	token := getTokenForLoggedInUser(t, session) | ||||
| 	req := NewRequestf(t, "DELETE", "/api/v1/repos/user2/repo1/branch_protections/%s?token=%s", branchName, token) | ||||
| 	session.MakeRequest(t, req, expectedHTTPStatus) | ||||
| } | ||||
| 
 | ||||
| func TestAPIGetBranch(t *testing.T) { | ||||
| 	for _, test := range []struct { | ||||
| 		BranchName string | ||||
|  | @ -43,3 +91,23 @@ func TestAPIGetBranch(t *testing.T) { | |||
| 		testAPIGetBranch(t, test.BranchName, test.Exists) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAPIBranchProtection(t *testing.T) { | ||||
| 	defer prepareTestEnv(t)() | ||||
| 
 | ||||
| 	// Branch protection only on branch that exist
 | ||||
| 	testAPICreateBranchProtection(t, "master/doesnotexist", http.StatusNotFound) | ||||
| 	// Get branch protection on branch that exist but not branch protection
 | ||||
| 	testAPIGetBranchProtection(t, "master", http.StatusNotFound) | ||||
| 
 | ||||
| 	testAPICreateBranchProtection(t, "master", http.StatusCreated) | ||||
| 	// Can only create once
 | ||||
| 	testAPICreateBranchProtection(t, "master", http.StatusForbidden) | ||||
| 
 | ||||
| 	testAPIGetBranchProtection(t, "master", http.StatusOK) | ||||
| 	testAPIEditBranchProtection(t, "master", &api.BranchProtection{ | ||||
| 		EnablePush: true, | ||||
| 	}, http.StatusOK) | ||||
| 
 | ||||
| 	testAPIDeleteBranchProtection(t, "master", http.StatusNoContent) | ||||
| } | ||||
|  |  | |||
|  | @ -553,6 +553,23 @@ func GetTeam(orgID int64, name string) (*Team, error) { | |||
| 	return getTeam(x, orgID, name) | ||||
| } | ||||
| 
 | ||||
| // GetTeamIDsByNames returns a slice of team ids corresponds to names.
 | ||||
| func GetTeamIDsByNames(orgID int64, names []string, ignoreNonExistent bool) ([]int64, error) { | ||||
| 	ids := make([]int64, 0, len(names)) | ||||
| 	for _, name := range names { | ||||
| 		u, err := GetTeam(orgID, name) | ||||
| 		if err != nil { | ||||
| 			if ignoreNonExistent { | ||||
| 				continue | ||||
| 			} else { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
| 		ids = append(ids, u.ID) | ||||
| 	} | ||||
| 	return ids, nil | ||||
| } | ||||
| 
 | ||||
| // getOwnerTeam returns team by given team name and organization.
 | ||||
| func getOwnerTeam(e Engine, orgID int64) (*Team, error) { | ||||
| 	return getTeam(e, orgID, ownerTeamName) | ||||
|  | @ -574,6 +591,22 @@ func GetTeamByID(teamID int64) (*Team, error) { | |||
| 	return getTeamByID(x, teamID) | ||||
| } | ||||
| 
 | ||||
| // GetTeamNamesByID returns team's lower name from a list of team ids.
 | ||||
| func GetTeamNamesByID(teamIDs []int64) ([]string, error) { | ||||
| 	if len(teamIDs) == 0 { | ||||
| 		return []string{}, nil | ||||
| 	} | ||||
| 
 | ||||
| 	var teamNames []string | ||||
| 	err := x.Table("team"). | ||||
| 		Select("lower_name"). | ||||
| 		In("id", teamIDs). | ||||
| 		Asc("name"). | ||||
| 		Find(&teamNames) | ||||
| 
 | ||||
| 	return teamNames, err | ||||
| } | ||||
| 
 | ||||
| // UpdateTeam updates information of team.
 | ||||
| func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) { | ||||
| 	if len(t.Name) == 0 { | ||||
|  |  | |||
|  | @ -1386,6 +1386,17 @@ func GetMaileableUsersByIDs(ids []int64) ([]*User, error) { | |||
| 		Find(&ous) | ||||
| } | ||||
| 
 | ||||
| // GetUserNamesByIDs returns usernames for all resolved users from a list of Ids.
 | ||||
| func GetUserNamesByIDs(ids []int64) ([]string, error) { | ||||
| 	unames := make([]string, 0, len(ids)) | ||||
| 	err := x.In("id", ids). | ||||
| 		Table("user"). | ||||
| 		Asc("name"). | ||||
| 		Cols("name"). | ||||
| 		Find(&unames) | ||||
| 	return unames, err | ||||
| } | ||||
| 
 | ||||
| // GetUsersByIDs returns all resolved users from a list of Ids.
 | ||||
| func GetUsersByIDs(ids []int64) ([]*User, error) { | ||||
| 	ous := make([]*User, 0, len(ids)) | ||||
|  |  | |||
|  | @ -30,28 +30,86 @@ func ToEmail(email *models.EmailAddress) *api.Email { | |||
| } | ||||
| 
 | ||||
| // ToBranch convert a git.Commit and git.Branch to an api.Branch
 | ||||
| func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models.ProtectedBranch, user *models.User) *api.Branch { | ||||
| func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models.ProtectedBranch, user *models.User, isRepoAdmin bool) *api.Branch { | ||||
| 	if bp == nil { | ||||
| 		return &api.Branch{ | ||||
| 			Name:                b.Name, | ||||
| 			Commit:              ToCommit(repo, c), | ||||
| 			Protected:           false, | ||||
| 			RequiredApprovals:   0, | ||||
| 			EnableStatusCheck:   false, | ||||
| 			StatusCheckContexts: []string{}, | ||||
| 			UserCanPush:         true, | ||||
| 			UserCanMerge:        true, | ||||
| 			Name:                          b.Name, | ||||
| 			Commit:                        ToCommit(repo, c), | ||||
| 			Protected:                     false, | ||||
| 			RequiredApprovals:             0, | ||||
| 			EnableStatusCheck:             false, | ||||
| 			StatusCheckContexts:           []string{}, | ||||
| 			UserCanPush:                   true, | ||||
| 			UserCanMerge:                  true, | ||||
| 			EffectiveBranchProtectionName: "", | ||||
| 		} | ||||
| 	} | ||||
| 	branchProtectionName := "" | ||||
| 	if isRepoAdmin { | ||||
| 		branchProtectionName = bp.BranchName | ||||
| 	} | ||||
| 
 | ||||
| 	return &api.Branch{ | ||||
| 		Name:                b.Name, | ||||
| 		Commit:              ToCommit(repo, c), | ||||
| 		Protected:           true, | ||||
| 		RequiredApprovals:   bp.RequiredApprovals, | ||||
| 		EnableStatusCheck:   bp.EnableStatusCheck, | ||||
| 		StatusCheckContexts: bp.StatusCheckContexts, | ||||
| 		UserCanPush:         bp.CanUserPush(user.ID), | ||||
| 		UserCanMerge:        bp.IsUserMergeWhitelisted(user.ID), | ||||
| 		Name:                          b.Name, | ||||
| 		Commit:                        ToCommit(repo, c), | ||||
| 		Protected:                     true, | ||||
| 		RequiredApprovals:             bp.RequiredApprovals, | ||||
| 		EnableStatusCheck:             bp.EnableStatusCheck, | ||||
| 		StatusCheckContexts:           bp.StatusCheckContexts, | ||||
| 		UserCanPush:                   bp.CanUserPush(user.ID), | ||||
| 		UserCanMerge:                  bp.IsUserMergeWhitelisted(user.ID), | ||||
| 		EffectiveBranchProtectionName: branchProtectionName, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // ToBranchProtection convert a ProtectedBranch to api.BranchProtection
 | ||||
| func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection { | ||||
| 	pushWhitelistUsernames, err := models.GetUserNamesByIDs(bp.WhitelistUserIDs) | ||||
| 	if err != nil { | ||||
| 		log.Error("GetUserNamesByIDs (WhitelistUserIDs): %v", err) | ||||
| 	} | ||||
| 	mergeWhitelistUsernames, err := models.GetUserNamesByIDs(bp.MergeWhitelistUserIDs) | ||||
| 	if err != nil { | ||||
| 		log.Error("GetUserNamesByIDs (MergeWhitelistUserIDs): %v", err) | ||||
| 	} | ||||
| 	approvalsWhitelistUsernames, err := models.GetUserNamesByIDs(bp.ApprovalsWhitelistUserIDs) | ||||
| 	if err != nil { | ||||
| 		log.Error("GetUserNamesByIDs (ApprovalsWhitelistUserIDs): %v", err) | ||||
| 	} | ||||
| 	pushWhitelistTeams, err := models.GetTeamNamesByID(bp.WhitelistTeamIDs) | ||||
| 	if err != nil { | ||||
| 		log.Error("GetTeamNamesByID (WhitelistTeamIDs): %v", err) | ||||
| 	} | ||||
| 	mergeWhitelistTeams, err := models.GetTeamNamesByID(bp.MergeWhitelistTeamIDs) | ||||
| 	if err != nil { | ||||
| 		log.Error("GetTeamNamesByID (MergeWhitelistTeamIDs): %v", err) | ||||
| 	} | ||||
| 	approvalsWhitelistTeams, err := models.GetTeamNamesByID(bp.ApprovalsWhitelistTeamIDs) | ||||
| 	if err != nil { | ||||
| 		log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return &api.BranchProtection{ | ||||
| 		BranchName:                  bp.BranchName, | ||||
| 		EnablePush:                  bp.CanPush, | ||||
| 		EnablePushWhitelist:         bp.EnableWhitelist, | ||||
| 		PushWhitelistUsernames:      pushWhitelistUsernames, | ||||
| 		PushWhitelistTeams:          pushWhitelistTeams, | ||||
| 		PushWhitelistDeployKeys:     bp.WhitelistDeployKeys, | ||||
| 		EnableMergeWhitelist:        bp.EnableMergeWhitelist, | ||||
| 		MergeWhitelistUsernames:     mergeWhitelistUsernames, | ||||
| 		MergeWhitelistTeams:         mergeWhitelistTeams, | ||||
| 		EnableStatusCheck:           bp.EnableStatusCheck, | ||||
| 		StatusCheckContexts:         bp.StatusCheckContexts, | ||||
| 		RequiredApprovals:           bp.RequiredApprovals, | ||||
| 		EnableApprovalsWhitelist:    bp.EnableApprovalsWhitelist, | ||||
| 		ApprovalsWhitelistUsernames: approvalsWhitelistUsernames, | ||||
| 		ApprovalsWhitelistTeams:     approvalsWhitelistTeams, | ||||
| 		BlockOnRejectedReviews:      bp.BlockOnRejectedReviews, | ||||
| 		DismissStaleApprovals:       bp.DismissStaleApprovals, | ||||
| 		RequireSignedCommits:        bp.RequireSignedCommits, | ||||
| 		Created:                     bp.CreatedUnix.AsTime(), | ||||
| 		Updated:                     bp.UpdatedUnix.AsTime(), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,14 +4,88 @@ | |||
| 
 | ||||
| package structs | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // Branch represents a repository branch
 | ||||
| type Branch struct { | ||||
| 	Name                string         `json:"name"` | ||||
| 	Commit              *PayloadCommit `json:"commit"` | ||||
| 	Protected           bool           `json:"protected"` | ||||
| 	RequiredApprovals   int64          `json:"required_approvals"` | ||||
| 	EnableStatusCheck   bool           `json:"enable_status_check"` | ||||
| 	StatusCheckContexts []string       `json:"status_check_contexts"` | ||||
| 	UserCanPush         bool           `json:"user_can_push"` | ||||
| 	UserCanMerge        bool           `json:"user_can_merge"` | ||||
| 	Name                          string         `json:"name"` | ||||
| 	Commit                        *PayloadCommit `json:"commit"` | ||||
| 	Protected                     bool           `json:"protected"` | ||||
| 	RequiredApprovals             int64          `json:"required_approvals"` | ||||
| 	EnableStatusCheck             bool           `json:"enable_status_check"` | ||||
| 	StatusCheckContexts           []string       `json:"status_check_contexts"` | ||||
| 	UserCanPush                   bool           `json:"user_can_push"` | ||||
| 	UserCanMerge                  bool           `json:"user_can_merge"` | ||||
| 	EffectiveBranchProtectionName string         `json:"effective_branch_protection_name"` | ||||
| } | ||||
| 
 | ||||
| // BranchProtection represents a branch protection for a repository
 | ||||
| type BranchProtection struct { | ||||
| 	BranchName                  string   `json:"branch_name"` | ||||
| 	EnablePush                  bool     `json:"enable_push"` | ||||
| 	EnablePushWhitelist         bool     `json:"enable_push_whitelist"` | ||||
| 	PushWhitelistUsernames      []string `json:"push_whitelist_usernames"` | ||||
| 	PushWhitelistTeams          []string `json:"push_whitelist_teams"` | ||||
| 	PushWhitelistDeployKeys     bool     `json:"push_whitelist_deploy_keys"` | ||||
| 	EnableMergeWhitelist        bool     `json:"enable_merge_whitelist"` | ||||
| 	MergeWhitelistUsernames     []string `json:"merge_whitelist_usernames"` | ||||
| 	MergeWhitelistTeams         []string `json:"merge_whitelist_teams"` | ||||
| 	EnableStatusCheck           bool     `json:"enable_status_check"` | ||||
| 	StatusCheckContexts         []string `json:"status_check_contexts"` | ||||
| 	RequiredApprovals           int64    `json:"required_approvals"` | ||||
| 	EnableApprovalsWhitelist    bool     `json:"enable_approvals_whitelist"` | ||||
| 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | ||||
| 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | ||||
| 	BlockOnRejectedReviews      bool     `json:"block_on_rejected_reviews"` | ||||
| 	DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"` | ||||
| 	RequireSignedCommits        bool     `json:"require_signed_commits"` | ||||
| 	// swagger:strfmt date-time
 | ||||
| 	Created time.Time `json:"created_at"` | ||||
| 	// swagger:strfmt date-time
 | ||||
| 	Updated time.Time `json:"updated_at"` | ||||
| } | ||||
| 
 | ||||
| // CreateBranchProtectionOption options for creating a branch protection
 | ||||
| type CreateBranchProtectionOption struct { | ||||
| 	BranchName                  string   `json:"branch_name"` | ||||
| 	EnablePush                  bool     `json:"enable_push"` | ||||
| 	EnablePushWhitelist         bool     `json:"enable_push_whitelist"` | ||||
| 	PushWhitelistUsernames      []string `json:"push_whitelist_usernames"` | ||||
| 	PushWhitelistTeams          []string `json:"push_whitelist_teams"` | ||||
| 	PushWhitelistDeployKeys     bool     `json:"push_whitelist_deploy_keys"` | ||||
| 	EnableMergeWhitelist        bool     `json:"enable_merge_whitelist"` | ||||
| 	MergeWhitelistUsernames     []string `json:"merge_whitelist_usernames"` | ||||
| 	MergeWhitelistTeams         []string `json:"merge_whitelist_teams"` | ||||
| 	EnableStatusCheck           bool     `json:"enable_status_check"` | ||||
| 	StatusCheckContexts         []string `json:"status_check_contexts"` | ||||
| 	RequiredApprovals           int64    `json:"required_approvals"` | ||||
| 	EnableApprovalsWhitelist    bool     `json:"enable_approvals_whitelist"` | ||||
| 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | ||||
| 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | ||||
| 	BlockOnRejectedReviews      bool     `json:"block_on_rejected_reviews"` | ||||
| 	DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"` | ||||
| 	RequireSignedCommits        bool     `json:"require_signed_commits"` | ||||
| } | ||||
| 
 | ||||
| // EditBranchProtectionOption options for editing a branch protection
 | ||||
| type EditBranchProtectionOption struct { | ||||
| 	EnablePush                  *bool    `json:"enable_push"` | ||||
| 	EnablePushWhitelist         *bool    `json:"enable_push_whitelist"` | ||||
| 	PushWhitelistUsernames      []string `json:"push_whitelist_usernames"` | ||||
| 	PushWhitelistTeams          []string `json:"push_whitelist_teams"` | ||||
| 	PushWhitelistDeployKeys     *bool    `json:"push_whitelist_deploy_keys"` | ||||
| 	EnableMergeWhitelist        *bool    `json:"enable_merge_whitelist"` | ||||
| 	MergeWhitelistUsernames     []string `json:"merge_whitelist_usernames"` | ||||
| 	MergeWhitelistTeams         []string `json:"merge_whitelist_teams"` | ||||
| 	EnableStatusCheck           *bool    `json:"enable_status_check"` | ||||
| 	StatusCheckContexts         []string `json:"status_check_contexts"` | ||||
| 	RequiredApprovals           *int64   `json:"required_approvals"` | ||||
| 	EnableApprovalsWhitelist    *bool    `json:"enable_approvals_whitelist"` | ||||
| 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | ||||
| 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | ||||
| 	BlockOnRejectedReviews      *bool    `json:"block_on_rejected_reviews"` | ||||
| 	DismissStaleApprovals       *bool    `json:"dismiss_stale_approvals"` | ||||
| 	RequireSignedCommits        *bool    `json:"require_signed_commits"` | ||||
| } | ||||
|  |  | |||
|  | @ -656,6 +656,15 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| 					m.Get("", repo.ListBranches) | ||||
| 					m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch) | ||||
| 				}, reqRepoReader(models.UnitTypeCode)) | ||||
| 				m.Group("/branch_protections", func() { | ||||
| 					m.Get("", repo.ListBranchProtections) | ||||
| 					m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection) | ||||
| 					m.Group("/:name", func() { | ||||
| 						m.Get("", repo.GetBranchProtection) | ||||
| 						m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection) | ||||
| 						m.Delete("", repo.DeleteBranchProtection) | ||||
| 					}) | ||||
| 				}, reqToken(), reqAdmin()) | ||||
| 				m.Group("/tags", func() { | ||||
| 					m.Get("", repo.ListTags) | ||||
| 				}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true)) | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ package repo | |||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	"code.gitea.io/gitea/modules/convert" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
|  | @ -71,7 +72,7 @@ func GetBranch(ctx *context.APIContext) { | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(http.StatusOK, convert.ToBranch(ctx.Repo.Repository, branch, c, branchProtection, ctx.User)) | ||||
| 	ctx.JSON(http.StatusOK, convert.ToBranch(ctx.Repo.Repository, branch, c, branchProtection, ctx.User, ctx.Repo.IsAdmin())) | ||||
| } | ||||
| 
 | ||||
| // ListBranches list all the branches of a repository
 | ||||
|  | @ -114,8 +115,509 @@ func ListBranches(ctx *context.APIContext) { | |||
| 			ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) | ||||
| 			return | ||||
| 		} | ||||
| 		apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.User) | ||||
| 		apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.User, ctx.Repo.IsAdmin()) | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(http.StatusOK, &apiBranches) | ||||
| } | ||||
| 
 | ||||
| // 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") | ||||
| 	bp, err := models.GetProtectedBranchBy(repo.ID, bpName) | ||||
| 	if err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) | ||||
| 		return | ||||
| 	} | ||||
| 	if bp == nil || bp.RepoID != repo.ID { | ||||
| 		ctx.NotFound() | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp)) | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| 	bps, err := repo.GetProtectedBranches() | ||||
| 	if err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err) | ||||
| 		return | ||||
| 	} | ||||
| 	apiBps := make([]*api.BranchProtection, len(bps)) | ||||
| 	for i := range bps { | ||||
| 		apiBps[i] = convert.ToBranchProtection(bps[i]) | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(http.StatusOK, apiBps) | ||||
| } | ||||
| 
 | ||||
| // CreateBranchProtection creates a branch protection for a repo
 | ||||
| func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtectionOption) { | ||||
| 	// 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"
 | ||||
| 
 | ||||
| 	repo := ctx.Repo.Repository | ||||
| 
 | ||||
| 	// Currently protection must match an actual branch
 | ||||
| 	if !git.IsBranchExist(ctx.Repo.Repository.RepoPath(), form.BranchName) { | ||||
| 		ctx.NotFound() | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	protectBranch, err := models.GetProtectedBranchBy(repo.ID, form.BranchName) | ||||
| 	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 | ||||
| 	} | ||||
| 
 | ||||
| 	whitelistUsers, err := models.GetUserIDsByNames(form.PushWhitelistUsernames, false) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrUserNotExist(err) { | ||||
| 			ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err) | ||||
| 		return | ||||
| 	} | ||||
| 	mergeWhitelistUsers, err := models.GetUserIDsByNames(form.MergeWhitelistUsernames, false) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrUserNotExist(err) { | ||||
| 			ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err) | ||||
| 		return | ||||
| 	} | ||||
| 	approvalsWhitelistUsers, err := models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrUserNotExist(err) { | ||||
| 			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() { | ||||
| 		whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrTeamNotExist(err) { | ||||
| 				ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) | ||||
| 				return | ||||
| 			} | ||||
| 			ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) | ||||
| 			return | ||||
| 		} | ||||
| 		mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrTeamNotExist(err) { | ||||
| 				ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) | ||||
| 				return | ||||
| 			} | ||||
| 			ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) | ||||
| 			return | ||||
| 		} | ||||
| 		approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrTeamNotExist(err) { | ||||
| 				ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) | ||||
| 				return | ||||
| 			} | ||||
| 			ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	protectBranch = &models.ProtectedBranch{ | ||||
| 		RepoID:                   ctx.Repo.Repository.ID, | ||||
| 		BranchName:               form.BranchName, | ||||
| 		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, | ||||
| 		DismissStaleApprovals:    form.DismissStaleApprovals, | ||||
| 		RequireSignedCommits:     form.RequireSignedCommits, | ||||
| 	} | ||||
| 
 | ||||
| 	err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ | ||||
| 		UserIDs:          whitelistUsers, | ||||
| 		TeamIDs:          whitelistTeams, | ||||
| 		MergeUserIDs:     mergeWhitelistUsers, | ||||
| 		MergeTeamIDs:     mergeWhitelistTeams, | ||||
| 		ApprovalsUserIDs: approvalsWhitelistUsers, | ||||
| 		ApprovalsTeamIDs: approvalsWhitelistTeams, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Reload from db to get all whitelists
 | ||||
| 	bp, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, form.BranchName) | ||||
| 	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 | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(http.StatusCreated, convert.ToBranchProtection(bp)) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // EditBranchProtection edits a branch protection for a repo
 | ||||
| func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtectionOption) { | ||||
| 	// 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"
 | ||||
| 
 | ||||
| 	repo := ctx.Repo.Repository | ||||
| 	bpName := ctx.Params(":name") | ||||
| 	protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName) | ||||
| 	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 | ||||
| 	} | ||||
| 	if protectBranch.EnableStatusCheck { | ||||
| 		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 | ||||
| 	} | ||||
| 
 | ||||
| 	if form.DismissStaleApprovals != nil { | ||||
| 		protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals | ||||
| 	} | ||||
| 
 | ||||
| 	if form.RequireSignedCommits != nil { | ||||
| 		protectBranch.RequireSignedCommits = *form.RequireSignedCommits | ||||
| 	} | ||||
| 
 | ||||
| 	var whitelistUsers []int64 | ||||
| 	if form.PushWhitelistUsernames != nil { | ||||
| 		whitelistUsers, err = models.GetUserIDsByNames(form.PushWhitelistUsernames, false) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrUserNotExist(err) { | ||||
| 				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 { | ||||
| 		mergeWhitelistUsers, err = models.GetUserIDsByNames(form.MergeWhitelistUsernames, false) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrUserNotExist(err) { | ||||
| 				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 { | ||||
| 		approvalsWhitelistUsers, err = models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrUserNotExist(err) { | ||||
| 				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 { | ||||
| 			whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false) | ||||
| 			if err != nil { | ||||
| 				if models.IsErrTeamNotExist(err) { | ||||
| 					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 { | ||||
| 			mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false) | ||||
| 			if err != nil { | ||||
| 				if models.IsErrTeamNotExist(err) { | ||||
| 					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 { | ||||
| 			approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false) | ||||
| 			if err != nil { | ||||
| 				if models.IsErrTeamNotExist(err) { | ||||
| 					ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) | ||||
| 					return | ||||
| 				} | ||||
| 				ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} else { | ||||
| 			approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ | ||||
| 		UserIDs:          whitelistUsers, | ||||
| 		TeamIDs:          whitelistTeams, | ||||
| 		MergeUserIDs:     mergeWhitelistUsers, | ||||
| 		MergeTeamIDs:     mergeWhitelistTeams, | ||||
| 		ApprovalsUserIDs: approvalsWhitelistUsers, | ||||
| 		ApprovalsTeamIDs: approvalsWhitelistTeams, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Reload from db to ensure get all whitelists
 | ||||
| 	bp, err := models.GetProtectedBranchBy(repo.ID, bpName) | ||||
| 	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 | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp)) | ||||
| } | ||||
| 
 | ||||
| // 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") | ||||
| 	bp, err := models.GetProtectedBranchBy(repo.ID, bpName) | ||||
| 	if err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) | ||||
| 		return | ||||
| 	} | ||||
| 	if bp == nil || bp.RepoID != repo.ID { | ||||
| 		ctx.NotFound() | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if err := ctx.Repo.Repository.DeleteProtectedBranch(bp.ID); err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Status(http.StatusNoContent) | ||||
| } | ||||
|  |  | |||
|  | @ -128,4 +128,10 @@ type swaggerParameterBodies struct { | |||
| 
 | ||||
| 	// in:body
 | ||||
| 	EditReactionOption api.EditReactionOption | ||||
| 
 | ||||
| 	// in:body
 | ||||
| 	CreateBranchProtectionOption api.CreateBranchProtectionOption | ||||
| 
 | ||||
| 	// in:body
 | ||||
| 	EditBranchProtectionOption api.EditBranchProtectionOption | ||||
| } | ||||
|  |  | |||
|  | @ -36,6 +36,20 @@ type swaggerResponseBranchList struct { | |||
| 	Body []api.Branch `json:"body"` | ||||
| } | ||||
| 
 | ||||
| // BranchProtection
 | ||||
| // swagger:response BranchProtection
 | ||||
| type swaggerResponseBranchProtection struct { | ||||
| 	// in:body
 | ||||
| 	Body api.BranchProtection `json:"body"` | ||||
| } | ||||
| 
 | ||||
| // BranchProtectionList
 | ||||
| // swagger:response BranchProtectionList
 | ||||
| type swaggerResponseBranchProtectionList struct { | ||||
| 	// in:body
 | ||||
| 	Body []api.BranchProtection `json:"body"` | ||||
| } | ||||
| 
 | ||||
| // TagList
 | ||||
| // swagger:response TagList
 | ||||
| type swaggerResponseTagList struct { | ||||
|  |  | |||
|  | @ -1797,6 +1797,227 @@ | |||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/repos/{owner}/{repo}/branch_protections": { | ||||
|       "get": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "List branch protections for a repository", | ||||
|         "operationId": "repoListBranchProtection", | ||||
|         "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 | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "$ref": "#/responses/BranchProtectionList" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "post": { | ||||
|         "consumes": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Create a branch protections for a repository", | ||||
|         "operationId": "repoCreateBranchProtection", | ||||
|         "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 | ||||
|           }, | ||||
|           { | ||||
|             "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" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/repos/{owner}/{repo}/branch_protections/{name}": { | ||||
|       "get": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Get a specific branch protection for the repository", | ||||
|         "operationId": "repoGetBranchProtection", | ||||
|         "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": "name of protected branch", | ||||
|             "name": "name", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "$ref": "#/responses/BranchProtection" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "delete": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Delete a specific branch protection for the repository", | ||||
|         "operationId": "repoDeleteBranchProtection", | ||||
|         "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": "name of protected branch", | ||||
|             "name": "name", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "204": { | ||||
|             "$ref": "#/responses/empty" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "patch": { | ||||
|         "consumes": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Edit a branch protections for a repository. Only fields that are set will be changed", | ||||
|         "operationId": "repoEditBranchProtection", | ||||
|         "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": "name of protected branch", | ||||
|             "name": "name", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "name": "body", | ||||
|             "in": "body", | ||||
|             "schema": { | ||||
|               "$ref": "#/definitions/EditBranchProtectionOption" | ||||
|             } | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "$ref": "#/responses/BranchProtection" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           }, | ||||
|           "422": { | ||||
|             "$ref": "#/responses/validationError" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/repos/{owner}/{repo}/branches": { | ||||
|       "get": { | ||||
|         "produces": [ | ||||
|  | @ -9394,6 +9615,10 @@ | |||
|         "commit": { | ||||
|           "$ref": "#/definitions/PayloadCommit" | ||||
|         }, | ||||
|         "effective_branch_protection_name": { | ||||
|           "type": "string", | ||||
|           "x-go-name": "EffectiveBranchProtectionName" | ||||
|         }, | ||||
|         "enable_status_check": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableStatusCheck" | ||||
|  | @ -9429,6 +9654,117 @@ | |||
|       }, | ||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
|     }, | ||||
|     "BranchProtection": { | ||||
|       "description": "BranchProtection represents a branch protection for a repository", | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "approvals_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "ApprovalsWhitelistTeams" | ||||
|         }, | ||||
|         "approvals_whitelist_username": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "ApprovalsWhitelistUsernames" | ||||
|         }, | ||||
|         "block_on_rejected_reviews": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "BlockOnRejectedReviews" | ||||
|         }, | ||||
|         "branch_name": { | ||||
|           "type": "string", | ||||
|           "x-go-name": "BranchName" | ||||
|         }, | ||||
|         "created_at": { | ||||
|           "type": "string", | ||||
|           "format": "date-time", | ||||
|           "x-go-name": "Created" | ||||
|         }, | ||||
|         "dismiss_stale_approvals": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "DismissStaleApprovals" | ||||
|         }, | ||||
|         "enable_approvals_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableApprovalsWhitelist" | ||||
|         }, | ||||
|         "enable_merge_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableMergeWhitelist" | ||||
|         }, | ||||
|         "enable_push": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnablePush" | ||||
|         }, | ||||
|         "enable_push_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnablePushWhitelist" | ||||
|         }, | ||||
|         "enable_status_check": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableStatusCheck" | ||||
|         }, | ||||
|         "merge_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "MergeWhitelistTeams" | ||||
|         }, | ||||
|         "merge_whitelist_usernames": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "MergeWhitelistUsernames" | ||||
|         }, | ||||
|         "push_whitelist_deploy_keys": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "PushWhitelistDeployKeys" | ||||
|         }, | ||||
|         "push_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "PushWhitelistTeams" | ||||
|         }, | ||||
|         "push_whitelist_usernames": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "PushWhitelistUsernames" | ||||
|         }, | ||||
|         "require_signed_commits": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "RequireSignedCommits" | ||||
|         }, | ||||
|         "required_approvals": { | ||||
|           "type": "integer", | ||||
|           "format": "int64", | ||||
|           "x-go-name": "RequiredApprovals" | ||||
|         }, | ||||
|         "status_check_contexts": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "StatusCheckContexts" | ||||
|         }, | ||||
|         "updated_at": { | ||||
|           "type": "string", | ||||
|           "format": "date-time", | ||||
|           "x-go-name": "Updated" | ||||
|         } | ||||
|       }, | ||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
|     }, | ||||
|     "Comment": { | ||||
|       "description": "Comment represents a comment on a commit or issue", | ||||
|       "type": "object", | ||||
|  | @ -9634,6 +9970,107 @@ | |||
|       }, | ||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
|     }, | ||||
|     "CreateBranchProtectionOption": { | ||||
|       "description": "CreateBranchProtectionOption options for creating a branch protection", | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "approvals_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "ApprovalsWhitelistTeams" | ||||
|         }, | ||||
|         "approvals_whitelist_username": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "ApprovalsWhitelistUsernames" | ||||
|         }, | ||||
|         "block_on_rejected_reviews": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "BlockOnRejectedReviews" | ||||
|         }, | ||||
|         "branch_name": { | ||||
|           "type": "string", | ||||
|           "x-go-name": "BranchName" | ||||
|         }, | ||||
|         "dismiss_stale_approvals": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "DismissStaleApprovals" | ||||
|         }, | ||||
|         "enable_approvals_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableApprovalsWhitelist" | ||||
|         }, | ||||
|         "enable_merge_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableMergeWhitelist" | ||||
|         }, | ||||
|         "enable_push": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnablePush" | ||||
|         }, | ||||
|         "enable_push_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnablePushWhitelist" | ||||
|         }, | ||||
|         "enable_status_check": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableStatusCheck" | ||||
|         }, | ||||
|         "merge_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "MergeWhitelistTeams" | ||||
|         }, | ||||
|         "merge_whitelist_usernames": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "MergeWhitelistUsernames" | ||||
|         }, | ||||
|         "push_whitelist_deploy_keys": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "PushWhitelistDeployKeys" | ||||
|         }, | ||||
|         "push_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "PushWhitelistTeams" | ||||
|         }, | ||||
|         "push_whitelist_usernames": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "PushWhitelistUsernames" | ||||
|         }, | ||||
|         "require_signed_commits": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "RequireSignedCommits" | ||||
|         }, | ||||
|         "required_approvals": { | ||||
|           "type": "integer", | ||||
|           "format": "int64", | ||||
|           "x-go-name": "RequiredApprovals" | ||||
|         }, | ||||
|         "status_check_contexts": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "StatusCheckContexts" | ||||
|         } | ||||
|       }, | ||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
|     }, | ||||
|     "CreateEmailOption": { | ||||
|       "description": "CreateEmailOption options when creating email addresses", | ||||
|       "type": "object", | ||||
|  | @ -10318,6 +10755,103 @@ | |||
|       }, | ||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
|     }, | ||||
|     "EditBranchProtectionOption": { | ||||
|       "description": "EditBranchProtectionOption options for editing a branch protection", | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "approvals_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "ApprovalsWhitelistTeams" | ||||
|         }, | ||||
|         "approvals_whitelist_username": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "ApprovalsWhitelistUsernames" | ||||
|         }, | ||||
|         "block_on_rejected_reviews": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "BlockOnRejectedReviews" | ||||
|         }, | ||||
|         "dismiss_stale_approvals": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "DismissStaleApprovals" | ||||
|         }, | ||||
|         "enable_approvals_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableApprovalsWhitelist" | ||||
|         }, | ||||
|         "enable_merge_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableMergeWhitelist" | ||||
|         }, | ||||
|         "enable_push": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnablePush" | ||||
|         }, | ||||
|         "enable_push_whitelist": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnablePushWhitelist" | ||||
|         }, | ||||
|         "enable_status_check": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "EnableStatusCheck" | ||||
|         }, | ||||
|         "merge_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "MergeWhitelistTeams" | ||||
|         }, | ||||
|         "merge_whitelist_usernames": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "MergeWhitelistUsernames" | ||||
|         }, | ||||
|         "push_whitelist_deploy_keys": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "PushWhitelistDeployKeys" | ||||
|         }, | ||||
|         "push_whitelist_teams": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "PushWhitelistTeams" | ||||
|         }, | ||||
|         "push_whitelist_usernames": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "PushWhitelistUsernames" | ||||
|         }, | ||||
|         "require_signed_commits": { | ||||
|           "type": "boolean", | ||||
|           "x-go-name": "RequireSignedCommits" | ||||
|         }, | ||||
|         "required_approvals": { | ||||
|           "type": "integer", | ||||
|           "format": "int64", | ||||
|           "x-go-name": "RequiredApprovals" | ||||
|         }, | ||||
|         "status_check_contexts": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "StatusCheckContexts" | ||||
|         } | ||||
|       }, | ||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
|     }, | ||||
|     "EditDeadlineOption": { | ||||
|       "description": "EditDeadlineOption options for creating a deadline", | ||||
|       "type": "object", | ||||
|  | @ -12880,6 +13414,21 @@ | |||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "BranchProtection": { | ||||
|       "description": "BranchProtection", | ||||
|       "schema": { | ||||
|         "$ref": "#/definitions/BranchProtection" | ||||
|       } | ||||
|     }, | ||||
|     "BranchProtectionList": { | ||||
|       "description": "BranchProtectionList", | ||||
|       "schema": { | ||||
|         "type": "array", | ||||
|         "items": { | ||||
|           "$ref": "#/definitions/BranchProtection" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "Comment": { | ||||
|       "description": "Comment", | ||||
|       "schema": { | ||||
|  | @ -13410,7 +13959,7 @@ | |||
|     "parameterBodies": { | ||||
|       "description": "parameterBodies", | ||||
|       "schema": { | ||||
|         "$ref": "#/definitions/EditReactionOption" | ||||
|         "$ref": "#/definitions/EditBranchProtectionOption" | ||||
|       } | ||||
|     }, | ||||
|     "redirect": { | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue