Make database supports glob branch name protected branch
This commit is contained in:
parent
a4e91c4197
commit
9f9f4882f9
18 changed files with 572 additions and 500 deletions
|
@ -7,433 +7,15 @@ package git
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
|
||||
StatusCheckContexts []string `xorm:"JSON TEXT"`
|
||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
|
||||
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ProtectedFilePatterns string `xorm:"TEXT"`
|
||||
UnprotectedFilePatterns string `xorm:"TEXT"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(ProtectedBranch))
|
||||
db.RegisterModel(new(DeletedBranch))
|
||||
db.RegisterModel(new(RenamedBranch))
|
||||
}
|
||||
|
||||
// IsProtected returns if the branch is protected
|
||||
func (protectBranch *ProtectedBranch) IsProtected() bool {
|
||||
return protectBranch.ID > 0
|
||||
}
|
||||
|
||||
// CanUserPush returns if some user could push to this protected branch
|
||||
func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool {
|
||||
if !protectBranch.CanPush {
|
||||
return false
|
||||
}
|
||||
|
||||
if !protectBranch.EnableWhitelist {
|
||||
if user, err := user_model.GetUserByID(userID); err != nil {
|
||||
log.Error("GetUserByID: %v", err)
|
||||
return false
|
||||
} else if repo, err := repo_model.GetRepositoryByID(protectBranch.RepoID); err != nil {
|
||||
log.Error("repo_model.GetRepositoryByID: %v", err)
|
||||
return false
|
||||
} else if writeAccess, err := access_model.HasAccessUnit(db.DefaultContext, user, repo, unit.TypeCode, perm.AccessModeWrite); err != nil {
|
||||
log.Error("HasAccessUnit: %v", err)
|
||||
return false
|
||||
} else {
|
||||
return writeAccess
|
||||
}
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.WhitelistUserIDs, userID) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(protectBranch.WhitelistTeamIDs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
in, err := organization.IsUserInTeams(db.DefaultContext, userID, protectBranch.WhitelistTeamIDs)
|
||||
if err != nil {
|
||||
log.Error("IsUserInTeams: %v", err)
|
||||
return false
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
|
||||
func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool {
|
||||
if !protectBranch.EnableMergeWhitelist {
|
||||
// Then we need to fall back on whether the user has write permission
|
||||
return permissionInRepo.CanWrite(unit.TypeCode)
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(protectBranch.MergeWhitelistTeamIDs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
in, err := organization.IsUserInTeams(ctx, userID, protectBranch.MergeWhitelistTeamIDs)
|
||||
if err != nil {
|
||||
log.Error("IsUserInTeams: %v", err)
|
||||
return false
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
// IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals)
|
||||
func IsUserOfficialReviewer(protectBranch *ProtectedBranch, user *user_model.User) (bool, error) {
|
||||
return IsUserOfficialReviewerCtx(db.DefaultContext, protectBranch, user)
|
||||
}
|
||||
|
||||
// IsUserOfficialReviewerCtx check if user is official reviewer for the branch (counts towards required approvals)
|
||||
func IsUserOfficialReviewerCtx(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) {
|
||||
repo, err := repo_model.GetRepositoryByIDCtx(ctx, protectBranch.RepoID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !protectBranch.EnableApprovalsWhitelist {
|
||||
// Anyone with write access is considered official reviewer
|
||||
writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return writeAccess, nil
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
inTeam, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ApprovalsWhitelistTeamIDs)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return inTeam, nil
|
||||
}
|
||||
|
||||
// GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice
|
||||
func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob {
|
||||
return getFilePatterns(protectBranch.ProtectedFilePatterns)
|
||||
}
|
||||
|
||||
// GetUnprotectedFilePatterns parses a semicolon separated list of unprotected file patterns and returns a glob.Glob slice
|
||||
func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob {
|
||||
return getFilePatterns(protectBranch.UnprotectedFilePatterns)
|
||||
}
|
||||
|
||||
func getFilePatterns(filePatterns string) []glob.Glob {
|
||||
extarr := make([]glob.Glob, 0, 10)
|
||||
for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") {
|
||||
expr = strings.TrimSpace(expr)
|
||||
if expr != "" {
|
||||
if g, err := glob.Compile(expr, '.', '/'); err != nil {
|
||||
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
||||
} else {
|
||||
extarr = append(extarr, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
return extarr
|
||||
}
|
||||
|
||||
// MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change
|
||||
func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(changedProtectedFiles []string) bool {
|
||||
glob := protectBranch.GetProtectedFilePatterns()
|
||||
if len(glob) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(changedProtectedFiles) > 0
|
||||
}
|
||||
|
||||
// IsProtectedFile return if path is protected
|
||||
func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path string) bool {
|
||||
if len(patterns) == 0 {
|
||||
patterns = protectBranch.GetProtectedFilePatterns()
|
||||
if len(patterns) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
lpath := strings.ToLower(strings.TrimSpace(path))
|
||||
|
||||
r := false
|
||||
for _, pat := range patterns {
|
||||
if pat.Match(lpath) {
|
||||
r = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// IsUnprotectedFile return if path is unprotected
|
||||
func (protectBranch *ProtectedBranch) IsUnprotectedFile(patterns []glob.Glob, path string) bool {
|
||||
if len(patterns) == 0 {
|
||||
patterns = protectBranch.GetUnprotectedFilePatterns()
|
||||
if len(patterns) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
lpath := strings.ToLower(strings.TrimSpace(path))
|
||||
|
||||
r := false
|
||||
for _, pat := range patterns {
|
||||
if pat.Match(lpath) {
|
||||
r = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// GetProtectedBranchBy getting protected branch by ID/Name
|
||||
func GetProtectedBranchBy(ctx context.Context, repoID int64, branchName string) (*ProtectedBranch, error) {
|
||||
rel := &ProtectedBranch{RepoID: repoID, BranchName: branchName}
|
||||
has, err := db.GetByBean(ctx, rel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, nil
|
||||
}
|
||||
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(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) {
|
||||
if err = repo.GetOwner(ctx); err != nil {
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
whitelist, err := updateUserWhitelist(ctx, repo, protectBranch.WhitelistUserIDs, opts.UserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.WhitelistUserIDs = whitelist
|
||||
|
||||
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.MergeWhitelistUserIDs = whitelist
|
||||
|
||||
whitelist, err = updateApprovalWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.ApprovalsWhitelistUserIDs = whitelist
|
||||
|
||||
// if the repo is in an organization
|
||||
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.WhitelistTeamIDs = whitelist
|
||||
|
||||
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.MergeWhitelistTeamIDs = whitelist
|
||||
|
||||
whitelist, err = updateTeamWhitelist(ctx, 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 = db.GetEngine(ctx).Insert(protectBranch); err != nil {
|
||||
return fmt.Errorf("Insert: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
|
||||
return fmt.Errorf("Update: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProtectedBranches get all protected branches
|
||||
func GetProtectedBranches(repoID int64) ([]*ProtectedBranch, error) {
|
||||
protectedBranches := make([]*ProtectedBranch, 0)
|
||||
return protectedBranches, db.GetEngine(db.DefaultContext).Find(&protectedBranches, &ProtectedBranch{RepoID: repoID})
|
||||
}
|
||||
|
||||
// IsProtectedBranch checks if branch is protected
|
||||
func IsProtectedBranch(repoID int64, branchName string) (bool, error) {
|
||||
protectedBranch := &ProtectedBranch{
|
||||
RepoID: repoID,
|
||||
BranchName: branchName,
|
||||
}
|
||||
|
||||
has, err := db.GetEngine(db.DefaultContext).Exist(protectedBranch)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return has, nil
|
||||
}
|
||||
|
||||
// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with
|
||||
// the users from newWhitelist which have explicit read or write access to the repo.
|
||||
func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||
hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
|
||||
if !hasUsersChanged {
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
whitelist = make([]int64, 0, len(newWhitelist))
|
||||
for _, userID := range newWhitelist {
|
||||
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
|
||||
return nil, err
|
||||
} else if !reader {
|
||||
continue
|
||||
}
|
||||
whitelist = append(whitelist, userID)
|
||||
}
|
||||
|
||||
return whitelist, err
|
||||
}
|
||||
|
||||
// updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with
|
||||
// the users from newWhitelist which have write access to the repo.
|
||||
func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||
hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
|
||||
if !hasUsersChanged {
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
whitelist = make([]int64, 0, len(newWhitelist))
|
||||
for _, userID := range newWhitelist {
|
||||
user, err := user_model.GetUserByIDCtx(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
||||
}
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
||||
}
|
||||
|
||||
if !perm.CanWrite(unit.TypeCode) {
|
||||
continue // Drop invalid user ID
|
||||
}
|
||||
|
||||
whitelist = append(whitelist, userID)
|
||||
}
|
||||
|
||||
return whitelist, err
|
||||
}
|
||||
|
||||
// updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with
|
||||
// the teams from newWhitelist which have write access to the repo.
|
||||
func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||
hasTeamsChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
|
||||
if !hasTeamsChanged {
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.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 util.IsInt64InSlice(teams[i].ID, newWhitelist) {
|
||||
whitelist = append(whitelist, teams[i].ID)
|
||||
}
|
||||
}
|
||||
|
||||
return whitelist, err
|
||||
}
|
||||
|
||||
// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
|
||||
func DeleteProtectedBranch(repoID, id int64) (err error) {
|
||||
protectedBranch := &ProtectedBranch{
|
||||
RepoID: repoID,
|
||||
ID: id,
|
||||
}
|
||||
|
||||
if affected, err := db.GetEngine(db.DefaultContext).Delete(protectedBranch); err != nil {
|
||||
return err
|
||||
} else if affected != 1 {
|
||||
return fmt.Errorf("delete protected branch ID(%v) failed", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletedBranch struct
|
||||
type DeletedBranch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
@ -445,6 +27,11 @@ type DeletedBranch struct {
|
|||
DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(DeletedBranch))
|
||||
db.RegisterModel(new(RenamedBranch))
|
||||
}
|
||||
|
||||
// AddDeletedBranch adds a deleted branch to the database
|
||||
func AddDeletedBranch(repoID int64, branchName, commit string, deletedByID int64) error {
|
||||
deletedBranch := &DeletedBranch{
|
||||
|
|
436
models/git/protected_branch.go
Normal file
436
models/git/protected_branch.go
Normal file
|
@ -0,0 +1,436 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
|
||||
StatusCheckContexts []string `xorm:"JSON TEXT"`
|
||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
|
||||
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ProtectedFilePatterns string `xorm:"TEXT"`
|
||||
UnprotectedFilePatterns string `xorm:"TEXT"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(ProtectedBranch))
|
||||
}
|
||||
|
||||
// IsProtected returns if the branch is protected
|
||||
func (protectBranch *ProtectedBranch) IsProtected() bool {
|
||||
return protectBranch.ID > 0
|
||||
}
|
||||
|
||||
// Match tests if branchName matches the rule
|
||||
func (protectBranch *ProtectedBranch) Match(branchName string) bool {
|
||||
if strings.EqualFold(protectBranch.BranchName, branchName) {
|
||||
return true
|
||||
}
|
||||
return glob.MustCompile(protectBranch.BranchName).Match(branchName)
|
||||
}
|
||||
|
||||
// CanUserPush returns if some user could push to this protected branch
|
||||
func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool {
|
||||
if !protectBranch.CanPush {
|
||||
return false
|
||||
}
|
||||
|
||||
if !protectBranch.EnableWhitelist {
|
||||
if user, err := user_model.GetUserByID(userID); err != nil {
|
||||
log.Error("GetUserByID: %v", err)
|
||||
return false
|
||||
} else if repo, err := repo_model.GetRepositoryByID(protectBranch.RepoID); err != nil {
|
||||
log.Error("repo_model.GetRepositoryByID: %v", err)
|
||||
return false
|
||||
} else if writeAccess, err := access_model.HasAccessUnit(db.DefaultContext, user, repo, unit.TypeCode, perm.AccessModeWrite); err != nil {
|
||||
log.Error("HasAccessUnit: %v", err)
|
||||
return false
|
||||
} else {
|
||||
return writeAccess
|
||||
}
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.WhitelistUserIDs, userID) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(protectBranch.WhitelistTeamIDs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
in, err := organization.IsUserInTeams(db.DefaultContext, userID, protectBranch.WhitelistTeamIDs)
|
||||
if err != nil {
|
||||
log.Error("IsUserInTeams: %v", err)
|
||||
return false
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
|
||||
func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool {
|
||||
if !protectBranch.EnableMergeWhitelist {
|
||||
// Then we need to fall back on whether the user has write permission
|
||||
return permissionInRepo.CanWrite(unit.TypeCode)
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(protectBranch.MergeWhitelistTeamIDs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
in, err := organization.IsUserInTeams(ctx, userID, protectBranch.MergeWhitelistTeamIDs)
|
||||
if err != nil {
|
||||
log.Error("IsUserInTeams: %v", err)
|
||||
return false
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
// IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals)
|
||||
func IsUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) {
|
||||
repo, err := repo_model.GetRepositoryByIDCtx(ctx, protectBranch.RepoID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !protectBranch.EnableApprovalsWhitelist {
|
||||
// Anyone with write access is considered official reviewer
|
||||
writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return writeAccess, nil
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
inTeam, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ApprovalsWhitelistTeamIDs)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return inTeam, nil
|
||||
}
|
||||
|
||||
// GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice
|
||||
func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob {
|
||||
return getFilePatterns(protectBranch.ProtectedFilePatterns)
|
||||
}
|
||||
|
||||
// GetUnprotectedFilePatterns parses a semicolon separated list of unprotected file patterns and returns a glob.Glob slice
|
||||
func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob {
|
||||
return getFilePatterns(protectBranch.UnprotectedFilePatterns)
|
||||
}
|
||||
|
||||
func getFilePatterns(filePatterns string) []glob.Glob {
|
||||
extarr := make([]glob.Glob, 0, 10)
|
||||
for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") {
|
||||
expr = strings.TrimSpace(expr)
|
||||
if expr != "" {
|
||||
if g, err := glob.Compile(expr, '.', '/'); err != nil {
|
||||
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
||||
} else {
|
||||
extarr = append(extarr, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
return extarr
|
||||
}
|
||||
|
||||
// MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change
|
||||
func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(changedProtectedFiles []string) bool {
|
||||
glob := protectBranch.GetProtectedFilePatterns()
|
||||
if len(glob) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(changedProtectedFiles) > 0
|
||||
}
|
||||
|
||||
// IsProtectedFile return if path is protected
|
||||
func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path string) bool {
|
||||
if len(patterns) == 0 {
|
||||
patterns = protectBranch.GetProtectedFilePatterns()
|
||||
if len(patterns) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
lpath := strings.ToLower(strings.TrimSpace(path))
|
||||
|
||||
r := false
|
||||
for _, pat := range patterns {
|
||||
if pat.Match(lpath) {
|
||||
r = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// IsUnprotectedFile return if path is unprotected
|
||||
func (protectBranch *ProtectedBranch) IsUnprotectedFile(patterns []glob.Glob, path string) bool {
|
||||
if len(patterns) == 0 {
|
||||
patterns = protectBranch.GetUnprotectedFilePatterns()
|
||||
if len(patterns) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
lpath := strings.ToLower(strings.TrimSpace(path))
|
||||
|
||||
r := false
|
||||
for _, pat := range patterns {
|
||||
if pat.Match(lpath) {
|
||||
r = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// GetProtectedBranchBy getting protected branch by ID/Name
|
||||
func GetProtectedBranchBy(ctx context.Context, repoID int64, branchName string) (*ProtectedBranch, error) {
|
||||
rel := &ProtectedBranch{RepoID: repoID, BranchName: branchName}
|
||||
has, err := db.GetByBean(ctx, rel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, nil
|
||||
}
|
||||
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(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) {
|
||||
if err = repo.GetOwner(ctx); err != nil {
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
whitelist, err := updateUserWhitelist(ctx, repo, protectBranch.WhitelistUserIDs, opts.UserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.WhitelistUserIDs = whitelist
|
||||
|
||||
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.MergeWhitelistUserIDs = whitelist
|
||||
|
||||
whitelist, err = updateApprovalWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.ApprovalsWhitelistUserIDs = whitelist
|
||||
|
||||
// if the repo is in an organization
|
||||
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.WhitelistTeamIDs = whitelist
|
||||
|
||||
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protectBranch.MergeWhitelistTeamIDs = whitelist
|
||||
|
||||
whitelist, err = updateTeamWhitelist(ctx, 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 = db.GetEngine(ctx).Insert(protectBranch); err != nil {
|
||||
return fmt.Errorf("Insert: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
|
||||
return fmt.Errorf("Update: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProtectedBranches get all protected branches
|
||||
func GetProtectedBranches(repoID int64) ([]*ProtectedBranch, error) {
|
||||
protectedBranches := make([]*ProtectedBranch, 0)
|
||||
return protectedBranches, db.GetEngine(db.DefaultContext).Find(&protectedBranches, &ProtectedBranch{RepoID: repoID})
|
||||
}
|
||||
|
||||
// IsProtectedBranch checks if branch is protected
|
||||
func IsProtectedBranch(repoID int64, branchName string) (bool, error) {
|
||||
protectedBranch := &ProtectedBranch{
|
||||
RepoID: repoID,
|
||||
BranchName: branchName,
|
||||
}
|
||||
|
||||
has, err := db.GetEngine(db.DefaultContext).Exist(protectedBranch)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return has, nil
|
||||
}
|
||||
|
||||
// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with
|
||||
// the users from newWhitelist which have explicit read or write access to the repo.
|
||||
func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||
hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
|
||||
if !hasUsersChanged {
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
whitelist = make([]int64, 0, len(newWhitelist))
|
||||
for _, userID := range newWhitelist {
|
||||
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
|
||||
return nil, err
|
||||
} else if !reader {
|
||||
continue
|
||||
}
|
||||
whitelist = append(whitelist, userID)
|
||||
}
|
||||
|
||||
return whitelist, err
|
||||
}
|
||||
|
||||
// updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with
|
||||
// the users from newWhitelist which have write access to the repo.
|
||||
func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||
hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
|
||||
if !hasUsersChanged {
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
whitelist = make([]int64, 0, len(newWhitelist))
|
||||
for _, userID := range newWhitelist {
|
||||
user, err := user_model.GetUserByIDCtx(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
||||
}
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
||||
}
|
||||
|
||||
if !perm.CanWrite(unit.TypeCode) {
|
||||
continue // Drop invalid user ID
|
||||
}
|
||||
|
||||
whitelist = append(whitelist, userID)
|
||||
}
|
||||
|
||||
return whitelist, err
|
||||
}
|
||||
|
||||
// updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with
|
||||
// the teams from newWhitelist which have write access to the repo.
|
||||
func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||
hasTeamsChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
|
||||
if !hasTeamsChanged {
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.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 util.IsInt64InSlice(teams[i].ID, newWhitelist) {
|
||||
whitelist = append(whitelist, teams[i].ID)
|
||||
}
|
||||
}
|
||||
|
||||
return whitelist, err
|
||||
}
|
||||
|
||||
// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
|
||||
func DeleteProtectedBranch(repoID, id int64) (err error) {
|
||||
protectedBranch := &ProtectedBranch{
|
||||
RepoID: repoID,
|
||||
ID: id,
|
||||
}
|
||||
|
||||
if affected, err := db.GetEngine(db.DefaultContext).Delete(protectedBranch); err != nil {
|
||||
return err
|
||||
} else if affected != 1 {
|
||||
return fmt.Errorf("delete protected branch ID(%v) failed", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
38
models/git/protected_branch_list.go
Normal file
38
models/git/protected_branch_list.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
)
|
||||
|
||||
type ProtectedBranchRules []*ProtectedBranch
|
||||
|
||||
func (rules ProtectedBranchRules) GetFirstMatched(branchName string) *ProtectedBranch {
|
||||
for _, rule := range rules {
|
||||
if rule.Match(branchName) {
|
||||
return rule
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindMatchedProtectedBranchRules load all repository's protected rules
|
||||
func FindMatchedProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedBranchRules, error) {
|
||||
var rules []*ProtectedBranch
|
||||
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Asc("created_unix").Find(&rules)
|
||||
return rules, err
|
||||
}
|
||||
|
||||
// GetFirstMatchProtectedBranchRule returns the first matched rules
|
||||
func GetFirstMatchProtectedBranchRule(ctx context.Context, repoID int64, branchName string) (*ProtectedBranch, error) {
|
||||
rules, err := FindMatchedProtectedBranchRules(ctx, repoID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rules.GetFirstMatched(branchName), nil
|
||||
}
|
|
@ -157,7 +157,7 @@ type PullRequest struct {
|
|||
HeadBranch string
|
||||
HeadCommitID string `xorm:"-"`
|
||||
BaseBranch string
|
||||
ProtectedBranch *git_model.ProtectedBranch `xorm:"-"`
|
||||
// ProtectedBranch *git_model.ProtectedBranch `xorm:"-"`
|
||||
MergeBase string `xorm:"VARCHAR(40)"`
|
||||
AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
||||
|
@ -305,13 +305,8 @@ func (pr *PullRequest) LoadIssueCtx(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// LoadProtectedBranch loads the protected branch of the base branch
|
||||
func (pr *PullRequest) LoadProtectedBranch() (err error) {
|
||||
return pr.LoadProtectedBranchCtx(db.DefaultContext)
|
||||
}
|
||||
|
||||
// LoadProtectedBranchCtx loads the protected branch of the base branch
|
||||
func (pr *PullRequest) LoadProtectedBranchCtx(ctx context.Context) (err error) {
|
||||
// LoadProtectedBranchRules loads the protected branch of the base branch
|
||||
/*func (pr *PullRequest) LoadProtectedBranchRules(ctx context.Context) (err error) {
|
||||
if pr.ProtectedBranch == nil {
|
||||
if pr.BaseRepo == nil {
|
||||
if pr.BaseRepoID == 0 {
|
||||
|
@ -322,10 +317,10 @@ func (pr *PullRequest) LoadProtectedBranchCtx(ctx context.Context) (err error) {
|
|||
return
|
||||
}
|
||||
}
|
||||
pr.ProtectedBranch, err = git_model.GetProtectedBranchBy(ctx, pr.BaseRepo.ID, pr.BaseBranch)
|
||||
pr.ProtectedBranch, err = git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepo.ID, pr.BaseBranch)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}*/
|
||||
|
||||
// ReviewCount represents a count of Reviews
|
||||
type ReviewCount struct {
|
||||
|
|
|
@ -264,15 +264,17 @@ func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_mo
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = pr.LoadProtectedBranchCtx(ctx); err != nil {
|
||||
|
||||
rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if pr.ProtectedBranch == nil {
|
||||
if rule == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for _, reviewer := range reviewers {
|
||||
official, err := git_model.IsUserOfficialReviewerCtx(ctx, pr.ProtectedBranch, reviewer)
|
||||
official, err := git_model.IsUserOfficialReviewer(ctx, rule, reviewer)
|
||||
if official || err != nil {
|
||||
return official, err
|
||||
}
|
||||
|
@ -287,18 +289,19 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = pr.LoadProtectedBranchCtx(ctx); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if pr.ProtectedBranch == nil {
|
||||
if pb == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !pr.ProtectedBranch.EnableApprovalsWhitelist {
|
||||
if !pb.EnableApprovalsWhitelist {
|
||||
return team.UnitAccessModeCtx(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil
|
||||
}
|
||||
|
||||
return base.Int64sContains(pr.ProtectedBranch.ApprovalsWhitelistTeamIDs, team.ID), nil
|
||||
return base.Int64sContains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil
|
||||
}
|
||||
|
||||
// CreateReview creates a new review based on opts
|
||||
|
|
|
@ -118,9 +118,10 @@ type CanCommitToBranchResults struct {
|
|||
}
|
||||
|
||||
// CanCommitToBranch returns true if repository is editable and user has proper access level
|
||||
//
|
||||
// and branch is not protected for push
|
||||
func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
|
||||
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, r.Repository.ID, r.BranchName)
|
||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, r.Repository.ID, r.BranchName)
|
||||
if err != nil {
|
||||
return CanCommitToBranchResults{}, err
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func GetBranch(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branchName)
|
||||
branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
||||
return
|
||||
|
@ -207,7 +207,7 @@ func CreateBranch(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch.Name)
|
||||
branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branch.Name)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
||||
return
|
||||
|
@ -260,6 +260,12 @@ func ListBranches(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
rules, err := git_model.FindMatchedProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiBranches := make([]*api.Branch, 0, len(branches))
|
||||
for i := range branches {
|
||||
c, err := branches[i].GetCommit()
|
||||
|
@ -272,11 +278,7 @@ func ListBranches(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
||||
return
|
||||
}
|
||||
branchProtection := rules.GetFirstMatched(branches[i].Name)
|
||||
apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
|
||||
|
|
|
@ -157,7 +157,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN
|
|||
return
|
||||
}
|
||||
|
||||
protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, branchName)
|
||||
protectBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
|
||||
if err != nil {
|
||||
log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||
|
|
|
@ -1630,22 +1630,23 @@ func ViewIssue(ctx *context.Context) {
|
|||
}
|
||||
ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage
|
||||
|
||||
if err = pull.LoadProtectedBranch(); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadProtectedBranch", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["ShowMergeInstructions"] = true
|
||||
if pull.ProtectedBranch != nil {
|
||||
if pb != nil {
|
||||
var showMergeInstructions bool
|
||||
if ctx.Doer != nil {
|
||||
showMergeInstructions = pull.ProtectedBranch.CanUserPush(ctx.Doer.ID)
|
||||
showMergeInstructions = pb.CanUserPush(ctx.Doer.ID)
|
||||
}
|
||||
ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pull.ProtectedBranch, pull)
|
||||
ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pull.ProtectedBranch, pull)
|
||||
ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pull.ProtectedBranch, pull)
|
||||
ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pull.ProtectedBranch, pull)
|
||||
ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pull.ProtectedBranch, pull)
|
||||
ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits
|
||||
ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
|
||||
ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
|
||||
ctx.Data["RequireSigned"] = pb.RequireSignedCommits
|
||||
ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
|
||||
ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0
|
||||
ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
|
||||
|
|
|
@ -414,11 +414,12 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
|||
|
||||
setMergeTarget(ctx, pull)
|
||||
|
||||
if err := pull.LoadProtectedBranch(); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pull.BaseBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadProtectedBranch", err)
|
||||
return nil
|
||||
}
|
||||
ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
|
||||
ctx.Data["EnableStatusCheck"] = pb != nil && pb.EnableStatusCheck
|
||||
|
||||
var baseGitRepo *git.Repository
|
||||
if pull.BaseRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
|
||||
|
@ -544,16 +545,16 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
|||
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
|
||||
}
|
||||
|
||||
if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
|
||||
if pb != nil && pb.EnableStatusCheck {
|
||||
ctx.Data["is_context_required"] = func(context string) bool {
|
||||
for _, c := range pull.ProtectedBranch.StatusCheckContexts {
|
||||
for _, c := range pb.StatusCheckContexts {
|
||||
if c == context {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
|
||||
ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pb.StatusCheckContexts)
|
||||
}
|
||||
|
||||
ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
|
||||
|
@ -726,16 +727,17 @@ func ViewPullFiles(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err = pull.LoadProtectedBranch(); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadProtectedBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
if pull.ProtectedBranch != nil {
|
||||
glob := pull.ProtectedBranch.GetProtectedFilePatterns()
|
||||
if pb != nil {
|
||||
glob := pb.GetProtectedFilePatterns()
|
||||
if len(glob) != 0 {
|
||||
for _, file := range diff.Files {
|
||||
file.IsProtected = pull.ProtectedBranch.IsProtectedFile(glob, file.Name)
|
||||
file.IsProtected = pb.IsProtectedFile(glob, file.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -311,7 +311,7 @@ Loop:
|
|||
return false, "", nil, &ErrWontSign{twofa}
|
||||
}
|
||||
case approved:
|
||||
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, pr.BaseBranch)
|
||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return false, "", nil, err
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
|
@ -127,11 +128,12 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
|
|||
|
||||
// isSignedIfRequired check if merge will be signed if required
|
||||
func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) (bool, error) {
|
||||
if err := pr.LoadProtectedBranchCtx(ctx); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if pr.ProtectedBranch == nil || !pr.ProtectedBranch.RequireSignedCommits {
|
||||
if pb == nil || !pb.RequireSignedCommits {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -84,10 +84,11 @@ func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requ
|
|||
|
||||
// IsPullCommitStatusPass returns if all required status checks PASS
|
||||
func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
|
||||
if err := pr.LoadProtectedBranchCtx(ctx); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "GetLatestCommitStatus")
|
||||
}
|
||||
if pr.ProtectedBranch == nil || !pr.ProtectedBranch.EnableStatusCheck {
|
||||
if pb == nil || !pb.EnableStatusCheck {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
@ -138,12 +139,13 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
|
|||
return "", errors.Wrap(err, "GetLatestCommitStatus")
|
||||
}
|
||||
|
||||
if err := pr.LoadProtectedBranchCtx(ctx); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "LoadProtectedBranch")
|
||||
}
|
||||
var requiredContexts []string
|
||||
if pr.ProtectedBranch != nil {
|
||||
requiredContexts = pr.ProtectedBranch.StatusCheckContexts
|
||||
if pb != nil {
|
||||
requiredContexts = pb.StatusCheckContexts
|
||||
}
|
||||
|
||||
return MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts), nil
|
||||
|
|
|
@ -744,12 +744,12 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a
|
|||
return false, nil
|
||||
}
|
||||
|
||||
err := pr.LoadProtectedBranchCtx(ctx)
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if (p.CanWrite(unit.TypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && git_model.IsUserMergeWhitelisted(ctx, pr.ProtectedBranch, user.ID, p)) {
|
||||
if (p.CanWrite(unit.TypeCode) && pb == nil) || (pb != nil && git_model.IsUserMergeWhitelisted(ctx, pb, user.ID, p)) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
@ -762,10 +762,11 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
|
|||
return fmt.Errorf("LoadBaseRepo: %v", err)
|
||||
}
|
||||
|
||||
if err = pr.LoadProtectedBranchCtx(ctx); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("LoadProtectedBranch: %v", err)
|
||||
}
|
||||
if pr.ProtectedBranch == nil {
|
||||
if pb == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -779,23 +780,23 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
|
|||
}
|
||||
}
|
||||
|
||||
if !issues_model.HasEnoughApprovals(ctx, pr.ProtectedBranch, pr) {
|
||||
if !issues_model.HasEnoughApprovals(ctx, pb, pr) {
|
||||
return models.ErrDisallowedToMerge{
|
||||
Reason: "Does not have enough approvals",
|
||||
}
|
||||
}
|
||||
if issues_model.MergeBlockedByRejectedReview(ctx, pr.ProtectedBranch, pr) {
|
||||
if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) {
|
||||
return models.ErrDisallowedToMerge{
|
||||
Reason: "There are requested changes",
|
||||
}
|
||||
}
|
||||
if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pr.ProtectedBranch, pr) {
|
||||
if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) {
|
||||
return models.ErrDisallowedToMerge{
|
||||
Reason: "There are official review requests",
|
||||
}
|
||||
}
|
||||
|
||||
if issues_model.MergeBlockedByOutdatedBranch(pr.ProtectedBranch, pr) {
|
||||
if issues_model.MergeBlockedByOutdatedBranch(pb, pr) {
|
||||
return models.ErrDisallowedToMerge{
|
||||
Reason: "The head branch is behind the base branch",
|
||||
}
|
||||
|
@ -805,7 +806,7 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
|
|||
return nil
|
||||
}
|
||||
|
||||
if pr.ProtectedBranch.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
|
||||
if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
|
||||
return models.ErrDisallowedToMerge{
|
||||
Reason: "Changed protected files",
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
@ -102,7 +103,7 @@ func TestPatch(pr *issues_model.PullRequest) error {
|
|||
}
|
||||
|
||||
// 3. Check for protected files changes
|
||||
if err = checkPullFilesProtection(pr, gitRepo); err != nil {
|
||||
if err = checkPullFilesProtection(ctx, pr, gitRepo); err != nil {
|
||||
return fmt.Errorf("pr.CheckPullFilesProtection(): %v", err)
|
||||
}
|
||||
|
||||
|
@ -527,23 +528,23 @@ func CheckUnprotectedFiles(repo *git.Repository, oldCommitID, newCommitID string
|
|||
}
|
||||
|
||||
// checkPullFilesProtection check if pr changed protected files and save results
|
||||
func checkPullFilesProtection(pr *issues_model.PullRequest, gitRepo *git.Repository) error {
|
||||
func checkPullFilesProtection(ctx context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) error {
|
||||
if pr.Status == issues_model.PullRequestStatusEmpty {
|
||||
pr.ChangedProtectedFiles = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pr.LoadProtectedBranch(); err != nil {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pr.ProtectedBranch == nil {
|
||||
if pb == nil {
|
||||
pr.ChangedProtectedFiles = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.MergeBase, "tracking", pr.ProtectedBranch.GetProtectedFilePatterns(), 10, os.Environ())
|
||||
pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.MergeBase, "tracking", pb.GetProtectedFilePatterns(), 10, os.Environ())
|
||||
if err != nil && !models.IsErrFilePathProtected(err) {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
|
@ -97,13 +98,13 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest,
|
|||
BaseBranch: pull.HeadBranch,
|
||||
}
|
||||
|
||||
err = pr.LoadProtectedBranch()
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
// can't do rebase on protected branch because need force push
|
||||
if pr.ProtectedBranch == nil {
|
||||
if pb == nil {
|
||||
prUnit, err := pr.BaseRepo.GetUnit(unit.TypePullRequests)
|
||||
if err != nil {
|
||||
log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
|
||||
|
@ -113,7 +114,7 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest,
|
|||
}
|
||||
|
||||
// Update function need push permission
|
||||
if pr.ProtectedBranch != nil && !pr.ProtectedBranch.CanUserPush(user.ID) {
|
||||
if pb != nil && !pb.CanUserPush(user.ID) {
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, opts.OldBranch)
|
||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, opts.OldBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -463,7 +463,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
|
|||
|
||||
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
|
||||
func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName, treePath string) error {
|
||||
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, branchName)
|
||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue