mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-19 21:28:33 +02:00
Merge 5df3166510d3aecb44493633f6b691306bb77926 into 6599efb3b1400ac06d06e1c8b68ae6037fbb7952
This commit is contained in:
commit
f7b1dca7e2
@ -58,6 +58,7 @@ type ProtectedBranch struct {
|
||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BlockOnCodeownerReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
|
||||
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"code.gitea.io/gitea/models/migrations/v1_22"
|
||||
"code.gitea.io/gitea/models/migrations/v1_23"
|
||||
"code.gitea.io/gitea/models/migrations/v1_24"
|
||||
"code.gitea.io/gitea/models/migrations/v1_25"
|
||||
"code.gitea.io/gitea/models/migrations/v1_6"
|
||||
"code.gitea.io/gitea/models/migrations/v1_7"
|
||||
"code.gitea.io/gitea/models/migrations/v1_8"
|
||||
@ -382,6 +383,10 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode),
|
||||
newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable),
|
||||
newMigration(320, "Migrate two_factor_policy to login_source table", v1_24.MigrateSkipTwoFactor),
|
||||
|
||||
// Gitea 1.24.0 ends at migration ID number 320 (database version 321)
|
||||
|
||||
newMigration(321, "Add block on codeowner reviews branch protection", v1_25.AddBlockOnCodeownerReviews),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
16
models/migrations/v1_25/v321.go
Normal file
16
models/migrations/v1_25/v321.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_25
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddBlockOnCodeownerReviews(x *xorm.Engine) error {
|
||||
type ProtectedBranch struct {
|
||||
BlockOnCodeownerReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ProtectedBranch))
|
||||
}
|
@ -47,6 +47,7 @@ type BranchProtection struct {
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
|
||||
BlockOnCodeownerReviews bool `json:"block_on_codeowner_reviews"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals"`
|
||||
@ -87,6 +88,7 @@ type CreateBranchProtectionOption struct {
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
|
||||
BlockOnCodeownerReviews bool `json:"block_on_codeowner_reviews"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals"`
|
||||
@ -120,6 +122,7 @@ type EditBranchProtectionOption struct {
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
|
||||
BlockOnCodeownerReviews *bool `json:"block_on_codeowner_reviews"`
|
||||
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
|
||||
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
|
||||
IgnoreStaleApprovals *bool `json:"ignore_stale_approvals"`
|
||||
|
@ -1903,6 +1903,7 @@ pulls.required_status_check_administrator = As an administrator, you may still m
|
||||
pulls.blocked_by_approvals = "This pull request doesn't have enough required approvals yet. %d of %d official approvals granted."
|
||||
pulls.blocked_by_approvals_whitelisted = "This pull request doesn't have enough required approvals yet. %d of %d approvals granted from users or teams on the allowlist."
|
||||
pulls.blocked_by_rejection = "This pull request has changes requested by an official reviewer."
|
||||
pulls.blocked_by_codeowners = "This pull request is missing approval from one or more code owners."
|
||||
pulls.blocked_by_official_review_requests = "This pull request has official review requests."
|
||||
pulls.blocked_by_outdated_branch = "This pull request is blocked because it's outdated."
|
||||
pulls.blocked_by_changed_protected_files_1= "This pull request is blocked because it changes a protected file:"
|
||||
@ -2538,6 +2539,8 @@ settings.block_rejected_reviews = Block merge on rejected reviews
|
||||
settings.block_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals.
|
||||
settings.block_on_official_review_requests = Block merge on official review requests
|
||||
settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
|
||||
settings.block_on_codeowner_reviews = Require approval from code owners
|
||||
settings.block_on_codeowner_reviews_desc = Merging will only be possible if at least one code owner per code owner rule has given an approving review.
|
||||
settings.block_outdated_branch = Block merge if pull request is outdated
|
||||
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
|
||||
settings.block_admin_merge_override = Administrators must follow branch protection rules
|
||||
|
@ -855,6 +855,10 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
|
||||
}
|
||||
|
||||
if form.BlockOnCodeownerReviews != nil {
|
||||
protectBranch.BlockOnCodeownerReviews = *form.BlockOnCodeownerReviews
|
||||
}
|
||||
|
||||
if form.DismissStaleApprovals != nil {
|
||||
protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
|
||||
}
|
||||
|
@ -947,6 +947,7 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss
|
||||
ctx.Data["ProtectedBranch"] = pb
|
||||
ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByCodeowners"] = !issue_service.HasAllRequiredCodeownerReviews(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)
|
||||
|
@ -255,6 +255,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||
}
|
||||
protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
|
||||
protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
|
||||
protectBranch.BlockOnCodeownerReviews = f.BlockOnCodeownerReviews
|
||||
protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
|
||||
protectBranch.IgnoreStaleApprovals = f.IgnoreStaleApprovals
|
||||
protectBranch.RequireSignedCommits = f.RequireSignedCommits
|
||||
|
@ -189,6 +189,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo
|
||||
ApprovalsWhitelistTeams: approvalsWhitelistTeams,
|
||||
BlockOnRejectedReviews: bp.BlockOnRejectedReviews,
|
||||
BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests,
|
||||
BlockOnCodeownerReviews: bp.BlockOnCodeownerReviews,
|
||||
BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch,
|
||||
DismissStaleApprovals: bp.DismissStaleApprovals,
|
||||
IgnoreStaleApprovals: bp.IgnoreStaleApprovals,
|
||||
|
@ -189,6 +189,7 @@ type ProtectBranchForm struct {
|
||||
ApprovalsWhitelistTeams string
|
||||
BlockOnRejectedReviews bool
|
||||
BlockOnOfficialReviewRequests bool
|
||||
BlockOnCodeownerReviews bool
|
||||
BlockOnOutdatedBranch bool
|
||||
DismissStaleApprovals bool
|
||||
IgnoreStaleApprovals bool
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@ -47,6 +48,151 @@ func IsCodeOwnerFile(f string) bool {
|
||||
return slices.Contains(codeOwnerFiles, f)
|
||||
}
|
||||
|
||||
func HasAllRequiredCodeownerReviews(ctx context.Context, pb *git_model.ProtectedBranch, pr *issues_model.PullRequest) bool {
|
||||
if !pb.BlockOnCodeownerReviews {
|
||||
return true
|
||||
}
|
||||
|
||||
if err := pr.LoadHeadRepo(ctx); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pr.Issue.Repo = pr.BaseRepo
|
||||
|
||||
if pr.BaseRepo.IsFork {
|
||||
return true
|
||||
}
|
||||
|
||||
repo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
defer repo.Close()
|
||||
|
||||
commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var data string
|
||||
|
||||
for _, file := range codeOwnerFiles {
|
||||
if blob, err := commit.GetBlobByPath(file); err == nil {
|
||||
data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no code owner file = no one to approve
|
||||
if data == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data)
|
||||
if len(rules) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// get the mergebase
|
||||
mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed
|
||||
// between the merge base and the head commit but not the base branch and the head commit
|
||||
changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitRefName())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
reviews, err := issues_model.FindLatestReviews(ctx, issues_model.FindReviewOptions{
|
||||
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
|
||||
IssueID: pr.IssueID,
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
hasApprovals := true
|
||||
|
||||
for _, rule := range rules {
|
||||
for _, f := range changedFiles {
|
||||
if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) {
|
||||
hasPotentialReviewers := false
|
||||
hasRuleApproval := false
|
||||
|
||||
for _, u := range rule.Users {
|
||||
if u.ID == pr.Issue.OriginalAuthorID {
|
||||
continue
|
||||
}
|
||||
|
||||
hasPotentialReviewers = true
|
||||
|
||||
for _, review := range reviews {
|
||||
if review.ReviewerID == u.ID {
|
||||
hasRuleApproval = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasRuleApproval {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasRuleApproval {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, t := range rule.Teams {
|
||||
if !hasPotentialReviewers {
|
||||
if err := t.LoadMembers(ctx); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, m := range t.Members {
|
||||
if m.ID != pr.Issue.OriginalAuthorID {
|
||||
hasPotentialReviewers = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, review := range reviews {
|
||||
if review.ReviewerTeamID == t.ID {
|
||||
hasRuleApproval = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasRuleApproval {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasRuleApproval && hasPotentialReviewers {
|
||||
hasApprovals = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasApprovals
|
||||
}
|
||||
|
||||
func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
return nil, err
|
||||
|
@ -592,6 +592,10 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
|
||||
return util.ErrorWrap(ErrNotReadyToMerge, "The head branch is behind the base branch")
|
||||
}
|
||||
|
||||
if !issue_service.HasAllRequiredCodeownerReviews(ctx, pb, pr) {
|
||||
return util.ErrorWrap(ErrNotReadyToMerge, "There are missing code owner reviews.")
|
||||
}
|
||||
|
||||
if skipProtectedFilesCheck {
|
||||
return nil
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
{{- else if .IsBlockedByApprovals}}red
|
||||
{{- else if .IsBlockedByRejection}}red
|
||||
{{- else if .IsBlockedByOfficialReviewRequests}}red
|
||||
{{- else if .IsBlockedByCodeowners}}red
|
||||
{{- else if .IsBlockedByOutdatedBranch}}red
|
||||
{{- else if .IsBlockedByChangedProtectedFiles}}red
|
||||
{{- else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsFailure .RequiredStatusCheckState.IsError)}}red
|
||||
@ -131,6 +132,11 @@
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByCodeowners}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_codeowners"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByOutdatedBranch}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
@ -167,7 +173,7 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
|
||||
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByCodeowners .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
|
||||
|
||||
{{/* admin can merge without checks, writer can merge when checks succeed */}}
|
||||
{{$canMergeNow := and (or (and (not $.ProtectedBranch.BlockAdminMergeOverride) $.IsRepoAdmin) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
|
||||
@ -337,6 +343,11 @@
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByCodeowners}}
|
||||
<div class="item text red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_codeowners"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByOutdatedBranch}}
|
||||
<div class="item text red">
|
||||
{{svg "octicon-x"}}
|
||||
|
@ -320,6 +320,13 @@
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.block_on_official_review_requests_desc"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input name="block_on_codeowner_reviews" type="checkbox" {{if .Rule.BlockOnCodeownerReviews}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "repo.settings.block_on_codeowner_reviews"}}</label>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.block_on_codeowner_reviews_desc"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input name="block_on_outdated_branch" type="checkbox" {{if .Rule.BlockOnOutdatedBranch}}checked{{end}}>
|
||||
|
12
templates/swagger/v1_json.tmpl
generated
12
templates/swagger/v1_json.tmpl
generated
@ -21731,6 +21731,10 @@
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockAdminMergeOverride"
|
||||
},
|
||||
"block_on_codeowner_reviews": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockOnCodeownerReviews"
|
||||
},
|
||||
"block_on_official_review_requests": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockOnOfficialReviewRequests"
|
||||
@ -22524,6 +22528,10 @@
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockAdminMergeOverride"
|
||||
},
|
||||
"block_on_codeowner_reviews": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockOnCodeownerReviews"
|
||||
},
|
||||
"block_on_official_review_requests": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockOnOfficialReviewRequests"
|
||||
@ -23794,6 +23802,10 @@
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockAdminMergeOverride"
|
||||
},
|
||||
"block_on_codeowner_reviews": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockOnCodeownerReviews"
|
||||
},
|
||||
"block_on_official_review_requests": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "BlockOnOfficialReviewRequests"
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
@ -19,6 +20,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
"code.gitea.io/gitea/tests"
|
||||
@ -49,6 +51,8 @@ func TestPullView_ReviewerMissed(t *testing.T) {
|
||||
func TestPullView_CodeOwner(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
user8 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 8})
|
||||
|
||||
// Create the repo.
|
||||
repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
|
||||
@ -60,6 +64,17 @@ func TestPullView_CodeOwner(t *testing.T) {
|
||||
}, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create code owner branch protection
|
||||
protectBranch := git_model.ProtectedBranch{
|
||||
BlockOnCodeownerReviews: true,
|
||||
RepoID: repo.ID,
|
||||
RuleName: "master",
|
||||
CanPush: true,
|
||||
}
|
||||
|
||||
err = pull_service.CreateOrUpdateProtectedBranch(db.DefaultContext, repo, &protectBranch, git_model.WhitelistOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add CODEOWNERS to default branch
|
||||
_, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||
OldBranch: repo.DefaultBranch,
|
||||
@ -96,7 +111,7 @@ func TestPullView_CodeOwner(t *testing.T) {
|
||||
assert.NoError(t, pr.LoadIssue(db.DefaultContext))
|
||||
|
||||
// update the file on the pr branch
|
||||
_, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||
resp, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||
OldBranch: "codeowner-basebranch",
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
@ -124,6 +139,22 @@ func TestPullView_CodeOwner(t *testing.T) {
|
||||
prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.NoError(t, prUpdated2.LoadIssue(db.DefaultContext))
|
||||
assert.Equal(t, "Test Pull Request2", prUpdated2.Issue.Title)
|
||||
|
||||
// ensure it cannot be merged
|
||||
hasCodeownerReviews := issue_service.HasAllRequiredCodeownerReviews(db.DefaultContext, &protectBranch, pr)
|
||||
assert.False(t, hasCodeownerReviews)
|
||||
|
||||
issues_model.SubmitReview(db.DefaultContext, user5, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
|
||||
|
||||
// should still fail (we also need user8)
|
||||
hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(db.DefaultContext, &protectBranch, pr)
|
||||
assert.False(t, hasCodeownerReviews)
|
||||
|
||||
issues_model.SubmitReview(db.DefaultContext, user8, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
|
||||
|
||||
// now we should be able to merge
|
||||
hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(db.DefaultContext, &protectBranch, pr)
|
||||
assert.True(t, hasCodeownerReviews)
|
||||
})
|
||||
|
||||
// change the default branch CODEOWNERS file to change README.md's codeowner
|
||||
@ -140,7 +171,7 @@ func TestPullView_CodeOwner(t *testing.T) {
|
||||
|
||||
t.Run("Second Pull Request", func(t *testing.T) {
|
||||
// create a new branch to prepare for pull request
|
||||
_, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||
resp, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||
NewBranch: "codeowner-basebranch2",
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
@ -158,6 +189,15 @@ func TestPullView_CodeOwner(t *testing.T) {
|
||||
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch2"})
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
|
||||
|
||||
// should need user8 approval only now
|
||||
hasCodeownerReviews := issue_service.HasAllRequiredCodeownerReviews(db.DefaultContext, &protectBranch, pr)
|
||||
assert.False(t, hasCodeownerReviews)
|
||||
|
||||
issues_model.SubmitReview(db.DefaultContext, user8, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
|
||||
|
||||
hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(db.DefaultContext, &protectBranch, pr)
|
||||
assert.True(t, hasCodeownerReviews)
|
||||
})
|
||||
|
||||
t.Run("Forked Repo Pull Request", func(t *testing.T) {
|
||||
@ -169,7 +209,7 @@ func TestPullView_CodeOwner(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create a new branch to prepare for pull request
|
||||
_, err = files_service.ChangeRepoFiles(db.DefaultContext, forkedRepo, user5, &files_service.ChangeRepoFilesOptions{
|
||||
resp, err := files_service.ChangeRepoFiles(db.DefaultContext, forkedRepo, user5, &files_service.ChangeRepoFilesOptions{
|
||||
NewBranch: "codeowner-basebranch-forked",
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
@ -194,6 +234,21 @@ func TestPullView_CodeOwner(t *testing.T) {
|
||||
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadRepoID: forkedRepo.ID, HeadBranch: "codeowner-basebranch-forked"})
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
|
||||
|
||||
// will also need user8 for this
|
||||
hasCodeownerReviews := issue_service.HasAllRequiredCodeownerReviews(db.DefaultContext, &protectBranch, pr)
|
||||
assert.False(t, hasCodeownerReviews)
|
||||
|
||||
issues_model.SubmitReview(db.DefaultContext, user5, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
|
||||
|
||||
// should still fail (user5 is not a code owner for this PR)
|
||||
hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(db.DefaultContext, &protectBranch, pr)
|
||||
assert.False(t, hasCodeownerReviews)
|
||||
|
||||
issues_model.SubmitReview(db.DefaultContext, user8, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
|
||||
|
||||
hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(db.DefaultContext, &protectBranch, pr)
|
||||
assert.True(t, hasCodeownerReviews)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user