match.go
1package manifest
2
3import (
4 "slices"
5 "strings"
6
7 "github.com/bmatcuk/doublestar/v4"
8)
9
10// Event is what a trigger is matched against.
11type Event struct {
12 Kind string // push | manual
13 Ref string // full ref, e.g. refs/heads/main (push)
14 ChangedFiles []string // push; nil means unknown, which never fails a paths filter
15}
16
17// Matches reports whether the workflow should run for ev, with upstream's
18// semantics: manual dispatch always matches (the caller picked the
19// workflow); a workflow with no conditions always runs; otherwise any one
20// condition must match its event, ref and paths.
21func (m *Manifest) Matches(ev Event) bool {
22 if ev.Kind == "manual" {
23 return true
24 }
25 if len(m.When) == 0 {
26 return true
27 }
28 for _, c := range m.When {
29 if !slices.Contains(c.Event, ev.Kind) {
30 continue
31 }
32 if ev.Kind == "push" && !matchRef(ev.Ref, c.Branch, c.Tag) {
33 continue
34 }
35 if len(c.Paths) > 0 && ev.ChangedFiles != nil && !anyFileMatches(ev.ChangedFiles, c.Paths) {
36 continue
37 }
38 return true
39 }
40 return false
41}
42
43func matchRef(ref string, branches, tags []string) bool {
44 if b, ok := strings.CutPrefix(ref, "refs/heads/"); ok {
45 return anyMatch(b, branches)
46 }
47 if t, ok := strings.CutPrefix(ref, "refs/tags/"); ok {
48 return anyMatch(t, tags)
49 }
50 return false
51}
52
53func anyMatch(name string, patterns []string) bool {
54 for _, p := range patterns {
55 if ok, _ := doublestar.Match(p, name); ok {
56 return true
57 }
58 }
59 return false
60}
61
62func anyFileMatches(files, patterns []string) bool {
63 for _, f := range files {
64 if anyMatch(f, patterns) {
65 return true
66 }
67 }
68 return false
69}
70
71func validGlob(p string) bool { return p != "" && doublestar.ValidatePattern(p) }