Glob syntax¶
The same glob syntax is used everywhere a pattern appears in GitHooks:
- Hook conditions:
only-on/exclude-on(branch patterns) andonly-files/exclude-files(file patterns) inside a hook entry. - Per-flow-entry admission:
only-files/exclude-filesnext to a job insideflows.<X>.jobs. - Branch-driven execution mode: the keys of the
flows.<X>.onmap (branch patterns). --exclude-patternCLI flag: filters paths in--files/--files-fromruns.- Job
pathsfiltering: not a glob (it is a directory prefix list), but the same conventions apply when you combine it with--files.
This page is the canonical reference for the syntax.
Operators¶
| Pattern | Matches | Notes |
|---|---|---|
* |
Any characters except / (file patterns) |
src/*.php matches src/User.php but not src/Models/User.php. |
* |
Any characters including / (branch patterns) |
release/* matches release/v2.0 and release/hotfix/x. Branch patterns are flat strings — / is just a character. |
** |
Zero or more directory levels (file patterns only) | src/**/*.php matches src/User.php, src/Models/User.php, src/A/B/C/User.php. |
? |
Exactly one character (not / in file patterns) |
file?.php matches file1.php but not file10.php. |
[abc] |
One character from the set | file[12].php matches file1.php and file2.php. |
[!abc] |
One character not in the set | file[!0-9].php matches fileA.php but not file1.php. |
{a,b,c} |
Alternation | **/*.{js,ts,vue} matches app/foo.js, app/bar.ts, app/baz.vue. |
File patterns vs branch patterns
In file patterns, * stops at / and ** is needed to cross directory levels — same semantics as .gitignore. In branch patterns, branch names are flat strings (Git allows / as a separator but it has no special meaning to globs), so * matches across slashes and ** has no extra meaning.
Common patterns¶
// All PHP files under src/ (any depth)
'src/**/*.php'
// Just the top level of src/
'src/*.php'
// Tests only
'tests/**' // shorthand for tests/**/*
'**/*Test.php' // by suffix anywhere
'tests/**/*Test.php' // restrict to tests/
// Front-end change set
'**/*.{js,ts,jsx,tsx,vue}'
'resources/js/**'
// Config / lockfiles
'composer.{json,lock}'
'**/.env*' // .env, .env.local, .env.testing
// Generated code (exclude)
'**/Generated/**'
'**/*.generated.php'
// Branch patterns
'master' // exact match
'main'
'release/v*' // release/v1, release/v2.5
'feature/*' // a single feature segment
'*' // catch-all (recommended last in `on`)
Composition and precedence¶
Inside the same hook or flow entry, only-* and exclude-* AND-compose:
The hook entry runs when at least one file matches only-files and that same file is not excluded by exclude-files. The match is per-file, then the rule decides admission for the whole entry.
When the same kind of rule appears at multiple levels (hook-level + flow-entry-level), each level evaluates independently and both must admit the work:
hooks.<event>.<ref>.only-files ← hook-level: does the ref run at all?
│ if yes:
flows.<X>.jobs[].only-files ← per-job: does this specific job run?
See Composition with hook-level rules.
Pattern matching order in flows.<X>.on¶
In a flows.<X>.on => [branch_pattern => attrs] map, the order of the keys is the priority order: first declared, first matched. There is no longest-match resolution — the user controls precedence by ordering the map.
'on' => [
'release/v*' => ['execution' => 'full'], // matches release/v1, release/v2.5
'release/*' => ['execution' => 'fast-branch'], // catches the rest of release/*
'*' => ['execution' => 'fast'], // everything else
],
conf:check emits a warning when no catch-all '*' is declared, so non-matching branches don't silently fall through.
Anti-patterns¶
only-files: ['src/']— a directory prefix, not a glob. It won't match files inside; use'src/**'instead.only-files: ['*.php']— top-level only. If you meant "all PHP", use'**/*.php'.only-files: ['src/**/file.php']—**only crosses directories when followed by/. This works in many implementations but prefer'src/**/*.php'or'**/file.php'for clarity.only-files: []— meaningless and rejected byconf:check. Usenullto cancel an inherited rule from.local.php.- Regular-expression syntax (
^src/.*\.php$,(?:foo|bar)) — not supported; use the operators in the table above. on => ['master' => …, 'master' => …]— PHP collapses duplicate map keys silently; only the last entry survives. Use a single entry per pattern.
Quick mental model¶
If you have used .gitignore, the file-pattern rules above are the same. The two notable differences:
- Branch patterns don't treat
/as special. - Alternation with
{a,b,c}is supported (some.gitignoreparsers don't).