Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2433,7 +2433,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 <code>*</code>, events for all branches are reported. See <a href="%[1]s">%[2]s</a> documentation for syntax. Examples: <code>master</code>, <code>{master,release*}</code>.
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 <code>*</code>, events for all branches and tags are reported.
settings.branch_filter_desc_2 = Use <code>refs/heads/</code> or <code>refs/tags/</code> prefix to match full ref names.
settings.branch_filter_desc_doc = See <a href="%[1]s">%[2]s</a> 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
Expand Down
42 changes: 23 additions & 19 deletions services/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"net/http"
"strings"

"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
Expand Down Expand Up @@ -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 ""
}
Expand Down Expand Up @@ -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)
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.
Expand All @@ -144,11 +150,9 @@ 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)
// Check the payload's git ref against the webhook's branch filter, if any.
if ref := getPayloadRef(p); ref != "" {
if !checkBranchFilter(w.BranchFilter, ref) {
return nil
}
}
Expand Down
28 changes: 28 additions & 0 deletions services/webhook/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
}
11 changes: 10 additions & 1 deletion templates/repo/settings/webhook/settings.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,16 @@
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.branch_filter"}}</label>
<input name="branch_filter" type="text" value="{{or .Webhook.BranchFilter "*"}}">
<span class="help">{{ctx.Locale.Tr "repo.settings.branch_filter_desc" "https://pkg.go.dev/github.com/gobwas/glob#Compile" "github.com/gobwas/glob"}}</span>
<span class="help">
{{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"}}
<ul>
<li><code>main</code></li>
<li><code>{main,feature/*}</code></li>
<li><code>{refs/heads/feature/*,refs/tags/release/*}</code></li>
</ul>
</span>
</div>

<div class="field">
Expand Down
16 changes: 11 additions & 5 deletions web_src/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ samp,
font-size: 0.95em; /* compensate for monospace fonts being usually slightly larger */
}

code {
padding: 1px 4px;
border-radius: .24em;
background-color: var(--color-label-bg);
}

b,
strong,
h1,
Expand Down Expand Up @@ -177,6 +183,11 @@ table {
border-collapse: collapse;
}

ul {
margin: 0.5em 0;
padding: 0 1.5em;
}

button {
cursor: pointer;
}
Expand Down Expand Up @@ -603,11 +614,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;
Expand Down
1 change: 1 addition & 0 deletions web_src/css/form.css
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ textarea:focus,

.form .help {
color: var(--color-secondary-dark-5);
margin-top: 0.25em;
padding-bottom: 0.6em;
display: inline-block;
text-wrap: balance;
Expand Down
7 changes: 0 additions & 7 deletions web_src/css/repo/issue-list.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}