mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Refuse merge until all required status checks success (#7481)
* refuse merge until ci successfully * deny merge request when required status checkes not succeed on merge Post and API * add database migration for added columns on protected_branch * fix migration * fix protected branch check bug * fix protected branch settings * remove duplicated code on check pull request's required commit statuses pass * remove unused codes * fix migration * add newline for template file * fix go mod * rename function name and some other fixes * fix template * fix bug pull view * remove go1.12 wrong dependencies * add administrator bypass when protected branch status check enabled * fix bug * improve the codes
This commit is contained in:
		
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -80,6 +80,7 @@ require ( | |||||||
| 	github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | 	github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | ||||||
| 	github.com/oliamb/cutter v0.2.2 | 	github.com/oliamb/cutter v0.2.2 | ||||||
| 	github.com/philhofer/fwd v1.0.0 // indirect | 	github.com/philhofer/fwd v1.0.0 // indirect | ||||||
|  | 	github.com/pkg/errors v0.8.1 | ||||||
| 	github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e | 	github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e | ||||||
| 	github.com/prometheus/client_golang v1.1.0 | 	github.com/prometheus/client_golang v1.1.0 | ||||||
| 	github.com/prometheus/procfs v0.0.4 // indirect | 	github.com/prometheus/procfs v0.0.4 // indirect | ||||||
|   | |||||||
| @@ -36,6 +36,8 @@ type ProtectedBranch struct { | |||||||
| 	EnableMergeWhitelist      bool               `xorm:"NOT NULL DEFAULT false"` | 	EnableMergeWhitelist      bool               `xorm:"NOT NULL DEFAULT false"` | ||||||
| 	MergeWhitelistUserIDs     []int64            `xorm:"JSON TEXT"` | 	MergeWhitelistUserIDs     []int64            `xorm:"JSON TEXT"` | ||||||
| 	MergeWhitelistTeamIDs     []int64            `xorm:"JSON TEXT"` | 	MergeWhitelistTeamIDs     []int64            `xorm:"JSON TEXT"` | ||||||
|  | 	EnableStatusCheck         bool               `xorm:"NOT NULL DEFAULT false"` | ||||||
|  | 	StatusCheckContexts       []string           `xorm:"JSON TEXT"` | ||||||
| 	ApprovalsWhitelistUserIDs []int64            `xorm:"JSON TEXT"` | 	ApprovalsWhitelistUserIDs []int64            `xorm:"JSON TEXT"` | ||||||
| 	ApprovalsWhitelistTeamIDs []int64            `xorm:"JSON TEXT"` | 	ApprovalsWhitelistTeamIDs []int64            `xorm:"JSON TEXT"` | ||||||
| 	RequiredApprovals         int64              `xorm:"NOT NULL DEFAULT 0"` | 	RequiredApprovals         int64              `xorm:"NOT NULL DEFAULT 0"` | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import ( | |||||||
| 	"crypto/sha1" | 	"crypto/sha1" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| @@ -205,6 +206,27 @@ func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitSta | |||||||
| 	return statuses, x.In("id", ids).Find(&statuses) | 	return statuses, x.In("id", ids).Find(&statuses) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts | ||||||
|  | func FindRepoRecentCommitStatusContexts(repoID int64, before time.Duration) ([]string, error) { | ||||||
|  | 	start := timeutil.TimeStampNow().AddDuration(-before) | ||||||
|  | 	ids := make([]int64, 0, 10) | ||||||
|  | 	if err := x.Table("commit_status"). | ||||||
|  | 		Where("repo_id = ?", repoID). | ||||||
|  | 		And("updated_unix >= ?", start). | ||||||
|  | 		Select("max( id ) as id"). | ||||||
|  | 		GroupBy("context_hash").OrderBy("max( id ) desc"). | ||||||
|  | 		Find(&ids); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var contexts = make([]string, 0, len(ids)) | ||||||
|  | 	if len(ids) == 0 { | ||||||
|  | 		return contexts, nil | ||||||
|  | 	} | ||||||
|  | 	return contexts, x.Select("context").Table("commit_status").In("id", ids).Find(&contexts) | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| // NewCommitStatusOptions holds options for creating a CommitStatus | // NewCommitStatusOptions holds options for creating a CommitStatus | ||||||
| type NewCommitStatusOptions struct { | type NewCommitStatusOptions struct { | ||||||
| 	Repo         *Repository | 	Repo         *Repository | ||||||
|   | |||||||
| @@ -242,6 +242,8 @@ var migrations = []Migration{ | |||||||
| 	NewMigration("remove orphaned repository index statuses", removeLingeringIndexStatus), | 	NewMigration("remove orphaned repository index statuses", removeLingeringIndexStatus), | ||||||
| 	// v93 -> v94 | 	// v93 -> v94 | ||||||
| 	NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), | 	NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), | ||||||
|  | 	// v94 -> v95 | ||||||
|  | 	NewMigration("add enable_status_check, status_check_contexts to protected_branch", addStatusCheckColumnsForProtectedBranches), | ||||||
| } | } | ||||||
|  |  | ||||||
| // Migrate database to current version | // Migrate database to current version | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								models/migrations/v94.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								models/migrations/v94.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | // Copyright 2019 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 "github.com/go-xorm/xorm" | ||||||
|  |  | ||||||
|  | func addStatusCheckColumnsForProtectedBranches(x *xorm.Engine) error { | ||||||
|  | 	type ProtectedBranch struct { | ||||||
|  | 		EnableStatusCheck   bool     `xorm:"NOT NULL DEFAULT false"` | ||||||
|  | 		StatusCheckContexts []string `xorm:"JSON TEXT"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := x.Sync2(new(ProtectedBranch)); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := x.Cols("enable_status_check", "status_check_contexts").Update(&ProtectedBranch{ | ||||||
|  | 		EnableStatusCheck:   false, | ||||||
|  | 		StatusCheckContexts: []string{}, | ||||||
|  | 	}) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
| @@ -99,6 +99,20 @@ func (pr *PullRequest) LoadAttributes() error { | |||||||
| 	return pr.loadAttributes(x) | 	return pr.loadAttributes(x) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // LoadBaseRepo loads pull request base repository from database | ||||||
|  | func (pr *PullRequest) LoadBaseRepo() error { | ||||||
|  | 	if pr.BaseRepo == nil { | ||||||
|  | 		var repo Repository | ||||||
|  | 		if has, err := x.ID(pr.BaseRepoID).Get(&repo); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} else if !has { | ||||||
|  | 			return ErrRepoNotExist{ID: pr.BaseRepoID} | ||||||
|  | 		} | ||||||
|  | 		pr.BaseRepo = &repo | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // LoadIssue loads issue information from database | // LoadIssue loads issue information from database | ||||||
| func (pr *PullRequest) LoadIssue() (err error) { | func (pr *PullRequest) LoadIssue() (err error) { | ||||||
| 	return pr.loadIssue(x) | 	return pr.loadIssue(x) | ||||||
|   | |||||||
| @@ -155,6 +155,8 @@ type ProtectBranchForm struct { | |||||||
| 	EnableMergeWhitelist    bool | 	EnableMergeWhitelist    bool | ||||||
| 	MergeWhitelistUsers     string | 	MergeWhitelistUsers     string | ||||||
| 	MergeWhitelistTeams     string | 	MergeWhitelistTeams     string | ||||||
|  | 	EnableStatusCheck       bool `xorm:"NOT NULL DEFAULT false"` | ||||||
|  | 	StatusCheckContexts     []string | ||||||
| 	RequiredApprovals       int64 | 	RequiredApprovals       int64 | ||||||
| 	ApprovalsWhitelistUsers string | 	ApprovalsWhitelistUsers string | ||||||
| 	ApprovalsWhitelistTeams string | 	ApprovalsWhitelistTeams string | ||||||
|   | |||||||
							
								
								
									
										70
									
								
								modules/pull/commit_status.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								modules/pull/commit_status.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | // Copyright 2019 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 pull | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/modules/git" | ||||||
|  |  | ||||||
|  | 	"github.com/pkg/errors" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // IsCommitStatusContextSuccess returns true if all required status check contexts succeed. | ||||||
|  | func IsCommitStatusContextSuccess(commitStatuses []*models.CommitStatus, requiredContexts []string) bool { | ||||||
|  | 	for _, ctx := range requiredContexts { | ||||||
|  | 		var found bool | ||||||
|  | 		for _, commitStatus := range commitStatuses { | ||||||
|  | 			if commitStatus.Context == ctx { | ||||||
|  | 				if commitStatus.State != models.CommitStatusSuccess { | ||||||
|  | 					return false | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				found = true | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !found { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsPullCommitStatusPass returns if all required status checks PASS | ||||||
|  | func IsPullCommitStatusPass(pr *models.PullRequest) (bool, error) { | ||||||
|  | 	if err := pr.LoadProtectedBranch(); err != nil { | ||||||
|  | 		return false, errors.Wrap(err, "GetLatestCommitStatus") | ||||||
|  | 	} | ||||||
|  | 	if pr.ProtectedBranch == nil || !pr.ProtectedBranch.EnableStatusCheck { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// check if all required status checks are successful | ||||||
|  | 	headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, errors.Wrap(err, "OpenRepository") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !headGitRepo.IsBranchExist(pr.HeadBranch) { | ||||||
|  | 		return false, errors.New("Head branch does not exist, can not merge") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sha, err := headGitRepo.GetBranchCommitID(pr.HeadBranch) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, errors.Wrap(err, "GetBranchCommitID") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := pr.LoadBaseRepo(); err != nil { | ||||||
|  | 		return false, errors.Wrap(err, "LoadBaseRepo") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	commitStatuses, err := models.GetLatestCommitStatus(pr.BaseRepo, sha, 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, errors.Wrap(err, "GetLatestCommitStatus") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return IsCommitStatusContextSuccess(commitStatuses, pr.ProtectedBranch.StatusCheckContexts), nil | ||||||
|  | } | ||||||
| @@ -981,6 +981,8 @@ pulls.cannot_merge_work_in_progress = This pull request is marked as a work in p | |||||||
| pulls.data_broken = This pull request is broken due to missing fork information. | pulls.data_broken = This pull request is broken due to missing fork information. | ||||||
| pulls.files_conflicted = This pull request has changes conflicting with the target branch. | pulls.files_conflicted = This pull request has changes conflicting with the target branch. | ||||||
| pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." | pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." | ||||||
|  | pulls.required_status_check_failed = Some required checks were not successful. | ||||||
|  | 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.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. | ||||||
| @@ -988,6 +990,7 @@ pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. | |||||||
| pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. | pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. | ||||||
| pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. | pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. | ||||||
| pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. | pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. | ||||||
|  | pulls.no_merge_status_check = This pull request cannot be merged because not all required status checkes are successful. | ||||||
| pulls.merge_pull_request = Merge Pull Request | pulls.merge_pull_request = Merge Pull Request | ||||||
| pulls.rebase_merge_pull_request = Rebase and Merge | pulls.rebase_merge_pull_request = Rebase and Merge | ||||||
| pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff) | pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff) | ||||||
| @@ -1311,6 +1314,9 @@ settings.protect_merge_whitelist_committers = Enable Merge Whitelist | |||||||
| settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch. | settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch. | ||||||
| settings.protect_merge_whitelist_users = Whitelisted users for merging: | settings.protect_merge_whitelist_users = Whitelisted users for merging: | ||||||
| settings.protect_merge_whitelist_teams = Whitelisted teams for merging: | settings.protect_merge_whitelist_teams = Whitelisted teams for merging: | ||||||
|  | settings.protect_check_status_contexts = Enable Status Check | ||||||
|  | settings.protect_check_status_contexts_desc = Require status checks to pass before merging Choose which status checks must pass before branches can be merged into a branch that matches this rule. When enabled, commits must first be pushed to another branch, then merged or pushed directly to a branch that matches this rule after status checks have passed. | ||||||
|  | settings.protect_check_status_contexts_list = Status checks found in the last week for this repository | ||||||
| settings.protect_required_approvals = Required approvals: | settings.protect_required_approvals = Required approvals: | ||||||
| settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews of whitelisted users or teams. | settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews of whitelisted users or teams. | ||||||
| settings.protect_approvals_whitelist_users = Whitelisted reviewers: | settings.protect_approvals_whitelist_users = Whitelisted reviewers: | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/notification" | 	"code.gitea.io/gitea/modules/notification" | ||||||
| 	"code.gitea.io/gitea/modules/pull" | 	"code.gitea.io/gitea/modules/pull" | ||||||
|  | 	pull_service "code.gitea.io/gitea/modules/pull" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	milestone_service "code.gitea.io/gitea/services/milestone" | 	milestone_service "code.gitea.io/gitea/services/milestone" | ||||||
| @@ -571,6 +572,17 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	isPass, err := pull_service.IsPullCommitStatusPass(pr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "IsPullCommitStatusPass", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !isPass && !ctx.IsUserRepoAdmin() { | ||||||
|  | 		ctx.Status(405) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(form.Do) == 0 { | 	if len(form.Do) == 0 { | ||||||
| 		form.Do = string(models.MergeStyleMerge) | 		form.Do = string(models.MergeStyleMerge) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/notification" | 	"code.gitea.io/gitea/modules/notification" | ||||||
| 	"code.gitea.io/gitea/modules/pull" | 	"code.gitea.io/gitea/modules/pull" | ||||||
|  | 	pull_service "code.gitea.io/gitea/modules/pull" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/services/gitdiff" | 	"code.gitea.io/gitea/services/gitdiff" | ||||||
| @@ -322,6 +323,12 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare | |||||||
|  |  | ||||||
| 	setMergeTarget(ctx, pull) | 	setMergeTarget(ctx, pull) | ||||||
|  |  | ||||||
|  | 	if err = pull.LoadProtectedBranch(); err != nil { | ||||||
|  | 		ctx.ServerError("GetLatestCommitStatus", err) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck | ||||||
|  |  | ||||||
| 	var headGitRepo *git.Repository | 	var headGitRepo *git.Repository | ||||||
| 	var headBranchExist bool | 	var headBranchExist bool | ||||||
| 	// HeadRepo may be missing | 	// HeadRepo may be missing | ||||||
| @@ -350,6 +357,18 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare | |||||||
| 				ctx.Data["LatestCommitStatuses"] = commitStatuses | 				ctx.Data["LatestCommitStatuses"] = commitStatuses | ||||||
| 				ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) | 				ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck { | ||||||
|  | 				ctx.Data["is_context_required"] = func(context string) bool { | ||||||
|  | 					for _, c := range pull.ProtectedBranch.StatusCheckContexts { | ||||||
|  | 						if c == context { | ||||||
|  | 							return true | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					return false | ||||||
|  | 				} | ||||||
|  | 				ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -608,6 +627,17 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	isPass, err := pull_service.IsPullCommitStatusPass(pr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("IsPullCommitStatusPass", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !isPass && !ctx.IsUserRepoAdmin() { | ||||||
|  | 		ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_status_check")) | ||||||
|  | 		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if ctx.HasError() { | 	if ctx.HasError() { | ||||||
| 		ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) | 		ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) | ||||||
| 		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) | 		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ package repo | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/auth" | 	"code.gitea.io/gitea/modules/auth" | ||||||
| @@ -125,6 +126,29 @@ func SettingsProtectedBranch(c *context.Context) { | |||||||
| 	c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistUserIDs), ",") | 	c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistUserIDs), ",") | ||||||
| 	c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistUserIDs), ",") | 	c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistUserIDs), ",") | ||||||
| 	c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistUserIDs), ",") | 	c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistUserIDs), ",") | ||||||
|  | 	contexts, _ := models.FindRepoRecentCommitStatusContexts(c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts | ||||||
|  | 	for _, context := range protectBranch.StatusCheckContexts { | ||||||
|  | 		var found bool | ||||||
|  | 		for _, ctx := range contexts { | ||||||
|  | 			if ctx == context { | ||||||
|  | 				found = true | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !found { | ||||||
|  | 			contexts = append(contexts, context) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.Data["branch_status_check_contexts"] = contexts | ||||||
|  | 	c.Data["is_context_required"] = func(context string) bool { | ||||||
|  | 		for _, c := range protectBranch.StatusCheckContexts { | ||||||
|  | 			if c == context { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if c.Repo.Owner.IsOrganization() { | 	if c.Repo.Owner.IsOrganization() { | ||||||
| 		teams, err := c.Repo.Owner.TeamsWithAccessToRepo(c.Repo.Repository.ID, models.AccessModeRead) | 		teams, err := c.Repo.Owner.TeamsWithAccessToRepo(c.Repo.Repository.ID, models.AccessModeRead) | ||||||
| @@ -186,6 +210,10 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) | |||||||
| 		if strings.TrimSpace(f.MergeWhitelistTeams) != "" { | 		if strings.TrimSpace(f.MergeWhitelistTeams) != "" { | ||||||
| 			mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ",")) | 			mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ",")) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		protectBranch.EnableStatusCheck = f.EnableStatusCheck | ||||||
|  | 		protectBranch.StatusCheckContexts = f.StatusCheckContexts | ||||||
|  |  | ||||||
| 		protectBranch.RequiredApprovals = f.RequiredApprovals | 		protectBranch.RequiredApprovals = f.RequiredApprovals | ||||||
| 		if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" { | 		if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" { | ||||||
| 			approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ",")) | 			approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ",")) | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ | |||||||
| 	{{else if .IsFilesConflicted}}grey | 	{{else if .IsFilesConflicted}}grey | ||||||
| 	{{else if .IsPullRequestBroken}}red | 	{{else if .IsPullRequestBroken}}red | ||||||
| 	{{else if .IsBlockedByApprovals}}red | 	{{else if .IsBlockedByApprovals}}red | ||||||
|  | 	{{else if and .EnableStatusCheck (not .IsRequiredStatusCheckSuccess)}}red | ||||||
| 	{{else if .Issue.PullRequest.IsChecking}}yellow | 	{{else if .Issue.PullRequest.IsChecking}}yellow | ||||||
| 	{{else if .Issue.PullRequest.CanAutoMerge}}green | 	{{else if .Issue.PullRequest.CanAutoMerge}}green | ||||||
| 	{{else}}red{{end}}"><span class="mega-octicon octicon-git-merge"></span></a> | 	{{else}}red{{end}}"><span class="mega-octicon octicon-git-merge"></span></a> | ||||||
| @@ -104,11 +105,30 @@ | |||||||
| 					<span class="octicon octicon-sync"></span> | 					<span class="octicon octicon-sync"></span> | ||||||
| 					{{$.i18n.Tr "repo.pulls.is_checking"}} | 					{{$.i18n.Tr "repo.pulls.is_checking"}} | ||||||
| 				</div> | 				</div> | ||||||
|  | 			{{else if and (not .Issue.PullRequest.CanAutoMerge) .EnableStatusCheck (not .IsRequiredStatusCheckSuccess)}} | ||||||
|  | 				<div class="item text red"> | ||||||
|  | 					<span class="octicon octicon-x"></span> | ||||||
|  | 					{{$.i18n.Tr "repo.pulls.required_status_check_failed"}} | ||||||
|  | 				</div> | ||||||
| 			{{else if .Issue.PullRequest.CanAutoMerge}} | 			{{else if .Issue.PullRequest.CanAutoMerge}} | ||||||
|  | 				{{if and .EnableStatusCheck (not .IsRequiredStatusCheckSuccess)}} | ||||||
|  | 				<div class="item text red"> | ||||||
|  | 					<span class="octicon octicon-x"></span> | ||||||
|  | 					{{$.i18n.Tr "repo.pulls.required_status_check_failed"}} | ||||||
|  | 				</div> | ||||||
|  | 				{{end}} | ||||||
|  | 				{{if or $.IsRepoAdmin (not .EnableStatusCheck) .IsRequiredStatusCheckSuccess}} | ||||||
|  | 					{{if and $.IsRepoAdmin .EnableStatusCheck (not .IsRequiredStatusCheckSuccess)}} | ||||||
|  | 						<div class="item text yellow"> | ||||||
|  | 							<span class="octicon octicon-primitive-dot"></span> | ||||||
|  | 							{{$.i18n.Tr "repo.pulls.required_status_check_administrator"}} | ||||||
|  | 						</div> | ||||||
|  | 					{{else}} | ||||||
| 						<div class="item text green"> | 						<div class="item text green"> | ||||||
| 							<span class="octicon octicon-check"></span> | 							<span class="octicon octicon-check"></span> | ||||||
| 							{{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}} | 							{{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}} | ||||||
| 						</div> | 						</div> | ||||||
|  | 					{{end}} | ||||||
| 					{{if .AllowMerge}} | 					{{if .AllowMerge}} | ||||||
| 						{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}} | 						{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}} | ||||||
| 						{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}} | 						{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}} | ||||||
| @@ -230,6 +250,7 @@ | |||||||
| 							</div> | 							</div> | ||||||
| 						{{end}} | 						{{end}} | ||||||
| 					{{end}} | 					{{end}} | ||||||
|  | 				{{end}} | ||||||
| 			{{else}} | 			{{else}} | ||||||
| 				<div class="item text red"> | 				<div class="item text red"> | ||||||
| 					<span class="octicon octicon-x"></span> | 					<span class="octicon octicon-x"></span> | ||||||
|   | |||||||
| @@ -15,7 +15,12 @@ | |||||||
|         <div class="ui attached segment"> |         <div class="ui attached segment"> | ||||||
|             <span>{{template "repo/commit_status" .}}</span> |             <span>{{template "repo/commit_status" .}}</span> | ||||||
|             <span class="ui">{{.Context}} <span class="text grey">{{.Description}}</span></span> |             <span class="ui">{{.Context}} <span class="text grey">{{.Description}}</span></span> | ||||||
|             <div class="ui right">{{if .TargetURL}}<a href="{{.TargetURL}}">Details</a>{{end}}</div> |             <div class="ui right"> | ||||||
|  |                 {{if $.is_context_required}} | ||||||
|  |                     {{if (call $.is_context_required .Context)}}<div class="ui label">Required</div>{{end}}  | ||||||
|  |                 {{end}} | ||||||
|  |                 <span class="ui">{{if .TargetURL}}<a href="{{.TargetURL}}">Details</a>{{end}}</span> | ||||||
|  |             </div> | ||||||
|         </div> |         </div> | ||||||
|     {{end}} |     {{end}} | ||||||
| {{end}} | {{end}} | ||||||
| @@ -104,6 +104,38 @@ | |||||||
| 					{{end}} | 					{{end}} | ||||||
| 					</div> | 					</div> | ||||||
|  |  | ||||||
|  | 					<div class="field"> | ||||||
|  | 						<div class="ui checkbox"> | ||||||
|  | 							<input class="enable-statuscheck" name="enable_status_check" type="checkbox" data-target="#statuscheck_contexts_box" {{if .Branch.EnableStatusCheck}}checked{{end}}> | ||||||
|  | 							<label>{{.i18n.Tr "repo.settings.protect_check_status_contexts"}}</label> | ||||||
|  | 							<p class="help">{{.i18n.Tr "repo.settings.protect_check_status_contexts_desc"}}</p> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  |  | ||||||
|  | 					<div id="statuscheck_contexts_box" class="fields {{if not .Branch.EnableStatusCheck}}disabled{{end}}"> | ||||||
|  | 						<div class="field"> | ||||||
|  | 							<table class="ui celled table six column"> | ||||||
|  | 								<thead> | ||||||
|  | 									<tr><th> | ||||||
|  | 										{{.i18n.Tr "repo.settings.protect_check_status_contexts_list"}} | ||||||
|  | 									</th> | ||||||
|  | 									</tr> | ||||||
|  | 								</thead> | ||||||
|  | 								<tbody> | ||||||
|  | 								{{range $.branch_status_check_contexts}} | ||||||
|  | 									<tr><td> | ||||||
|  | 										<span class="ui checkbox"> | ||||||
|  | 											<input class="enable-whitelist" name="status_check_contexts" value="{{.}}" type="checkbox" {{if $.is_context_require}}{{if call $.is_context_required .}}checked{{end}}{{end}}> | ||||||
|  | 										</span> | ||||||
|  | 										{{.}} | ||||||
|  | 										{{if $.is_context_required}}{{if call $.is_context_required .}}<div class="ui label right">Required</div>{{end}}{{end}} | ||||||
|  | 									</td></tr> | ||||||
|  | 								{{end}} | ||||||
|  | 								</tbody> | ||||||
|  | 							</table> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  |  | ||||||
| 					<div class="field"> | 					<div class="field"> | ||||||
| 						<label for="required-approvals">{{.i18n.Tr "repo.settings.protect_required_approvals"}}</label> | 						<label for="required-approvals">{{.i18n.Tr "repo.settings.protect_required_approvals"}}</label> | ||||||
| 						<input name="required_approvals" id="required-approvals" type="number" value="{{.Branch.RequiredApprovals}}"> | 						<input name="required_approvals" id="required-approvals" type="number" value="{{.Branch.RequiredApprovals}}"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user