diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 64bc3b1871..03017ce674 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2434,7 +2434,9 @@ settings.event_workflow_job_desc = Gitea Actions Workflow job queued, waiting, i settings.event_package = Package settings.event_package_desc = Package created or deleted in a repository. settings.branch_filter = Branch filter -settings.branch_filter_desc = Branch whitelist for push, branch creation and branch deletion events, specified as glob pattern. If empty or *, events for all branches are reported. See %[2]s documentation for syntax. Examples: master, {master,release*}. +settings.branch_filter_desc_1 = Branch (and ref name) allowlist for push, branch creation and branch deletion events, specified as glob pattern. If empty or *, events for all branches and tags are reported. +settings.branch_filter_desc_2 = Use refs/heads/ or refs/tags/ prefix to match full ref names. +settings.branch_filter_desc_doc = See %[2]s documentation for syntax. settings.authorization_header = Authorization Header settings.authorization_header_desc = Will be included as authorization header for requests when present. Examples: %s. settings.active = Active diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go index 2b276207a1..a35997dda0 100644 --- a/services/webhook/webhook.go +++ b/services/webhook/webhook.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "net/http" - "strings" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" @@ -46,21 +45,25 @@ func IsValidHookTaskType(name string) bool { // hookQueue is a global queue of web hooks var hookQueue *queue.WorkerPoolQueue[int64] -// getPayloadBranch returns branch for hook event, if applicable. -func getPayloadBranch(p api.Payloader) string { +// getPayloadRef returns the full ref name for hook event, if applicable. +func getPayloadRef(p api.Payloader) git.RefName { switch pp := p.(type) { case *api.CreatePayload: - if pp.RefType == "branch" { - return pp.Ref + switch pp.RefType { + case "branch": + return git.RefNameFromBranch(pp.Ref) + case "tag": + return git.RefNameFromTag(pp.Ref) } case *api.DeletePayload: - if pp.RefType == "branch" { - return pp.Ref + switch pp.RefType { + case "branch": + return git.RefNameFromBranch(pp.Ref) + case "tag": + return git.RefNameFromTag(pp.Ref) } case *api.PushPayload: - if strings.HasPrefix(pp.Ref, git.BranchPrefix) { - return pp.Ref[len(git.BranchPrefix):] - } + return git.RefName(pp.Ref) } return "" } @@ -108,19 +111,22 @@ func enqueueHookTask(taskID int64) error { return nil } -func checkBranch(w *webhook_model.Webhook, branch string) bool { - if w.BranchFilter == "" || w.BranchFilter == "*" { +func checkBranchFilter(branchFilter string, ref git.RefName) bool { + if branchFilter == "" || branchFilter == "*" || branchFilter == "**" { return true } - g, err := glob.Compile(w.BranchFilter) + g, err := glob.Compile(branchFilter) if err != nil { // should not really happen as BranchFilter is validated - log.Error("CheckBranch failed: %s", err) + log.Debug("checkBranchFilter failed to compile filer %q, err: %s", branchFilter, err) return false } - return g.Match(branch) + if ref.IsBranch() && g.Match(ref.BranchName()) { + return true + } + return g.Match(ref.String()) } // PrepareWebhook creates a hook task and enqueues it for processing. @@ -144,11 +150,10 @@ func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook return nil } - // If payload has no associated branch (e.g. it's a new tag, issue, etc.), - // branch filter has no effect. - if branch := getPayloadBranch(p); branch != "" { - if !checkBranch(w, branch) { - log.Info("Branch %q doesn't match branch filter %q, skipping", branch, w.BranchFilter) + // If payload has no associated branch (e.g. it's a new tag, issue, etc.), branch filter has no effect. + if ref := getPayloadRef(p); ref != "" { + // Check the payload's git ref against the webhook's branch filter. + if !checkBranchFilter(w.BranchFilter, ref) { return nil } } diff --git a/services/webhook/webhook_test.go b/services/webhook/webhook_test.go index 34bcfd3df9..f4432cc3f1 100644 --- a/services/webhook/webhook_test.go +++ b/services/webhook/webhook_test.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -90,3 +91,30 @@ func TestWebhookUserMail(t *testing.T) { assert.Equal(t, user.GetPlaceholderEmail(), convert.ToUser(t.Context(), user, nil).Email) assert.Equal(t, user.Email, convert.ToUser(t.Context(), user, user).Email) } + +func TestCheckBranchFilter(t *testing.T) { + cases := []struct { + filter string + ref git.RefName + match bool + }{ + {"", "any-ref", true}, + {"*", "any-ref", true}, + {"**", "any-ref", true}, + + {"main", git.RefNameFromBranch("main"), true}, + {"main", git.RefNameFromTag("main"), false}, + + {"feature/*", git.RefNameFromBranch("feature"), false}, + {"feature/*", git.RefNameFromBranch("feature/foo"), true}, + {"feature/*", git.RefNameFromTag("feature/foo"), false}, + + {"{refs/heads/feature/*,refs/tags/release/*}", git.RefNameFromBranch("feature/foo"), true}, + {"{refs/heads/feature/*,refs/tags/release/*}", git.RefNameFromBranch("main"), false}, + {"{refs/heads/feature/*,refs/tags/release/*}", git.RefNameFromTag("release/bar"), true}, + {"{refs/heads/feature/*,refs/tags/release/*}", git.RefNameFromTag("dev"), false}, + } + for _, v := range cases { + assert.Equal(t, v.match, checkBranchFilter(v.filter, v.ref), "filter: %q ref: %q", v.filter, v.ref) + } +} diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl index a8ad1d6c9e..1f71b9cfab 100644 --- a/templates/repo/settings/webhook/settings.tmpl +++ b/templates/repo/settings/webhook/settings.tmpl @@ -44,7 +44,16 @@
- {{ctx.Locale.Tr "repo.settings.branch_filter_desc" "https://pkg.go.dev/github.com/gobwas/glob#Compile" "github.com/gobwas/glob"}} + + {{ctx.Locale.Tr "repo.settings.branch_filter_desc_1"}} + {{ctx.Locale.Tr "repo.settings.branch_filter_desc_2"}} + {{ctx.Locale.Tr "repo.settings.branch_filter_desc_doc" "https://pkg.go.dev/github.com/gobwas/glob#Compile" "github.com/gobwas/glob"}} + +
diff --git a/web_src/css/base.css b/web_src/css/base.css index 6ff060f1f9..9cef92019d 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -101,6 +101,13 @@ samp, font-size: 0.95em; /* compensate for monospace fonts being usually slightly larger */ } +code { + padding: 1px 4px; + border-radius: var(--border-radius); + background-color: var(--color-label-bg); + color: var(--color-label-text); +} + b, strong, h1, @@ -177,6 +184,11 @@ table { border-collapse: collapse; } +ul { + margin: 0.5em 0; + padding: 0 0 0 1.5em; +} + button { cursor: pointer; } @@ -603,11 +615,6 @@ img.ui.avatar, text-align: center; } -.ui .message > ul { - margin: 0; - padding: 0 1em; -} - .ui .header > i + .content { padding-left: 0.75rem; vertical-align: middle; diff --git a/web_src/css/form.css b/web_src/css/form.css index c51eba1bc9..757edf7297 100644 --- a/web_src/css/form.css +++ b/web_src/css/form.css @@ -218,11 +218,16 @@ textarea:focus, .form .help { color: var(--color-secondary-dark-5); + margin-top: 0.25em; padding-bottom: 0.6em; display: inline-block; text-wrap: balance; } +.form .help code { + color: var(--color-text-light-1); +} + .m-captcha-style { width: 100%; height: 5em; diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css index bf8ff00b7e..33888101b6 100644 --- a/web_src/css/repo/issue-list.css +++ b/web_src/css/repo/issue-list.css @@ -84,10 +84,3 @@ margin-right: 8px; text-align: left; } - -.label-filter-exclude-info code { - border: 1px solid var(--color-secondary); - border-radius: var(--border-radius); - padding: 1px 2px; - font-size: 11px; -}