* 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 codestags/v1.21.12.1
| @@ -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 | ||||
| @@ -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 | ||||
| @@ -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,130 +105,150 @@ | |||||
| <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}} | ||||
| <div class="item text green"> | |||||
| <span class="octicon octicon-check"></span> | |||||
| {{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}} | |||||
| {{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> | </div> | ||||
| {{if .AllowMerge}} | |||||
| {{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}} | |||||
| {{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}} | |||||
| <div class="ui divider"></div> | |||||
| {{if $prUnit.PullRequestsConfig.AllowMerge}} | |||||
| <div class="ui form merge-fields" style="display: none"> | |||||
| <form action="{{.Link}}/merge" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <div class="field"> | |||||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}"></textarea> | |||||
| </div> | |||||
| <button class="ui green button" type="submit" name="do" value="merge"> | |||||
| {{$.i18n.Tr "repo.pulls.merge_pull_request"}} | |||||
| </button> | |||||
| <button class="ui button merge-cancel"> | |||||
| {{$.i18n.Tr "cancel"}} | |||||
| </button> | |||||
| </form> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowRebase}} | |||||
| <div class="ui form rebase-fields" style="display: none"> | |||||
| <form action="{{.Link}}/merge" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <button class="ui green button" type="submit" name="do" value="rebase"> | |||||
| {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}} | |||||
| </button> | |||||
| <button class="ui button merge-cancel"> | |||||
| {{$.i18n.Tr "cancel"}} | |||||
| </button> | |||||
| </form> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowRebaseMerge}} | |||||
| <div class="ui form rebase-merge-fields" style="display: none"> | |||||
| <form action="{{.Link}}/merge" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <div class="field"> | |||||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}"></textarea> | |||||
| </div> | |||||
| <button class="ui green button" type="submit" name="do" value="rebase-merge"> | |||||
| {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}} | |||||
| </button> | |||||
| <button class="ui button merge-cancel"> | |||||
| {{$.i18n.Tr "cancel"}} | |||||
| </button> | |||||
| </form> | |||||
| {{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> | </div> | ||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowSquash}} | |||||
| <div class="ui form squash-fields" style="display: none"> | |||||
| <form action="{{.Link}}/merge" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <div class="field"> | |||||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultSquashMessage}}"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}"></textarea> | |||||
| </div> | |||||
| <button class="ui green button" type="submit" name="do" value="squash"> | |||||
| {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}} | |||||
| </button> | |||||
| <button class="ui button merge-cancel"> | |||||
| {{$.i18n.Tr "cancel"}} | |||||
| </button> | |||||
| </form> | |||||
| {{else}} | |||||
| <div class="item text green"> | |||||
| <span class="octicon octicon-check"></span> | |||||
| {{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}} | |||||
| </div> | </div> | ||||
| {{end}} | |||||
| <div class="ui green buttons merge-button"> | |||||
| <button class="ui button" data-do="{{.MergeStyle}}"> | |||||
| <span class="octicon octicon-git-merge"></span> | |||||
| <span class="button-text"> | |||||
| {{if eq .MergeStyle "merge"}} | |||||
| {{$.i18n.Tr "repo.pulls.merge_pull_request"}} | |||||
| {{end}} | |||||
| {{if eq .MergeStyle "rebase"}} | |||||
| {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}} | |||||
| {{end}} | |||||
| {{if eq .MergeStyle "rebase-merge"}} | |||||
| {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}} | |||||
| {{end}} | |||||
| {{if eq .MergeStyle "squash"}} | |||||
| {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}} | |||||
| {{end}} | |||||
| </span> | |||||
| </button> | |||||
| <div class="ui dropdown icon button"> | |||||
| <i class="dropdown icon"></i> | |||||
| <div class="menu"> | |||||
| {{if $prUnit.PullRequestsConfig.AllowMerge}} | |||||
| <div class="item{{if eq .MergeStyle "merge"}} active selected{{end}}" data-do="merge">{{$.i18n.Tr "repo.pulls.merge_pull_request"}}</div> | |||||
| {{end}} | |||||
| {{if .AllowMerge}} | |||||
| {{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}} | |||||
| {{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}} | |||||
| <div class="ui divider"></div> | |||||
| {{if $prUnit.PullRequestsConfig.AllowMerge}} | |||||
| <div class="ui form merge-fields" style="display: none"> | |||||
| <form action="{{.Link}}/merge" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <div class="field"> | |||||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}"></textarea> | |||||
| </div> | |||||
| <button class="ui green button" type="submit" name="do" value="merge"> | |||||
| {{$.i18n.Tr "repo.pulls.merge_pull_request"}} | |||||
| </button> | |||||
| <button class="ui button merge-cancel"> | |||||
| {{$.i18n.Tr "cancel"}} | |||||
| </button> | |||||
| </form> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowRebase}} | |||||
| <div class="ui form rebase-fields" style="display: none"> | |||||
| <form action="{{.Link}}/merge" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <button class="ui green button" type="submit" name="do" value="rebase"> | |||||
| {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}} | |||||
| </button> | |||||
| <button class="ui button merge-cancel"> | |||||
| {{$.i18n.Tr "cancel"}} | |||||
| </button> | |||||
| </form> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowRebaseMerge}} | |||||
| <div class="ui form rebase-merge-fields" style="display: none"> | |||||
| <form action="{{.Link}}/merge" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <div class="field"> | |||||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}"></textarea> | |||||
| </div> | |||||
| <button class="ui green button" type="submit" name="do" value="rebase-merge"> | |||||
| {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}} | |||||
| </button> | |||||
| <button class="ui button merge-cancel"> | |||||
| {{$.i18n.Tr "cancel"}} | |||||
| </button> | |||||
| </form> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowSquash}} | |||||
| <div class="ui form squash-fields" style="display: none"> | |||||
| <form action="{{.Link}}/merge" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <div class="field"> | |||||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultSquashMessage}}"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}"></textarea> | |||||
| </div> | |||||
| <button class="ui green button" type="submit" name="do" value="squash"> | |||||
| {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}} | |||||
| </button> | |||||
| <button class="ui button merge-cancel"> | |||||
| {{$.i18n.Tr "cancel"}} | |||||
| </button> | |||||
| </form> | |||||
| </div> | |||||
| {{end}} | |||||
| <div class="ui green buttons merge-button"> | |||||
| <button class="ui button" data-do="{{.MergeStyle}}"> | |||||
| <span class="octicon octicon-git-merge"></span> | |||||
| <span class="button-text"> | |||||
| {{if eq .MergeStyle "merge"}} | |||||
| {{$.i18n.Tr "repo.pulls.merge_pull_request"}} | |||||
| {{end}} | {{end}} | ||||
| {{if $prUnit.PullRequestsConfig.AllowRebase}} | |||||
| <div class="item{{if eq .MergeStyle "rebase"}} active selected{{end}}" data-do="rebase">{{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}}</div> | |||||
| {{if eq .MergeStyle "rebase"}} | |||||
| {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}} | |||||
| {{end}} | {{end}} | ||||
| {{if $prUnit.PullRequestsConfig.AllowRebaseMerge}} | |||||
| <div class="item{{if eq .MergeStyle "rebase-merge"}} active selected{{end}}" data-do="rebase-merge">{{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}}</div> | |||||
| {{if eq .MergeStyle "rebase-merge"}} | |||||
| {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}} | |||||
| {{end}} | {{end}} | ||||
| {{if $prUnit.PullRequestsConfig.AllowSquash}} | |||||
| <div class="item{{if eq .MergeStyle "squash"}} active selected{{end}}" data-do="squash">{{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}}</div> | |||||
| {{if eq .MergeStyle "squash"}} | |||||
| {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}} | |||||
| {{end}} | {{end}} | ||||
| </span> | |||||
| </button> | |||||
| <div class="ui dropdown icon button"> | |||||
| <i class="dropdown icon"></i> | |||||
| <div class="menu"> | |||||
| {{if $prUnit.PullRequestsConfig.AllowMerge}} | |||||
| <div class="item{{if eq .MergeStyle "merge"}} active selected{{end}}" data-do="merge">{{$.i18n.Tr "repo.pulls.merge_pull_request"}}</div> | |||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowRebase}} | |||||
| <div class="item{{if eq .MergeStyle "rebase"}} active selected{{end}}" data-do="rebase">{{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}}</div> | |||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowRebaseMerge}} | |||||
| <div class="item{{if eq .MergeStyle "rebase-merge"}} active selected{{end}}" data-do="rebase-merge">{{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}}</div> | |||||
| {{end}} | |||||
| {{if $prUnit.PullRequestsConfig.AllowSquash}} | |||||
| <div class="item{{if eq .MergeStyle "squash"}} active selected{{end}}" data-do="squash">{{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}}</div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | |||||
| {{else}} | |||||
| <div class="item text red"> | |||||
| <span class="octicon octicon-x"></span> | |||||
| {{$.i18n.Tr "repo.pulls.no_merge_desc"}} | |||||
| </div> | |||||
| <div class="item text grey"> | |||||
| <span class="octicon octicon-info"></span> | |||||
| {{$.i18n.Tr "repo.pulls.no_merge_helper"}} | |||||
| </div> | |||||
| {{else}} | |||||
| <div class="item text red"> | |||||
| <span class="octicon octicon-x"></span> | |||||
| {{$.i18n.Tr "repo.pulls.no_merge_desc"}} | |||||
| </div> | |||||
| <div class="item text grey"> | |||||
| <span class="octicon octicon-info"></span> | |||||
| {{$.i18n.Tr "repo.pulls.no_merge_helper"}} | |||||
| </div> | |||||
| {{end}} | |||||
| {{end}} | {{end}} | ||||
| {{end}} | {{end}} | ||||
| {{else}} | {{else}} | ||||
| @@ -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}}"> | ||||