- all: add `AddWith()`, which is identical to `Add()` but allows passing
options. ([#521])
+- all: support recursively watching paths with `Add("path/...")`. ([#540])
+
- windows: allow setting the buffer size with `fsnotify.WithBufferSize()`; the
default of 64K is the highest value that works on all platforms and is enough
for most purposes, but in some cases a highest buffer is needed. ([#521])
- other: use the backend_other.go no-op if the `appengine` build tag is set;
Google AppEngine forbids usage of the unsafe package so the inotify backend
- won't work there.
+ won't compile there.
[#371]: https://github.com/fsnotify/fsnotify/pull/371
[#526]: https://github.com/fsnotify/fsnotify/pull/526
[#528]: https://github.com/fsnotify/fsnotify/pull/528
[#537]: https://github.com/fsnotify/fsnotify/pull/537
+[#540]: https://github.com/fsnotify/fsnotify/pull/540
1.6.0 - 2022-10-13
-------------------
//go:build solaris
// +build solaris
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
package fsnotify
import (
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// The default buffer size is 64K, which is the largest value that is guaranteed
+// to work with SMB filesystems. If you have many events in quick succession
+// this may not be enough, and you will have to use [WithBufferSize] to increase
+// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
-// added.
+// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
+// after the watcher is started. By default subdirectories are not watched (i.e.
+// it's non-recursive), but if the path ends with "/..." all files and
+// subdirectories are watched too.
//
// # Watching files
//
// Remove stops monitoring the path for changes.
//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
+// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
+// entire recursive watch will be removed. You can use either "/tmp/dir" or
+// "/tmp/dir/..." (they behave identically).
+//
+// You cannot remove individual files or subdirectories from recursive watches;
+// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
+//
+// For other watches directories are removed non-recursively. For example, if
+// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
//go:build linux && !appengine
// +build linux,!appengine
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
package fsnotify
import (
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// The default buffer size is 64K, which is the largest value that is guaranteed
+// to work with SMB filesystems. If you have many events in quick succession
+// this may not be enough, and you will have to use [WithBufferSize] to increase
+// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
-// added.
+// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
+// after the watcher is started. By default subdirectories are not watched (i.e.
+// it's non-recursive), but if the path ends with "/..." all files and
+// subdirectories are watched too.
//
// # Watching files
//
// Remove stops monitoring the path for changes.
//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
+// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
+// entire recursive watch will be removed. You can use either "/tmp/dir" or
+// "/tmp/dir/..." (they behave identically).
+//
+// You cannot remove individual files or subdirectories from recursive watches;
+// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
+//
+// For other watches directories are removed non-recursively. For example, if
+// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
w.collect(t)
rm(t, file)
+ eventSeparator()
e := w.events(t)
cmpEvents(t, tmp, e, newEvents(t, `chmod /file`))
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
// +build freebsd openbsd netbsd dragonfly darwin
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
package fsnotify
import (
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// The default buffer size is 64K, which is the largest value that is guaranteed
+// to work with SMB filesystems. If you have many events in quick succession
+// this may not be enough, and you will have to use [WithBufferSize] to increase
+// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
-// added.
+// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
+// after the watcher is started. By default subdirectories are not watched (i.e.
+// it's non-recursive), but if the path ends with "/..." all files and
+// subdirectories are watched too.
//
// # Watching files
//
// Remove stops monitoring the path for changes.
//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
+// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
+// entire recursive watch will be removed. You can use either "/tmp/dir" or
+// "/tmp/dir/..." (they behave identically).
+//
+// You cannot remove individual files or subdirectories from recursive watches;
+// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
+//
+// For other watches directories are removed non-recursively. For example, if
+// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
package fsnotify
import "errors"
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// The default buffer size is 64K, which is the largest value that is guaranteed
+// to work with SMB filesystems. If you have many events in quick succession
+// this may not be enough, and you will have to use [WithBufferSize] to increase
+// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
-// added.
+// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
+// after the watcher is started. By default subdirectories are not watched (i.e.
+// it's non-recursive), but if the path ends with "/..." all files and
+// subdirectories are watched too.
//
// # Watching files
//
// Remove stops monitoring the path for changes.
//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
+// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
+// entire recursive watch will be removed. You can use either "/tmp/dir" or
+// "/tmp/dir/..." (they behave identically).
+//
+// You cannot remove individual files or subdirectories from recursive watches;
+// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
+//
+// For other watches directories are removed non-recursively. For example, if
+// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
//go:build windows
// +build windows
+// Windows backend based on ReadDirectoryChangesW()
+//
+// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
+//
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
package fsnotify
import (
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// The default buffer size is 64K, which is the largest value that is guaranteed
+// to work with SMB filesystems. If you have many events in quick succession
+// this may not be enough, and you will have to use [WithBufferSize] to increase
+// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
-// added.
+// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
+// after the watcher is started. By default subdirectories are not watched (i.e.
+// it's non-recursive), but if the path ends with "/..." all files and
+// subdirectories are watched too.
//
// # Watching files
//
// Remove stops monitoring the path for changes.
//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
+// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
+// entire recursive watch will be removed. You can use either "/tmp/dir" or
+// "/tmp/dir/..." (they behave identically).
+//
+// You cannot remove individual files or subdirectories from recursive watches;
+// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
+//
+// For other watches directories are removed non-recursively. For example, if
+// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
}
type watch struct {
- ov windows.Overlapped
- ino *inode // i-number
- path string // Directory path
- mask uint64 // Directory itself is being watched with these notify flags
- names map[string]uint64 // Map of names being watched and their notify flags
- rename string // Remembers the old name while renaming a file
- buf []byte // buffer, allocated later
+ ov windows.Overlapped
+ ino *inode // i-number
+ recurse bool // Recursive watch?
+ path string // Directory path
+ mask uint64 // Directory itself is being watched with these notify flags
+ names map[string]uint64 // Map of names being watched and their notify flags
+ rename string // Remembers the old name while renaming a file
+ buf []byte // buffer, allocated later
}
type (
// Must run within the I/O thread.
func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
+ pathname, recurse := recursivePath(pathname)
dir, err := w.getDir(pathname)
if err != nil {
return err
return os.NewSyscallError("CreateIoCompletionPort", err)
}
watchEntry = &watch{
- ino: ino,
- path: dir,
- names: make(map[string]uint64),
- buf: make([]byte, bufsize),
+ ino: ino,
+ path: dir,
+ names: make(map[string]uint64),
+ recurse: recurse,
+ buf: make([]byte, bufsize),
}
w.mu.Lock()
w.watches.set(ino, watchEntry)
// Must run within the I/O thread.
func (w *Watcher) remWatch(pathname string) error {
+ pathname, recurse := recursivePath(pathname)
+
dir, err := w.getDir(pathname)
if err != nil {
return err
watch := w.watches.get(ino)
w.mu.Unlock()
+ if recurse && !watch.recurse {
+ return fmt.Errorf("can't use \\... with non-recursive watch %q", pathname)
+ }
+
err = windows.CloseHandle(ino.handle)
if err != nil {
w.sendError(os.NewSyscallError("CloseHandle", err))
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf))
rdErr := windows.ReadDirectoryChanges(watch.ino.handle,
(*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len),
- false, mask, nil, &watch.ov, 0)
+ watch.recurse, mask, nil, &watch.ov, 0)
if rdErr != nil {
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
runtime.LockOSThread()
for {
+ // This error is handled after the watch == nil check below.
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
- // This error is handled after the watch == nil check below. NOTE: this
- // seems odd, note sure if it's correct.
watch := (*watch)(unsafe.Pointer(ov))
if watch == nil {
import (
"errors"
"fmt"
+ "path/filepath"
"strings"
)
func WithBufferSize(bytes int) addOpt {
return func(opt *withOpts) { opt.bufsize = bytes }
}
+
+// Check if this path is recursive (ends with "/..." or "\..."), and return the
+// path with the /... stripped.
+func recursivePath(path string) (string, bool) {
+ if filepath.Base(path) == "..." {
+ return filepath.Dir(path), true
+ }
+ return path, false
+}
}
}
-func TestWatchRm(t *testing.T) {
+func TestWatchRemove(t *testing.T) {
tests := []testCase{
{"remove watched file", func(t *testing.T, w *Watcher, tmp string) {
file := join(tmp, "file")
WRITE "/j"
WRITE "/j"
`},
+
+ {"remove recursive", func(t *testing.T, w *Watcher, tmp string) {
+ recurseOnly(t)
+
+ mkdirAll(t, tmp, "dir1", "subdir")
+ mkdirAll(t, tmp, "dir2", "subdir")
+ touch(t, tmp, "dir1", "subdir", "file")
+ touch(t, tmp, "dir2", "subdir", "file")
+
+ addWatch(t, w, tmp, "dir1", "...")
+ addWatch(t, w, tmp, "dir2", "...")
+ cat(t, "asd", tmp, "dir1", "subdir", "file")
+ cat(t, "asd", tmp, "dir2", "subdir", "file")
+
+ if err := w.Remove(join(tmp, "dir1")); err != nil {
+ t.Fatal(err)
+ }
+ if err := w.Remove(join(tmp, "dir2", "...")); err != nil {
+ t.Fatal(err)
+ }
+
+ if w := w.WatchList(); len(w) != 0 {
+ t.Errorf("WatchList not empty: %s", w)
+ }
+
+ cat(t, "asd", tmp, "dir1", "subdir", "file")
+ cat(t, "asd", tmp, "dir2", "subdir", "file")
+ }, `
+ write /dir1/subdir
+ write /dir1/subdir/file
+ write /dir2/subdir
+ write /dir2/subdir/file
+ `},
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ tt.run(t)
+ }
+}
+
+func TestWatchRecursive(t *testing.T) {
+ recurseOnly(t)
+
+ tests := []testCase{
+ // Make a nested directory tree, then write some files there.
+ {"basic", func(t *testing.T, w *Watcher, tmp string) {
+ mkdirAll(t, tmp, "/one/two/three/four")
+ addWatch(t, w, tmp, "/...")
+
+ cat(t, "asd", tmp, "/file.txt")
+ cat(t, "asd", tmp, "/one/two/three/file.txt")
+ }, `
+ create /file.txt # cat asd >file.txt
+ write /file.txt
+
+ write /one/two/three # cat asd >one/two/three/file.txt
+ create /one/two/three/file.txt
+ write /one/two/three/file.txt
+ `},
+
+ // Create a new directory tree and then some files under that.
+ {"add directory", func(t *testing.T, w *Watcher, tmp string) {
+ mkdirAll(t, tmp, "/one/two/three/four")
+ addWatch(t, w, tmp, "/...")
+
+ mkdirAll(t, tmp, "/one/two/new/dir")
+ touch(t, tmp, "/one/two/new/file")
+ touch(t, tmp, "/one/two/new/dir/file")
+ }, `
+ write /one/two # mkdir -p one/two/new/dir
+ create /one/two/new
+ create /one/two/new/dir
+
+ write /one/two/new # touch one/two/new/file
+ create /one/two/new/file
+
+ create /one/two/new/dir/file # touch one/two/new/dir/file
+ `},
+
+ // Remove nested directory
+ {"remove directory", func(t *testing.T, w *Watcher, tmp string) {
+ mkdirAll(t, tmp, "one/two/three/four")
+ addWatch(t, w, tmp, "...")
+
+ cat(t, "asd", tmp, "one/two/three/file.txt")
+ rmAll(t, tmp, "one/two")
+ }, `
+ write /one/two/three # cat asd >one/two/three/file.txt
+ create /one/two/three/file.txt
+ write /one/two/three/file.txt
+
+ write /one/two # rm -r one/two
+ write /one/two/three
+ remove /one/two/three/file.txt
+ remove /one/two/three/four
+ write /one/two/three
+ remove /one/two/three
+ write /one/two
+ remove /one/two
+ `},
+
+ // Rename nested directory
+ {"rename directory", func(t *testing.T, w *Watcher, tmp string) {
+ mkdirAll(t, tmp, "/one/two/three/four")
+ addWatch(t, w, tmp, "...")
+
+ mv(t, join(tmp, "one"), tmp, "one-rename")
+ touch(t, tmp, "one-rename/file")
+ touch(t, tmp, "one-rename/two/three/file")
+ }, `
+ rename "/one" # mv one one-rename
+ create "/one-rename"
+
+ write "/one-rename" # touch one-rename/file
+ create "/one-rename/file"
+
+ write "/one-rename/two/three" # touch one-rename/two/three/file
+ create "/one-rename/two/three/file"
+ `},
+
+ {"remove watched directory", func(t *testing.T, w *Watcher, tmp string) {
+ mk := func(r string) {
+ touch(t, r, "a")
+ touch(t, r, "b")
+ touch(t, r, "c")
+ touch(t, r, "d")
+ touch(t, r, "e")
+ touch(t, r, "f")
+ touch(t, r, "g")
+
+ mkdir(t, r, "h")
+ mkdir(t, r, "h", "a")
+ mkdir(t, r, "i")
+ mkdir(t, r, "i", "a")
+ mkdir(t, r, "j")
+ mkdir(t, r, "j", "a")
+ }
+ mk(tmp)
+ mkdir(t, tmp, "sub")
+ mk(join(tmp, "sub"))
+
+ addWatch(t, w, tmp, "...")
+ rmAll(t, tmp)
+ }, `
+ remove "/a"
+ remove "/b"
+ remove "/c"
+ remove "/d"
+ remove "/e"
+ remove "/f"
+ remove "/g"
+ write "/h"
+ remove "/h/a"
+ write "/h"
+ remove "/h"
+ write "/i"
+ remove "/i/a"
+ write "/i"
+ remove "/i"
+ write "/j"
+ remove "/j/a"
+ write "/j"
+ remove "/j"
+ write "/sub"
+ remove "/sub/a"
+ remove "/sub/b"
+ remove "/sub/c"
+ remove "/sub/d"
+ remove "/sub/e"
+ remove "/sub/f"
+ remove "/sub/g"
+ write "/sub/h"
+ remove "/sub/h/a"
+ write "/sub/h"
+ remove "/sub/h"
+ write "/sub/i"
+ remove "/sub/i/a"
+ write "/sub/i"
+ remove "/sub/i"
+ write "/sub/j"
+ remove "/sub/j/a"
+ write "/sub/j"
+ remove "/sub/j"
+ write "/sub"
+ remove "/sub"
+ remove "/"
+ `},
}
for _, tt := range tests {
w.Close()
}
})
+
+ t.Run("remove with ... when non-recursive", func(t *testing.T) {
+ recurseOnly(t)
+ t.Parallel()
+
+ tmp := t.TempDir()
+ w := newWatcher(t)
+ addWatch(t, w, tmp)
+
+ if err := w.Remove(join(tmp, "...")); err == nil {
+ t.Fatal("err was nil")
+ }
+ if err := w.Remove(tmp); err != nil {
+ t.Fatal(err)
+ }
+ })
+
}
func TestEventString(t *testing.T) {
}
// mkdir -p
-// func mkdirAll(t *testing.T, path ...string) {
-// t.Helper()
-// if len(path) < 1 {
-// t.Fatalf("mkdirAll: path must have at least one element: %s", path)
-// }
-// err := os.MkdirAll(join(path...), 0o0755)
-// if err != nil {
-// t.Fatalf("mkdirAll(%q): %s", join(path...), err)
-// }
-// if shouldWait(path...) {
-// eventSeparator()
-// }
-// }
+func mkdirAll(t *testing.T, path ...string) {
+ t.Helper()
+ if len(path) < 1 {
+ t.Fatalf("mkdirAll: path must have at least one element: %s", path)
+ }
+ err := os.MkdirAll(join(path...), 0o0755)
+ if err != nil {
+ t.Fatalf("mkdirAll(%q): %s", join(path...), err)
+ }
+ if shouldWait(path...) {
+ eventSeparator()
+ }
+}
// ln -s
func symlink(t *testing.T, target string, link ...string) {
}
return false
}
+
+func recurseOnly(t *testing.T) {
+ switch runtime.GOOS {
+ case "windows":
+ // Run test.
+ default:
+ t.Skip("recursion not yet supported on " + runtime.GOOS)
+ }
+}
[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
setopt err_exit no_unset pipefail extended_glob
-# Simple script to update the godoc comments on all watchers. Probably took me
-# more time to write this than doing it manually, but ah well 🙃
+# Simple script to update the godoc comments on all watchers so you don't need
+# to update the same comment 5 times.
watcher=$(<<EOF
// Watcher watches a set of paths, delivering events on a channel.
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\\path\\to\\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// The default buffer size is 64K, which is the largest value that is guaranteed
+// to work with SMB filesystems. If you have many events in quick succession
+// this may not be enough, and you will have to use [WithBufferSize] to increase
+// the value.
EOF
)
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
-// added.
+// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
+// after the watcher is started. By default subdirectories are not watched (i.e.
+// it's non-recursive), but if the path ends with "/..." all files and
+// subdirectories are watched too.
//
// # Watching files
//
remove=$(<<EOF
// Remove stops monitoring the path for changes.
//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
+// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
+// entire recursive watch will be removed. You can use either "/tmp/dir" or
+// "/tmp/dir/..." (they behave identically).
+//
+// You cannot remove individual files or subdirectories from recursive watches;
+// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
+//
+// For other watches directories are removed non-recursively. For example, if
+// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a