diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2023-01-16 16:00:22 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-16 16:00:22 +0800 |
commit | 2782c1439679402a1f8731a94dc66214781282ba (patch) | |
tree | 66739f30beb529119694290bdcdba9e02bdcfabd /services | |
parent | cc1f8cbe96c195aab79761c48bc4ec0bff2b3431 (diff) | |
download | gitea-2782c1439679402a1f8731a94dc66214781282ba.tar.gz gitea-2782c1439679402a1f8731a94dc66214781282ba.zip |
Supports wildcard protected branch (#20825)
This PR introduce glob match for protected branch name. The separator is
`/` and you can use `*` matching non-separator chars and use `**` across
separator.
It also supports input an exist or non-exist branch name as matching
condition and branch name condition has high priority than glob rule.
Should fix #2529 and #15705
screenshots
<img width="1160" alt="image"
src="https://user-images.githubusercontent.com/81045/205651179-ebb5492a-4ade-4bb4-a13c-965e8c927063.png">
Co-authored-by: zeripath <art27@cantab.net>
Diffstat (limited to 'services')
-rw-r--r-- | services/asymkey/sign.go | 2 | ||||
-rw-r--r-- | services/convert/convert.go | 13 | ||||
-rw-r--r-- | services/forms/repo_form.go | 2 | ||||
-rw-r--r-- | services/pull/check.go | 10 | ||||
-rw-r--r-- | services/pull/commit_status.go | 12 | ||||
-rw-r--r-- | services/pull/merge.go | 24 | ||||
-rw-r--r-- | services/pull/patch.go | 16 | ||||
-rw-r--r-- | services/pull/update.go | 21 | ||||
-rw-r--r-- | services/repository/branch.go | 8 | ||||
-rw-r--r-- | services/repository/files/patch.go | 11 | ||||
-rw-r--r-- | services/repository/files/update.go | 5 |
11 files changed, 77 insertions, 47 deletions
diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index 227e0bbf33..01718ebe77 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -310,7 +310,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 } diff --git a/services/convert/convert.go b/services/convert/convert.go index 2ce51bf063..17f7e3d650 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -79,7 +79,7 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git } if isRepoAdmin { - branch.EffectiveBranchProtectionName = bp.BranchName + branch.EffectiveBranchProtectionName = bp.RuleName } if user != nil { @@ -87,7 +87,8 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git if err != nil { return nil, err } - branch.UserCanPush = bp.CanUserPush(db.DefaultContext, user.ID) + bp.Repo = repo + branch.UserCanPush = bp.CanUserPush(db.DefaultContext, user) branch.UserCanMerge = git_model.IsUserMergeWhitelisted(db.DefaultContext, bp, user.ID, permission) } @@ -121,8 +122,14 @@ func ToBranchProtection(bp *git_model.ProtectedBranch) *api.BranchProtection { log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err) } + branchName := "" + if !git_model.IsRuleNameSpecial(bp.RuleName) { + branchName = bp.RuleName + } + return &api.BranchProtection{ - BranchName: bp.BranchName, + BranchName: branchName, + RuleName: bp.RuleName, EnablePush: bp.CanPush, EnablePushWhitelist: bp.EnableWhitelist, PushWhitelistUsernames: pushWhitelistUsernames, diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 89a013d9af..b7687af2b5 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -186,7 +186,7 @@ func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) bindi // ProtectBranchForm form for changing protected branch settings type ProtectBranchForm struct { - Protected bool + RuleName string `binding:"Required"` EnablePush string WhitelistUsers string WhitelistTeams string diff --git a/services/pull/check.go b/services/pull/check.go index 86460cd49c..db86378909 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -14,6 +14,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" @@ -126,11 +127,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.LoadProtectedBranch(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 } @@ -348,8 +350,8 @@ func testPR(id int64) { checkAndUpdateStatus(ctx, pr) } -// CheckPrsForBaseBranch check all pulls with bseBrannch -func CheckPrsForBaseBranch(baseRepo *repo_model.Repository, baseBranchName string) error { +// CheckPRsForBaseBranch check all pulls with baseBrannch +func CheckPRsForBaseBranch(baseRepo *repo_model.Repository, baseBranchName string) error { prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(baseRepo.ID, baseBranchName) if err != nil { return err diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index e075248a36..bfdb3f7291 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -83,10 +83,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.LoadProtectedBranch(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 } @@ -137,12 +138,13 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR return "", errors.Wrap(err, "GetLatestCommitStatus") } - if err := pr.LoadProtectedBranch(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 diff --git a/services/pull/merge.go b/services/pull/merge.go index 7a936163f1..d0ec943cfa 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -760,12 +760,12 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a return false, nil } - err := pr.LoadProtectedBranch(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 } @@ -778,10 +778,11 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques return fmt.Errorf("LoadBaseRepo: %w", err) } - if err = pr.LoadProtectedBranch(ctx); err != nil { - return fmt.Errorf("LoadProtectedBranch: %w", err) + 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 } @@ -795,23 +796,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", } @@ -821,7 +822,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", } @@ -836,6 +837,9 @@ func MergedManually(pr *issues_model.PullRequest, doer *user_model.User, baseGit defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID)) if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err := pr.LoadBaseRepo(ctx); err != nil { + return err + } prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests) if err != nil { return err diff --git a/services/pull/patch.go b/services/pull/patch.go index 9ef8b86043..26a72a7371 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -14,7 +14,7 @@ import ( "strings" "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" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/container" @@ -106,8 +106,8 @@ func TestPatch(pr *issues_model.PullRequest) error { } // 3. Check for protected files changes - if err = checkPullFilesProtection(pr, gitRepo); err != nil { - return fmt.Errorf("pr.CheckPullFilesProtection(): %w", err) + if err = checkPullFilesProtection(ctx, pr, gitRepo); err != nil { + return fmt.Errorf("pr.CheckPullFilesProtection(): %v", err) } if len(pr.ChangedProtectedFiles) > 0 { @@ -544,23 +544,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(db.DefaultContext); 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 } diff --git a/services/pull/update.go b/services/pull/update.go index 6f976140c5..9e29f63c7c 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -8,6 +8,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" @@ -92,20 +93,29 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, return false, false, err } + if err := pull.LoadBaseRepo(ctx); err != nil { + return false, false, err + } + pr := &issues_model.PullRequest{ HeadRepoID: pull.BaseRepoID, + HeadRepo: pull.BaseRepo, BaseRepoID: pull.HeadRepoID, + BaseRepo: pull.HeadRepo, HeadBranch: pull.BaseBranch, BaseBranch: pull.HeadBranch, } - err = pr.LoadProtectedBranch(ctx) + 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 { + if err := pr.LoadBaseRepo(ctx); err != nil { + return false, false, err + } prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests) if err != nil { log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err) @@ -115,8 +125,11 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, } // Update function need push permission - if pr.ProtectedBranch != nil && !pr.ProtectedBranch.CanUserPush(ctx, user.ID) { - return false, false, nil + if pb != nil { + pb.Repo = pull.BaseRepo + if !pb.CanUserPush(ctx, user) { + return false, false, nil + } } baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user) diff --git a/services/repository/branch.go b/services/repository/branch.go index 8717fee23b..291fb4a92b 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -149,8 +149,7 @@ func RenameBranch(repo *repo_model.Repository, doer *user_model.User, gitRepo *g // enmuerates all branch related errors var ( - ErrBranchIsDefault = errors.New("branch is default") - ErrBranchIsProtected = errors.New("branch is protected") + ErrBranchIsDefault = errors.New("branch is default") ) // DeleteBranch delete branch @@ -159,13 +158,12 @@ func DeleteBranch(doer *user_model.User, repo *repo_model.Repository, gitRepo *g return ErrBranchIsDefault } - isProtected, err := git_model.IsProtectedBranch(db.DefaultContext, repo.ID, branchName) + isProtected, err := git_model.IsBranchProtected(db.DefaultContext, repo.ID, branchName) if err != nil { return err } - if isProtected { - return ErrBranchIsProtected + return git_model.ErrBranchIsProtected } commit, err := gitRepo.GetBranchCommit(branchName) diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 33f4b6c9dc..73ee0fa815 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -66,13 +66,16 @@ 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 } - if protectedBranch != nil && !protectedBranch.CanUserPush(ctx, doer.ID) { - return models.ErrUserCannotCommit{ - UserName: doer.LowerName, + if protectedBranch != nil { + protectedBranch.Repo = repo + if !protectedBranch.CanUserPush(ctx, doer) { + return models.ErrUserCannotCommit{ + UserName: doer.LowerName, + } } } if protectedBranch != nil && protectedBranch.RequireSignedCommits { diff --git a/services/repository/files/update.go b/services/repository/files/update.go index 30cfd9e2dd..58b7a5e082 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -463,17 +463,18 @@ 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 } if protectedBranch != nil { + protectedBranch.Repo = repo isUnprotectedFile := false glob := protectedBranch.GetUnprotectedFilePatterns() if len(glob) != 0 { isUnprotectedFile = protectedBranch.IsUnprotectedFile(glob, treePath) } - if !protectedBranch.CanUserPush(ctx, doer.ID) && !isUnprotectedFile { + if !protectedBranch.CanUserPush(ctx, doer) && !isUnprotectedFile { return models.ErrUserCannotCommit{ UserName: doer.LowerName, } |