mirror of
https://github.com/go-gitea/gitea.git
synced 2025-06-21 13:38:00 +02:00
Approvals at Branch Protection (#5350)
* Add branch protection for approvals Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add required approvals Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add missing comments and fmt Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add type = approval and group by reviewer_id to review * Prevent users from adding negative review limits * Add migration for approval whitelists Signed-off-by: Jonas Franz <info@jonasfranz.software>
This commit is contained in:
@ -23,18 +23,21 @@ const (
|
||||
|
||||
// ProtectedBranch struct
|
||||
type ProtectedBranch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
BranchName string `xorm:"UNIQUE(s)"`
|
||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||
EnableWhitelist bool
|
||||
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
CreatedUnix util.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix util.TimeStamp `xorm:"updated"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
BranchName string `xorm:"UNIQUE(s)"`
|
||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||
EnableWhitelist bool
|
||||
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
CreatedUnix util.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix util.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
// IsProtected returns if the branch is protected
|
||||
@ -86,6 +89,41 @@ func (protectBranch *ProtectedBranch) CanUserMerge(userID int64) bool {
|
||||
return in
|
||||
}
|
||||
|
||||
// HasEnoughApprovals returns true if pr has enough granted approvals.
|
||||
func (protectBranch *ProtectedBranch) HasEnoughApprovals(pr *PullRequest) bool {
|
||||
if protectBranch.RequiredApprovals == 0 {
|
||||
return true
|
||||
}
|
||||
return protectBranch.GetGrantedApprovalsCount(pr) >= protectBranch.RequiredApprovals
|
||||
}
|
||||
|
||||
// GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist.
|
||||
func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest) int64 {
|
||||
reviews, err := GetReviewersByPullID(pr.ID)
|
||||
if err != nil {
|
||||
log.Error(1, "GetUniqueApprovalsByPullRequestID:", err)
|
||||
return 0
|
||||
}
|
||||
approvals := int64(0)
|
||||
userIDs := make([]int64, 0)
|
||||
for _, review := range reviews {
|
||||
if review.Type != ReviewTypeApprove {
|
||||
continue
|
||||
}
|
||||
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, review.ID) {
|
||||
approvals++
|
||||
continue
|
||||
}
|
||||
userIDs = append(userIDs, review.ID)
|
||||
}
|
||||
approvalTeamCount, err := UsersInTeamsCount(userIDs, protectBranch.ApprovalsWhitelistTeamIDs)
|
||||
if err != nil {
|
||||
log.Error(1, "UsersInTeamsCount:", err)
|
||||
return 0
|
||||
}
|
||||
return approvalTeamCount + approvals
|
||||
}
|
||||
|
||||
// GetProtectedBranchByRepoID getting protected branch by repo ID
|
||||
func GetProtectedBranchByRepoID(RepoID int64) ([]*ProtectedBranch, error) {
|
||||
protectedBranches := make([]*ProtectedBranch, 0)
|
||||
@ -118,40 +156,64 @@ func GetProtectedBranchByID(id int64) (*ProtectedBranch, error) {
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
// WhitelistOptions represent all sorts of whitelists used for protected branches
|
||||
type WhitelistOptions struct {
|
||||
UserIDs []int64
|
||||
TeamIDs []int64
|
||||
|
||||
MergeUserIDs []int64
|
||||
MergeTeamIDs []int64
|
||||
|
||||
ApprovalsUserIDs []int64
|
||||
ApprovalsTeamIDs []int64
|
||||
}
|
||||
|
||||
// UpdateProtectBranch saves branch protection options of repository.
|
||||
// If ID is 0, it creates a new record. Otherwise, updates existing record.
|
||||
// This function also performs check if whitelist user and team's IDs have been changed
|
||||
// to avoid unnecessary whitelist delete and regenerate.
|
||||
func UpdateProtectBranch(repo *Repository, protectBranch *ProtectedBranch, whitelistUserIDs, whitelistTeamIDs, mergeWhitelistUserIDs, mergeWhitelistTeamIDs []int64) (err error) {
|
||||
func UpdateProtectBranch(repo *Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) {
|
||||
if err = repo.GetOwner(); err != nil {
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
whitelist, err := updateUserWhitelist(repo, protectBranch.WhitelistUserIDs, whitelistUserIDs)
|
||||
whitelist, err := updateUserWhitelist(repo, protectBranch.WhitelistUserIDs, opts.UserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.WhitelistUserIDs = whitelist
|
||||
|
||||
whitelist, err = updateUserWhitelist(repo, protectBranch.MergeWhitelistUserIDs, mergeWhitelistUserIDs)
|
||||
whitelist, err = updateUserWhitelist(repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.MergeWhitelistUserIDs = whitelist
|
||||
|
||||
whitelist, err = updateUserWhitelist(repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.ApprovalsWhitelistUserIDs = whitelist
|
||||
|
||||
// if the repo is in an organization
|
||||
whitelist, err = updateTeamWhitelist(repo, protectBranch.WhitelistTeamIDs, whitelistTeamIDs)
|
||||
whitelist, err = updateTeamWhitelist(repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.WhitelistTeamIDs = whitelist
|
||||
|
||||
whitelist, err = updateTeamWhitelist(repo, protectBranch.MergeWhitelistTeamIDs, mergeWhitelistTeamIDs)
|
||||
whitelist, err = updateTeamWhitelist(repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.MergeWhitelistTeamIDs = whitelist
|
||||
|
||||
whitelist, err = updateTeamWhitelist(repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.ApprovalsWhitelistTeamIDs = whitelist
|
||||
|
||||
// Make sure protectBranch.ID is not 0 for whitelists
|
||||
if protectBranch.ID == 0 {
|
||||
if _, err = x.Insert(protectBranch); err != nil {
|
||||
@ -213,7 +275,7 @@ func (repo *Repository) IsProtectedBranchForPush(branchName string, doer *User)
|
||||
}
|
||||
|
||||
// IsProtectedBranchForMerging checks if branch is protected for merging
|
||||
func (repo *Repository) IsProtectedBranchForMerging(branchName string, doer *User) (bool, error) {
|
||||
func (repo *Repository) IsProtectedBranchForMerging(pr *PullRequest, branchName string, doer *User) (bool, error) {
|
||||
if doer == nil {
|
||||
return true, nil
|
||||
}
|
||||
@ -227,7 +289,7 @@ func (repo *Repository) IsProtectedBranchForMerging(branchName string, doer *Use
|
||||
if err != nil {
|
||||
return true, err
|
||||
} else if has {
|
||||
return !protectedBranch.CanUserMerge(doer.ID), nil
|
||||
return !protectedBranch.CanUserMerge(doer.ID) || !protectedBranch.HasEnoughApprovals(pr), nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
@ -270,14 +332,14 @@ func updateTeamWhitelist(repo *Repository, currentWhitelist, newWhitelist []int6
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, AccessModeWrite)
|
||||
teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, AccessModeRead)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
|
||||
}
|
||||
|
||||
whitelist = make([]int64, 0, len(teams))
|
||||
for i := range teams {
|
||||
if teams[i].HasWriteAccess() && com.IsSliceContainsInt64(newWhitelist, teams[i].ID) {
|
||||
if com.IsSliceContainsInt64(newWhitelist, teams[i].ID) {
|
||||
whitelist = append(whitelist, teams[i].ID)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user