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) }