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 <me@silverwind.io>tags/v1.20.0-rc0
@@ -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 |
@@ -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": { |
@@ -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", |
@@ -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 | |||
} | |||
} |
@@ -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 | |||
} |
@@ -199,7 +199,7 @@ type ProtectBranchForm struct { | |||
MergeWhitelistUsers string | |||
MergeWhitelistTeams string | |||
EnableStatusCheck bool | |||
StatusCheckContexts []string | |||
StatusCheckContexts string | |||
RequiredApprovals int64 | |||
EnableApprovalsWhitelist bool | |||
ApprovalsWhitelistUsers string |
@@ -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 | |||
} | |||
@@ -157,6 +157,9 @@ | |||
</div> | |||
</div> | |||
<div id="statuscheck_contexts_box" class="checkbox-sub-item field {{if not .Rule.EnableStatusCheck}}disabled{{end}}"> | |||
<label>{{.locale.Tr "repo.settings.protect_status_check_patterns"}}</label> | |||
<textarea id="status_check_contexts" name="status_check_contexts" rows="3">{{.status_check_contexts}}</textarea> | |||
<p class="help">{{.locale.Tr "repo.settings.protect_status_check_patterns_desc"}}</p> | |||
<table class="ui celled table"> | |||
<thead> | |||
<tr> | |||
@@ -164,13 +167,11 @@ | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{{range $.branch_status_check_contexts}} | |||
{{range $.recent_status_checks}} | |||
<tr> | |||
<td> | |||
<span class="ui checkbox"> | |||
<label>{{.}}</label> | |||
<input name="status_check_contexts" value="{{.}}" type="checkbox" {{if SliceUtils.Contains $.Rule.StatusCheckContexts .}}checked{{end}}> | |||
</span> | |||
<span>{{.}}</span> | |||
<span class="status-check-matched-mark gt-hidden" data-status-check="{{.}}">{{$.locale.Tr "repo.settings.protect_status_check_matched"}}</span> | |||
</td> | |||
</tr> | |||
{{else}} |
@@ -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; | |||
} |
@@ -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)); | |||
} |