From e7c2231dee356df5cbe5a47c07e31e3a8d090a6f Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Wed, 17 May 2023 16:11:13 +0800 Subject: [PATCH] Support for status check pattern (#24633) This PR is to allow users to specify status checks by patterns. Users can enter patterns in the "Status Check Pattern" `textarea` to match status checks and each line specifies a pattern. If "Status Check" is enabled, patterns cannot be empty and user must enter at least one pattern. Users will no longer be able to choose status checks from the table. But a __*`Matched`*__ mark will be added to the matched checks to help users enter patterns. Benefits: - Even if no status checks have been completed, users can specify necessary status checks in advance. - More flexible. Users can specify a series of status checks by one pattern. Before: ![image](https://github.com/go-gitea/gitea/assets/15528715/635738ad-580c-49cd-941d-c721e5b99be4) After: ![image](https://github.com/go-gitea/gitea/assets/15528715/16aa7b1b-abf1-4170-9bfa-ae6fc9803a82) --------- Co-authored-by: silverwind --- options/locale/locale_en-US.ini | 7 +- package-lock.json | 197 +++++++++++++++--- package.json | 1 + routers/web/repo/pull.go | 4 +- routers/web/repo/setting_protected_branch.go | 40 ++-- services/forms/repo_form.go | 2 +- services/pull/commit_status.go | 54 +++-- templates/repo/settings/protected_branch.tmpl | 11 +- web_src/css/repo.css | 5 + web_src/js/features/repo-settings.js | 24 +++ 10 files changed, 267 insertions(+), 78 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 906a32dc2d73..2167471aace5 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2190,8 +2190,13 @@ settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users settings.protect_merge_whitelist_users = Whitelisted users 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. If no contexts are selected, the last commit must be successful regardless of context. +settings.protect_status_check_patterns = Status check patterns: +settings.protect_status_check_patterns_desc = Enter patterns to specify which status checks must pass before branches can be merged into a branch that matches this rule. Each line specifies a pattern. Patterns cannot be empty. +settings.protect_check_status_contexts_desc = Require status checks to pass before merging. 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. If no contexts are matched, the last commit must be successful regardless of context. settings.protect_check_status_contexts_list = Status checks found in the last week for this repository +settings.protect_status_check_matched = Matched +settings.protect_invalid_status_check_pattern = Invalid status check pattern: "%s". +settings.protect_no_valid_status_check_patterns = No valid status check patterns. settings.protect_required_approvals = Required approvals: settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews. settings.protect_approvals_whitelist_enabled = Restrict approvals to whitelisted users or teams diff --git a/package-lock.json b/package-lock.json index d00dda5cd7e7..7f30fe5ebc7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "license-checker-webpack-plugin": "0.2.1", "mermaid": "10.1.0", "mini-css-extract-plugin": "2.7.5", + "minimatch": "9.0.0", "monaco-editor": "0.38.0", "monaco-editor-webpack-plugin": "7.0.1", "pretty-ms": "8.0.0", @@ -855,12 +856,34 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { "version": "8.40.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", @@ -907,6 +930,28 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1480,6 +1525,28 @@ "node": "^12.20 || >=14.13" } }, + "node_modules/@stoplight/spectral-core/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@stoplight/spectral-core/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@stoplight/spectral-formats": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@stoplight/spectral-formats/-/spectral-formats-1.5.0.tgz", @@ -2668,12 +2735,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -4574,6 +4640,16 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -4595,6 +4671,18 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -4767,12 +4855,34 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.5.2", "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", @@ -5273,6 +5383,26 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -6396,6 +6526,26 @@ "webpack": "^4.4.0 || ^5.4.0" } }, + "node_modules/license-checker-webpack-plugin/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/license-checker-webpack-plugin/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/license-checker-webpack-plugin/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -6645,15 +6795,6 @@ "node": ">=14" } }, - "node_modules/markdownlint-cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/markdownlint-cli/node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -6691,21 +6832,6 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, - "node_modules/markdownlint-cli/node_modules/minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/markdownlint-micromark": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.2.tgz", @@ -6939,14 +7065,17 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { diff --git a/package.json b/package.json index cf3f5af99abe..6ec0d755dcb1 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "license-checker-webpack-plugin": "0.2.1", "mermaid": "10.1.0", "mini-css-extract-plugin": "2.7.5", + "minimatch": "9.0.0", "monaco-editor": "0.38.0", "monaco-editor-webpack-plugin": "7.0.1", "pretty-ms": "8.0.0", diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 070fc109dcec..8821e74c951e 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -45,6 +45,8 @@ import ( "code.gitea.io/gitea/services/gitdiff" pull_service "code.gitea.io/gitea/services/pull" repo_service "code.gitea.io/gitea/services/repository" + + "github.com/gobwas/glob" ) const ( @@ -575,7 +577,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C if pb != nil && pb.EnableStatusCheck { ctx.Data["is_context_required"] = func(context string) bool { for _, c := range pb.StatusCheckContexts { - if c == context { + if gp, err := glob.Compile(c); err == nil && gp.Match(context) { return true } } diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index 932bd373d7ae..1a944799c23f 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -6,6 +6,7 @@ package repo import ( "fmt" "net/http" + "net/url" "strings" "time" @@ -23,6 +24,8 @@ import ( "code.gitea.io/gitea/services/forms" pull_service "code.gitea.io/gitea/services/pull" "code.gitea.io/gitea/services/repository" + + "github.com/gobwas/glob" ) const ( @@ -115,21 +118,10 @@ func SettingsProtectedBranch(c *context.Context) { c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",") c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",") c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",") + c.Data["status_check_contexts"] = strings.Join(rule.StatusCheckContexts, "\n") contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c, c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts - for _, ctx := range rule.StatusCheckContexts { - var found bool - for i := range contexts { - if contexts[i] == ctx { - found = true - break - } - } - if !found { - contexts = append(contexts, ctx) - } - } + c.Data["recent_status_checks"] = contexts - c.Data["branch_status_check_contexts"] = contexts if c.Repo.Owner.IsOrganization() { teams, err := organization.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c.Repo.Repository.ID, perm.AccessModeRead) if err != nil { @@ -237,7 +229,27 @@ func SettingsProtectedBranchPost(ctx *context.Context) { protectBranch.EnableStatusCheck = f.EnableStatusCheck if f.EnableStatusCheck { - protectBranch.StatusCheckContexts = f.StatusCheckContexts + patterns := strings.Split(strings.ReplaceAll(f.StatusCheckContexts, "\r", "\n"), "\n") + validPatterns := make([]string, 0, len(patterns)) + for _, pattern := range patterns { + trimmed := strings.TrimSpace(pattern) + if trimmed == "" { + continue + } + if _, err := glob.Compile(trimmed); err != nil { + ctx.Flash.Error(ctx.Tr("repo.settings.protect_invalid_status_check_pattern", pattern)) + ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, url.QueryEscape(protectBranch.RuleName))) + return + } + validPatterns = append(validPatterns, trimmed) + } + if len(validPatterns) == 0 { + // if status check is enabled, patterns slice is not allowed to be empty + ctx.Flash.Error(ctx.Tr("repo.settings.protect_no_valid_status_check_patterns")) + ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, url.QueryEscape(protectBranch.RuleName))) + return + } + protectBranch.StatusCheckContexts = validPatterns } else { protectBranch.StatusCheckContexts = nil } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 41d7dc7d2b0a..d705ecad3f7d 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -199,7 +199,7 @@ type ProtectBranchForm struct { MergeWhitelistUsers string MergeWhitelistTeams string EnableStatusCheck bool - StatusCheckContexts []string + StatusCheckContexts string RequiredApprovals int64 EnableApprovalsWhitelist bool ApprovalsWhitelistUsers string diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index bfdb3f7291b8..51ba06da2758 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -11,43 +11,53 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" + "github.com/gobwas/glob" "github.com/pkg/errors" ) // MergeRequiredContextsCommitStatus returns a commit status state for given required contexts func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) structs.CommitStatusState { - if len(requiredContexts) == 0 { - status := git_model.CalcCommitStatus(commitStatuses) - if status != nil { - return status.State + // matchedCount is the number of `CommitStatus.Context` that match any context of `requiredContexts` + matchedCount := 0 + returnedStatus := structs.CommitStatusSuccess + + if len(requiredContexts) > 0 { + requiredContextsGlob := make(map[string]glob.Glob, len(requiredContexts)) + for _, ctx := range requiredContexts { + if gp, err := glob.Compile(ctx); err != nil { + log.Error("glob.Compile %s failed. Error: %v", ctx, err) + } else { + requiredContextsGlob[ctx] = gp + } } - return structs.CommitStatusSuccess - } - returnedStatus := structs.CommitStatusSuccess - for _, ctx := range requiredContexts { - var targetStatus structs.CommitStatusState for _, commitStatus := range commitStatuses { - if commitStatus.Context == ctx { - targetStatus = commitStatus.State - break + var targetStatus structs.CommitStatusState + for _, gp := range requiredContextsGlob { + if gp.Match(commitStatus.Context) { + targetStatus = commitStatus.State + matchedCount++ + break + } } - } - if targetStatus == "" { - targetStatus = structs.CommitStatusPending - commitStatuses = append(commitStatuses, &git_model.CommitStatus{ - State: targetStatus, - Context: ctx, - Description: "Pending", - }) + if targetStatus != "" && targetStatus.NoBetterThan(returnedStatus) { + returnedStatus = targetStatus + } } - if targetStatus.NoBetterThan(returnedStatus) { - returnedStatus = targetStatus + } + + if matchedCount == 0 { + status := git_model.CalcCommitStatus(commitStatuses) + if status != nil { + return status.State } + return structs.CommitStatusSuccess } + return returnedStatus } diff --git a/templates/repo/settings/protected_branch.tmpl b/templates/repo/settings/protected_branch.tmpl index 494cadfcc1be..cad98f8a7f69 100644 --- a/templates/repo/settings/protected_branch.tmpl +++ b/templates/repo/settings/protected_branch.tmpl @@ -157,6 +157,9 @@
+ + +

{{.locale.Tr "repo.settings.protect_status_check_patterns_desc"}}

@@ -164,13 +167,11 @@ - {{range $.branch_status_check_contexts}} + {{range $.recent_status_checks}} {{else}} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index eafe022cee66..73096a643465 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1990,6 +1990,11 @@ padding-left: 26px; } +.repository.settings.branches .branch-protection .status-check-matched-mark { + font-weight: var(--font-weight-bold); + font-style: italic; +} + .repository.settings.webhook .events .column { padding-bottom: 0; } diff --git a/web_src/js/features/repo-settings.js b/web_src/js/features/repo-settings.js index 7105a14b3026..8cc016fdc27e 100644 --- a/web_src/js/features/repo-settings.js +++ b/web_src/js/features/repo-settings.js @@ -1,5 +1,7 @@ import $ from 'jquery'; +import {minimatch} from 'minimatch'; import {createMonaco} from './codeeditor.js'; +import {onInputDebounce, toggleElem} from '../utils/dom.js'; const {appSubUrl, csrfToken} = window.config; @@ -81,4 +83,26 @@ export function initRepoSettingBranches() { const $target = $($(this).attr('data-target')); if (this.checked) $target.addClass('disabled'); // only disable, do not auto enable }); + + // show the `Matched` mark for the status checks that match the pattern + const markMatchedStatusChecks = () => { + const patterns = (document.getElementById('status_check_contexts').value || '').split(/[\r\n]+/); + const validPatterns = patterns.map((item) => item.trim()).filter(Boolean); + const marks = document.getElementsByClassName('status-check-matched-mark'); + + for (const el of marks) { + let matched = false; + const statusCheck = el.getAttribute('data-status-check'); + for (const pattern of validPatterns) { + if (minimatch(statusCheck, pattern)) { + matched = true; + break; + } + } + + toggleElem(el, matched); + } + }; + markMatchedStatusChecks(); + document.getElementById('status_check_contexts').addEventListener('input', onInputDebounce(markMatchedStatusChecks)); }
- - - - + {{.}} + {{$.locale.Tr "repo.settings.protect_status_check_matched"}}