mirror of
				https://gitcode.com/gitea/gitea.git
				synced 2025-10-31 16:38:10 +08:00 
			
		
		
		
	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:
		| @ -30,6 +30,54 @@ func testAPIGetBranch(t *testing.T, branchName string, exists bool) { | |||||||
| 	assert.EqualValues(t, branchName, branch.Name) | 	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) { | func TestAPIGetBranch(t *testing.T) { | ||||||
| 	for _, test := range []struct { | 	for _, test := range []struct { | ||||||
| 		BranchName string | 		BranchName string | ||||||
| @ -43,3 +91,23 @@ func TestAPIGetBranch(t *testing.T) { | |||||||
| 		testAPIGetBranch(t, test.BranchName, test.Exists) | 		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) | 	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. | // getOwnerTeam returns team by given team name and organization. | ||||||
| func getOwnerTeam(e Engine, orgID int64) (*Team, error) { | func getOwnerTeam(e Engine, orgID int64) (*Team, error) { | ||||||
| 	return getTeam(e, orgID, ownerTeamName) | 	return getTeam(e, orgID, ownerTeamName) | ||||||
| @ -574,6 +591,22 @@ func GetTeamByID(teamID int64) (*Team, error) { | |||||||
| 	return getTeamByID(x, teamID) | 	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. | // UpdateTeam updates information of team. | ||||||
| func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) { | func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) { | ||||||
| 	if len(t.Name) == 0 { | 	if len(t.Name) == 0 { | ||||||
|  | |||||||
| @ -1386,6 +1386,17 @@ func GetMaileableUsersByIDs(ids []int64) ([]*User, error) { | |||||||
| 		Find(&ous) | 		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. | // GetUsersByIDs returns all resolved users from a list of Ids. | ||||||
| func GetUsersByIDs(ids []int64) ([]*User, error) { | func GetUsersByIDs(ids []int64) ([]*User, error) { | ||||||
| 	ous := make([]*User, 0, len(ids)) | 	ous := make([]*User, 0, len(ids)) | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ func ToEmail(email *models.EmailAddress) *api.Email { | |||||||
| } | } | ||||||
|  |  | ||||||
| // ToBranch convert a git.Commit and git.Branch to an api.Branch | // 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 { | 	if bp == nil { | ||||||
| 		return &api.Branch{ | 		return &api.Branch{ | ||||||
| 			Name:                          b.Name, | 			Name:                          b.Name, | ||||||
| @ -41,8 +41,14 @@ func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models. | |||||||
| 			StatusCheckContexts:           []string{}, | 			StatusCheckContexts:           []string{}, | ||||||
| 			UserCanPush:                   true, | 			UserCanPush:                   true, | ||||||
| 			UserCanMerge:                  true, | 			UserCanMerge:                  true, | ||||||
|  | 			EffectiveBranchProtectionName: "", | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	branchProtectionName := "" | ||||||
|  | 	if isRepoAdmin { | ||||||
|  | 		branchProtectionName = bp.BranchName | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return &api.Branch{ | 	return &api.Branch{ | ||||||
| 		Name:                          b.Name, | 		Name:                          b.Name, | ||||||
| 		Commit:                        ToCommit(repo, c), | 		Commit:                        ToCommit(repo, c), | ||||||
| @ -52,6 +58,58 @@ func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models. | |||||||
| 		StatusCheckContexts:           bp.StatusCheckContexts, | 		StatusCheckContexts:           bp.StatusCheckContexts, | ||||||
| 		UserCanPush:                   bp.CanUserPush(user.ID), | 		UserCanPush:                   bp.CanUserPush(user.ID), | ||||||
| 		UserCanMerge:                  bp.IsUserMergeWhitelisted(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,6 +4,10 @@ | |||||||
|  |  | ||||||
| package structs | package structs | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
| // Branch represents a repository branch | // Branch represents a repository branch | ||||||
| type Branch struct { | type Branch struct { | ||||||
| 	Name                          string         `json:"name"` | 	Name                          string         `json:"name"` | ||||||
| @ -14,4 +18,74 @@ type Branch struct { | |||||||
| 	StatusCheckContexts           []string       `json:"status_check_contexts"` | 	StatusCheckContexts           []string       `json:"status_check_contexts"` | ||||||
| 	UserCanPush                   bool           `json:"user_can_push"` | 	UserCanPush                   bool           `json:"user_can_push"` | ||||||
| 	UserCanMerge                  bool           `json:"user_can_merge"` | 	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("", repo.ListBranches) | ||||||
| 					m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch) | 					m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch) | ||||||
| 				}, reqRepoReader(models.UnitTypeCode)) | 				}, 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.Group("/tags", func() { | ||||||
| 					m.Get("", repo.ListTags) | 					m.Get("", repo.ListTags) | ||||||
| 				}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true)) | 				}, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true)) | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ package repo | |||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/convert" | 	"code.gitea.io/gitea/modules/convert" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| @ -71,7 +72,7 @@ func GetBranch(ctx *context.APIContext) { | |||||||
| 		return | 		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 | // ListBranches list all the branches of a repository | ||||||
| @ -114,8 +115,509 @@ func ListBranches(ctx *context.APIContext) { | |||||||
| 			ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) | 			ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err) | ||||||
| 			return | 			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) | 	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 | 	// in:body | ||||||
| 	EditReactionOption api.EditReactionOption | 	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"` | 	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 | // TagList | ||||||
| // swagger:response TagList | // swagger:response TagList | ||||||
| type swaggerResponseTagList struct { | 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": { |     "/repos/{owner}/{repo}/branches": { | ||||||
|       "get": { |       "get": { | ||||||
|         "produces": [ |         "produces": [ | ||||||
| @ -9394,6 +9615,10 @@ | |||||||
|         "commit": { |         "commit": { | ||||||
|           "$ref": "#/definitions/PayloadCommit" |           "$ref": "#/definitions/PayloadCommit" | ||||||
|         }, |         }, | ||||||
|  |         "effective_branch_protection_name": { | ||||||
|  |           "type": "string", | ||||||
|  |           "x-go-name": "EffectiveBranchProtectionName" | ||||||
|  |         }, | ||||||
|         "enable_status_check": { |         "enable_status_check": { | ||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
|           "x-go-name": "EnableStatusCheck" |           "x-go-name": "EnableStatusCheck" | ||||||
| @ -9429,6 +9654,117 @@ | |||||||
|       }, |       }, | ||||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" |       "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": { |     "Comment": { | ||||||
|       "description": "Comment represents a comment on a commit or issue", |       "description": "Comment represents a comment on a commit or issue", | ||||||
|       "type": "object", |       "type": "object", | ||||||
| @ -9634,6 +9970,107 @@ | |||||||
|       }, |       }, | ||||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" |       "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": { |     "CreateEmailOption": { | ||||||
|       "description": "CreateEmailOption options when creating email addresses", |       "description": "CreateEmailOption options when creating email addresses", | ||||||
|       "type": "object", |       "type": "object", | ||||||
| @ -10318,6 +10755,103 @@ | |||||||
|       }, |       }, | ||||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" |       "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": { |     "EditDeadlineOption": { | ||||||
|       "description": "EditDeadlineOption options for creating a deadline", |       "description": "EditDeadlineOption options for creating a deadline", | ||||||
|       "type": "object", |       "type": "object", | ||||||
| @ -12880,6 +13414,21 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "BranchProtection": { | ||||||
|  |       "description": "BranchProtection", | ||||||
|  |       "schema": { | ||||||
|  |         "$ref": "#/definitions/BranchProtection" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "BranchProtectionList": { | ||||||
|  |       "description": "BranchProtectionList", | ||||||
|  |       "schema": { | ||||||
|  |         "type": "array", | ||||||
|  |         "items": { | ||||||
|  |           "$ref": "#/definitions/BranchProtection" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "Comment": { |     "Comment": { | ||||||
|       "description": "Comment", |       "description": "Comment", | ||||||
|       "schema": { |       "schema": { | ||||||
| @ -13410,7 +13959,7 @@ | |||||||
|     "parameterBodies": { |     "parameterBodies": { | ||||||
|       "description": "parameterBodies", |       "description": "parameterBodies", | ||||||
|       "schema": { |       "schema": { | ||||||
|         "$ref": "#/definitions/EditReactionOption" |         "$ref": "#/definitions/EditBranchProtectionOption" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "redirect": { |     "redirect": { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 David Svantesson
					David Svantesson