mirror of
https://github.com/docker/compose.git
synced 2026-05-13 13:58:02 +00:00
Implement PathMatcher ignores in Darwin FSEvents watcher
- Store PathMatcher on fseventNotify and filter events with shouldIgnore and shouldNotify, matching the naive watcher Signed-off-by: ManManavadaria <manmanavadaria@gmail.com>
This commit is contained in:
parent
62a24caecd
commit
cf46c2c020
2 changed files with 148 additions and 2 deletions
|
|
@ -26,6 +26,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/fsnotify/fsevents"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
pathutil "github.com/docker/compose/v5/internal/paths"
|
||||
)
|
||||
|
|
@ -39,6 +40,7 @@ type fseventNotify struct {
|
|||
stop chan struct{}
|
||||
|
||||
pathsWereWatching map[string]any
|
||||
ignore PathMatcher
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +64,10 @@ func (d *fseventNotify) loop() {
|
|||
continue
|
||||
}
|
||||
|
||||
if !d.shouldNotify(e.Path) {
|
||||
continue
|
||||
}
|
||||
|
||||
d.events <- NewFileEvent(e.Path)
|
||||
}
|
||||
}
|
||||
|
|
@ -115,7 +121,38 @@ func (d *fseventNotify) Errors() chan error {
|
|||
return d.errors
|
||||
}
|
||||
|
||||
func newWatcher(paths []string, _ ...PathMatcher) (Notify, error) {
|
||||
func (d *fseventNotify) shouldNotify(path string) bool {
|
||||
if d.shouldIgnore(path) {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := d.pathsWereWatching[path]; ok {
|
||||
stat, err := os.Lstat(path)
|
||||
isDir := err == nil && stat.IsDir()
|
||||
return !isDir
|
||||
}
|
||||
|
||||
for root := range d.pathsWereWatching {
|
||||
if pathutil.IsChild(root, path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *fseventNotify) shouldIgnore(path string) bool {
|
||||
if d.ignore == nil {
|
||||
return false
|
||||
}
|
||||
matches, err := d.ignore.Matches(path)
|
||||
if err != nil {
|
||||
logrus.Debugf("error checking ignored path %q: %v", path, err)
|
||||
return false
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
func newWatcher(paths []string, ignore PathMatcher) (Notify, error) {
|
||||
dw := &fseventNotify{
|
||||
stream: &fsevents.EventStream{
|
||||
Latency: 50 * time.Millisecond,
|
||||
|
|
@ -127,6 +164,7 @@ func newWatcher(paths []string, _ ...PathMatcher) (Notify, error) {
|
|||
events: make(chan FileEvent),
|
||||
errors: make(chan error),
|
||||
stop: make(chan struct{}),
|
||||
ignore: ignore,
|
||||
}
|
||||
|
||||
paths = pathutil.EncompassingPaths(paths)
|
||||
|
|
|
|||
|
|
@ -19,15 +19,24 @@
|
|||
package watch
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func newFseventNotifyFixture(repo string, ignore PathMatcher) *fseventNotify {
|
||||
return &fseventNotify{
|
||||
pathsWereWatching: map[string]any{repo: struct{}{}},
|
||||
ignore: ignore,
|
||||
}
|
||||
}
|
||||
|
||||
func TestFseventNotifyCloseIdempotent(t *testing.T) {
|
||||
// Create a watcher with a temporary directory
|
||||
tmpDir := t.TempDir()
|
||||
watcher, err := newWatcher([]string{tmpDir})
|
||||
watcher, err := newWatcher([]string{tmpDir}, nil)
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Start the watcher
|
||||
|
|
@ -46,3 +55,102 @@ func TestFseventNotifyCloseIdempotent(t *testing.T) {
|
|||
err = watcher.Close()
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestFseventNotifyShouldNotifyNilIgnore(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
child := filepath.Join(repo, "child.txt")
|
||||
assert.NilError(t, os.WriteFile(child, []byte("x"), 0o644))
|
||||
|
||||
d := newFseventNotifyFixture(repo, nil)
|
||||
assert.Assert(t, d.shouldNotify(child), "expected file under watched root to notify")
|
||||
assert.Assert(t, !d.shouldNotify(repo), "expected directory event at watched root to be skipped")
|
||||
}
|
||||
|
||||
func TestFseventNotifyShouldNotifyWatchedFileRoot(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
fileRoot := filepath.Join(repo, "watched.go")
|
||||
assert.NilError(t, os.WriteFile(fileRoot, []byte("package main\n"), 0o644))
|
||||
|
||||
d := newFseventNotifyFixture(fileRoot, nil)
|
||||
assert.Assert(t, d.shouldNotify(fileRoot), "expected file that is the watch root to notify")
|
||||
}
|
||||
|
||||
func TestFseventNotifyShouldNotifyOutsideWatchedTree(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
other := t.TempDir()
|
||||
|
||||
d := newFseventNotifyFixture(repo, nil)
|
||||
outPath := filepath.Join(other, "outside.txt")
|
||||
assert.NilError(t, os.WriteFile(outPath, []byte("x"), 0o644))
|
||||
assert.Assert(t, !d.shouldNotify(outPath), "expected path outside watch roots not to notify")
|
||||
}
|
||||
|
||||
func TestFseventNotifyShouldNotifyRespectsDockerignore(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
ignore, err := DockerIgnoreTesterFromContents(repo, "vendor/\n")
|
||||
assert.NilError(t, err)
|
||||
|
||||
d := newFseventNotifyFixture(repo, ignore)
|
||||
kept := filepath.Join(repo, "src", "main.go")
|
||||
assert.NilError(t, os.MkdirAll(filepath.Dir(kept), 0o755))
|
||||
assert.NilError(t, os.WriteFile(kept, []byte("x"), 0o644))
|
||||
assert.Assert(t, d.shouldNotify(kept), "expected non-ignored path to notify")
|
||||
|
||||
ignored := filepath.Join(repo, "vendor", "mod", "x.go")
|
||||
assert.NilError(t, os.MkdirAll(filepath.Dir(ignored), 0o755))
|
||||
assert.NilError(t, os.WriteFile(ignored, []byte("x"), 0o644))
|
||||
assert.Assert(t, !d.shouldNotify(ignored), "expected dockerignored path not to notify")
|
||||
}
|
||||
|
||||
func TestFseventNotifyShouldNotifyDockerignoreNegation(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
ignore, err := DockerIgnoreTesterFromContents(repo, "bazel-bin/\n!bazel-bin/app-binary\n")
|
||||
assert.NilError(t, err)
|
||||
|
||||
d := newFseventNotifyFixture(repo, ignore)
|
||||
|
||||
ignoredChild := filepath.Join(repo, "bazel-bin", "cache", "out")
|
||||
assert.NilError(t, os.MkdirAll(filepath.Dir(ignoredChild), 0o755))
|
||||
assert.NilError(t, os.WriteFile(ignoredChild, []byte("x"), 0o644))
|
||||
assert.Assert(t, !d.shouldNotify(ignoredChild), "expected ignored subtree under bazel-bin not to notify")
|
||||
|
||||
excepted := filepath.Join(repo, "bazel-bin", "app-binary", "binary")
|
||||
assert.NilError(t, os.MkdirAll(filepath.Dir(excepted), 0o755))
|
||||
assert.NilError(t, os.WriteFile(excepted, []byte("x"), 0o644))
|
||||
assert.Assert(t, d.shouldNotify(excepted), "expected negated dockerignore path to notify")
|
||||
}
|
||||
|
||||
func TestFseventNotifyShouldNotifyIntersectMatcher(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
ignoreVendor, err := DockerIgnoreTesterFromContents(repo, "vendor/\n")
|
||||
assert.NilError(t, err)
|
||||
ignoreTmp, err := DockerIgnoreTesterFromContents(repo, "tmp/\n")
|
||||
assert.NilError(t, err)
|
||||
|
||||
d := newFseventNotifyFixture(repo, NewIntersectMatcher(ignoreVendor, ignoreTmp))
|
||||
vendorFile := filepath.Join(repo, "vendor", "x", "go.mod")
|
||||
assert.NilError(t, os.MkdirAll(filepath.Dir(vendorFile), 0o755))
|
||||
assert.NilError(t, os.WriteFile(vendorFile, []byte("module x\n"), 0o644))
|
||||
assert.Assert(t, d.shouldNotify(vendorFile), "vendor must notify when only one intersect matcher ignores it")
|
||||
|
||||
ignoreBuild1, err := DockerIgnoreTesterFromContents(repo, "build/\n")
|
||||
assert.NilError(t, err)
|
||||
ignoreBuild2, err := DockerIgnoreTesterFromContents(repo, "build/\n")
|
||||
assert.NilError(t, err)
|
||||
d2 := newFseventNotifyFixture(repo, NewIntersectMatcher(ignoreBuild1, ignoreBuild2))
|
||||
buildFile := filepath.Join(repo, "build", "out", "a")
|
||||
assert.NilError(t, os.MkdirAll(filepath.Dir(buildFile), 0o755))
|
||||
assert.NilError(t, os.WriteFile(buildFile, []byte("x"), 0o644))
|
||||
assert.Assert(t, !d2.shouldNotify(buildFile), "expected path ignored by every intersect matcher not to notify")
|
||||
}
|
||||
|
||||
func TestFseventNotifyShouldIgnoreDockerignoreDirectory(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
ignore, err := DockerIgnoreTesterFromContents(repo, "bazel-bin/\n!bazel-bin/app-binary\n")
|
||||
assert.NilError(t, err)
|
||||
|
||||
d := newFseventNotifyFixture(repo, ignore)
|
||||
bazelBin := filepath.Join(repo, "bazel-bin")
|
||||
assert.NilError(t, os.MkdirAll(bazelBin, 0o755))
|
||||
assert.Assert(t, d.shouldIgnore(bazelBin), "expected directory path to match dockerignore")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue