Skip to content

Conversation

@jordikroon
Copy link

Resolve #2095

Add exclusion pattern support to allow users to exclude specific paths from triggering reloads. Patterns prefixed with '!' are now parsed as exclusions and stored separately. The globalWatcher checks if events match any exclusion patterns before processing them, preventing excluded paths from triggering reloads while still allowing them to be matched for filtering purposes.

  • Add isExclude field to track exclusion patterns
  • Parse '!' prefix during pattern initialization
  • Rename allowReload to matchesEvent for clarity on matching logic
  • Extract exclusion check into separate allowReload wrapper
  • Add isExcludedEvent method to globalWatcher for event filtering
  • Filter excluded events before debouncing in event listener

Add exclusion pattern support to allow users to exclude specific paths from
triggering reloads. Patterns prefixed with '!' are now parsed as exclusions and
stored separately. The globalWatcher checks if events match any exclusion
patterns before processing them, preventing excluded paths from triggering
reloads while still allowing them to be matched for filtering purposes.

- Add isExclude field to track exclusion patterns
- Parse '!' prefix during pattern initialization
- Rename allowReload to matchesEvent for clarity on matching logic
- Extract exclusion check into separate allowReload wrapper
- Add isExcludedEvent method to globalWatcher for event filtering
- Filter excluded events before debouncing in event listener
Add test case for worker watch configuration with exclude patterns.
Verifies that exclude patterns (prefixed with !) are correctly parsed
and stored alongside include patterns in the watch configuration.
@AlliBalliBaba
Copy link
Contributor

I wonder if it would make more sense to stick to the bash/zsh syntax and do exclusions via !(file1|file2). Something like:

watch "/app/!(public|vendor|.git)/**/*.php"

@jordikroon
Copy link
Author

In my honest opinion the (ext)glob syntax is the most superior but the drawback is could also be very complicated.
By only allowing the ! prefix, we make it more similar to how git/eslint ignore files work according to their specification(https://git-scm.com/docs/gitignore). Something that is more self explanatory and intuitive.

Curious to what @dunglas thinks, as he is the initial proposer of this feature.

@AlliBalliBaba
Copy link
Contributor

IMO it's hard to make a pattern matching implementation that's very fast. If speed isn't critical, you can probably just do something similar to matchCurlyBracePattern().

boilerplate (haven't tried):

// check for the following syntax: /path/!(vendor,storage)/
func matchWithExclusions(pattern string, fileName string) bool {
	inclusionAndExclusions := expandExclusions(pattern)
	inclusion := inclusionAndExclusions[0]
	exclusions := inclusionAndExclusions[1:]

	for _, exclusion := range exclusions {
		if matchPattern(exclusion, fileName) {
			return false
		}
	}

	return matchPattern(inclusion, fileName)
}

// !(dir1|dir2)/path -> []string{"*/path", "dir1/path", "dir2/path"}
func expandExclusions(s string) []string {
	before, rest, found := strings.Cut(s, "!(")
	if !found {
		return []string{s}
	}

	inside, after, found := strings.Cut(rest, ")")
	if !found {
		return []string{s} // no closing ')'
	}

	out := expandExclusions(before+"*"+after)
    for _, subPattern := range strings.Split(inside, "|") {
        out = append(out, expandExclusions(before+subPattern+after)...)
    }

	return out
}

@henderkes
Copy link
Contributor

Very silly idea... but couldn't we just offer a regex mode instead?

@jordikroon
Copy link
Author

I don't think a regex mode would be achievable without a BC break. Unless we add an extra config option exclude or something.

@henderkes
Copy link
Contributor

We could arbitrarily decide on a pattern like #regex# to use the regex mode and fall back to the regular glob pattern one otherwise. Or a regex_watch directive instead.

@AlliBalliBaba
Copy link
Contributor

Yeah we could also just allow something like this:

watch "regex:^/app/(?!storage/|vendor/)[^/]+(?:/.*)?\.php$"

Just would be nice to have a common enough syntax, so long-term the pattern matching could also be moved to the watcher dependency itself.

@jordikroon
Copy link
Author

I like the idea. I will wait a couple of days to give others the opportunity to give their comments then I will implement if nobody objects.

@withinboredom
Copy link
Member

Why not use a library? https://github.com/gobwas/glob?

@henderkes
Copy link
Contributor

I'm not opposed to it either, but I think the biggest issue with the current implementation of the globs plus the proposed exclusion pattern is standardization. With regexes everyone know their syntax (bar a few dialect specific details).

I don't know how fast or slow regexes are in Go, though. After profiling our C++ regexes once I nearly had a heart attack.

@dunglas
Copy link
Member

dunglas commented Jan 5, 2026

I would also prefer switching to a popular library like https://github.com/gobwas/glob.
We should also use the most "standard" glob syntax as possible (the one supported by the library sounds good enough).

Supporting regex could be nice, but it's a bit late, I would prefer having only one way to do the thing.

@dunglas
Copy link
Member

dunglas commented Jan 5, 2026

See also gobwas/glob#67

@henderkes
Copy link
Contributor

The library doesn't seem that widespread to me and saw its last commit a decade ago.

Supporting regex could be nice, but it's a bit late, I would prefer having only one way to do the thing.

It will only ever get later - I'm typically very happy with using existing libraries, but if they aren't actively maintained and don't support what we need, it's better to switch to a standard approach now, rather than later.

@dunglas
Copy link
Member

dunglas commented Jan 5, 2026

There is https://github.com/bmatcuk/doublestar, that looks popular and maintained.

@henderkes
Copy link
Contributor

Looks good, but doesn't support exclusions. :/

@henderkes
Copy link
Contributor

e-dant/watcher#103
Perhaps we can just wait for this or help upstream an (existing?) implementation. With it interfacing with C there's got to be a bash globbing library, which would be optimal for people to be familiar with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exclude directory from watching

5 participants