Prevent merge of outdated PRs on protected branches (#11012)
* Block PR on Outdated Branch * finalize * cleanup * fix typo and sentences thanks @guillep2k Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
		
							parent
							
								
									2cb5878529
								
							
						
					
					
						commit
						c52d48aae4
					
				
					 15 changed files with 81 additions and 5 deletions
				
			
		|  | @ -47,6 +47,7 @@ type ProtectedBranch struct { | ||||||
| 	ApprovalsWhitelistTeamIDs []int64  `xorm:"JSON TEXT"` | 	ApprovalsWhitelistTeamIDs []int64  `xorm:"JSON TEXT"` | ||||||
| 	RequiredApprovals         int64    `xorm:"NOT NULL DEFAULT 0"` | 	RequiredApprovals         int64    `xorm:"NOT NULL DEFAULT 0"` | ||||||
| 	BlockOnRejectedReviews    bool     `xorm:"NOT NULL DEFAULT false"` | 	BlockOnRejectedReviews    bool     `xorm:"NOT NULL DEFAULT false"` | ||||||
|  | 	BlockOnOutdatedBranch     bool     `xorm:"NOT NULL DEFAULT false"` | ||||||
| 	DismissStaleApprovals     bool     `xorm:"NOT NULL DEFAULT false"` | 	DismissStaleApprovals     bool     `xorm:"NOT NULL DEFAULT false"` | ||||||
| 	RequireSignedCommits      bool     `xorm:"NOT NULL DEFAULT false"` | 	RequireSignedCommits      bool     `xorm:"NOT NULL DEFAULT false"` | ||||||
| 	ProtectedFilePatterns     string   `xorm:"TEXT"` | 	ProtectedFilePatterns     string   `xorm:"TEXT"` | ||||||
|  | @ -194,6 +195,11 @@ func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullReque | ||||||
| 	return rejectExist | 	return rejectExist | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // MergeBlockedByOutdatedBranch returns true if merge is blocked by an outdated head branch
 | ||||||
|  | func (protectBranch *ProtectedBranch) MergeBlockedByOutdatedBranch(pr *PullRequest) bool { | ||||||
|  | 	return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice
 | // GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice
 | ||||||
| func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob { | func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob { | ||||||
| 	extarr := make([]glob.Glob, 0, 10) | 	extarr := make([]glob.Glob, 0, 10) | ||||||
|  |  | ||||||
|  | @ -202,10 +202,12 @@ var migrations = []Migration{ | ||||||
| 	NewMigration("Add EmailHash Table", addEmailHashTable), | 	NewMigration("Add EmailHash Table", addEmailHashTable), | ||||||
| 	// v134 -> v135
 | 	// v134 -> v135
 | ||||||
| 	NewMigration("Refix merge base for merged pull requests", refixMergeBase), | 	NewMigration("Refix merge base for merged pull requests", refixMergeBase), | ||||||
| 	// v135 -> 136
 | 	// v135 -> v136
 | ||||||
| 	NewMigration("Add OrgID column to Labels table", addOrgIDLabelColumn), | 	NewMigration("Add OrgID column to Labels table", addOrgIDLabelColumn), | ||||||
| 	// v136 -> 137
 | 	// v136 -> v137
 | ||||||
| 	NewMigration("Add CommitsAhead and CommitsBehind Column to PullRequest Table", addCommitDivergenceToPulls), | 	NewMigration("Add CommitsAhead and CommitsBehind Column to PullRequest Table", addCommitDivergenceToPulls), | ||||||
|  | 	// v137 -> v138
 | ||||||
|  | 	NewMigration("Add Branch Protection Block Outdated Branch", addBlockOnOutdatedBranch), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetCurrentDBVersion returns the current db version
 | // GetCurrentDBVersion returns the current db version
 | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								models/migrations/v137.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								models/migrations/v137.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | // Copyright 2020 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 migrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"xorm.io/xorm" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func addBlockOnOutdatedBranch(x *xorm.Engine) error { | ||||||
|  | 	type ProtectedBranch struct { | ||||||
|  | 		BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"` | ||||||
|  | 	} | ||||||
|  | 	return x.Sync2(new(ProtectedBranch)) | ||||||
|  | } | ||||||
|  | @ -173,6 +173,7 @@ type ProtectBranchForm struct { | ||||||
| 	ApprovalsWhitelistUsers  string | 	ApprovalsWhitelistUsers  string | ||||||
| 	ApprovalsWhitelistTeams  string | 	ApprovalsWhitelistTeams  string | ||||||
| 	BlockOnRejectedReviews   bool | 	BlockOnRejectedReviews   bool | ||||||
|  | 	BlockOnOutdatedBranch    bool | ||||||
| 	DismissStaleApprovals    bool | 	DismissStaleApprovals    bool | ||||||
| 	RequireSignedCommits     bool | 	RequireSignedCommits     bool | ||||||
| 	ProtectedFilePatterns    string | 	ProtectedFilePatterns    string | ||||||
|  |  | ||||||
|  | @ -118,6 +118,7 @@ func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection { | ||||||
| 		ApprovalsWhitelistUsernames: approvalsWhitelistUsernames, | 		ApprovalsWhitelistUsernames: approvalsWhitelistUsernames, | ||||||
| 		ApprovalsWhitelistTeams:     approvalsWhitelistTeams, | 		ApprovalsWhitelistTeams:     approvalsWhitelistTeams, | ||||||
| 		BlockOnRejectedReviews:      bp.BlockOnRejectedReviews, | 		BlockOnRejectedReviews:      bp.BlockOnRejectedReviews, | ||||||
|  | 		BlockOnOutdatedBranch:       bp.BlockOnOutdatedBranch, | ||||||
| 		DismissStaleApprovals:       bp.DismissStaleApprovals, | 		DismissStaleApprovals:       bp.DismissStaleApprovals, | ||||||
| 		RequireSignedCommits:        bp.RequireSignedCommits, | 		RequireSignedCommits:        bp.RequireSignedCommits, | ||||||
| 		ProtectedFilePatterns:       bp.ProtectedFilePatterns, | 		ProtectedFilePatterns:       bp.ProtectedFilePatterns, | ||||||
|  |  | ||||||
|  | @ -39,6 +39,7 @@ type BranchProtection struct { | ||||||
| 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | ||||||
| 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | ||||||
| 	BlockOnRejectedReviews      bool     `json:"block_on_rejected_reviews"` | 	BlockOnRejectedReviews      bool     `json:"block_on_rejected_reviews"` | ||||||
|  | 	BlockOnOutdatedBranch       bool     `json:"block_on_outdated_branch"` | ||||||
| 	DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"` | 	DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"` | ||||||
| 	RequireSignedCommits        bool     `json:"require_signed_commits"` | 	RequireSignedCommits        bool     `json:"require_signed_commits"` | ||||||
| 	ProtectedFilePatterns       string   `json:"protected_file_patterns"` | 	ProtectedFilePatterns       string   `json:"protected_file_patterns"` | ||||||
|  | @ -66,6 +67,7 @@ type CreateBranchProtectionOption struct { | ||||||
| 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | ||||||
| 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | ||||||
| 	BlockOnRejectedReviews      bool     `json:"block_on_rejected_reviews"` | 	BlockOnRejectedReviews      bool     `json:"block_on_rejected_reviews"` | ||||||
|  | 	BlockOnOutdatedBranch       bool     `json:"block_on_outdated_branch"` | ||||||
| 	DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"` | 	DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"` | ||||||
| 	RequireSignedCommits        bool     `json:"require_signed_commits"` | 	RequireSignedCommits        bool     `json:"require_signed_commits"` | ||||||
| 	ProtectedFilePatterns       string   `json:"protected_file_patterns"` | 	ProtectedFilePatterns       string   `json:"protected_file_patterns"` | ||||||
|  | @ -88,6 +90,7 @@ type EditBranchProtectionOption struct { | ||||||
| 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | 	ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` | ||||||
| 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | 	ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"` | ||||||
| 	BlockOnRejectedReviews      *bool    `json:"block_on_rejected_reviews"` | 	BlockOnRejectedReviews      *bool    `json:"block_on_rejected_reviews"` | ||||||
|  | 	BlockOnOutdatedBranch       *bool    `json:"block_on_outdated_branch"` | ||||||
| 	DismissStaleApprovals       *bool    `json:"dismiss_stale_approvals"` | 	DismissStaleApprovals       *bool    `json:"dismiss_stale_approvals"` | ||||||
| 	RequireSignedCommits        *bool    `json:"require_signed_commits"` | 	RequireSignedCommits        *bool    `json:"require_signed_commits"` | ||||||
| 	ProtectedFilePatterns       *string  `json:"protected_file_patterns"` | 	ProtectedFilePatterns       *string  `json:"protected_file_patterns"` | ||||||
|  |  | ||||||
|  | @ -1102,6 +1102,7 @@ pulls.required_status_check_missing = Some required checks are missing. | ||||||
| pulls.required_status_check_administrator = As an administrator, you may still merge this pull request. | pulls.required_status_check_administrator = As an administrator, you may still merge this pull request. | ||||||
| pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." | pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." | ||||||
| pulls.blocked_by_rejection = "This Pull Request has changes requested by an official reviewer." | pulls.blocked_by_rejection = "This Pull Request has changes requested by an official reviewer." | ||||||
|  | pulls.blocked_by_outdated_branch = "This Pull Request is blocked because it's outdated." | ||||||
| pulls.can_auto_merge_desc = This pull request can be merged automatically. | pulls.can_auto_merge_desc = This pull request can be merged automatically. | ||||||
| pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts. | pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts. | ||||||
| pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. | pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. | ||||||
|  | @ -1528,6 +1529,8 @@ settings.protected_branch_deletion = Disable Branch Protection | ||||||
| settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? | settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? | ||||||
| settings.block_rejected_reviews = Block merge on rejected reviews | 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_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals. | ||||||
|  | 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.default_branch_desc = Select a default repository branch for pull requests and code commits: | settings.default_branch_desc = Select a default repository branch for pull requests and code commits: | ||||||
| settings.choose_branch = Choose a branch… | settings.choose_branch = Choose a branch… | ||||||
| settings.no_protected_branch = There are no protected branches. | settings.no_protected_branch = There are no protected branches. | ||||||
|  |  | ||||||
|  | @ -340,6 +340,7 @@ func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtec | ||||||
| 		DismissStaleApprovals:    form.DismissStaleApprovals, | 		DismissStaleApprovals:    form.DismissStaleApprovals, | ||||||
| 		RequireSignedCommits:     form.RequireSignedCommits, | 		RequireSignedCommits:     form.RequireSignedCommits, | ||||||
| 		ProtectedFilePatterns:    form.ProtectedFilePatterns, | 		ProtectedFilePatterns:    form.ProtectedFilePatterns, | ||||||
|  | 		BlockOnOutdatedBranch:    form.BlockOnOutdatedBranch, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ | 	err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ | ||||||
|  | @ -475,6 +476,10 @@ func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtection | ||||||
| 		protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns | 		protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if form.BlockOnOutdatedBranch != nil { | ||||||
|  | 		protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	var whitelistUsers []int64 | 	var whitelistUsers []int64 | ||||||
| 	if form.PushWhitelistUsernames != nil { | 	if form.PushWhitelistUsernames != nil { | ||||||
| 		whitelistUsers, err = models.GetUserIDsByNames(form.PushWhitelistUsernames, false) | 		whitelistUsers, err = models.GetUserIDsByNames(form.PushWhitelistUsernames, false) | ||||||
|  |  | ||||||
|  | @ -263,6 +263,7 @@ func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) { | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			// Detect Protected file pattern
 | ||||||
| 			globs := protectBranch.GetProtectedFilePatterns() | 			globs := protectBranch.GetProtectedFilePatterns() | ||||||
| 			if len(globs) > 0 { | 			if len(globs) > 0 { | ||||||
| 				err := checkFileProtection(oldCommitID, newCommitID, globs, gitRepo, env) | 				err := checkFileProtection(oldCommitID, newCommitID, globs, gitRepo, env) | ||||||
|  |  | ||||||
|  | @ -1065,6 +1065,7 @@ func ViewIssue(ctx *context.Context) { | ||||||
| 			cnt := pull.ProtectedBranch.GetGrantedApprovalsCount(pull) | 			cnt := pull.ProtectedBranch.GetGrantedApprovalsCount(pull) | ||||||
| 			ctx.Data["IsBlockedByApprovals"] = !pull.ProtectedBranch.HasEnoughApprovals(pull) | 			ctx.Data["IsBlockedByApprovals"] = !pull.ProtectedBranch.HasEnoughApprovals(pull) | ||||||
| 			ctx.Data["IsBlockedByRejection"] = pull.ProtectedBranch.MergeBlockedByRejectedReview(pull) | 			ctx.Data["IsBlockedByRejection"] = pull.ProtectedBranch.MergeBlockedByRejectedReview(pull) | ||||||
|  | 			ctx.Data["IsBlockedByOutdatedBranch"] = pull.ProtectedBranch.MergeBlockedByOutdatedBranch(pull) | ||||||
| 			ctx.Data["GrantedApprovals"] = cnt | 			ctx.Data["GrantedApprovals"] = cnt | ||||||
| 			ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits | 			ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -248,6 +248,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) | ||||||
| 		protectBranch.DismissStaleApprovals = f.DismissStaleApprovals | 		protectBranch.DismissStaleApprovals = f.DismissStaleApprovals | ||||||
| 		protectBranch.RequireSignedCommits = f.RequireSignedCommits | 		protectBranch.RequireSignedCommits = f.RequireSignedCommits | ||||||
| 		protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns | 		protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns | ||||||
|  | 		protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch | ||||||
| 
 | 
 | ||||||
| 		err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ | 		err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ | ||||||
| 			UserIDs:          whitelistUsers, | 			UserIDs:          whitelistUsers, | ||||||
|  |  | ||||||
|  | @ -574,16 +574,22 @@ func CheckPRReadyToMerge(pr *models.PullRequest) (err error) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if enoughApprovals := pr.ProtectedBranch.HasEnoughApprovals(pr); !enoughApprovals { | 	if !pr.ProtectedBranch.HasEnoughApprovals(pr) { | ||||||
| 		return models.ErrNotAllowedToMerge{ | 		return models.ErrNotAllowedToMerge{ | ||||||
| 			Reason: "Does not have enough approvals", | 			Reason: "Does not have enough approvals", | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if rejected := pr.ProtectedBranch.MergeBlockedByRejectedReview(pr); rejected { | 	if pr.ProtectedBranch.MergeBlockedByRejectedReview(pr) { | ||||||
| 		return models.ErrNotAllowedToMerge{ | 		return models.ErrNotAllowedToMerge{ | ||||||
| 			Reason: "There are requested changes", | 			Reason: "There are requested changes", | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if pr.ProtectedBranch.MergeBlockedByOutdatedBranch(pr) { | ||||||
|  | 		return models.ErrNotAllowedToMerge{ | ||||||
|  | 			Reason: "The head branch is behind the base branch", | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -66,6 +66,7 @@ | ||||||
| 	{{- else if .IsPullRequestBroken}}red | 	{{- else if .IsPullRequestBroken}}red | ||||||
| 	{{- else if .IsBlockedByApprovals}}red | 	{{- else if .IsBlockedByApprovals}}red | ||||||
| 	{{- else if .IsBlockedByRejection}}red | 	{{- else if .IsBlockedByRejection}}red | ||||||
|  | 	{{- else if .IsBlockedByOutdatedBranch}}red | ||||||
| 	{{- else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsFailure .RequiredStatusCheckState.IsError)}}red | 	{{- else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsFailure .RequiredStatusCheckState.IsError)}}red | ||||||
| 	{{- else if and .EnableStatusCheck (or (not $.LatestCommitStatus) .RequiredStatusCheckState.IsPending .RequiredStatusCheckState.IsWarning)}}yellow | 	{{- else if and .EnableStatusCheck (or (not $.LatestCommitStatus) .RequiredStatusCheckState.IsPending .RequiredStatusCheckState.IsWarning)}}yellow | ||||||
| 	{{- else if and .RequireSigned (not .WillSign)}}}red | 	{{- else if and .RequireSigned (not .WillSign)}}}red | ||||||
|  | @ -138,6 +139,11 @@ | ||||||
| 						<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i> | 						<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i> | ||||||
| 					{{$.i18n.Tr "repo.pulls.blocked_by_rejection"}} | 					{{$.i18n.Tr "repo.pulls.blocked_by_rejection"}} | ||||||
| 					</div> | 					</div> | ||||||
|  | 				{{else if .IsBlockedByOutdatedBranch}} | ||||||
|  | 					<div class="item text red"> | ||||||
|  | 						<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i> | ||||||
|  | 					{{$.i18n.Tr "repo.pulls.blocked_by_outdated_branch"}} | ||||||
|  | 					</div> | ||||||
| 				{{else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsError .RequiredStatusCheckState.IsFailure)}} | 				{{else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsError .RequiredStatusCheckState.IsFailure)}} | ||||||
| 					<div class="item text red"> | 					<div class="item text red"> | ||||||
| 						<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i> | 						<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i> | ||||||
|  | @ -158,7 +164,7 @@ | ||||||
| 						{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }} | 						{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }} | ||||||
| 					</div> | 					</div> | ||||||
| 				{{end}} | 				{{end}} | ||||||
| 				{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}} | 				{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOutdatedBranch (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}} | ||||||
| 				{{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .RequireSigned) .WillSign)}} | 				{{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .RequireSigned) .WillSign)}} | ||||||
| 					{{if $notAllOverridableChecksOk}} | 					{{if $notAllOverridableChecksOk}} | ||||||
| 						<div class="item text yellow"> | 						<div class="item text yellow"> | ||||||
|  | @ -342,6 +348,11 @@ | ||||||
| 						{{svg "octicon-x" 16}} | 						{{svg "octicon-x" 16}} | ||||||
| 					{{$.i18n.Tr "repo.pulls.blocked_by_rejection"}} | 					{{$.i18n.Tr "repo.pulls.blocked_by_rejection"}} | ||||||
| 					</div> | 					</div> | ||||||
|  | 				{{else if .IsBlockedByOutdatedBranch}} | ||||||
|  | 					<div class="item text red"> | ||||||
|  | 						<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i> | ||||||
|  | 					{{$.i18n.Tr "repo.pulls.blocked_by_outdated_branch"}} | ||||||
|  | 					</div> | ||||||
| 				{{else if and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess)}} | 				{{else if and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess)}} | ||||||
| 					<div class="item text red"> | 					<div class="item text red"> | ||||||
| 						{{svg "octicon-x" 16}} | 						{{svg "octicon-x" 16}} | ||||||
|  |  | ||||||
|  | @ -225,6 +225,13 @@ | ||||||
| 							<p class="help">{{.i18n.Tr "repo.settings.require_signed_commits_desc"}}</p> | 							<p class="help">{{.i18n.Tr "repo.settings.require_signed_commits_desc"}}</p> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
|  | 					<div class="field"> | ||||||
|  | 						<div class="ui checkbox"> | ||||||
|  | 							<input name="block_on_outdated_branch" type="checkbox" {{if .Branch.BlockOnOutdatedBranch}}checked{{end}}> | ||||||
|  | 							<label for="block_on_outdated_branch">{{.i18n.Tr "repo.settings.block_outdated_branch"}}</label> | ||||||
|  | 							<p class="help">{{.i18n.Tr "repo.settings.block_outdated_branch_desc"}}</p> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
| 					<div class="field"> | 					<div class="field"> | ||||||
| 						<label for="protected_file_patterns">{{.i18n.Tr "repo.settings.protect_protected_file_patterns"}}</label> | 						<label for="protected_file_patterns">{{.i18n.Tr "repo.settings.protect_protected_file_patterns"}}</label> | ||||||
| 						<input name="protected_file_patterns" id="protected_file_patterns" type="text" value="{{.Branch.ProtectedFilePatterns}}"> | 						<input name="protected_file_patterns" id="protected_file_patterns" type="text" value="{{.Branch.ProtectedFilePatterns}}"> | ||||||
|  |  | ||||||
|  | @ -10072,6 +10072,10 @@ | ||||||
|           }, |           }, | ||||||
|           "x-go-name": "ApprovalsWhitelistUsernames" |           "x-go-name": "ApprovalsWhitelistUsernames" | ||||||
|         }, |         }, | ||||||
|  |         "block_on_outdated_branch": { | ||||||
|  |           "type": "boolean", | ||||||
|  |           "x-go-name": "BlockOnOutdatedBranch" | ||||||
|  |         }, | ||||||
|         "block_on_rejected_reviews": { |         "block_on_rejected_reviews": { | ||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
|           "x-go-name": "BlockOnRejectedReviews" |           "x-go-name": "BlockOnRejectedReviews" | ||||||
|  | @ -10392,6 +10396,10 @@ | ||||||
|           }, |           }, | ||||||
|           "x-go-name": "ApprovalsWhitelistUsernames" |           "x-go-name": "ApprovalsWhitelistUsernames" | ||||||
|         }, |         }, | ||||||
|  |         "block_on_outdated_branch": { | ||||||
|  |           "type": "boolean", | ||||||
|  |           "x-go-name": "BlockOnOutdatedBranch" | ||||||
|  |         }, | ||||||
|         "block_on_rejected_reviews": { |         "block_on_rejected_reviews": { | ||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
|           "x-go-name": "BlockOnRejectedReviews" |           "x-go-name": "BlockOnRejectedReviews" | ||||||
|  | @ -11204,6 +11212,10 @@ | ||||||
|           }, |           }, | ||||||
|           "x-go-name": "ApprovalsWhitelistUsernames" |           "x-go-name": "ApprovalsWhitelistUsernames" | ||||||
|         }, |         }, | ||||||
|  |         "block_on_outdated_branch": { | ||||||
|  |           "type": "boolean", | ||||||
|  |           "x-go-name": "BlockOnOutdatedBranch" | ||||||
|  |         }, | ||||||
|         "block_on_rejected_reviews": { |         "block_on_rejected_reviews": { | ||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
|           "x-go-name": "BlockOnRejectedReviews" |           "x-go-name": "BlockOnRejectedReviews" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue