OSDN Git Service

add rjeczalik/notify to vendor
authorpaladz <453256728@qq.com>
Wed, 1 Nov 2017 09:00:17 +0000 (17:00 +0800)
committerpaladz <453256728@qq.com>
Wed, 1 Nov 2017 09:00:17 +0000 (17:00 +0800)
62 files changed:
vendor/github.com/rjeczalik/notify/.gitignore [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/.travis.yml [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/AUTHORS [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/LICENSE [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/README.md [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/appveyor.yml [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/debug.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/debug_debug.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/doc.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event_fen.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event_fsevents.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event_inotify.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event_kqueue.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event_readdcw.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event_stub.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/event_trigger.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/example_fsevents_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/example_inotify_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/example_readdcw_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/example_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/node.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/notify.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/notify_inotify_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/notify_readdcw_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/notify_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/sync_readdcw_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/sync_unix_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/testdata/vfs.txt [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/testing_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/tree.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/tree_nonrecursive.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/tree_nonrecursive_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/tree_recursive.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/tree_recursive_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/util.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/util_darwin_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/util_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/util_unix_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_fen.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_fen_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_fsevents.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_fsevents_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_inotify.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_inotify_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_kqueue.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_kqueue_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_readdcw.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_readdcw_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_recursive_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_stub.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_trigger.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watcher_trigger_test.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watchpoint.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watchpoint_other.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watchpoint_readdcw.go [new file with mode: 0644]
vendor/github.com/rjeczalik/notify/watchpoint_test.go [new file with mode: 0644]

diff --git a/vendor/github.com/rjeczalik/notify/.gitignore b/vendor/github.com/rjeczalik/notify/.gitignore
new file mode 100644 (file)
index 0000000..32c6eb9
--- /dev/null
@@ -0,0 +1,92 @@
+# Created by https://www.gitignore.io
+
+### OSX ###
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+### Windows ###
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+
+### Linux ###
+*~
+
+# KDE directory preferences
+.directory
+
+
+### Go ###
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+
+### vim ###
+[._]*.s[a-w][a-z]
+[._]s[a-w][a-z]
+*.un~
+Session.vim
+.netrwhist
+*~
+
+### JetBrains files ###
+.idea/
+*.iml
\ No newline at end of file
diff --git a/vendor/github.com/rjeczalik/notify/.travis.yml b/vendor/github.com/rjeczalik/notify/.travis.yml
new file mode 100644 (file)
index 0000000..0f38a35
--- /dev/null
@@ -0,0 +1,28 @@
+language: go
+
+go:
+ - 1.7.5
+
+os:
+ - linux
+ - osx
+
+matrix:
+  include:
+   - os: osx
+     go: 1.7.5
+     env:
+      - GOFLAGS="-tags kqueue"
+
+env:
+  global:
+   - GOBIN=$HOME/bin
+   - PATH=$HOME/bin:$PATH
+
+install:
+ - go get -t -v ./...
+
+script:
+ - "(go version | grep -q 1.4) || go tool vet -all ."
+ - go install $GOFLAGS ./...
+ - go test -v -race $GOFLAGS ./...
diff --git a/vendor/github.com/rjeczalik/notify/AUTHORS b/vendor/github.com/rjeczalik/notify/AUTHORS
new file mode 100644 (file)
index 0000000..9262eae
--- /dev/null
@@ -0,0 +1,10 @@
+# List of individuals who contributed to the Notify package.
+#
+# The up-to-date list of the authors one may obtain with:
+#
+#   ~ $ git shortlog -es | cut -f2 | rev | uniq -f1 | rev
+#
+
+Pawel Blaszczyk <blaszczykpb@gmail.com>
+Pawel Knap      <pawelknap88@gmail.com>
+Rafal Jeczalik  <rjeczalik@gmail.com>
diff --git a/vendor/github.com/rjeczalik/notify/LICENSE b/vendor/github.com/rjeczalik/notify/LICENSE
new file mode 100644 (file)
index 0000000..3e67881
--- /dev/null
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2015 The Notify Authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/rjeczalik/notify/README.md b/vendor/github.com/rjeczalik/notify/README.md
new file mode 100644 (file)
index 0000000..02a5f32
--- /dev/null
@@ -0,0 +1,21 @@
+notify [![GoDoc](https://godoc.org/github.com/rjeczalik/notify?status.svg)](https://godoc.org/github.com/rjeczalik/notify) [![Build Status](https://img.shields.io/travis/rjeczalik/notify/master.svg)](https://travis-ci.org/rjeczalik/notify "inotify + FSEvents + kqueue") [![Build status](https://img.shields.io/appveyor/ci/rjeczalik/notify-246.svg)](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [![Coverage Status](https://img.shields.io/coveralls/rjeczalik/notify/master.svg)](https://coveralls.io/r/rjeczalik/notify?branch=master)
+======
+
+Filesystem event notification library on steroids. (under active development)
+
+*Documentation*
+
+[godoc.org/github.com/rjeczalik/notify](https://godoc.org/github.com/rjeczalik/notify)
+
+*Installation*
+
+```
+~ $ go get -u github.com/rjeczalik/notify
+```
+
+*Projects using notify*
+
+- [github.com/rjeczalik/cmd/notify](https://godoc.org/github.com/rjeczalik/cmd/notify)
+- [github.com/cortesi/devd](https://github.com/cortesi/devd)
+- [github.com/cortesi/modd](https://github.com/cortesi/modd)
+- [github.com/syncthing/syncthing-inotify](https://github.com/syncthing/syncthing-inotify)
diff --git a/vendor/github.com/rjeczalik/notify/appveyor.yml b/vendor/github.com/rjeczalik/notify/appveyor.yml
new file mode 100644 (file)
index 0000000..8e762d0
--- /dev/null
@@ -0,0 +1,23 @@
+version: "{build}"
+
+os: Windows Server 2012 R2
+
+clone_folder: c:\projects\src\github.com\rjeczalik\notify
+
+environment:
+ PATH: c:\projects\bin;%PATH%
+ GOPATH: c:\projects
+ NOTIFY_TIMEOUT: 5s
+
+install:
+ - go version
+ - go get -v -t ./...
+
+build_script:
+ - go tool vet -all .
+ - go build ./...
+ - go test -v -race ./...
+
+test: off
+
+deploy: off
diff --git a/vendor/github.com/rjeczalik/notify/debug.go b/vendor/github.com/rjeczalik/notify/debug.go
new file mode 100644 (file)
index 0000000..bd9bc46
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !debug
+
+package notify
+
+func dbgprint(...interface{}) {}
+
+func dbgprintf(string, ...interface{}) {}
diff --git a/vendor/github.com/rjeczalik/notify/debug_debug.go b/vendor/github.com/rjeczalik/notify/debug_debug.go
new file mode 100644 (file)
index 0000000..f062291
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build debug
+
+package notify
+
+import (
+       "fmt"
+       "os"
+       "runtime"
+       "strings"
+)
+
+func dbgprint(v ...interface{}) {
+       fmt.Printf("[D] ")
+       fmt.Print(v...)
+       fmt.Printf("\n\n")
+}
+
+func dbgprintf(format string, v ...interface{}) {
+       fmt.Printf("[D] ")
+       fmt.Printf(format, v...)
+       fmt.Printf("\n\n")
+}
+
+func dbgcallstack(max int) []string {
+       pc, stack := make([]uintptr, max), make([]string, 0, max)
+       runtime.Callers(2, pc)
+       for _, pc := range pc {
+               if f := runtime.FuncForPC(pc); f != nil {
+                       fname := f.Name()
+                       idx := strings.LastIndex(fname, string(os.PathSeparator))
+                       if idx != -1 {
+                               stack = append(stack, fname[idx+1:])
+                       } else {
+                               stack = append(stack, fname)
+                       }
+               }
+       }
+       return stack
+}
diff --git a/vendor/github.com/rjeczalik/notify/doc.go b/vendor/github.com/rjeczalik/notify/doc.go
new file mode 100644 (file)
index 0000000..60543c0
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// Package notify implements access to filesystem events.
+//
+// Notify is a high-level abstraction over filesystem watchers like inotify,
+// kqueue, FSEvents, FEN or ReadDirectoryChangesW. Watcher implementations are
+// split into two groups: ones that natively support recursive notifications
+// (FSEvents and ReadDirectoryChangesW) and ones that do not (inotify, kqueue, FEN).
+// For more details see watcher and recursiveWatcher interfaces in watcher.go
+// source file.
+//
+// On top of filesystem watchers notify maintains a watchpoint tree, which provides
+// a strategy for creating and closing filesystem watches and dispatching filesystem
+// events to user channels.
+//
+// An event set is just an event list joint using bitwise OR operator
+// into a single event value.
+// Both the platform-independent (see Constants) and specific events can be used.
+// Refer to the event_*.go source files for information about the available
+// events.
+//
+// A filesystem watch or just a watch is platform-specific entity which represents
+// a single path registered for notifications for specific event set. Setting a watch
+// means using platform-specific API calls for creating / initializing said watch.
+// For each watcher the API call is:
+//
+//   - FSEvents: FSEventStreamCreate
+//   - inotify:  notify_add_watch
+//   - kqueue:   kevent
+//   - ReadDirectoryChangesW: CreateFile+ReadDirectoryChangesW
+//   - FEN:      port_get
+//
+// To rewatch means to either shrink or expand an event set that was previously
+// registered during watch operation for particular filesystem watch.
+//
+// A watchpoint is a list of user channel and event set pairs for particular
+// path (watchpoint tree's node). A single watchpoint can contain multiple
+// different user channels registered to listen for one or more events. A single
+// user channel can be registered in one or more watchpoints, recursive and
+// non-recursive ones as well.
+package notify
diff --git a/vendor/github.com/rjeczalik/notify/event.go b/vendor/github.com/rjeczalik/notify/event.go
new file mode 100644 (file)
index 0000000..c128841
--- /dev/null
@@ -0,0 +1,143 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+       "fmt"
+       "strings"
+)
+
+// Event represents the type of filesystem action.
+//
+// Number of available event values is dependent on the target system or the
+// watcher implmenetation used (e.g. it's possible to use either kqueue or
+// FSEvents on Darwin).
+//
+// Please consult documentation for your target platform to see list of all
+// available events.
+type Event uint32
+
+// Create, Remove, Write and Rename are the only event values guaranteed to be
+// present on all platforms.
+const (
+       Create = osSpecificCreate
+       Remove = osSpecificRemove
+       Write  = osSpecificWrite
+       Rename = osSpecificRename
+
+       // All is handful alias for all platform-independent event values.
+       All = Create | Remove | Write | Rename
+)
+
+const internal = recursive | omit
+
+// String implements fmt.Stringer interface.
+func (e Event) String() string {
+       var s []string
+       for _, strmap := range []map[Event]string{estr, osestr} {
+               for ev, str := range strmap {
+                       if e&ev == ev {
+                               s = append(s, str)
+                       }
+               }
+       }
+       return strings.Join(s, "|")
+}
+
+// EventInfo describes an event reported by the underlying filesystem notification
+// subsystem.
+//
+// It always describes single event, even if the OS reported a coalesced action.
+// Reported path is absolute and clean.
+//
+// For non-recursive watchpoints its base is always equal to the path passed
+// to corresponding Watch call.
+//
+// The value of Sys if system-dependent and can be nil.
+//
+// Sys
+//
+// Under Darwin (FSEvents) Sys() always returns a non-nil *notify.FSEvent value,
+// which is defined as:
+//
+//   type FSEvent struct {
+//       Path  string // real path of the file or directory
+//       ID    uint64 // ID of the event (FSEventStreamEventId)
+//       Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
+//   }
+//
+// For possible values of Flags see Darwin godoc for notify or FSEvents
+// documentation for FSEventStreamEventFlags constants:
+//
+//    https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags
+//
+// Under Linux (inotify) Sys() always returns a non-nil *unix.InotifyEvent
+// value, defined as:
+//
+//   type InotifyEvent struct {
+//       Wd     int32    // Watch descriptor
+//       Mask   uint32   // Mask describing event
+//       Cookie uint32   // Unique cookie associating related events (for rename(2))
+//       Len    uint32   // Size of name field
+//       Name   [0]uint8 // Optional null-terminated name
+//   }
+//
+// More information about inotify masks and the usage of inotify_event structure
+// can be found at:
+//
+//    http://man7.org/linux/man-pages/man7/inotify.7.html
+//
+// Under Darwin, DragonFlyBSD, FreeBSD, NetBSD, OpenBSD (kqueue) Sys() always
+// returns a non-nil *notify.Kevent value, which is defined as:
+//
+//   type Kevent struct {
+//       Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure
+//       FI     os.FileInfo       // FI describes file/dir
+//   }
+//
+// More information about syscall.Kevent_t can be found at:
+//
+//    https://www.freebsd.org/cgi/man.cgi?query=kqueue
+//
+// Under Windows (ReadDirectoryChangesW) Sys() always returns nil. The documentation
+// of watcher's WinAPI function can be found at:
+//
+//    https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx
+type EventInfo interface {
+       Event() Event     // event value for the filesystem action
+       Path() string     // real path of the file or directory
+       Sys() interface{} // underlying data source (can return nil)
+}
+
+type isDirer interface {
+       isDir() (bool, error)
+}
+
+var _ fmt.Stringer = (*event)(nil)
+var _ isDirer = (*event)(nil)
+
+// String implements fmt.Stringer interface.
+func (e *event) String() string {
+       return e.Event().String() + `: "` + e.Path() + `"`
+}
+
+var estr = map[Event]string{
+       Create: "notify.Create",
+       Remove: "notify.Remove",
+       Write:  "notify.Write",
+       Rename: "notify.Rename",
+       // Display name for recursive event is added only for debugging
+       // purposes. It's an internal event after all and won't be exposed to the
+       // user. Having Recursive event printable is helpful, e.g. for reading
+       // testing failure messages:
+       //
+       //    --- FAIL: TestWatchpoint (0.00 seconds)
+       //    watchpoint_test.go:64: want diff=[notify.Remove notify.Create|notify.Remove];
+       //    got [notify.Remove notify.Remove|notify.Create] (i=1)
+       //
+       // Yup, here the diff have Recursive event inside. Go figure.
+       recursive: "recursive",
+       omit:      "omit",
+}
diff --git a/vendor/github.com/rjeczalik/notify/event_fen.go b/vendor/github.com/rjeczalik/notify/event_fen.go
new file mode 100644 (file)
index 0000000..767f04f
--- /dev/null
@@ -0,0 +1,57 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build solaris
+
+package notify
+
+const (
+       osSpecificCreate Event = 0x00000100 << iota
+       osSpecificRemove
+       osSpecificWrite
+       osSpecificRename
+       // internal
+       // recursive is used to distinguish recursive eventsets from non-recursive ones
+       recursive
+       // omit is used for dispatching internal events; only those events are sent
+       // for which both the event and the watchpoint has omit in theirs event sets.
+       omit
+)
+
+const (
+       // FileAccess is an event reported when monitored file/directory was accessed.
+       FileAccess = fileAccess
+       // FileModified is an event reported when monitored file/directory was modified.
+       FileModified = fileModified
+       // FileAttrib is an event reported when monitored file/directory's ATTRIB
+       // was changed.
+       FileAttrib = fileAttrib
+       // FileDelete is an event reported when monitored file/directory was deleted.
+       FileDelete = fileDelete
+       // FileRenameTo to is an event reported when monitored file/directory was renamed.
+       FileRenameTo = fileRenameTo
+       // FileRenameFrom is an event reported when monitored file/directory was renamed.
+       FileRenameFrom = fileRenameFrom
+       // FileTrunc is an event reported when monitored file/directory was truncated.
+       FileTrunc = fileTrunc
+       // FileNoFollow is an flag to indicate not to follow symbolic links.
+       FileNoFollow = fileNoFollow
+       // Unmounted is an event reported when monitored filesystem was unmounted.
+       Unmounted = unmounted
+       // MountedOver is an event reported when monitored file/directory was mounted on.
+       MountedOver = mountedOver
+)
+
+var osestr = map[Event]string{
+       FileAccess:     "notify.FileAccess",
+       FileModified:   "notify.FileModified",
+       FileAttrib:     "notify.FileAttrib",
+       FileDelete:     "notify.FileDelete",
+       FileRenameTo:   "notify.FileRenameTo",
+       FileRenameFrom: "notify.FileRenameFrom",
+       FileTrunc:      "notify.FileTrunc",
+       FileNoFollow:   "notify.FileNoFollow",
+       Unmounted:      "notify.Unmounted",
+       MountedOver:    "notify.MountedOver",
+}
diff --git a/vendor/github.com/rjeczalik/notify/event_fsevents.go b/vendor/github.com/rjeczalik/notify/event_fsevents.go
new file mode 100644 (file)
index 0000000..6ded80b
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify
+
+const (
+       osSpecificCreate = Event(FSEventsCreated)
+       osSpecificRemove = Event(FSEventsRemoved)
+       osSpecificWrite  = Event(FSEventsModified)
+       osSpecificRename = Event(FSEventsRenamed)
+       // internal = Event(0x100000)
+       // recursive is used to distinguish recursive eventsets from non-recursive ones
+       recursive = Event(0x200000)
+       // omit is used for dispatching internal events; only those events are sent
+       // for which both the event and the watchpoint has omit in theirs event sets.
+       omit = Event(0x400000)
+)
+
+// FSEvents specific event values.
+const (
+       FSEventsMustScanSubDirs Event = 0x00001
+       FSEventsUserDropped           = 0x00002
+       FSEventsKernelDropped         = 0x00004
+       FSEventsEventIdsWrapped       = 0x00008
+       FSEventsHistoryDone           = 0x00010
+       FSEventsRootChanged           = 0x00020
+       FSEventsMount                 = 0x00040
+       FSEventsUnmount               = 0x00080
+       FSEventsCreated               = 0x00100
+       FSEventsRemoved               = 0x00200
+       FSEventsInodeMetaMod          = 0x00400
+       FSEventsRenamed               = 0x00800
+       FSEventsModified              = 0x01000
+       FSEventsFinderInfoMod         = 0x02000
+       FSEventsChangeOwner           = 0x04000
+       FSEventsXattrMod              = 0x08000
+       FSEventsIsFile                = 0x10000
+       FSEventsIsDir                 = 0x20000
+       FSEventsIsSymlink             = 0x40000
+)
+
+var osestr = map[Event]string{
+       FSEventsMustScanSubDirs: "notify.FSEventsMustScanSubDirs",
+       FSEventsUserDropped:     "notify.FSEventsUserDropped",
+       FSEventsKernelDropped:   "notify.FSEventsKernelDropped",
+       FSEventsEventIdsWrapped: "notify.FSEventsEventIdsWrapped",
+       FSEventsHistoryDone:     "notify.FSEventsHistoryDone",
+       FSEventsRootChanged:     "notify.FSEventsRootChanged",
+       FSEventsMount:           "notify.FSEventsMount",
+       FSEventsUnmount:         "notify.FSEventsUnmount",
+       FSEventsInodeMetaMod:    "notify.FSEventsInodeMetaMod",
+       FSEventsFinderInfoMod:   "notify.FSEventsFinderInfoMod",
+       FSEventsChangeOwner:     "notify.FSEventsChangeOwner",
+       FSEventsXattrMod:        "notify.FSEventsXattrMod",
+       FSEventsIsFile:          "notify.FSEventsIsFile",
+       FSEventsIsDir:           "notify.FSEventsIsDir",
+       FSEventsIsSymlink:       "notify.FSEventsIsSymlink",
+}
+
+type event struct {
+       fse   FSEvent
+       event Event
+}
+
+func (ei *event) Event() Event         { return ei.event }
+func (ei *event) Path() string         { return ei.fse.Path }
+func (ei *event) Sys() interface{}     { return &ei.fse }
+func (ei *event) isDir() (bool, error) { return ei.fse.Flags&FSEventsIsDir != 0, nil }
diff --git a/vendor/github.com/rjeczalik/notify/event_inotify.go b/vendor/github.com/rjeczalik/notify/event_inotify.go
new file mode 100644 (file)
index 0000000..1f32bb7
--- /dev/null
@@ -0,0 +1,75 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import "golang.org/x/sys/unix"
+
+// Platform independent event values.
+const (
+       osSpecificCreate Event = 0x100000 << iota
+       osSpecificRemove
+       osSpecificWrite
+       osSpecificRename
+       // internal
+       // recursive is used to distinguish recursive eventsets from non-recursive ones
+       recursive
+       // omit is used for dispatching internal events; only those events are sent
+       // for which both the event and the watchpoint has omit in theirs event sets.
+       omit
+)
+
+// Inotify specific masks are legal, implemented events that are guaranteed to
+// work with notify package on linux-based systems.
+const (
+       InAccess       = Event(unix.IN_ACCESS)        // File was accessed
+       InModify       = Event(unix.IN_MODIFY)        // File was modified
+       InAttrib       = Event(unix.IN_ATTRIB)        // Metadata changed
+       InCloseWrite   = Event(unix.IN_CLOSE_WRITE)   // Writtable file was closed
+       InCloseNowrite = Event(unix.IN_CLOSE_NOWRITE) // Unwrittable file closed
+       InOpen         = Event(unix.IN_OPEN)          // File was opened
+       InMovedFrom    = Event(unix.IN_MOVED_FROM)    // File was moved from X
+       InMovedTo      = Event(unix.IN_MOVED_TO)      // File was moved to Y
+       InCreate       = Event(unix.IN_CREATE)        // Subfile was created
+       InDelete       = Event(unix.IN_DELETE)        // Subfile was deleted
+       InDeleteSelf   = Event(unix.IN_DELETE_SELF)   // Self was deleted
+       InMoveSelf     = Event(unix.IN_MOVE_SELF)     // Self was moved
+)
+
+var osestr = map[Event]string{
+       InAccess:       "notify.InAccess",
+       InModify:       "notify.InModify",
+       InAttrib:       "notify.InAttrib",
+       InCloseWrite:   "notify.InCloseWrite",
+       InCloseNowrite: "notify.InCloseNowrite",
+       InOpen:         "notify.InOpen",
+       InMovedFrom:    "notify.InMovedFrom",
+       InMovedTo:      "notify.InMovedTo",
+       InCreate:       "notify.InCreate",
+       InDelete:       "notify.InDelete",
+       InDeleteSelf:   "notify.InDeleteSelf",
+       InMoveSelf:     "notify.InMoveSelf",
+}
+
+// Inotify behavior events are not **currently** supported by notify package.
+const (
+       inDontFollow = Event(unix.IN_DONT_FOLLOW)
+       inExclUnlink = Event(unix.IN_EXCL_UNLINK)
+       inMaskAdd    = Event(unix.IN_MASK_ADD)
+       inOneshot    = Event(unix.IN_ONESHOT)
+       inOnlydir    = Event(unix.IN_ONLYDIR)
+)
+
+type event struct {
+       sys   unix.InotifyEvent
+       path  string
+       event Event
+}
+
+func (e *event) Event() Event         { return e.event }
+func (e *event) Path() string         { return e.path }
+func (e *event) Sys() interface{}     { return &e.sys }
+func (e *event) isDir() (bool, error) { return e.sys.Mask&unix.IN_ISDIR != 0, nil }
diff --git a/vendor/github.com/rjeczalik/notify/event_kqueue.go b/vendor/github.com/rjeczalik/notify/event_kqueue.go
new file mode 100644 (file)
index 0000000..422ac87
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,kqueue dragonfly freebsd netbsd openbsd
+
+package notify
+
+import "syscall"
+
+// TODO(pblaszczyk): ensure in runtime notify built-in event values do not
+// overlap with platform-defined ones.
+
+// Platform independent event values.
+const (
+       osSpecificCreate Event = 0x0100 << iota
+       osSpecificRemove
+       osSpecificWrite
+       osSpecificRename
+       // internal
+       // recursive is used to distinguish recursive eventsets from non-recursive ones
+       recursive
+       // omit is used for dispatching internal events; only those events are sent
+       // for which both the event and the watchpoint has omit in theirs event sets.
+       omit
+)
+
+const (
+       // NoteDelete is an event reported when the unlink() system call was called
+       // on the file referenced by the descriptor.
+       NoteDelete = Event(syscall.NOTE_DELETE)
+       // NoteWrite is an event reported when a write occurred on the file
+       // referenced by the descriptor.
+       NoteWrite = Event(syscall.NOTE_WRITE)
+       // NoteExtend is an event reported when the file referenced by the
+       // descriptor was extended.
+       NoteExtend = Event(syscall.NOTE_EXTEND)
+       // NoteAttrib is an event reported when the file referenced
+       // by the descriptor had its attributes changed.
+       NoteAttrib = Event(syscall.NOTE_ATTRIB)
+       // NoteLink is an event reported when the link count on the file changed.
+       NoteLink = Event(syscall.NOTE_LINK)
+       // NoteRename is an event reported when the file referenced
+       // by the descriptor was renamed.
+       NoteRename = Event(syscall.NOTE_RENAME)
+       // NoteRevoke is an event reported when access to the file was revoked via
+       // revoke(2) or the underlying file system was unmounted.
+       NoteRevoke = Event(syscall.NOTE_REVOKE)
+)
+
+var osestr = map[Event]string{
+       NoteDelete: "notify.NoteDelete",
+       NoteWrite:  "notify.NoteWrite",
+       NoteExtend: "notify.NoteExtend",
+       NoteAttrib: "notify.NoteAttrib",
+       NoteLink:   "notify.NoteLink",
+       NoteRename: "notify.NoteRename",
+       NoteRevoke: "notify.NoteRevoke",
+}
diff --git a/vendor/github.com/rjeczalik/notify/event_readdcw.go b/vendor/github.com/rjeczalik/notify/event_readdcw.go
new file mode 100644 (file)
index 0000000..f7b82de
--- /dev/null
@@ -0,0 +1,118 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import (
+       "os"
+       "path/filepath"
+       "syscall"
+)
+
+// Platform independent event values.
+const (
+       osSpecificCreate Event = 1 << (20 + iota)
+       osSpecificRemove
+       osSpecificWrite
+       osSpecificRename
+       // recursive is used to distinguish recursive eventsets from non-recursive ones
+       recursive
+       // omit is used for dispatching internal events; only those events are sent
+       // for which both the event and the watchpoint has omit in theirs event sets.
+       omit
+       // dirmarker TODO(pknap)
+       dirmarker
+)
+
+// ReadDirectoryChangesW filters
+// On Windows the following events can be passed to Watch. A different set of
+// events (see actions below) are received on the channel passed to Watch.
+// For more information refer to
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx
+const (
+       FileNotifyChangeFileName   = Event(syscall.FILE_NOTIFY_CHANGE_FILE_NAME)
+       FileNotifyChangeDirName    = Event(syscall.FILE_NOTIFY_CHANGE_DIR_NAME)
+       FileNotifyChangeAttributes = Event(syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES)
+       FileNotifyChangeSize       = Event(syscall.FILE_NOTIFY_CHANGE_SIZE)
+       FileNotifyChangeLastWrite  = Event(syscall.FILE_NOTIFY_CHANGE_LAST_WRITE)
+       FileNotifyChangeLastAccess = Event(syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS)
+       FileNotifyChangeCreation   = Event(syscall.FILE_NOTIFY_CHANGE_CREATION)
+       FileNotifyChangeSecurity   = Event(syscallFileNotifyChangeSecurity)
+)
+
+const (
+       fileNotifyChangeAll      = 0x17f // logical sum of all FileNotifyChange* events.
+       fileNotifyChangeModified = fileNotifyChangeAll &^ (FileNotifyChangeFileName | FileNotifyChangeDirName)
+)
+
+// according to: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx
+// this flag should be declared in: http://golang.org/src/pkg/syscall/ztypes_windows.go
+const syscallFileNotifyChangeSecurity = 0x00000100
+
+// ReadDirectoryChangesW actions
+// The following events are returned on the channel passed to Watch, but cannot
+// be passed to Watch itself (see filters above). You can find a table showing
+// the relation between actions and filteres at
+// https://github.com/rjeczalik/notify/issues/10#issuecomment-66179535
+// The msdn documentation on actions is part of
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364391(v=vs.85).aspx
+const (
+       FileActionAdded          = Event(syscall.FILE_ACTION_ADDED) << 12
+       FileActionRemoved        = Event(syscall.FILE_ACTION_REMOVED) << 12
+       FileActionModified       = Event(syscall.FILE_ACTION_MODIFIED) << 14
+       FileActionRenamedOldName = Event(syscall.FILE_ACTION_RENAMED_OLD_NAME) << 15
+       FileActionRenamedNewName = Event(syscall.FILE_ACTION_RENAMED_NEW_NAME) << 16
+)
+
+const fileActionAll = 0x7f000 // logical sum of all FileAction* events.
+
+var osestr = map[Event]string{
+       FileNotifyChangeFileName:   "notify.FileNotifyChangeFileName",
+       FileNotifyChangeDirName:    "notify.FileNotifyChangeDirName",
+       FileNotifyChangeAttributes: "notify.FileNotifyChangeAttributes",
+       FileNotifyChangeSize:       "notify.FileNotifyChangeSize",
+       FileNotifyChangeLastWrite:  "notify.FileNotifyChangeLastWrite",
+       FileNotifyChangeLastAccess: "notify.FileNotifyChangeLastAccess",
+       FileNotifyChangeCreation:   "notify.FileNotifyChangeCreation",
+       FileNotifyChangeSecurity:   "notify.FileNotifyChangeSecurity",
+
+       FileActionAdded:          "notify.FileActionAdded",
+       FileActionRemoved:        "notify.FileActionRemoved",
+       FileActionModified:       "notify.FileActionModified",
+       FileActionRenamedOldName: "notify.FileActionRenamedOldName",
+       FileActionRenamedNewName: "notify.FileActionRenamedNewName",
+}
+
+const (
+       fTypeUnknown uint8 = iota
+       fTypeFile
+       fTypeDirectory
+)
+
+// TODO(ppknap) : doc.
+type event struct {
+       pathw  []uint16
+       name   string
+       ftype  uint8
+       action uint32
+       filter uint32
+       e      Event
+}
+
+func (e *event) Event() Event     { return e.e }
+func (e *event) Path() string     { return filepath.Join(syscall.UTF16ToString(e.pathw), e.name) }
+func (e *event) Sys() interface{} { return e.ftype }
+
+func (e *event) isDir() (bool, error) {
+       if e.ftype != fTypeUnknown {
+               return e.ftype == fTypeDirectory, nil
+       }
+       fi, err := os.Stat(e.Path())
+       if err != nil {
+               return false, err
+       }
+       return fi.IsDir(), nil
+}
diff --git a/vendor/github.com/rjeczalik/notify/event_stub.go b/vendor/github.com/rjeczalik/notify/event_stub.go
new file mode 100644 (file)
index 0000000..faac7c7
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows
+// +build !kqueue,!solaris
+
+package notify
+
+// Platform independent event values.
+const (
+       osSpecificCreate Event = 1 << iota
+       osSpecificRemove
+       osSpecificWrite
+       osSpecificRename
+       // internal
+       // recursive is used to distinguish recursive eventsets from non-recursive ones
+       recursive
+       // omit is used for dispatching internal events; only those events are sent
+       // for which both the event and the watchpoint has omit in theirs event sets.
+       omit
+)
+
+var osestr = map[Event]string{}
+
+type event struct{}
+
+func (e *event) Event() (_ Event)         { return }
+func (e *event) Path() (_ string)         { return }
+func (e *event) Sys() (_ interface{})     { return }
+func (e *event) isDir() (_ bool, _ error) { return }
diff --git a/vendor/github.com/rjeczalik/notify/event_test.go b/vendor/github.com/rjeczalik/notify/event_test.go
new file mode 100644 (file)
index 0000000..2803509
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+       "sort"
+       "strings"
+       "testing"
+)
+
+// S is a workaround for random event strings concatenation order.
+func s(s string) string {
+       z := strings.Split(s, "|")
+       sort.StringSlice(z).Sort()
+       return strings.Join(z, "|")
+}
+
+// This test is not safe to run in parallel with others.
+func TestEventString(t *testing.T) {
+       cases := map[Event]string{
+               Create:                  "notify.Create",
+               Create | Remove:         "notify.Create|notify.Remove",
+               Create | Remove | Write: "notify.Create|notify.Remove|notify.Write",
+               Create | Write | Rename: "notify.Create|notify.Rename|notify.Write",
+       }
+       for e, str := range cases {
+               if s := s(e.String()); s != str {
+                       t.Errorf("want s=%s; got %s (e=%#x)", str, s, e)
+               }
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/event_trigger.go b/vendor/github.com/rjeczalik/notify/event_trigger.go
new file mode 100644 (file)
index 0000000..94470fd
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
+
+package notify
+
+type event struct {
+       p  string
+       e  Event
+       d  bool
+       pe interface{}
+}
+
+func (e *event) Event() Event { return e.e }
+
+func (e *event) Path() string { return e.p }
+
+func (e *event) Sys() interface{} { return e.pe }
+
+func (e *event) isDir() (bool, error) { return e.d, nil }
diff --git a/vendor/github.com/rjeczalik/notify/example_fsevents_test.go b/vendor/github.com/rjeczalik/notify/example_fsevents_test.go
new file mode 100644 (file)
index 0000000..53d1b34
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify_test
+
+import (
+       "log"
+
+       "github.com/rjeczalik/notify"
+)
+
+// This example shows how to use FSEvents-specifc event values.
+func ExampleWatch_darwin() {
+       // Make the channel buffered to ensure no event is dropped. Notify will drop
+       // an event if the receiver is not able to keep up the sending pace.
+       c := make(chan notify.EventInfo, 1)
+
+       // Set up a watchpoint listening for FSEvents-specific events within a
+       // current working directory. Dispatch each FSEventsChangeOwner and FSEventsMount
+       // events separately to c.
+       if err := notify.Watch(".", c, notify.FSEventsChangeOwner, notify.FSEventsMount); err != nil {
+               log.Fatal(err)
+       }
+       defer notify.Stop(c)
+
+       // Block until an event is received.
+       switch ei := <-c; ei.Event() {
+       case notify.FSEventsChangeOwner:
+               log.Println("The owner of", ei.Path(), "has changed.")
+       case notify.FSEventsMount:
+               log.Println("The path", ei.Path(), "has been mounted.")
+       }
+}
+
+// This example shows how to work with EventInfo's underlying FSEvent struct.
+// Investigating notify.(*FSEvent).Flags field we are able to say whether
+// the event's path is a file or a directory and many more.
+func ExampleWatch_darwinDirFileSymlink() {
+       var must = func(err error) {
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+       var stop = func(c ...chan<- notify.EventInfo) {
+               for _, c := range c {
+                       notify.Stop(c)
+               }
+       }
+
+       // Make the channels buffered to ensure no event is dropped. Notify will drop
+       // an event if the receiver is not able to keep up the sending pace.
+       dir := make(chan notify.EventInfo, 1)
+       file := make(chan notify.EventInfo, 1)
+       symlink := make(chan notify.EventInfo, 1)
+       all := make(chan notify.EventInfo, 1)
+
+       // Set up a single watchpoint listening for FSEvents-specific events on
+       // multiple user-provided channels.
+       must(notify.Watch(".", dir, notify.FSEventsIsDir))
+       must(notify.Watch(".", file, notify.FSEventsIsFile))
+       must(notify.Watch(".", symlink, notify.FSEventsIsSymlink))
+       must(notify.Watch(".", all, notify.All))
+       defer stop(dir, file, symlink, all)
+
+       // Block until an event is received.
+       select {
+       case ei := <-dir:
+               log.Println("The directory", ei.Path(), "has changed")
+       case ei := <-file:
+               log.Println("The file", ei.Path(), "has changed")
+       case ei := <-symlink:
+               log.Println("The symlink", ei.Path(), "has changed")
+       case ei := <-all:
+               var kind string
+
+               // Investigate underlying *notify.FSEvent struct to access more
+               // information about the event.
+               switch flags := ei.Sys().(*notify.FSEvent).Flags; {
+               case flags&notify.FSEventsIsFile != 0:
+                       kind = "file"
+               case flags&notify.FSEventsIsDir != 0:
+                       kind = "dir"
+               case flags&notify.FSEventsIsSymlink != 0:
+                       kind = "symlink"
+               }
+
+               log.Printf("The %s under path %s has been %sd\n", kind, ei.Path(), ei.Event())
+       }
+}
+
+// FSEvents may report multiple filesystem actions with one, coalesced event.
+// Notify unscoalesces such event and dispatches series of single events
+// back to the user.
+//
+// This example shows how to coalesce events by investigating notify.(*FSEvent).ID
+// field, for the science.
+func ExampleWatch_darwinCoalesce() {
+       // Make the channels buffered to ensure no event is dropped. Notify will drop
+       // an event if the receiver is not able to keep up the sending pace.
+       c := make(chan notify.EventInfo, 4)
+
+       // Set up a watchpoint listetning for events within current working directory.
+       // Dispatch all platform-independent separately to c.
+       if err := notify.Watch(".", c, notify.All); err != nil {
+               log.Fatal(err)
+       }
+       defer notify.Stop(c)
+
+       var id uint64
+       var coalesced []notify.EventInfo
+
+       for ei := range c {
+               switch n := ei.Sys().(*notify.FSEvent).ID; {
+               case id == 0:
+                       id = n
+                       coalesced = []notify.EventInfo{ei}
+               case id == n:
+                       coalesced = append(coalesced, ei)
+               default:
+                       log.Printf("FSEvents reported a filesystem action with the following"+
+                               " coalesced events %v groupped by %d ID\n", coalesced, id)
+                       return
+               }
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/example_inotify_test.go b/vendor/github.com/rjeczalik/notify/example_inotify_test.go
new file mode 100644 (file)
index 0000000..e386012
--- /dev/null
@@ -0,0 +1,84 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify_test
+
+import (
+       "log"
+
+       "golang.org/x/sys/unix"
+
+       "github.com/rjeczalik/notify"
+)
+
+// This example shows how to watch changes made on file-system by text editor
+// when saving a file. Usually, either InCloseWrite or InMovedTo (when swapping
+// with a temporary file) event is created.
+func ExampleWatch_linux() {
+       // Make the channel buffered to ensure no event is dropped. Notify will drop
+       // an event if the receiver is not able to keep up the sending pace.
+       c := make(chan notify.EventInfo, 1)
+
+       // Set up a watchpoint listening for inotify-specific events within a
+       // current working directory. Dispatch each InCloseWrite and InMovedTo
+       // events separately to c.
+       if err := notify.Watch(".", c, notify.InCloseWrite, notify.InMovedTo); err != nil {
+               log.Fatal(err)
+       }
+       defer notify.Stop(c)
+
+       // Block until an event is received.
+       switch ei := <-c; ei.Event() {
+       case notify.InCloseWrite:
+               log.Println("Editing of", ei.Path(), "file is done.")
+       case notify.InMovedTo:
+               log.Println("File", ei.Path(), "was swapped/moved into the watched directory.")
+       }
+}
+
+// This example shows how to use Sys() method from EventInfo interface to tie
+// two separate events generated by rename(2) function.
+func ExampleWatch_linuxMove() {
+       // Make the channel buffered to ensure no event is dropped. Notify will drop
+       // an event if the receiver is not able to keep up the sending pace.
+       c := make(chan notify.EventInfo, 2)
+
+       // Set up a watchpoint listening for inotify-specific events within a
+       // current working directory. Dispatch each InMovedFrom and InMovedTo
+       // events separately to c.
+       if err := notify.Watch(".", c, notify.InMovedFrom, notify.InMovedTo); err != nil {
+               log.Fatal(err)
+       }
+       defer notify.Stop(c)
+
+       // Inotify reports move filesystem action by sending two events tied with
+       // unique cookie value (uint32): one of the events is of InMovedFrom type
+       // carrying move source path, while the second one is of InMoveTo type
+       // carrying move destination path.
+       moves := make(map[uint32]struct {
+               From string
+               To   string
+       })
+
+       // Wait for moves.
+       for ei := range c {
+               cookie := ei.Sys().(*unix.InotifyEvent).Cookie
+
+               info := moves[cookie]
+               switch ei.Event() {
+               case notify.InMovedFrom:
+                       info.From = ei.Path()
+               case notify.InMovedTo:
+                       info.To = ei.Path()
+               }
+               moves[cookie] = info
+
+               if cookie != 0 && info.From != "" && info.To != "" {
+                       log.Println("File:", info.From, "was renamed to", info.To)
+                       delete(moves, cookie)
+               }
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/example_readdcw_test.go b/vendor/github.com/rjeczalik/notify/example_readdcw_test.go
new file mode 100644 (file)
index 0000000..e9e05be
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify_test
+
+import (
+       "log"
+
+       "github.com/rjeczalik/notify"
+)
+
+// This example shows how to watch directory-name changes in the working directory subtree.
+func ExampleWatch_windows() {
+       // Make the channel buffered to ensure no event is dropped. Notify will drop
+       // an event if the receiver is not able to keep up the sending pace.
+       c := make(chan notify.EventInfo, 4)
+
+       // Since notify package behaves exactly like ReadDirectoryChangesW function,
+       // we must register notify.FileNotifyChangeDirName filter and wait for one
+       // of FileAction* events.
+       if err := notify.Watch("./...", c, notify.FileNotifyChangeDirName); err != nil {
+               log.Fatal(err)
+       }
+       defer notify.Stop(c)
+
+       // Wait for actions.
+       for ei := range c {
+               switch ei.Event() {
+               case notify.FileActionAdded, notify.FileActionRenamedNewName:
+                       log.Println("Created:", ei.Path())
+               case notify.FileActionRemoved, notify.FileActionRenamedOldName:
+                       log.Println("Removed:", ei.Path())
+               case notify.FileActionModified:
+                       panic("notify: unexpected action")
+               }
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/example_test.go b/vendor/github.com/rjeczalik/notify/example_test.go
new file mode 100644 (file)
index 0000000..3c8df71
--- /dev/null
@@ -0,0 +1,87 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify_test
+
+import (
+       "log"
+       "path/filepath"
+       "time"
+
+       "github.com/rjeczalik/notify"
+)
+
+// This is a basic example showing how to work with notify.Watch function.
+func ExampleWatch() {
+       // Make the channel buffered to ensure no event is dropped. Notify will drop
+       // an event if the receiver is not able to keep up the sending pace.
+       c := make(chan notify.EventInfo, 1)
+
+       // Set up a watchpoint listening on events within current working directory.
+       // Dispatch each create and remove events separately to c.
+       if err := notify.Watch(".", c, notify.Create, notify.Remove); err != nil {
+               log.Fatal(err)
+       }
+       defer notify.Stop(c)
+
+       // Block until an event is received.
+       ei := <-c
+       log.Println("Got event:", ei)
+}
+
+// This example shows how to set up a recursive watchpoint.
+func ExampleWatch_recursive() {
+       // Make the channel buffered to ensure no event is dropped. Notify will drop
+       // an event if the receiver is not able to keep up the sending pace.
+       c := make(chan notify.EventInfo, 1)
+
+       // Set up a watchpoint listening for events within a directory tree rooted
+       // at current working directory. Dispatch remove events to c.
+       if err := notify.Watch("./...", c, notify.Remove); err != nil {
+               log.Fatal(err)
+       }
+       defer notify.Stop(c)
+
+       // Block until an event is received.
+       ei := <-c
+       log.Println("Got event:", ei)
+}
+
+// This example shows why it is important to not create leaks by stoping
+// a channel when it's no longer being used.
+func ExampleStop() {
+       waitfor := func(path string, e notify.Event, timeout time.Duration) bool {
+               dir, file := filepath.Split(path)
+               c := make(chan notify.EventInfo, 1)
+
+               if err := notify.Watch(dir, c, e); err != nil {
+                       log.Fatal(err)
+               }
+               // Clean up watchpoint associated with c. If Stop was not called upon
+               // return the channel would be leaked as notify holds the only reference
+               // to it and does not release it on its own.
+               defer notify.Stop(c)
+
+               t := time.After(timeout)
+
+               for {
+                       select {
+                       case ei := <-c:
+                               if filepath.Base(ei.Path()) == file {
+                                       return true
+                               }
+                       case <-t:
+                               return false
+                       }
+               }
+       }
+
+       if waitfor("index.lock", notify.Create, 5*time.Second) {
+               log.Println("The git repository was locked")
+       }
+
+       if waitfor("index.lock", notify.Remove, 5*time.Second) {
+               log.Println("The git repository was unlocked")
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/node.go b/vendor/github.com/rjeczalik/notify/node.go
new file mode 100644 (file)
index 0000000..29c1bb2
--- /dev/null
@@ -0,0 +1,272 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+       "errors"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+)
+
+var errSkip = errors.New("notify: skip")
+
+type walkPathFunc func(nd node, isbase bool) error
+
+type walkFunc func(node) error
+
+func errnotexist(name string) error {
+       return &os.PathError{
+               Op:   "Node",
+               Path: name,
+               Err:  os.ErrNotExist,
+       }
+}
+
+type node struct {
+       Name  string
+       Watch watchpoint
+       Child map[string]node
+}
+
+func newnode(name string) node {
+       return node{
+               Name:  name,
+               Watch: make(watchpoint),
+               Child: make(map[string]node),
+       }
+}
+
+func (nd node) addchild(name, base string) node {
+       child, ok := nd.Child[base]
+       if !ok {
+               child = newnode(name)
+               nd.Child[base] = child
+       }
+       return child
+}
+
+func (nd node) Add(name string) node {
+       i := indexbase(nd.Name, name)
+       if i == -1 {
+               return node{}
+       }
+       for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+               nd = nd.addchild(name[:i+j], name[i:i+j])
+               i += j + 1
+       }
+       return nd.addchild(name, name[i:])
+}
+
+func (nd node) AddDir(fn walkFunc) error {
+       stack := []node{nd}
+Traverse:
+       for n := len(stack); n != 0; n = len(stack) {
+               nd, stack = stack[n-1], stack[:n-1]
+               switch err := fn(nd); err {
+               case nil:
+               case errSkip:
+                       continue Traverse
+               default:
+                       return fmt.Errorf("error while traversing %q: %v", nd.Name, err)
+               }
+               // TODO(rjeczalik): tolerate open failures - add failed names to
+               // AddDirError and notify users which names are not added to the tree.
+               fi, err := ioutil.ReadDir(nd.Name)
+               if err != nil {
+                       return err
+               }
+               for _, fi := range fi {
+                       if fi.Mode()&(os.ModeSymlink|os.ModeDir) == os.ModeDir {
+                               name := filepath.Join(nd.Name, fi.Name())
+                               stack = append(stack, nd.addchild(name, name[len(nd.Name)+1:]))
+                       }
+               }
+       }
+       return nil
+}
+
+func (nd node) Get(name string) (node, error) {
+       i := indexbase(nd.Name, name)
+       if i == -1 {
+               return node{}, errnotexist(name)
+       }
+       ok := false
+       for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+               if nd, ok = nd.Child[name[i:i+j]]; !ok {
+                       return node{}, errnotexist(name)
+               }
+               i += j + 1
+       }
+       if nd, ok = nd.Child[name[i:]]; !ok {
+               return node{}, errnotexist(name)
+       }
+       return nd, nil
+}
+
+func (nd node) Del(name string) error {
+       i := indexbase(nd.Name, name)
+       if i == -1 {
+               return errnotexist(name)
+       }
+       stack := []node{nd}
+       ok := false
+       for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+               if nd, ok = nd.Child[name[i:i+j]]; !ok {
+                       return errnotexist(name[:i+j])
+               }
+               stack = append(stack, nd)
+       }
+       if nd, ok = nd.Child[name[i:]]; !ok {
+               return errnotexist(name)
+       }
+       nd.Child = nil
+       nd.Watch = nil
+       for name, i = base(nd.Name), len(stack); i != 0; name, i = base(nd.Name), i-1 {
+               nd = stack[i-1]
+               if nd := nd.Child[name]; len(nd.Watch) > 1 || len(nd.Child) != 0 {
+                       break
+               } else {
+                       nd.Child = nil
+                       nd.Watch = nil
+               }
+               delete(nd.Child, name)
+       }
+       return nil
+}
+
+func (nd node) Walk(fn walkFunc) error {
+       stack := []node{nd}
+Traverse:
+       for n := len(stack); n != 0; n = len(stack) {
+               nd, stack = stack[n-1], stack[:n-1]
+               switch err := fn(nd); err {
+               case nil:
+               case errSkip:
+                       continue Traverse
+               default:
+                       return err
+               }
+               for name, nd := range nd.Child {
+                       if name == "" {
+                               // Node storing inactive watchpoints has empty name, skip it
+                               // form traversing. Root node has also an empty name, but it
+                               // never has a parent node.
+                               continue
+                       }
+                       stack = append(stack, nd)
+               }
+       }
+       return nil
+}
+
+func (nd node) WalkPath(name string, fn walkPathFunc) error {
+       i := indexbase(nd.Name, name)
+       if i == -1 {
+               return errnotexist(name)
+       }
+       ok := false
+       for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+               switch err := fn(nd, false); err {
+               case nil:
+               case errSkip:
+                       return nil
+               default:
+                       return err
+               }
+               if nd, ok = nd.Child[name[i:i+j]]; !ok {
+                       return errnotexist(name[:i+j])
+               }
+               i += j + 1
+       }
+       switch err := fn(nd, false); err {
+       case nil:
+       case errSkip:
+               return nil
+       default:
+               return err
+       }
+       if nd, ok = nd.Child[name[i:]]; !ok {
+               return errnotexist(name)
+       }
+       switch err := fn(nd, true); err {
+       case nil, errSkip:
+               return nil
+       default:
+               return err
+       }
+}
+
+type root struct {
+       nd node
+}
+
+func (r root) addroot(name string) node {
+       if vol := filepath.VolumeName(name); vol != "" {
+               root, ok := r.nd.Child[vol]
+               if !ok {
+                       root = r.nd.addchild(vol, vol)
+               }
+               return root
+       }
+       return r.nd
+}
+
+func (r root) root(name string) (node, error) {
+       if vol := filepath.VolumeName(name); vol != "" {
+               nd, ok := r.nd.Child[vol]
+               if !ok {
+                       return node{}, errnotexist(name)
+               }
+               return nd, nil
+       }
+       return r.nd, nil
+}
+
+func (r root) Add(name string) node {
+       return r.addroot(name).Add(name)
+}
+
+func (r root) AddDir(dir string, fn walkFunc) error {
+       return r.Add(dir).AddDir(fn)
+}
+
+func (r root) Del(name string) error {
+       nd, err := r.root(name)
+       if err != nil {
+               return err
+       }
+       return nd.Del(name)
+}
+
+func (r root) Get(name string) (node, error) {
+       nd, err := r.root(name)
+       if err != nil {
+               return node{}, err
+       }
+       if nd.Name != name {
+               if nd, err = nd.Get(name); err != nil {
+                       return node{}, err
+               }
+       }
+       return nd, nil
+}
+
+func (r root) Walk(name string, fn walkFunc) error {
+       nd, err := r.Get(name)
+       if err != nil {
+               return err
+       }
+       return nd.Walk(fn)
+}
+
+func (r root) WalkPath(name string, fn walkPathFunc) error {
+       nd, err := r.root(name)
+       if err != nil {
+               return err
+       }
+       return nd.WalkPath(name, fn)
+}
diff --git a/vendor/github.com/rjeczalik/notify/notify.go b/vendor/github.com/rjeczalik/notify/notify.go
new file mode 100644 (file)
index 0000000..7d2eec3
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// BUG(rjeczalik): Notify does not collect watchpoints, when underlying watches
+// were removed by their os-specific watcher implementations. Instead users are
+// advised to listen on persistent paths to have guarantee they receive events
+// for the whole lifetime of their applications (to discuss see #69).
+
+// BUG(ppknap): Linux (inotify) does not support watcher behavior masks like
+// InOneshot, InOnlydir etc. Instead users are advised to perform the filtering
+// themselves (to discuss see #71).
+
+// BUG(ppknap): Notify  was not tested for short path name support under Windows
+// (ReadDirectoryChangesW).
+
+// BUG(ppknap): Windows (ReadDirectoryChangesW) cannot recognize which notification
+// triggers FileActionModified event. (to discuss see #75).
+
+package notify
+
+var defaultTree = newTree()
+
+// Watch sets up a watchpoint on path listening for events given by the events
+// argument.
+//
+// File or directory given by the path must exist, otherwise Watch will fail
+// with non-nil error. Notify resolves, for its internal purpose, any symlinks
+// the provided path may contain, so it may fail if the symlinks form a cycle.
+// It does so, since not all watcher implementations treat passed paths as-is.
+// E.g. FSEvents reports a real path for every event, setting a watchpoint
+// on /tmp will report events with paths rooted at /private/tmp etc.
+//
+// The c almost always is a buffered channel. Watch will not block sending to c
+// - the caller must ensure that c has sufficient buffer space to keep up with
+// the expected event rate.
+//
+// It is allowed to pass the same channel multiple times with different event
+// list or different paths. Calling Watch with different event lists for a single
+// watchpoint expands its event set. The only way to shrink it, is to call
+// Stop on its channel.
+//
+// Calling Watch with empty event list does expand nor shrink watchpoint's event
+// set. If c is the first channel to listen for events on the given path, Watch
+// will seamlessly create a watch on the filesystem.
+//
+// Notify dispatches copies of single filesystem event to all channels registered
+// for each path. If a single filesystem event contains multiple coalesced events,
+// each of them is dispatched separately. E.g. the following filesystem change:
+//
+//   ~ $ echo Hello > Notify.txt
+//
+// dispatches two events - notify.Create and notify.Write. However, it may depend
+// on the underlying watcher implementation whether OS reports both of them.
+//
+// Windows and recursive watches
+//
+// If a directory which path was used to create recursive watch under Windows
+// gets deleted, the OS will not report such event. It is advised to keep in
+// mind this limitation while setting recursive watchpoints for your application,
+// e.g. use persistent paths like %userprofile% or watch additionally parent
+// directory of a recursive watchpoint in order to receive delete events for it.
+func Watch(path string, c chan<- EventInfo, events ...Event) error {
+       return defaultTree.Watch(path, c, events...)
+}
+
+// Stop removes all watchpoints registered for c. All underlying watches are
+// also removed, for which c was the last channel listening for events.
+//
+// Stop does not close c. When Stop returns, it is guaranteed that c will
+// receive no more signals.
+func Stop(c chan<- EventInfo) {
+       defaultTree.Stop(c)
+}
diff --git a/vendor/github.com/rjeczalik/notify/notify_inotify_test.go b/vendor/github.com/rjeczalik/notify/notify_inotify_test.go
new file mode 100644 (file)
index 0000000..c5984d3
--- /dev/null
@@ -0,0 +1,37 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import "testing"
+
+func TestNotifySystemAndGlobalMix(t *testing.T) {
+       n := NewNotifyTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(2)
+
+       n.Watch("src/github.com/rjeczalik/fs", ch[0], Create)
+       n.Watch("src/github.com/rjeczalik/fs", ch[1], InCreate)
+
+       cases := []NCase{
+               {
+                       Event:    icreate(n.W(), "src/github.com/rjeczalik/fs/.main.cc.swr"),
+                       Receiver: Chans{ch[0], ch[1]},
+               },
+       }
+
+       n.ExpectNotifyEvents(cases, ch)
+}
+
+func TestUnknownEvent(t *testing.T) {
+       n := NewNotifyTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(1)
+
+       n.WatchErr("src/github.com/rjeczalik/fs", ch[0], nil, inExclUnlink)
+}
diff --git a/vendor/github.com/rjeczalik/notify/notify_readdcw_test.go b/vendor/github.com/rjeczalik/notify/notify_readdcw_test.go
new file mode 100644 (file)
index 0000000..944b533
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import "testing"
+
+func TestNotifySystemSpecificEvent(t *testing.T) {
+       n := NewNotifyTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(1)
+
+       n.Watch("src/github.com/rjeczalik/fs", ch[0], FileNotifyChangeFileName, FileNotifyChangeSize)
+
+       cases := []NCase{
+               {
+                       Event:    rremove(n.W(), "src/github.com/rjeczalik/fs/fs.go"),
+                       Receiver: Chans{ch[0]},
+               },
+               {
+                       Event:    rwrite(n.W(), "src/github.com/rjeczalik/fs/README.md", []byte("XD")),
+                       Receiver: Chans{ch[0]},
+               },
+       }
+
+       n.ExpectNotifyEvents(cases, ch)
+}
+
+func TestUnknownEvent(t *testing.T) {
+       n := NewNotifyTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(1)
+
+       n.WatchErr("src/github.com/rjeczalik/fs", ch[0], nil, FileActionAdded)
+}
+
+func TestNotifySystemAndGlobalMix(t *testing.T) {
+       n := NewNotifyTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(3)
+
+       n.Watch("src/github.com/rjeczalik/fs", ch[0], Create)
+       n.Watch("src/github.com/rjeczalik/fs", ch[1], FileNotifyChangeFileName)
+       n.Watch("src/github.com/rjeczalik/fs", ch[2], FileNotifyChangeDirName)
+
+       cases := []NCase{
+               {
+                       Event:    rcreate(n.W(), "src/github.com/rjeczalik/fs/.main.cc.swr"),
+                       Receiver: Chans{ch[0], ch[1]},
+               },
+       }
+
+       n.ExpectNotifyEvents(cases, ch)
+}
diff --git a/vendor/github.com/rjeczalik/notify/notify_test.go b/vendor/github.com/rjeczalik/notify/notify_test.go
new file mode 100644 (file)
index 0000000..58c6ae7
--- /dev/null
@@ -0,0 +1,103 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin linux freebsd dragonfly netbsd openbsd windows solaris
+
+package notify
+
+import "testing"
+
+func TestNotifyExample(t *testing.T) {
+       n := NewNotifyTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(3)
+
+       // Watch-points can be set explicitly via Watch/Stop calls...
+       n.Watch("src/github.com/rjeczalik/fs", ch[0], Write)
+       n.Watch("src/github.com/pblaszczyk/qttu", ch[0], Write)
+       n.Watch("src/github.com/pblaszczyk/qttu/...", ch[1], Create)
+       n.Watch("src/github.com/rjeczalik/fs/cmd/...", ch[2], Remove)
+
+       cases := []NCase{
+               // i=0
+               {
+                       Event:    write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+                       Receiver: Chans{ch[0]},
+               },
+               // TODO(rjeczalik): #62
+               // i=1
+               // {
+               //      Event:    write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")),
+               //      Receiver: Chans{ch[0]},
+               // },
+               // i=2
+               {
+                       Event:    write(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go", []byte("XD")),
+                       Receiver: nil,
+               },
+               // i=3
+               {
+                       Event:    create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swp"),
+                       Receiver: Chans{ch[1]},
+               },
+               // i=4
+               {
+                       Event:    create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swo"),
+                       Receiver: Chans{ch[1]},
+               },
+               // i=5
+               {
+                       Event:    remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go"),
+                       Receiver: Chans{ch[2]},
+               },
+       }
+
+       n.ExpectNotifyEvents(cases, ch)
+
+       // ...or using Call structures.
+       stops := [...]Call{
+               // i=0
+               {
+                       F: FuncStop,
+                       C: ch[0],
+               },
+               // i=1
+               {
+                       F: FuncStop,
+                       C: ch[1],
+               },
+       }
+
+       n.Call(stops[:]...)
+
+       cases = []NCase{
+               // i=0
+               {
+                       Event:    write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+                       Receiver: nil,
+               },
+               // i=1
+               {
+                       Event:    write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")),
+                       Receiver: nil,
+               },
+               // i=2
+               {
+                       Event:    create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swr"),
+                       Receiver: nil,
+               },
+               // i=3
+               {
+                       Event:    remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/main.go"),
+                       Receiver: Chans{ch[2]},
+               },
+       }
+
+       n.ExpectNotifyEvents(cases, ch)
+}
+
+func TestStop(t *testing.T) {
+       t.Skip("TODO(rjeczalik)")
+}
diff --git a/vendor/github.com/rjeczalik/notify/sync_readdcw_test.go b/vendor/github.com/rjeczalik/notify/sync_readdcw_test.go
new file mode 100644 (file)
index 0000000..f299754
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import (
+       "syscall"
+       "time"
+       "unsafe"
+)
+
+var modkernel32 = syscall.NewLazyDLL("kernel32.dll")
+var procSetSystemFileCacheSize = modkernel32.NewProc("SetSystemFileCacheSize")
+var zero = uintptr(1<<(unsafe.Sizeof(uintptr(0))*8) - 1)
+
+func Sync() {
+       // TODO(pknap): does not work without admin privileges, but I'm going
+       // to hack it.
+       // r, _, err := procSetSystemFileCacheSize.Call(none, none, 0)
+       // if r == 0 {
+       //   dbgprint("SetSystemFileCacheSize error:", err)
+       // }
+}
+
+// UpdateWait pauses the program for some minimal amount of time. This function
+// is required only by implementations which work asynchronously. It gives
+// watcher structure time to update its internal state.
+func UpdateWait() {
+       time.Sleep(50 * time.Millisecond)
+}
diff --git a/vendor/github.com/rjeczalik/notify/sync_unix_test.go b/vendor/github.com/rjeczalik/notify/sync_unix_test.go
new file mode 100644 (file)
index 0000000..5e2c5dd
--- /dev/null
@@ -0,0 +1,18 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !windows
+
+package notify
+
+import "golang.org/x/sys/unix"
+
+func Sync() {
+       unix.Sync()
+}
+
+// UpdateWait is required only by windows watcher implementation. On other
+// platforms this function is no-op.
+func UpdateWait() {
+}
diff --git a/vendor/github.com/rjeczalik/notify/testdata/vfs.txt b/vendor/github.com/rjeczalik/notify/testdata/vfs.txt
new file mode 100644 (file)
index 0000000..88afbc6
--- /dev/null
@@ -0,0 +1,56 @@
+src/github.com/pblaszczyk/qttu/.travis.yml
+src/github.com/pblaszczyk/qttu/include/qttu/detail/registrator.hh
+src/github.com/pblaszczyk/qttu/include/qttu/detail/registry.hh
+src/github.com/pblaszczyk/qttu/include/qttu/runner.hh
+src/github.com/pblaszczyk/qttu/LICENSE
+src/github.com/pblaszczyk/qttu/qttu.pri
+src/github.com/pblaszczyk/qttu/qttu.pro
+src/github.com/pblaszczyk/qttu/README.md
+src/github.com/pblaszczyk/qttu/src/main.cc
+src/github.com/pblaszczyk/qttu/src/reg.cc
+src/github.com/ppknap/link/.travis.yml
+src/github.com/ppknap/link/include/coost/link/definitions.hpp
+src/github.com/ppknap/link/include/coost/link/detail/bundle.hpp
+src/github.com/ppknap/link/include/coost/link/detail/container_invoker.hpp
+src/github.com/ppknap/link/include/coost/link/detail/container_value_trait.hpp
+src/github.com/ppknap/link/include/coost/link/detail/dummy_type.hpp
+src/github.com/ppknap/link/include/coost/link/detail/function_trait.hpp
+src/github.com/ppknap/link/include/coost/link/detail/immediate_invoker.hpp
+src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/always_same.hpp
+src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/make_unique.hpp
+src/github.com/ppknap/link/include/coost/link/detail/vertex.hpp
+src/github.com/ppknap/link/include/coost/link/detail/wire.hpp
+src/github.com/ppknap/link/include/coost/link/link.hpp
+src/github.com/ppknap/link/include/coost/link.hpp
+src/github.com/ppknap/link/Jamroot.jam
+src/github.com/ppknap/link/LICENSE.md
+src/github.com/ppknap/link/README.md
+src/github.com/ppknap/link/test/counter_helper.hpp
+src/github.com/ppknap/link/test/Jamfile.jam
+src/github.com/ppknap/link/test/test_circular_calls.cpp
+src/github.com/ppknap/link/test/test_container.cpp
+src/github.com/ppknap/link/test/test_copy.cpp
+src/github.com/ppknap/link/test/test_destructor.cpp
+src/github.com/ppknap/link/test/test_immediate.cpp
+src/github.com/ppknap/link/test/test_initialize.cpp
+src/github.com/rjeczalik/fs/.travis.yml
+src/github.com/rjeczalik/fs/appveyor.yml
+src/github.com/rjeczalik/fs/cmd/gotree/go.go
+src/github.com/rjeczalik/fs/cmd/gotree/main.go
+src/github.com/rjeczalik/fs/cmd/mktree/main.go
+src/github.com/rjeczalik/fs/fs.go
+src/github.com/rjeczalik/fs/fsutil/fixture_test.go
+src/github.com/rjeczalik/fs/fsutil/fsutil.go
+src/github.com/rjeczalik/fs/fsutil/fsutil_test.go
+src/github.com/rjeczalik/fs/fsutil/rel.go
+src/github.com/rjeczalik/fs/fsutil/rel_test.go
+src/github.com/rjeczalik/fs/fsutil/tee.go
+src/github.com/rjeczalik/fs/fsutil/tee_test.go
+src/github.com/rjeczalik/fs/LICENSE
+src/github.com/rjeczalik/fs/memfs/memfs.go
+src/github.com/rjeczalik/fs/memfs/memfs_test.go
+src/github.com/rjeczalik/fs/memfs/tree.go
+src/github.com/rjeczalik/fs/memfs/tree_test.go
+src/github.com/rjeczalik/fs/memfs/util.go
+src/github.com/rjeczalik/fs/memfs/util_test.go
+src/github.com/rjeczalik/fs/README.md
diff --git a/vendor/github.com/rjeczalik/notify/testing_test.go b/vendor/github.com/rjeczalik/notify/testing_test.go
new file mode 100644 (file)
index 0000000..ea18c4f
--- /dev/null
@@ -0,0 +1,951 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+       "bufio"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "reflect"
+       "runtime"
+       "sort"
+       "strconv"
+       "strings"
+       "testing"
+       "time"
+)
+
+// NOTE(rjeczalik): some useful environment variables:
+//
+//   - NOTIFY_DEBUG gives some extra information about generated events
+//   - NOTIFY_TIMEOUT allows for changing default wait time for watcher's
+//     events
+//   - NOTIFY_TMP allows for changing location of temporary directory trees
+//     created for test purpose
+
+var wd string
+
+func init() {
+       var err error
+       if wd, err = os.Getwd(); err != nil {
+               panic("Getwd()=" + err.Error())
+       }
+}
+
+func timeout() time.Duration {
+       if s := os.Getenv("NOTIFY_TIMEOUT"); s != "" {
+               if t, err := time.ParseDuration(s); err == nil {
+                       return t
+               }
+       }
+       return 2 * time.Second
+}
+
+func vfs() (string, string) {
+       if s := os.Getenv("NOTIFY_TMP"); s != "" {
+               return filepath.Split(s)
+       }
+       return "testdata", ""
+}
+
+func isDir(path string) bool {
+       r := path[len(path)-1]
+       return r == '\\' || r == '/'
+}
+
+func tmpcreateall(tmp string, path string) error {
+       isdir := isDir(path)
+       path = filepath.Join(tmp, filepath.FromSlash(path))
+       if isdir {
+               if err := os.MkdirAll(path, 0755); err != nil {
+                       return err
+               }
+       } else {
+               if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+                       return err
+               }
+               f, err := os.Create(path)
+               if err != nil {
+                       return err
+               }
+               if err := nonil(f.Sync(), f.Close()); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func tmpcreate(root, path string) (bool, error) {
+       isdir := isDir(path)
+       path = filepath.Join(root, filepath.FromSlash(path))
+       if isdir {
+               if err := os.Mkdir(path, 0755); err != nil {
+                       return false, err
+               }
+       } else {
+               f, err := os.Create(path)
+               if err != nil {
+                       return false, err
+               }
+               if err := nonil(f.Sync(), f.Close()); err != nil {
+                       return false, err
+               }
+       }
+       return isdir, nil
+}
+
+func tmptree(root, list string) (string, error) {
+       f, err := os.Open(list)
+       if err != nil {
+               return "", err
+       }
+       defer f.Close()
+       if root == "" {
+               if root, err = ioutil.TempDir(vfs()); err != nil {
+                       return "", err
+               }
+       }
+       scanner := bufio.NewScanner(f)
+       for scanner.Scan() {
+               if err := tmpcreateall(root, scanner.Text()); err != nil {
+                       return "", err
+               }
+       }
+       if err := scanner.Err(); err != nil {
+               return "", err
+       }
+       return root, nil
+}
+
+func callern(n int) string {
+       _, file, line, ok := runtime.Caller(n)
+       if !ok {
+               return "<unknown>"
+       }
+       return filepath.Base(file) + ":" + strconv.Itoa(line)
+}
+
+func caller() string {
+       return callern(3)
+}
+
+type WCase struct {
+       Action func()
+       Events []EventInfo
+}
+
+func (cas WCase) String() string {
+       s := make([]string, 0, len(cas.Events))
+       for _, ei := range cas.Events {
+               s = append(s, "Event("+ei.Event().String()+")@"+filepath.FromSlash(ei.Path()))
+       }
+       return strings.Join(s, ", ")
+}
+
+type W struct {
+       Watcher watcher
+       C       chan EventInfo
+       Timeout time.Duration
+
+       t    *testing.T
+       root string
+}
+
+func newWatcherTest(t *testing.T, tree string) *W {
+       root, err := tmptree("", filepath.FromSlash(tree))
+       if err != nil {
+               t.Fatalf(`tmptree("", %q)=%v`, tree, err)
+       }
+       Sync()
+       return &W{
+               t:    t,
+               root: root,
+       }
+}
+
+func NewWatcherTest(t *testing.T, tree string, events ...Event) *W {
+       w := newWatcherTest(t, tree)
+       if len(events) == 0 {
+               events = []Event{Create, Remove, Write, Rename}
+       }
+       if rw, ok := w.watcher().(recursiveWatcher); ok {
+               if err := rw.RecursiveWatch(w.root, joinevents(events)); err != nil {
+                       t.Fatalf("RecursiveWatch(%q, All)=%v", w.root, err)
+               }
+       } else {
+               fn := func(path string, fi os.FileInfo, err error) error {
+                       if err != nil {
+                               return err
+                       }
+                       if fi.IsDir() {
+                               if err := w.watcher().Watch(path, joinevents(events)); err != nil {
+                                       return err
+                               }
+                       }
+                       return nil
+               }
+               if err := filepath.Walk(w.root, fn); err != nil {
+                       t.Fatalf("Walk(%q, fn)=%v", w.root, err)
+               }
+       }
+       drainall(w.C)
+       return w
+}
+
+func (w *W) clean(path string) string {
+       path, isrec, err := cleanpath(filepath.Join(w.root, path))
+       if err != nil {
+               w.Fatalf("cleanpath(%q)=%v", path, err)
+       }
+       if isrec {
+               path = path + "..."
+       }
+       return path
+}
+
+func (w *W) Fatal(v interface{}) {
+       w.t.Fatalf("%s: %v", caller(), v)
+}
+
+func (w *W) Fatalf(format string, v ...interface{}) {
+       w.t.Fatalf("%s: %s", caller(), fmt.Sprintf(format, v...))
+}
+
+func (w *W) Watch(path string, e Event) {
+       if err := w.watcher().Watch(w.clean(path), e); err != nil {
+               w.Fatalf("Watch(%s, %v)=%v", path, e, err)
+       }
+}
+
+func (w *W) Rewatch(path string, olde, newe Event) {
+       if err := w.watcher().Rewatch(w.clean(path), olde, newe); err != nil {
+               w.Fatalf("Rewatch(%s, %v, %v)=%v", path, olde, newe, err)
+       }
+}
+
+func (w *W) Unwatch(path string) {
+       if err := w.watcher().Unwatch(w.clean(path)); err != nil {
+               w.Fatalf("Unwatch(%s)=%v", path, err)
+       }
+}
+
+func (w *W) RecursiveWatch(path string, e Event) {
+       rw, ok := w.watcher().(recursiveWatcher)
+       if !ok {
+               w.Fatal("watcher does not implement recursive watching on this platform")
+       }
+       if err := rw.RecursiveWatch(w.clean(path), e); err != nil {
+               w.Fatalf("RecursiveWatch(%s, %v)=%v", path, e, err)
+       }
+}
+
+func (w *W) RecursiveRewatch(oldp, newp string, olde, newe Event) {
+       rw, ok := w.watcher().(recursiveWatcher)
+       if !ok {
+               w.Fatal("watcher does not implement recursive watching on this platform")
+       }
+       if err := rw.RecursiveRewatch(w.clean(oldp), w.clean(newp), olde, newe); err != nil {
+               w.Fatalf("RecursiveRewatch(%s, %s, %v, %v)=%v", oldp, newp, olde, newe, err)
+       }
+}
+
+func (w *W) RecursiveUnwatch(path string) {
+       rw, ok := w.watcher().(recursiveWatcher)
+       if !ok {
+               w.Fatal("watcher does not implement recursive watching on this platform")
+       }
+       if err := rw.RecursiveUnwatch(w.clean(path)); err != nil {
+               w.Fatalf("RecursiveUnwatch(%s)=%v", path, err)
+       }
+}
+
+func (w *W) initwatcher(buffer int) {
+       c := make(chan EventInfo, buffer)
+       w.Watcher = newWatcher(c)
+       w.C = c
+}
+
+func (w *W) watcher() watcher {
+       if w.Watcher == nil {
+               w.initwatcher(512)
+       }
+       return w.Watcher
+}
+
+func (w *W) c() chan EventInfo {
+       if w.C == nil {
+               w.initwatcher(512)
+       }
+       return w.C
+}
+
+func (w *W) timeout() time.Duration {
+       if w.Timeout != 0 {
+               return w.Timeout
+       }
+       return timeout()
+}
+
+func (w *W) Close() error {
+       defer os.RemoveAll(w.root)
+       if err := w.watcher().Close(); err != nil {
+               w.Fatalf("w.Watcher.Close()=%v", err)
+       }
+       return nil
+}
+
+func EqualEventInfo(want, got EventInfo) error {
+       if got.Event() != want.Event() {
+               return fmt.Errorf("want Event()=%v; got %v (path=%s)", want.Event(),
+                       got.Event(), want.Path())
+       }
+       path := strings.TrimRight(filepath.FromSlash(want.Path()), `/\`)
+       if !strings.HasSuffix(got.Path(), path) {
+               return fmt.Errorf("want Path()=%s; got %s (event=%v)", path, got.Path(),
+                       want.Event())
+       }
+       return nil
+}
+
+func HasEventInfo(want, got Event, p string) error {
+       if got&want != want {
+               return fmt.Errorf("want Event=%v; got %v (path=%s)", want,
+                       got, p)
+       }
+       return nil
+}
+
+func EqualCall(want, got Call) error {
+       if want.F != got.F {
+               return fmt.Errorf("want F=%v; got %v (want.P=%q, got.P=%q)", want.F, got.F, want.P, got.P)
+       }
+       if got.E != want.E {
+               return fmt.Errorf("want E=%v; got %v (want.P=%q, got.P=%q)", want.E, got.E, want.P, got.P)
+       }
+       if got.NE != want.NE {
+               return fmt.Errorf("want NE=%v; got %v (want.P=%q, got.P=%q)", want.NE, got.NE, want.P, got.P)
+       }
+       if want.C != got.C {
+               return fmt.Errorf("want C=%p; got %p (want.P=%q, got.P=%q)", want.C, got.C, want.P, got.P)
+       }
+       if want := filepath.FromSlash(want.P); !strings.HasSuffix(got.P, want) {
+               return fmt.Errorf("want P=%s; got %s", want, got.P)
+       }
+       if want := filepath.FromSlash(want.NP); !strings.HasSuffix(got.NP, want) {
+               return fmt.Errorf("want NP=%s; got %s", want, got.NP)
+       }
+       return nil
+}
+
+func create(w *W, path string) WCase {
+       return WCase{
+               Action: func() {
+                       isdir, err := tmpcreate(w.root, filepath.FromSlash(path))
+                       if err != nil {
+                               w.Fatalf("tmpcreate(%q, %q)=%v", w.root, path, err)
+                       }
+                       if isdir {
+                               dbgprintf("[FS] os.Mkdir(%q)\n", path)
+                       } else {
+                               dbgprintf("[FS] os.Create(%q)\n", path)
+                       }
+               },
+               Events: []EventInfo{
+                       &Call{P: path, E: Create},
+               },
+       }
+}
+
+func remove(w *W, path string) WCase {
+       return WCase{
+               Action: func() {
+                       if err := os.RemoveAll(filepath.Join(w.root, filepath.FromSlash(path))); err != nil {
+                               w.Fatal(err)
+                       }
+                       dbgprintf("[FS] os.Remove(%q)\n", path)
+               },
+               Events: []EventInfo{
+                       &Call{P: path, E: Remove},
+               },
+       }
+}
+
+func rename(w *W, oldpath, newpath string) WCase {
+       return WCase{
+               Action: func() {
+                       err := os.Rename(filepath.Join(w.root, filepath.FromSlash(oldpath)),
+                               filepath.Join(w.root, filepath.FromSlash(newpath)))
+                       if err != nil {
+                               w.Fatal(err)
+                       }
+                       dbgprintf("[FS] os.Rename(%q, %q)\n", oldpath, newpath)
+               },
+               Events: []EventInfo{
+                       &Call{P: newpath, E: Rename},
+               },
+       }
+}
+
+func write(w *W, path string, p []byte) WCase {
+       return WCase{
+               Action: func() {
+                       f, err := os.OpenFile(filepath.Join(w.root, filepath.FromSlash(path)),
+                               os.O_WRONLY, 0644)
+                       if err != nil {
+                               w.Fatalf("OpenFile(%q)=%v", path, err)
+                       }
+                       if _, err := f.Write(p); err != nil {
+                               w.Fatalf("Write(%q)=%v", path, err)
+                       }
+                       if err := nonil(f.Sync(), f.Close()); err != nil {
+                               w.Fatalf("Sync(%q)/Close(%q)=%v", path, path, err)
+                       }
+                       dbgprintf("[FS] Write(%q)\n", path)
+               },
+               Events: []EventInfo{
+                       &Call{P: path, E: Write},
+               },
+       }
+}
+
+func drainall(c chan EventInfo) (ei []EventInfo) {
+       time.Sleep(50 * time.Millisecond)
+       for {
+               select {
+               case e := <-c:
+                       ei = append(ei, e)
+                       runtime.Gosched()
+               default:
+                       return
+               }
+       }
+}
+
+type WCaseFunc func(i int, cas WCase, ei EventInfo) error
+
+func (w *W) ExpectAnyFunc(cases []WCase, fn WCaseFunc) {
+       UpdateWait() // Wait some time before starting the test.
+Test:
+       for i, cas := range cases {
+               dbgprintf("ExpectAny: i=%d\n", i)
+               cas.Action()
+               Sync()
+               switch cas.Events {
+               case nil:
+                       if ei := drainall(w.C); len(ei) != 0 {
+                               w.Fatalf("unexpected dangling events: %v (i=%d)", ei, i)
+                       }
+               default:
+                       select {
+                       case ei := <-w.C:
+                               dbgprintf("received: path=%q, event=%v, sys=%v (i=%d)", ei.Path(),
+                                       ei.Event(), ei.Sys(), i)
+                               for j, want := range cas.Events {
+                                       if err := EqualEventInfo(want, ei); err != nil {
+                                               dbgprint(err, j)
+                                               continue
+                                       }
+                                       if fn != nil {
+                                               if err := fn(i, cas, ei); err != nil {
+                                                       w.Fatalf("ExpectAnyFunc(%d, %v)=%v", i, ei, err)
+                                               }
+                                       }
+                                       drainall(w.C) // TODO(rjeczalik): revisit
+                                       continue Test
+                               }
+                               w.Fatalf("ExpectAny received an event which does not match any of "+
+                                       "the expected ones (i=%d): want one of %v; got %v", i, cas.Events, ei)
+                       case <-time.After(w.timeout()):
+                               w.Fatalf("timed out after %v waiting for one of %v (i=%d)", w.timeout(),
+                                       cas.Events, i)
+                       }
+                       drainall(w.C) // TODO(rjeczalik): revisit
+               }
+       }
+}
+
+func (w *W) ExpectAny(cases []WCase) {
+       w.ExpectAnyFunc(cases, nil)
+}
+
+func (w *W) aggregate(ei []EventInfo, pf string) (evs map[string]Event) {
+       evs = make(map[string]Event)
+       for _, cas := range ei {
+               p := cas.Path()
+               if pf != "" {
+                       p = filepath.Join(pf, p)
+               }
+               evs[p] |= cas.Event()
+       }
+       return
+}
+
+func (w *W) ExpectAllFunc(cases []WCase) {
+       UpdateWait() // Wait some time before starting the test.
+       for i, cas := range cases {
+               exp := w.aggregate(cas.Events, w.root)
+               dbgprintf("ExpectAll: i=%d\n", i)
+               cas.Action()
+               Sync()
+               got := w.aggregate(drainall(w.C), "")
+               for ep, ee := range exp {
+                       ge, ok := got[ep]
+                       if !ok {
+                               w.Fatalf("missing events for %q (%v)", ep, ee)
+                               continue
+                       }
+                       delete(got, ep)
+                       if err := HasEventInfo(ee, ge, ep); err != nil {
+                               w.Fatalf("ExpectAll received an event which does not match "+
+                                       "the expected ones for %q: want %v; got %v", ep, ee, ge)
+                               continue
+                       }
+               }
+               if len(got) != 0 {
+                       w.Fatalf("ExpectAll received unexpected events: %v", got)
+               }
+       }
+}
+
+// ExpectAll requires all requested events to be send.
+// It does not require events to be send in the same order or in the same
+// chunks (e.g. NoteWrite and NoteExtend reported as independent events are
+// treated the same as one NoteWrite|NoteExtend event).
+func (w *W) ExpectAll(cases []WCase) {
+       w.ExpectAllFunc(cases)
+}
+
+// FuncType represents enums for Watcher interface.
+type FuncType string
+
+const (
+       FuncWatch            = FuncType("Watch")
+       FuncUnwatch          = FuncType("Unwatch")
+       FuncRewatch          = FuncType("Rewatch")
+       FuncRecursiveWatch   = FuncType("RecursiveWatch")
+       FuncRecursiveUnwatch = FuncType("RecursiveUnwatch")
+       FuncRecursiveRewatch = FuncType("RecursiveRewatch")
+       FuncStop             = FuncType("Stop")
+)
+
+type Chans []chan EventInfo
+
+func NewChans(n int) Chans {
+       ch := make([]chan EventInfo, n)
+       for i := range ch {
+               ch[i] = make(chan EventInfo, buffer)
+       }
+       return ch
+}
+
+func (c Chans) Foreach(fn func(chan<- EventInfo, node)) {
+       for i, ch := range c {
+               fn(ch, node{Name: strconv.Itoa(i)})
+       }
+}
+
+func (c Chans) Drain() (ei []EventInfo) {
+       n := len(c)
+       stop := make(chan struct{})
+       eich := make(chan EventInfo, n*buffer)
+       go func() {
+               defer close(eich)
+               cases := make([]reflect.SelectCase, n+1)
+               for i := range c {
+                       cases[i].Chan = reflect.ValueOf(c[i])
+                       cases[i].Dir = reflect.SelectRecv
+               }
+               cases[n].Chan = reflect.ValueOf(stop)
+               cases[n].Dir = reflect.SelectRecv
+               for {
+                       i, v, ok := reflect.Select(cases)
+                       if i == n {
+                               return
+                       }
+                       if !ok {
+                               panic("(Chans).Drain(): unexpected chan close")
+                       }
+                       eich <- v.Interface().(EventInfo)
+               }
+       }()
+       <-time.After(50 * time.Duration(n) * time.Millisecond)
+       close(stop)
+       for e := range eich {
+               ei = append(ei, e)
+       }
+       return
+}
+
+// Call represents single call to Watcher issued by the Tree
+// and recorded by a spy Watcher mock.
+type Call struct {
+       F   FuncType       // denotes type of function to call, for both watcher and notifier interface
+       C   chan EventInfo // user channel being an argument to either Watch or Stop function
+       P   string         // regular Path argument and old path from RecursiveRewatch call
+       NP  string         // new Path argument from RecursiveRewatch call
+       E   Event          // regular Event argument and old Event from a Rewatch call
+       NE  Event          // new Event argument from Rewatch call
+       S   interface{}    // when Call is used as EventInfo, S is a value of Sys()
+       Dir bool           // when Call is used as EventInfo, Dir is a value of isDir()
+}
+
+// Call implements the EventInfo interface.
+func (c *Call) Event() Event         { return c.E }
+func (c *Call) Path() string         { return c.P }
+func (c *Call) String() string       { return fmt.Sprintf("%#v", c) }
+func (c *Call) Sys() interface{}     { return c.S }
+func (c *Call) isDir() (bool, error) { return c.Dir, nil }
+
+// CallSlice is a convenient wrapper for a slice of Call values, which allows
+// to sort them in ascending order.
+type CallSlice []Call
+
+// CallSlice implements sort.Interface inteface.
+func (cs CallSlice) Len() int           { return len(cs) }
+func (cs CallSlice) Less(i, j int) bool { return cs[i].P < cs[j].P }
+func (cs CallSlice) Swap(i, j int)      { cs[i], cs[j] = cs[j], cs[i] }
+func (cs CallSlice) Sort()              { sort.Sort(cs) }
+
+// Spy is a mock for Watcher interface, which records every call.
+type Spy []Call
+
+func (s *Spy) Close() (_ error) { return }
+
+func (s *Spy) Watch(p string, e Event) (_ error) {
+       dbgprintf("%s: (*Spy).Watch(%q, %v)", caller(), p, e)
+       *s = append(*s, Call{F: FuncWatch, P: p, E: e})
+       return
+}
+
+func (s *Spy) Unwatch(p string) (_ error) {
+       dbgprintf("%s: (*Spy).Unwatch(%q)", caller(), p)
+       *s = append(*s, Call{F: FuncUnwatch, P: p})
+       return
+}
+
+func (s *Spy) Rewatch(p string, olde, newe Event) (_ error) {
+       dbgprintf("%s: (*Spy).Rewatch(%q, %v, %v)", caller(), p, olde, newe)
+       *s = append(*s, Call{F: FuncRewatch, P: p, E: olde, NE: newe})
+       return
+}
+
+func (s *Spy) RecursiveWatch(p string, e Event) (_ error) {
+       dbgprintf("%s: (*Spy).RecursiveWatch(%q, %v)", caller(), p, e)
+       *s = append(*s, Call{F: FuncRecursiveWatch, P: p, E: e})
+       return
+}
+
+func (s *Spy) RecursiveUnwatch(p string) (_ error) {
+       dbgprintf("%s: (*Spy).RecursiveUnwatch(%q)", caller(), p)
+       *s = append(*s, Call{F: FuncRecursiveUnwatch, P: p})
+       return
+}
+
+func (s *Spy) RecursiveRewatch(oldp, newp string, olde, newe Event) (_ error) {
+       dbgprintf("%s: (*Spy).RecursiveRewatch(%q, %q, %v, %v)", caller(), oldp, newp, olde, newe)
+       *s = append(*s, Call{F: FuncRecursiveRewatch, P: oldp, NP: newp, E: olde, NE: newe})
+       return
+}
+
+type RCase struct {
+       Call   Call
+       Record []Call
+}
+
+type TCase struct {
+       Event    Call
+       Receiver Chans
+}
+
+type NCase struct {
+       Event    WCase
+       Receiver Chans
+}
+
+type N struct {
+       Timeout time.Duration
+
+       t    *testing.T
+       tree tree
+       w    *W
+       spy  *Spy
+       c    chan EventInfo
+       j    int // spy offset
+
+       realroot string
+}
+
+func newN(t *testing.T, tree string) *N {
+       n := &N{
+               t: t,
+               w: newWatcherTest(t, tree),
+       }
+       realroot, err := canonical(n.w.root)
+       if err != nil {
+               t.Fatalf("%s: unexpected fixture failure: %v", caller(), err)
+       }
+       n.realroot = realroot
+       return n
+}
+
+func newTreeN(t *testing.T, tree string) *N {
+       c := make(chan EventInfo, buffer)
+       n := newN(t, tree)
+       n.spy = &Spy{}
+       n.w.Watcher = n.spy
+       n.w.C = c
+       n.c = c
+       return n
+}
+
+func NewNotifyTest(t *testing.T, tree string) *N {
+       n := newN(t, tree)
+       if rw, ok := n.w.watcher().(recursiveWatcher); ok {
+               n.tree = newRecursiveTree(rw, n.w.c())
+       } else {
+               n.tree = newNonrecursiveTree(n.w.watcher(), n.w.c(), nil)
+       }
+       return n
+}
+
+func NewRecursiveTreeTest(t *testing.T, tree string) *N {
+       n := newTreeN(t, tree)
+       n.tree = newRecursiveTree(n.spy, n.c)
+       return n
+}
+
+func NewNonrecursiveTreeTest(t *testing.T, tree string) *N {
+       n := newTreeN(t, tree)
+       n.tree = newNonrecursiveTree(n.spy, n.c, nil)
+       return n
+}
+
+func NewNonrecursiveTreeTestC(t *testing.T, tree string) (*N, chan EventInfo) {
+       rec := make(chan EventInfo, buffer)
+       recinternal := make(chan EventInfo, buffer)
+       recuser := make(chan EventInfo, buffer)
+       go func() {
+               for ei := range rec {
+                       select {
+                       case recinternal <- ei:
+                       default:
+                               t.Fatalf("failed to send ei to recinternal: not ready")
+                       }
+                       select {
+                       case recuser <- ei:
+                       default:
+                               t.Fatalf("failed to send ei to recuser: not ready")
+                       }
+               }
+       }()
+       n := newTreeN(t, tree)
+       tr := newNonrecursiveTree(n.spy, n.c, recinternal)
+       tr.rec = rec
+       n.tree = tr
+       return n, recuser
+}
+
+func (n *N) timeout() time.Duration {
+       if n.Timeout != 0 {
+               return n.Timeout
+       }
+       return n.w.timeout()
+}
+
+func (n *N) W() *W {
+       return n.w
+}
+
+func (n *N) Close() error {
+       defer os.RemoveAll(n.w.root)
+       if err := n.tree.Close(); err != nil {
+               n.w.Fatalf("(notifier).Close()=%v", err)
+       }
+       return nil
+}
+
+func (n *N) Watch(path string, c chan<- EventInfo, events ...Event) {
+       UpdateWait() // we need to wait on Windows because of its asynchronous watcher.
+       path = filepath.Join(n.w.root, path)
+       if err := n.tree.Watch(path, c, events...); err != nil {
+               n.t.Errorf("Watch(%s, %p, %v)=%v", path, c, events, err)
+       }
+}
+
+func (n *N) WatchErr(path string, c chan<- EventInfo, err error, events ...Event) {
+       path = filepath.Join(n.w.root, path)
+       switch e := n.tree.Watch(path, c, events...); {
+       case err == nil && e == nil:
+               n.t.Errorf("Watch(%s, %p, %v)=nil", path, c, events)
+       case err != nil && e != err:
+               n.t.Errorf("Watch(%s, %p, %v)=%v != %v", path, c, events, e, err)
+       }
+}
+
+func (n *N) Stop(c chan<- EventInfo) {
+       n.tree.Stop(c)
+}
+
+func (n *N) Call(calls ...Call) {
+       for i := range calls {
+               switch calls[i].F {
+               case FuncWatch:
+                       n.Watch(calls[i].P, calls[i].C, calls[i].E)
+               case FuncStop:
+                       n.Stop(calls[i].C)
+               default:
+                       panic("unsupported call type: " + string(calls[i].F))
+               }
+       }
+}
+
+func (n *N) expectDry(ch Chans, i int) {
+       if ei := ch.Drain(); len(ei) != 0 {
+               n.w.Fatalf("unexpected dangling events: %v (i=%d)", ei, i)
+       }
+}
+
+func (n *N) ExpectRecordedCalls(cases []RCase) {
+       for i, cas := range cases {
+               dbgprintf("ExpectRecordedCalls: i=%d\n", i)
+               n.Call(cas.Call)
+               record := (*n.spy)[n.j:]
+               if len(cas.Record) == 0 && len(record) == 0 {
+                       continue
+               }
+               n.j = len(*n.spy)
+               if len(record) != len(cas.Record) {
+                       n.t.Fatalf("%s: want len(record)=%d; got %d [%+v] (i=%d)", caller(),
+                               len(cas.Record), len(record), record, i)
+               }
+               CallSlice(record).Sort()
+               for j := range cas.Record {
+                       if err := EqualCall(cas.Record[j], record[j]); err != nil {
+                               n.t.Fatalf("%s: %v (i=%d, j=%d)", caller(), err, i, j)
+                       }
+               }
+       }
+}
+
+func (n *N) collect(ch Chans) <-chan []EventInfo {
+       done := make(chan []EventInfo)
+       go func() {
+               cases := make([]reflect.SelectCase, len(ch))
+               unique := make(map[<-chan EventInfo]EventInfo, len(ch))
+               for i := range ch {
+                       cases[i].Chan = reflect.ValueOf(ch[i])
+                       cases[i].Dir = reflect.SelectRecv
+               }
+               for i := len(cases); i != 0; i = len(cases) {
+                       j, v, ok := reflect.Select(cases)
+                       if !ok {
+                               n.t.Fatal("unexpected chan close")
+                       }
+                       ch := cases[j].Chan.Interface().(chan EventInfo)
+                       got := v.Interface().(EventInfo)
+                       if ei, ok := unique[ch]; ok {
+                               n.t.Fatalf("duplicated event %v (previous=%v) received on collect", got, ei)
+                       }
+                       unique[ch] = got
+                       cases[j], cases = cases[i-1], cases[:i-1]
+               }
+               collected := make([]EventInfo, 0, len(ch))
+               for _, ch := range unique {
+                       collected = append(collected, ch)
+               }
+               done <- collected
+       }()
+       return done
+}
+
+func (n *N) abs(rel Call) *Call {
+       rel.P = filepath.Join(n.realroot, filepath.FromSlash(rel.P))
+       if !filepath.IsAbs(rel.P) {
+               rel.P = filepath.Join(wd, rel.P)
+       }
+       return &rel
+}
+
+func (n *N) ExpectTreeEvents(cases []TCase, all Chans) {
+       for i, cas := range cases {
+               dbgprintf("ExpectTreeEvents: i=%d\n", i)
+               // Ensure there're no dangling event left by previous test-case.
+               n.expectDry(all, i)
+               n.c <- n.abs(cas.Event)
+               switch cas.Receiver {
+               case nil:
+                       n.expectDry(all, i)
+               default:
+                       ch := n.collect(cas.Receiver)
+                       select {
+                       case collected := <-ch:
+                               for _, got := range collected {
+                                       if err := EqualEventInfo(&cas.Event, got); err != nil {
+                                               n.w.Fatalf("%s: %s (i=%d)", caller(), err, i)
+                                       }
+                               }
+                       case <-time.After(n.timeout()):
+                               n.w.Fatalf("ExpectTreeEvents has timed out after %v waiting for"+
+                                       " %v on %s (i=%d)", n.timeout(), cas.Event.E, cas.Event.P, i)
+                       }
+
+               }
+       }
+       n.expectDry(all, -1)
+}
+
+func (n *N) ExpectNotifyEvents(cases []NCase, all Chans) {
+       UpdateWait() // Wait some time before starting the test.
+       for i, cas := range cases {
+               dbgprintf("ExpectNotifyEvents: i=%d\n", i)
+               cas.Event.Action()
+               Sync()
+               switch cas.Receiver {
+               case nil:
+                       n.expectDry(all, i)
+               default:
+                       ch := n.collect(cas.Receiver)
+                       select {
+                       case collected := <-ch:
+                       Compare:
+                               for j, ei := range collected {
+                                       dbgprintf("received: path=%q, event=%v, sys=%v (i=%d, j=%d)", ei.Path(),
+                                               ei.Event(), ei.Sys(), i, j)
+                                       for _, want := range cas.Event.Events {
+                                               if err := EqualEventInfo(want, ei); err != nil {
+                                                       dbgprint(err, j)
+                                                       continue
+                                               }
+                                               continue Compare
+                                       }
+                                       n.w.Fatalf("ExpectNotifyEvents received an event which does not"+
+                                               " match any of the expected ones (i=%d): want one of %v; got %v", i,
+                                               cas.Event.Events, ei)
+                               }
+                       case <-time.After(n.timeout()):
+                               n.w.Fatalf("ExpectNotifyEvents did not receive any of the expected events [%v] "+
+                                       "after %v (i=%d)", cas.Event, n.timeout(), i)
+                       }
+               }
+       }
+       n.expectDry(all, -1)
+}
+
+func (n *N) Walk(fn walkFunc) {
+       switch t := n.tree.(type) {
+       case *recursiveTree:
+               if err := t.root.Walk("", fn); err != nil {
+                       n.w.Fatal(err)
+               }
+       case *nonrecursiveTree:
+               if err := t.root.Walk("", fn); err != nil {
+                       n.w.Fatal(err)
+               }
+       default:
+               n.t.Fatal("unknown tree type")
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/tree.go b/vendor/github.com/rjeczalik/notify/tree.go
new file mode 100644 (file)
index 0000000..cd6afd6
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+const buffer = 128
+
+type tree interface {
+       Watch(string, chan<- EventInfo, ...Event) error
+       Stop(chan<- EventInfo)
+       Close() error
+}
+
+func newTree() tree {
+       c := make(chan EventInfo, buffer)
+       w := newWatcher(c)
+       if rw, ok := w.(recursiveWatcher); ok {
+               return newRecursiveTree(rw, c)
+       }
+       return newNonrecursiveTree(w, c, make(chan EventInfo, buffer))
+}
diff --git a/vendor/github.com/rjeczalik/notify/tree_nonrecursive.go b/vendor/github.com/rjeczalik/notify/tree_nonrecursive.go
new file mode 100644 (file)
index 0000000..dfa72d1
--- /dev/null
@@ -0,0 +1,292 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import "sync"
+
+// nonrecursiveTree TODO(rjeczalik)
+type nonrecursiveTree struct {
+       rw   sync.RWMutex // protects root
+       root root
+       w    watcher
+       c    chan EventInfo
+       rec  chan EventInfo
+}
+
+// newNonrecursiveTree TODO(rjeczalik)
+func newNonrecursiveTree(w watcher, c, rec chan EventInfo) *nonrecursiveTree {
+       if rec == nil {
+               rec = make(chan EventInfo, buffer)
+       }
+       t := &nonrecursiveTree{
+               root: root{nd: newnode("")},
+               w:    w,
+               c:    c,
+               rec:  rec,
+       }
+       go t.dispatch(c)
+       go t.internal(rec)
+       return t
+}
+
+// dispatch TODO(rjeczalik)
+func (t *nonrecursiveTree) dispatch(c <-chan EventInfo) {
+       for ei := range c {
+               dbgprintf("dispatching %v on %q", ei.Event(), ei.Path())
+               go func(ei EventInfo) {
+                       var nd node
+                       var isrec bool
+                       dir, base := split(ei.Path())
+                       fn := func(it node, isbase bool) error {
+                               isrec = isrec || it.Watch.IsRecursive()
+                               if isbase {
+                                       nd = it
+                               } else {
+                                       it.Watch.Dispatch(ei, recursive)
+                               }
+                               return nil
+                       }
+                       t.rw.RLock()
+                       // Notify recursive watchpoints found on the path.
+                       if err := t.root.WalkPath(dir, fn); err != nil {
+                               dbgprint("dispatch did not reach leaf:", err)
+                               t.rw.RUnlock()
+                               return
+                       }
+                       // Notify parent watchpoint.
+                       nd.Watch.Dispatch(ei, 0)
+                       isrec = isrec || nd.Watch.IsRecursive()
+                       // If leaf watchpoint exists, notify it.
+                       if nd, ok := nd.Child[base]; ok {
+                               isrec = isrec || nd.Watch.IsRecursive()
+                               nd.Watch.Dispatch(ei, 0)
+                       }
+                       t.rw.RUnlock()
+                       // If the event describes newly leaf directory created within
+                       if !isrec || ei.Event() != Create {
+                               return
+                       }
+                       if ok, err := ei.(isDirer).isDir(); !ok || err != nil {
+                               return
+                       }
+                       t.rec <- ei
+               }(ei)
+       }
+}
+
+// internal TODO(rjeczalik)
+func (t *nonrecursiveTree) internal(rec <-chan EventInfo) {
+       for ei := range rec {
+               var nd node
+               var eset = internal
+               t.rw.Lock()
+               t.root.WalkPath(ei.Path(), func(it node, _ bool) error {
+                       if e := it.Watch[t.rec]; e != 0 && e > eset {
+                               eset = e
+                       }
+                       nd = it
+                       return nil
+               })
+               if eset == internal {
+                       t.rw.Unlock()
+                       continue
+               }
+               err := nd.Add(ei.Path()).AddDir(t.recFunc(eset))
+               t.rw.Unlock()
+               if err != nil {
+                       dbgprintf("internal(%p) error: %v", rec, err)
+               }
+       }
+}
+
+// watchAdd TODO(rjeczalik)
+func (t *nonrecursiveTree) watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff {
+       if e&recursive != 0 {
+               diff := nd.Watch.Add(t.rec, e|Create|omit)
+               nd.Watch.Add(c, e)
+               return diff
+       }
+       return nd.Watch.Add(c, e)
+}
+
+// watchDelMin TODO(rjeczalik)
+func (t *nonrecursiveTree) watchDelMin(min Event, nd node, c chan<- EventInfo, e Event) eventDiff {
+       old, ok := nd.Watch[t.rec]
+       if ok {
+               nd.Watch[t.rec] = min
+       }
+       diff := nd.Watch.Del(c, e)
+       if ok {
+               switch old &^= diff[0] &^ diff[1]; {
+               case old|internal == internal:
+                       delete(nd.Watch, t.rec)
+                       if set, ok := nd.Watch[nil]; ok && len(nd.Watch) == 1 && set == 0 {
+                               delete(nd.Watch, nil)
+                       }
+               default:
+                       nd.Watch.Add(t.rec, old|Create)
+                       switch {
+                       case diff == none:
+                       case diff[1]|Create == diff[0]:
+                               diff = none
+                       default:
+                               diff[1] |= Create
+                       }
+               }
+       }
+       return diff
+}
+
+// watchDel TODO(rjeczalik)
+func (t *nonrecursiveTree) watchDel(nd node, c chan<- EventInfo, e Event) eventDiff {
+       return t.watchDelMin(0, nd, c, e)
+}
+
+// Watch TODO(rjeczalik)
+func (t *nonrecursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error {
+       if c == nil {
+               panic("notify: Watch using nil channel")
+       }
+       // Expanding with empty event set is a nop.
+       if len(events) == 0 {
+               return nil
+       }
+       path, isrec, err := cleanpath(path)
+       if err != nil {
+               return err
+       }
+       eset := joinevents(events)
+       t.rw.Lock()
+       defer t.rw.Unlock()
+       nd := t.root.Add(path)
+       if isrec {
+               return t.watchrec(nd, c, eset|recursive)
+       }
+       return t.watch(nd, c, eset)
+}
+
+func (t *nonrecursiveTree) watch(nd node, c chan<- EventInfo, e Event) (err error) {
+       diff := nd.Watch.Add(c, e)
+       switch {
+       case diff == none:
+               return nil
+       case diff[1] == 0:
+               // TODO(rjeczalik): cleanup this panic after implementation is stable
+               panic("eset is empty: " + nd.Name)
+       case diff[0] == 0:
+               err = t.w.Watch(nd.Name, diff[1])
+       default:
+               err = t.w.Rewatch(nd.Name, diff[0], diff[1])
+       }
+       if err != nil {
+               nd.Watch.Del(c, diff.Event())
+               return err
+       }
+       return nil
+}
+
+func (t *nonrecursiveTree) recFunc(e Event) walkFunc {
+       return func(nd node) error {
+               switch diff := nd.Watch.Add(t.rec, e|omit|Create); {
+               case diff == none:
+               case diff[1] == 0:
+                       // TODO(rjeczalik): cleanup this panic after implementation is stable
+                       panic("eset is empty: " + nd.Name)
+               case diff[0] == 0:
+                       t.w.Watch(nd.Name, diff[1])
+               default:
+                       t.w.Rewatch(nd.Name, diff[0], diff[1])
+               }
+               return nil
+       }
+}
+
+func (t *nonrecursiveTree) watchrec(nd node, c chan<- EventInfo, e Event) error {
+       var traverse func(walkFunc) error
+       // Non-recursive tree listens on Create event for every recursive
+       // watchpoint in order to automagically set a watch for every
+       // created directory.
+       switch diff := nd.Watch.dryAdd(t.rec, e|Create); {
+       case diff == none:
+               t.watchAdd(nd, c, e)
+               nd.Watch.Add(t.rec, e|omit|Create)
+               return nil
+       case diff[1] == 0:
+               // TODO(rjeczalik): cleanup this panic after implementation is stable
+               panic("eset is empty: " + nd.Name)
+       case diff[0] == 0:
+               // TODO(rjeczalik): BFS into directories and skip subtree as soon as first
+               // recursive watchpoint is encountered.
+               traverse = nd.AddDir
+       default:
+               traverse = nd.Walk
+       }
+       // TODO(rjeczalik): account every path that failed to be (re)watched
+       // and retry.
+       if err := traverse(t.recFunc(e)); err != nil {
+               return err
+       }
+       t.watchAdd(nd, c, e)
+       return nil
+}
+
+type walkWatchpointFunc func(Event, node) error
+
+func (t *nonrecursiveTree) walkWatchpoint(nd node, fn walkWatchpointFunc) error {
+       type minode struct {
+               min Event
+               nd  node
+       }
+       mnd := minode{nd: nd}
+       stack := []minode{mnd}
+Traverse:
+       for n := len(stack); n != 0; n = len(stack) {
+               mnd, stack = stack[n-1], stack[:n-1]
+               // There must be no recursive watchpoints if the node has no watchpoints
+               // itself (every node in subtree rooted at recursive watchpoints must
+               // have at least nil (total) and t.rec watchpoints).
+               if len(mnd.nd.Watch) != 0 {
+                       switch err := fn(mnd.min, mnd.nd); err {
+                       case nil:
+                       case errSkip:
+                               continue Traverse
+                       default:
+                               return err
+                       }
+               }
+               for _, nd := range mnd.nd.Child {
+                       stack = append(stack, minode{mnd.nd.Watch[t.rec], nd})
+               }
+       }
+       return nil
+}
+
+// Stop TODO(rjeczalik)
+func (t *nonrecursiveTree) Stop(c chan<- EventInfo) {
+       fn := func(min Event, nd node) error {
+               // TODO(rjeczalik): aggregate watcher errors and retry; in worst case
+               // forward to the user.
+               switch diff := t.watchDelMin(min, nd, c, all); {
+               case diff == none:
+                       return nil
+               case diff[1] == 0:
+                       t.w.Unwatch(nd.Name)
+               default:
+                       t.w.Rewatch(nd.Name, diff[0], diff[1])
+               }
+               return nil
+       }
+       t.rw.Lock()
+       err := t.walkWatchpoint(t.root.nd, fn) // TODO(rjeczalik): store max root per c
+       t.rw.Unlock()
+       dbgprintf("Stop(%p) error: %v\n", c, err)
+}
+
+// Close TODO(rjeczalik)
+func (t *nonrecursiveTree) Close() error {
+       err := t.w.Close()
+       close(t.c)
+       return err
+}
diff --git a/vendor/github.com/rjeczalik/notify/tree_nonrecursive_test.go b/vendor/github.com/rjeczalik/notify/tree_nonrecursive_test.go
new file mode 100644 (file)
index 0000000..37625bf
--- /dev/null
@@ -0,0 +1,543 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+       "fmt"
+       "testing"
+)
+
+func TestNonrecursiveTree(t *testing.T) {
+       n := NewNonrecursiveTreeTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(5)
+
+       watches := [...]RCase{
+               // i=0
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/fs.go",
+                               C: ch[0],
+                               E: Rename,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs/fs.go",
+                                       E: Rename,
+                               },
+                       },
+               },
+               // i=1
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/cmd/...",
+                               C: ch[1],
+                               E: Remove,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd",
+                                       E: Create | Remove,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd/gotree",
+                                       E: Create | Remove,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd/mktree",
+                                       E: Create | Remove,
+                               },
+                       },
+               },
+               // i=2
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/cmd/...",
+                               C: ch[2],
+                               E: Rename,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/rjeczalik/fs/cmd",
+                                       E:  Create | Remove,
+                                       NE: Create | Remove | Rename,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/rjeczalik/fs/cmd/gotree",
+                                       E:  Create | Remove,
+                                       NE: Create | Remove | Rename,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/rjeczalik/fs/cmd/mktree",
+                                       E:  Create | Remove,
+                                       NE: Create | Remove | Rename,
+                               },
+                       },
+               },
+               // i=3
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/cmd/mktree/...",
+                               C: ch[2],
+                               E: Write,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/rjeczalik/fs/cmd/mktree",
+                                       E:  Create | Remove | Rename,
+                                       NE: Create | Remove | Rename | Write,
+                               },
+                       },
+               },
+               // i=4
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu/include",
+                               C: ch[3],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/pblaszczyk/qttu/include",
+                                       E: Create,
+                               },
+                       },
+               },
+               // i=5
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu/include/qttu/detail/...",
+                               C: ch[3],
+                               E: Write,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
+                                       E: Create | Write,
+                               },
+                       },
+               },
+               // i=6
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu/include/...",
+                               C: ch[0],
+                               E: Rename,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/include",
+                                       E:  Create,
+                                       NE: Create | Rename,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/pblaszczyk/qttu/include/qttu",
+                                       E: Create | Rename,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/include/qttu/detail",
+                                       E:  Create | Write,
+                                       NE: Create | Write | Rename,
+                               },
+                       },
+               },
+               // i=7
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/...",
+                               C: ch[1],
+                               E: Write,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/pblaszczyk",
+                                       E: Create | Write,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/pblaszczyk/qttu",
+                                       E: Create | Write,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/include",
+                                       E:  Create | Rename,
+                                       NE: Create | Rename | Write,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/include/qttu",
+                                       E:  Create | Rename,
+                                       NE: Create | Rename | Write,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/pblaszczyk/qttu/src",
+                                       E: Create | Write,
+                               },
+                       },
+               },
+               // i=8
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu/include/...",
+                               C: ch[4],
+                               E: Write,
+                       },
+                       Record: nil,
+               },
+               // i=9
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu",
+                               C: ch[3],
+                               E: Remove,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu",
+                                       E:  Create | Write,
+                                       NE: Create | Write | Remove,
+                               },
+                       },
+               },
+       }
+
+       n.ExpectRecordedCalls(watches[:])
+
+       events := [...]TCase{
+               // i=0
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
+                       Receiver: Chans{ch[0]},
+               },
+               // i=1
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Create},
+                       Receiver: nil,
+               },
+               // i=2
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/cmd.go", E: Remove},
+                       Receiver: Chans{ch[1]},
+               },
+               // i=3
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/doc.go", E: Write},
+                       Receiver: nil,
+               },
+               // i=4
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Write},
+                       Receiver: Chans{ch[2]},
+               },
+               // i=5
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/tree.go", E: Create},
+                       Receiver: nil,
+               },
+               // i=6
+               {
+                       Event:    Call{P: "src/github.com/pblaszczyk/qttu/include/.lock", E: Create},
+                       Receiver: Chans{ch[3]},
+               },
+               // i=7
+               {
+                       Event:    Call{P: "src/github.com/pblaszczyk/qttu/include/qttu/detail/registry.hh", E: Write},
+                       Receiver: Chans{ch[3], ch[1], ch[4]},
+               },
+               // i=8
+               {
+                       Event:    Call{P: "src/github.com/pblaszczyk/qttu/include/qttu", E: Remove},
+                       Receiver: nil,
+               },
+               // i=9
+               {
+                       Event:    Call{P: "src/github.com/pblaszczyk/qttu/include", E: Remove},
+                       Receiver: Chans{ch[3]},
+               },
+       }
+
+       n.ExpectTreeEvents(events[:], ch)
+
+       stops := [...]RCase{
+               // i=0
+               {
+                       Call: Call{
+                               F: FuncStop,
+                               C: ch[4],
+                       },
+                       Record: nil,
+               },
+               // i=1
+               {
+                       Call: Call{
+                               F: FuncStop,
+                               C: ch[3],
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu",
+                                       E:  Create | Write | Remove,
+                                       NE: Create | Write,
+                               },
+                       },
+               },
+               // i=2
+               {
+                       Call: Call{
+                               F: FuncStop,
+                               C: ch[2],
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/rjeczalik/fs/cmd",
+                                       E:  Create | Remove | Rename,
+                                       NE: Create | Remove,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/rjeczalik/fs/cmd/gotree",
+                                       E:  Create | Remove | Rename,
+                                       NE: Create | Remove,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/rjeczalik/fs/cmd/mktree",
+                                       E:  Create | Remove | Rename | Write,
+                                       NE: Create | Remove,
+                               },
+                       },
+               },
+               // i=3
+               {
+                       Call: Call{
+                               F: FuncStop,
+                               C: ch[1],
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/pblaszczyk",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/pblaszczyk/qttu",
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/include",
+                                       E:  Create | Rename | Write,
+                                       NE: Create | Rename,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/include/qttu",
+                                       E:  Create | Rename | Write,
+                                       NE: Create | Rename,
+                               },
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/include/qttu/detail",
+                                       E:  Create | Rename | Write,
+                                       NE: Create | Rename,
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/pblaszczyk/qttu/src",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd/gotree",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd/mktree",
+                               },
+                       },
+               },
+               // i=4
+               {
+                       Call: Call{
+                               F: FuncStop,
+                               C: ch[0],
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/pblaszczyk/qttu/include",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/pblaszczyk/qttu/include/qttu",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/rjeczalik/fs/fs.go",
+                               },
+                       },
+               },
+       }
+
+       n.ExpectRecordedCalls(stops[:])
+
+       n.Walk(func(nd node) error {
+               if len(nd.Watch) != 0 {
+                       return fmt.Errorf("unexpected watchpoint: name=%s, eventset=%v (len=%d)",
+                               nd.Name, nd.Watch.Total(), len(nd.Watch))
+               }
+               return nil
+       })
+}
+
+func TestNonrecursiveTreeInternal(t *testing.T) {
+       n, c := NewNonrecursiveTreeTestC(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(5)
+
+       watches := [...]RCase{
+               // i=0
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/cmd/...",
+                               C: ch[0],
+                               E: Remove,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd",
+                                       E: Create | Remove,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd/gotree",
+                                       E: Create | Remove,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd/mktree",
+                                       E: Create | Remove,
+                               },
+                       },
+               },
+               // i=1
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/ppknap/link/include/coost/...",
+                               C: ch[1],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/ppknap/link/include/coost",
+                                       E: Create,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/ppknap/link/include/coost/link",
+                                       E: Create,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/ppknap/link/include/coost/link/detail",
+                                       E: Create,
+                               },
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers",
+                                       E: Create,
+                               },
+                       },
+               },
+       }
+
+       n.ExpectRecordedCalls(watches[:])
+
+       events := [...]TCase{
+               // i=0
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/dir", E: Create, Dir: true},
+                       Receiver: Chans{c},
+               },
+               // i=1
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/dir/another", E: Create, Dir: true},
+                       Receiver: Chans{c},
+               },
+               // i=2
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/file", E: Create, Dir: false},
+                       Receiver: nil,
+               },
+               // i=3
+               {
+                       Event:    Call{P: "src/github.com/ppknap/link/include/coost/dir", E: Create, Dir: true},
+                       Receiver: Chans{ch[1], c},
+               },
+               // i=4
+               {
+                       Event:    Call{P: "src/github.com/ppknap/link/include/coost/dir/another", E: Create, Dir: true},
+                       Receiver: Chans{ch[1], c},
+               },
+               // i=5
+               {
+                       Event:    Call{P: "src/github.com/ppknap/link/include/coost/file", E: Create, Dir: false},
+                       Receiver: Chans{ch[1]},
+               },
+               // i=6
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/mktree", E: Remove},
+                       Receiver: Chans{ch[0]},
+               },
+               // i=7
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/rmtree", E: Create, Dir: true},
+                       Receiver: Chans{c},
+               },
+       }
+
+       n.ExpectTreeEvents(events[:], ch)
+}
diff --git a/vendor/github.com/rjeczalik/notify/tree_recursive.go b/vendor/github.com/rjeczalik/notify/tree_recursive.go
new file mode 100644 (file)
index 0000000..7f00dfe
--- /dev/null
@@ -0,0 +1,354 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import "sync"
+
+// watchAdd TODO(rjeczalik)
+func watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff {
+       diff := nd.Watch.Add(c, e)
+       if wp := nd.Child[""].Watch; len(wp) != 0 {
+               e = wp.Total()
+               diff[0] |= e
+               diff[1] |= e
+               if diff[0] == diff[1] {
+                       return none
+               }
+       }
+       return diff
+}
+
+// watchAddInactive TODO(rjeczalik)
+func watchAddInactive(nd node, c chan<- EventInfo, e Event) eventDiff {
+       wp := nd.Child[""].Watch
+       if wp == nil {
+               wp = make(watchpoint)
+               nd.Child[""] = node{Watch: wp}
+       }
+       diff := wp.Add(c, e)
+       e = nd.Watch.Total()
+       diff[0] |= e
+       diff[1] |= e
+       if diff[0] == diff[1] {
+               return none
+       }
+       return diff
+}
+
+// watchCopy TODO(rjeczalik)
+func watchCopy(src, dst node) {
+       for c, e := range src.Watch {
+               if c == nil {
+                       continue
+               }
+               watchAddInactive(dst, c, e)
+       }
+       if wpsrc := src.Child[""].Watch; len(wpsrc) != 0 {
+               wpdst := dst.Child[""].Watch
+               for c, e := range wpsrc {
+                       if c == nil {
+                               continue
+                       }
+                       wpdst.Add(c, e)
+               }
+       }
+}
+
+// watchDel TODO(rjeczalik)
+func watchDel(nd node, c chan<- EventInfo, e Event) eventDiff {
+       diff := nd.Watch.Del(c, e)
+       if wp := nd.Child[""].Watch; len(wp) != 0 {
+               diffInactive := wp.Del(c, e)
+               e = wp.Total()
+               // TODO(rjeczalik): add e if e != all?
+               diff[0] |= diffInactive[0] | e
+               diff[1] |= diffInactive[1] | e
+               if diff[0] == diff[1] {
+                       return none
+               }
+       }
+       return diff
+}
+
+// watchTotal TODO(rjeczalik)
+func watchTotal(nd node) Event {
+       e := nd.Watch.Total()
+       if wp := nd.Child[""].Watch; len(wp) != 0 {
+               e |= wp.Total()
+       }
+       return e
+}
+
+// watchIsRecursive TODO(rjeczalik)
+func watchIsRecursive(nd node) bool {
+       ok := nd.Watch.IsRecursive()
+       // TODO(rjeczalik): add a test for len(wp) != 0 change the condition.
+       if wp := nd.Child[""].Watch; len(wp) != 0 {
+               // If a watchpoint holds inactive watchpoints, it means it's a parent
+               // one, which is recursive by nature even though it may be not recursive
+               // itself.
+               ok = true
+       }
+       return ok
+}
+
+// recursiveTree TODO(rjeczalik)
+type recursiveTree struct {
+       rw   sync.RWMutex // protects root
+       root root
+       // TODO(rjeczalik): merge watcher + recursiveWatcher after #5 and #6
+       w interface {
+               watcher
+               recursiveWatcher
+       }
+       c chan EventInfo
+}
+
+// newRecursiveTree TODO(rjeczalik)
+func newRecursiveTree(w recursiveWatcher, c chan EventInfo) *recursiveTree {
+       t := &recursiveTree{
+               root: root{nd: newnode("")},
+               w: struct {
+                       watcher
+                       recursiveWatcher
+               }{w.(watcher), w},
+               c: c,
+       }
+       go t.dispatch()
+       return t
+}
+
+// dispatch TODO(rjeczalik)
+func (t *recursiveTree) dispatch() {
+       for ei := range t.c {
+               dbgprintf("dispatching %v on %q", ei.Event(), ei.Path())
+               go func(ei EventInfo) {
+                       nd, ok := node{}, false
+                       dir, base := split(ei.Path())
+                       fn := func(it node, isbase bool) error {
+                               if isbase {
+                                       nd = it
+                               } else {
+                                       it.Watch.Dispatch(ei, recursive)
+                               }
+                               return nil
+                       }
+                       t.rw.RLock()
+                       defer t.rw.RUnlock()
+                       // Notify recursive watchpoints found on the path.
+                       if err := t.root.WalkPath(dir, fn); err != nil {
+                               dbgprint("dispatch did not reach leaf:", err)
+                               return
+                       }
+                       // Notify parent watchpoint.
+                       nd.Watch.Dispatch(ei, 0)
+                       // If leaf watchpoint exists, notify it.
+                       if nd, ok = nd.Child[base]; ok {
+                               nd.Watch.Dispatch(ei, 0)
+                       }
+               }(ei)
+       }
+}
+
+// Watch TODO(rjeczalik)
+func (t *recursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error {
+       if c == nil {
+               panic("notify: Watch using nil channel")
+       }
+       // Expanding with empty event set is a nop.
+       if len(events) == 0 {
+               return nil
+       }
+       path, isrec, err := cleanpath(path)
+       if err != nil {
+               return err
+       }
+       eventset := joinevents(events)
+       if isrec {
+               eventset |= recursive
+       }
+       t.rw.Lock()
+       defer t.rw.Unlock()
+       // case 1: cur is a child
+       //
+       // Look for parent watch which already covers the given path.
+       parent := node{}
+       self := false
+       err = t.root.WalkPath(path, func(nd node, isbase bool) error {
+               if watchTotal(nd) != 0 {
+                       parent = nd
+                       self = isbase
+                       return errSkip
+               }
+               return nil
+       })
+       cur := t.root.Add(path) // add after the walk, so it's less to traverse
+       if err == nil && parent.Watch != nil {
+               // Parent watch found. Register inactive watchpoint, so we have enough
+               // information to shrink the eventset on eventual Stop.
+               // return t.resetwatchpoint(parent, parent, c, eventset|inactive)
+               var diff eventDiff
+               if self {
+                       diff = watchAdd(cur, c, eventset)
+               } else {
+                       diff = watchAddInactive(parent, c, eventset)
+               }
+               switch {
+               case diff == none:
+                       // the parent watchpoint already covers requested subtree with its
+                       // eventset
+               case diff[0] == 0:
+                       // TODO(rjeczalik): cleanup this panic after implementation is stable
+                       panic("dangling watchpoint: " + parent.Name)
+               default:
+                       if isrec || watchIsRecursive(parent) {
+                               err = t.w.RecursiveRewatch(parent.Name, parent.Name, diff[0], diff[1])
+                       } else {
+                               err = t.w.Rewatch(parent.Name, diff[0], diff[1])
+                       }
+                       if err != nil {
+                               watchDel(parent, c, diff.Event())
+                               return err
+                       }
+                       watchAdd(cur, c, eventset)
+                       // TODO(rjeczalik): account top-most path for c
+                       return nil
+               }
+               if !self {
+                       watchAdd(cur, c, eventset)
+               }
+               return nil
+       }
+       // case 2: cur is new parent
+       //
+       // Look for children nodes, unwatch n-1 of them and rewatch the last one.
+       var children []node
+       fn := func(nd node) error {
+               if len(nd.Watch) == 0 {
+                       return nil
+               }
+               children = append(children, nd)
+               return errSkip
+       }
+       switch must(cur.Walk(fn)); len(children) {
+       case 0:
+               // no child watches, cur holds a new watch
+       case 1:
+               watchAdd(cur, c, eventset) // TODO(rjeczalik): update cache c subtree root?
+               watchCopy(children[0], cur)
+               err = t.w.RecursiveRewatch(children[0].Name, cur.Name, watchTotal(children[0]),
+                       watchTotal(cur))
+               if err != nil {
+                       // Clean inactive watchpoint. The c chan did not exist before.
+                       cur.Child[""] = node{}
+                       delete(cur.Watch, c)
+                       return err
+               }
+               return nil
+       default:
+               watchAdd(cur, c, eventset)
+               // Copy children inactive watchpoints to the new parent.
+               for _, nd := range children {
+                       watchCopy(nd, cur)
+               }
+               // Watch parent subtree.
+               if err = t.w.RecursiveWatch(cur.Name, watchTotal(cur)); err != nil {
+                       // Clean inactive watchpoint. The c chan did not exist before.
+                       cur.Child[""] = node{}
+                       delete(cur.Watch, c)
+                       return err
+               }
+               // Unwatch children subtrees.
+               var e error
+               for _, nd := range children {
+                       if watchIsRecursive(nd) {
+                               e = t.w.RecursiveUnwatch(nd.Name)
+                       } else {
+                               e = t.w.Unwatch(nd.Name)
+                       }
+                       if e != nil {
+                               err = nonil(err, e)
+                               // TODO(rjeczalik): child is still watched, warn all its watchpoints
+                               // about possible duplicate events via Error event
+                       }
+               }
+               return err
+       }
+       // case 3: cur is new, alone node
+       switch diff := watchAdd(cur, c, eventset); {
+       case diff == none:
+               // TODO(rjeczalik): cleanup this panic after implementation is stable
+               panic("watch requested but no parent watchpoint found: " + cur.Name)
+       case diff[0] == 0:
+               if isrec {
+                       err = t.w.RecursiveWatch(cur.Name, diff[1])
+               } else {
+                       err = t.w.Watch(cur.Name, diff[1])
+               }
+               if err != nil {
+                       watchDel(cur, c, diff.Event())
+                       return err
+               }
+       default:
+               // TODO(rjeczalik): cleanup this panic after implementation is stable
+               panic("watch requested but no parent watchpoint found: " + cur.Name)
+       }
+       return nil
+}
+
+// Stop TODO(rjeczalik)
+//
+// TODO(rjeczalik): Split parent watchpoint - transfer watches to children
+// if parent is no longer needed. This carries a risk that underlying
+// watcher calls could fail - reconsider if it's worth the effort.
+func (t *recursiveTree) Stop(c chan<- EventInfo) {
+       var err error
+       fn := func(nd node) (e error) {
+               diff := watchDel(nd, c, all)
+               switch {
+               case diff == none && watchTotal(nd) == 0:
+                       // TODO(rjeczalik): There's no watchpoints deeper in the tree,
+                       // probably we should remove the nodes as well.
+                       return nil
+               case diff == none:
+                       // Removing c from nd does not require shrinking its eventset.
+               case diff[1] == 0:
+                       if watchIsRecursive(nd) {
+                               e = t.w.RecursiveUnwatch(nd.Name)
+                       } else {
+                               e = t.w.Unwatch(nd.Name)
+                       }
+               default:
+                       if watchIsRecursive(nd) {
+                               e = t.w.RecursiveRewatch(nd.Name, nd.Name, diff[0], diff[1])
+                       } else {
+                               e = t.w.Rewatch(nd.Name, diff[0], diff[1])
+                       }
+               }
+               fn := func(nd node) error {
+                       watchDel(nd, c, all)
+                       return nil
+               }
+               err = nonil(err, e, nd.Walk(fn))
+               // TODO(rjeczalik): if e != nil store dummy chan in nd.Watch just to
+               // retry un/rewatching next time and/or let the user handle the failure
+               // vie Error event?
+               return errSkip
+       }
+       t.rw.Lock()
+       e := t.root.Walk("", fn) // TODO(rjeczalik): use max root per c
+       t.rw.Unlock()
+       if e != nil {
+               err = nonil(err, e)
+       }
+       dbgprintf("Stop(%p) error: %v\n", c, err)
+}
+
+// Close TODO(rjeczalik)
+func (t *recursiveTree) Close() error {
+       err := t.w.Close()
+       close(t.c)
+       return err
+}
diff --git a/vendor/github.com/rjeczalik/notify/tree_recursive_test.go b/vendor/github.com/rjeczalik/notify/tree_recursive_test.go
new file mode 100644 (file)
index 0000000..329fca0
--- /dev/null
@@ -0,0 +1,524 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import "testing"
+
+func TestRecursiveTree(t *testing.T) {
+       n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(5)
+
+       watches := [...]RCase{
+               // i=0
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/fs.go",
+                               C: ch[0],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs/fs.go",
+                                       E: Create,
+                               },
+                       },
+               },
+               // i=1
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/cmd/...",
+                               C: ch[1],
+                               E: Remove,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncRecursiveWatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd",
+                                       E: Remove,
+                               },
+                       },
+               },
+               // i=2
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs",
+                               C: ch[2],
+                               E: Rename,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncRecursiveWatch,
+                                       P: "src/github.com/rjeczalik/fs",
+                                       E: Create | Remove | Rename,
+                               },
+                               {
+                                       F: FuncRecursiveUnwatch,
+                                       P: "src/github.com/rjeczalik/fs/cmd",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/rjeczalik/fs/fs.go",
+                               },
+                       },
+               },
+               // i=3
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/ppknap/link/README.md",
+                               C: ch[0],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/ppknap/link/README.md",
+                                       E: Create,
+                               },
+                       },
+               },
+               // i=4
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/ppknap/link/include/...",
+                               C: ch[3],
+                               E: Remove,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncRecursiveWatch,
+                                       P: "src/github.com/ppknap/link/include",
+                                       E: Remove,
+                               },
+                       },
+               },
+               // i=5
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/ppknap/link/include",
+                               C: ch[0],
+                               E: Write,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRecursiveRewatch,
+                                       P:  "src/github.com/ppknap/link/include",
+                                       NP: "src/github.com/ppknap/link/include",
+                                       E:  Remove,
+                                       NE: Remove | Write,
+                               },
+                       },
+               },
+               // i=6
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/ppknap/link/test/Jamfile.jam",
+                               C: ch[0],
+                               E: Rename,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/ppknap/link/test/Jamfile.jam",
+                                       E: Rename,
+                               },
+                       },
+               },
+               // i=7
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/ppknap/link/test/Jamfile.jam",
+                               C: ch[0],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/ppknap/link/test/Jamfile.jam",
+                                       E:  Rename,
+                                       NE: Rename | Create,
+                               },
+                       },
+               },
+               // i=8
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/ppknap/...",
+                               C: ch[0],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncRecursiveWatch,
+                                       P: "src/github.com/ppknap",
+                                       E: Create | Remove | Write | Rename,
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/ppknap/link/README.md",
+                               },
+                               {
+                                       F: FuncRecursiveUnwatch,
+                                       P: "src/github.com/ppknap/link/include",
+                               },
+                               {
+                                       F: FuncUnwatch,
+                                       P: "src/github.com/ppknap/link/test/Jamfile.jam",
+                               },
+                       },
+               },
+               // i=9
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/README.md",
+                               C: ch[0],
+                               E: Rename,
+                       },
+                       Record: nil,
+               },
+               // i=10
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/cmd/gotree",
+                               C: ch[2],
+                               E: Create | Remove,
+                       },
+                       Record: nil,
+               },
+               // i=11
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+                               C: ch[0],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+                                       E: Create,
+                               },
+                       },
+               },
+               // i=12
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+                               C: ch[0],
+                               E: Remove,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/src/main.cc",
+                                       E:  Create,
+                                       NE: Create | Remove,
+                               },
+                       },
+               },
+               // i=13
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+                               C: ch[0],
+                               E: Create | Remove,
+                       },
+                       Record: nil,
+               },
+               // i=14
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu/src",
+                               C: ch[0],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRecursiveRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/src/main.cc",
+                                       NP: "src/github.com/pblaszczyk/qttu/src",
+                                       E:  Create | Remove,
+                                       NE: Create | Remove,
+                               },
+                       },
+               },
+               // i=15
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/pblaszczyk/qttu",
+                               C: ch[4],
+                               E: Write,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRecursiveRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu/src",
+                                       NP: "src/github.com/pblaszczyk/qttu",
+                                       E:  Create | Remove,
+                                       NE: Create | Remove | Write,
+                               },
+                       },
+               },
+               // i=16
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/fs.go",
+                               C: ch[3],
+                               E: Rename,
+                       },
+                       Record: nil,
+               },
+       }
+
+       n.ExpectRecordedCalls(watches[:])
+
+       events := [...]TCase{
+               // i=0
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
+                       Receiver: Chans{ch[2], ch[3]},
+               },
+               // i=1
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Create},
+                       Receiver: Chans{ch[0]},
+               },
+               // i=2
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Create},
+                       Receiver: Chans{ch[0]},
+               },
+               // i=3
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs", E: Rename},
+                       Receiver: Chans{ch[2]},
+               },
+               // i=4
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs_test.go", E: Rename},
+                       Receiver: Chans{ch[2]},
+               },
+               // i=5
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Remove},
+                       Receiver: Chans{ch[1]},
+               },
+               // i=6
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/gotree", E: Remove},
+                       Receiver: Chans{ch[1], ch[2]},
+               },
+               // i=7
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd", E: Remove},
+                       Receiver: Chans{ch[1]},
+               },
+               // i=8
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Write},
+                       Receiver: nil,
+               },
+               // i=9
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Write},
+                       Receiver: nil,
+               },
+               // i=10
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs", E: Remove},
+                       Receiver: nil,
+               },
+               // i=11
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd", E: Rename},
+                       Receiver: Chans{ch[2]},
+               },
+               // i=12
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Write},
+                       Receiver: nil,
+               },
+               // i=13
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/gotree", E: Rename},
+                       Receiver: nil,
+               },
+               // i=14
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/file", E: Rename},
+                       Receiver: nil,
+               },
+               // i=15
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
+                       Receiver: Chans{ch[2], ch[3]},
+               },
+       }
+
+       n.ExpectTreeEvents(events[:], ch)
+
+       stops := [...]RCase{
+               // i=0
+               {
+                       Call: Call{
+                               F: FuncStop,
+                               C: ch[1],
+                       },
+                       Record: nil,
+               },
+               {
+                       Call: Call{
+                               F: FuncStop,
+                               C: ch[4],
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRecursiveRewatch,
+                                       P:  "src/github.com/pblaszczyk/qttu",
+                                       NP: "src/github.com/pblaszczyk/qttu",
+                                       E:  Create | Remove | Write,
+                                       NE: Create | Remove,
+                               },
+                       },
+               },
+       }
+
+       n.ExpectRecordedCalls(stops[:])
+}
+
+func TestRecursiveTreeWatchInactiveMerge(t *testing.T) {
+       n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       ch := NewChans(1)
+
+       watches := [...]RCase{
+               // i=0
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs",
+                               C: ch[0],
+                               E: Create,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs",
+                                       E: Create,
+                               },
+                       },
+               },
+               // i=1
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs/cmd/gotree/...",
+                               C: ch[0],
+                               E: Remove,
+                       },
+                       Record: []Call{
+                               {
+                                       F:  FuncRecursiveRewatch,
+                                       P:  "src/github.com/rjeczalik/fs",
+                                       NP: "src/github.com/rjeczalik/fs",
+                                       E:  Create,
+                                       NE: Create | Remove,
+                               },
+                       },
+               },
+       }
+
+       n.ExpectRecordedCalls(watches[:])
+
+       events := [...]TCase{
+               // i=0
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/.fs.go.swp", E: Create},
+                       Receiver: Chans{ch[0]},
+               },
+               // i=1
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/.fs.go.swp", E: Remove},
+                       Receiver: nil,
+               },
+               // i=2
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs", E: Remove},
+                       Receiver: nil,
+               },
+               // i=3
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/cmd/gotree/main.go", E: Remove},
+                       Receiver: Chans{ch[0]},
+               },
+       }
+
+       n.ExpectTreeEvents(events[:], ch)
+}
+
+func TestRecursiveTree_Windows(t *testing.T) {
+       n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
+       defer n.Close()
+
+       const ChangeFileName = Event(0x1)
+
+       ch := NewChans(1)
+
+       watches := [...]RCase{
+               // i=0
+               {
+                       Call: Call{
+                               F: FuncWatch,
+                               P: "src/github.com/rjeczalik/fs",
+                               C: ch[0],
+                               E: ChangeFileName,
+                       },
+                       Record: []Call{
+                               {
+                                       F: FuncWatch,
+                                       P: "src/github.com/rjeczalik/fs",
+                                       E: ChangeFileName,
+                               },
+                       },
+               },
+       }
+
+       n.ExpectRecordedCalls(watches[:])
+
+       events := [...]TCase{
+               // i=0
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs", E: ChangeFileName},
+                       Receiver: Chans{ch[0]},
+               },
+               // i=1
+               {
+                       Event:    Call{P: "src/github.com/rjeczalik/fs/fs.go", E: ChangeFileName},
+                       Receiver: Chans{ch[0]},
+               },
+       }
+
+       n.ExpectTreeEvents(events[:], ch)
+}
diff --git a/vendor/github.com/rjeczalik/notify/util.go b/vendor/github.com/rjeczalik/notify/util.go
new file mode 100644 (file)
index 0000000..67e01fb
--- /dev/null
@@ -0,0 +1,150 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+       "errors"
+       "os"
+       "path/filepath"
+       "strings"
+)
+
+const all = ^Event(0)
+const sep = string(os.PathSeparator)
+
+var errDepth = errors.New("exceeded allowed iteration count (circular symlink?)")
+
+func min(i, j int) int {
+       if i > j {
+               return j
+       }
+       return i
+}
+
+func max(i, j int) int {
+       if i < j {
+               return j
+       }
+       return i
+}
+
+// must panics if err is non-nil.
+func must(err error) {
+       if err != nil {
+               panic(err)
+       }
+}
+
+// nonil gives first non-nil error from the given arguments.
+func nonil(err ...error) error {
+       for _, err := range err {
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func cleanpath(path string) (realpath string, isrec bool, err error) {
+       if strings.HasSuffix(path, "...") {
+               isrec = true
+               path = path[:len(path)-3]
+       }
+       if path, err = filepath.Abs(path); err != nil {
+               return "", false, err
+       }
+       if path, err = canonical(path); err != nil {
+               return "", false, err
+       }
+       return path, isrec, nil
+}
+
+// canonical resolves any symlink in the given path and returns it in a clean form.
+// It expects the path to be absolute. It fails to resolve circular symlinks by
+// maintaining a simple iteration limit.
+func canonical(p string) (string, error) {
+       p, err := filepath.Abs(p)
+       if err != nil {
+               return "", err
+       }
+       for i, j, depth := 1, 0, 1; i < len(p); i, depth = i+1, depth+1 {
+               if depth > 128 {
+                       return "", &os.PathError{Op: "canonical", Path: p, Err: errDepth}
+               }
+               if j = strings.IndexRune(p[i:], '/'); j == -1 {
+                       j, i = i, len(p)
+               } else {
+                       j, i = i, i+j
+               }
+               fi, err := os.Lstat(p[:i])
+               if err != nil {
+                       return "", err
+               }
+               if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
+                       s, err := os.Readlink(p[:i])
+                       if err != nil {
+                               return "", err
+                       }
+                       if filepath.IsAbs(s) {
+                               p = "/" + s + p[i:]
+                       } else {
+                               p = p[:j] + s + p[i:]
+                       }
+                       i = 1 // no guarantee s is canonical, start all over
+               }
+       }
+       return filepath.Clean(p), nil
+}
+
+func joinevents(events []Event) (e Event) {
+       if len(events) == 0 {
+               e = All
+       } else {
+               for _, event := range events {
+                       e |= event
+               }
+       }
+       return
+}
+
+func split(s string) (string, string) {
+       if i := lastIndexSep(s); i != -1 {
+               return s[:i], s[i+1:]
+       }
+       return "", s
+}
+
+func base(s string) string {
+       if i := lastIndexSep(s); i != -1 {
+               return s[i+1:]
+       }
+       return s
+}
+
+func indexbase(root, name string) int {
+       if n, m := len(root), len(name); m >= n && name[:n] == root &&
+               (n == m || name[n] == os.PathSeparator) {
+               return min(n+1, m)
+       }
+       return -1
+}
+
+func indexSep(s string) int {
+       for i := 0; i < len(s); i++ {
+               if s[i] == os.PathSeparator {
+                       return i
+               }
+       }
+       return -1
+}
+
+func lastIndexSep(s string) int {
+       for i := len(s) - 1; i >= 0; i-- {
+               if s[i] == os.PathSeparator {
+                       return i
+               }
+       }
+       return -1
+}
diff --git a/vendor/github.com/rjeczalik/notify/util_darwin_test.go b/vendor/github.com/rjeczalik/notify/util_darwin_test.go
new file mode 100644 (file)
index 0000000..8c95db3
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin
+
+package notify
+
+import (
+       "os"
+       "testing"
+)
+
+func TestCanonicalDarwin(t *testing.T) {
+       cases := [...]caseCanonical{
+               {"/etc", "/private/etc"},
+               {"/etc/defaults", "/private/etc/defaults"},
+               {"/etc/hosts", "/private/etc/hosts"},
+               {"/tmp", "/private/tmp"},
+               {"/var", "/private/var"},
+       }
+       testCanonical(t, cases[:])
+}
+
+func TestCanonicalDarwinMultiple(t *testing.T) {
+       etcsym, err := symlink("/etc", "")
+       if err != nil {
+               t.Fatal(err)
+       }
+       tmpsym, err := symlink("/tmp", "")
+       if err != nil {
+               t.Fatal(nonil(err, os.Remove(etcsym)))
+       }
+       defer removeall(etcsym, tmpsym)
+       cases := [...]caseCanonical{
+               {etcsym, "/private/etc"},
+               {etcsym + "/hosts", "/private/etc/hosts"},
+               {tmpsym, "/private/tmp"},
+       }
+       testCanonical(t, cases[:])
+}
diff --git a/vendor/github.com/rjeczalik/notify/util_test.go b/vendor/github.com/rjeczalik/notify/util_test.go
new file mode 100644 (file)
index 0000000..dcf6616
--- /dev/null
@@ -0,0 +1,142 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+       "path/filepath"
+       "testing"
+)
+
+type caseCanonical struct {
+       path string
+       full string
+}
+
+func testCanonical(t *testing.T, cases []caseCanonical) {
+       for i, cas := range cases {
+               full, err := canonical(cas.path)
+               if err != nil {
+                       t.Errorf("want err=nil; got %v (i=%d)", err, i)
+                       continue
+               }
+               if full != cas.full {
+                       t.Errorf("want full=%q; got %q (i=%d)", cas.full, full, i)
+                       continue
+               }
+       }
+}
+
+func TestCanonicalNoSymlink(t *testing.T) {
+       td := filepath.Join(wd, "testdata")
+       cases := [...]caseCanonical{
+               {".", wd},
+               {"testdata", td},
+               {filepath.Join("testdata", ".."), wd},
+       }
+       testCanonical(t, cases[:])
+}
+
+func TestJoinevents(t *testing.T) {
+       cases := [...]struct {
+               evs []Event
+               ev  Event
+       }{
+               0: {nil, All},
+               1: {[]Event{}, All},
+               2: {[]Event{Create}, Create},
+               3: {[]Event{Rename}, Rename},
+               4: {[]Event{Create, Write, Remove}, Create | Write | Remove},
+       }
+       for i, cas := range cases {
+               if ev := joinevents(cas.evs); ev != cas.ev {
+                       t.Errorf("want event=%v; got %v (i=%d)", cas.ev, ev, i)
+               }
+       }
+}
+
+func TestTreeSplit(t *testing.T) {
+       cases := [...]struct {
+               path string
+               dir  string
+               base string
+       }{
+               {"/github.com/rjeczalik/fakerpc", "/github.com/rjeczalik", "fakerpc"},
+               {"/home/rjeczalik/src", "/home/rjeczalik", "src"},
+               {"/Users/pknap/porn/gopher.avi", "/Users/pknap/porn", "gopher.avi"},
+               {"C:/Documents and Users", "C:", "Documents and Users"},
+               {"C:/Documents and Users/pblaszczyk/wiertarka.exe", "C:/Documents and Users/pblaszczyk", "wiertarka.exe"},
+               {"/home/(╯°□°)╯︵ ┻━┻", "/home", "(╯°□°)╯︵ ┻━┻"},
+       }
+       for i, cas := range cases {
+               dir, base := split(filepath.FromSlash(cas.path))
+               if want := filepath.FromSlash(cas.dir); dir != want {
+                       t.Errorf("want dir=%s; got %s (i=%d)", want, dir, i)
+               }
+               if want := filepath.FromSlash(cas.base); base != want {
+                       t.Errorf("want base=%s; got %s (i=%d)", want, base, i)
+               }
+       }
+}
+
+func TestTreeBase(t *testing.T) {
+       cases := [...]struct {
+               path string
+               base string
+       }{
+               {"/github.com/rjeczalik/fakerpc", "fakerpc"},
+               {"/home/rjeczalik/src", "src"},
+               {"/Users/pknap/porn/gopher.avi", "gopher.avi"},
+               {"C:/Documents and Users", "Documents and Users"},
+               {"C:/Documents and Users/pblaszczyk/wiertarka.exe", "wiertarka.exe"},
+               {"/home/(╯°□°)╯︵ ┻━┻", "(╯°□°)╯︵ ┻━┻"},
+       }
+       for i, cas := range cases {
+               if base := base(filepath.FromSlash(cas.path)); base != cas.base {
+                       t.Errorf("want base=%s; got %s (i=%d)", cas.base, base, i)
+               }
+       }
+}
+
+func TestTreeIndexSep(t *testing.T) {
+       cases := [...]struct {
+               path string
+               n    int
+       }{
+               {"github.com/rjeczalik/fakerpc", 10},
+               {"home/rjeczalik/src", 4},
+               {"Users/pknap/porn/gopher.avi", 5},
+               {"C:/Documents and Users", 2},
+               {"Documents and Users/pblaszczyk/wiertarka.exe", 19},
+               {"(╯°□°)╯︵ ┻━┻/Downloads", 30},
+       }
+       for i, cas := range cases {
+               if n := indexSep(filepath.FromSlash(cas.path)); n != cas.n {
+                       t.Errorf("want n=%d; got %d (i=%d)", cas.n, n, i)
+               }
+       }
+}
+
+func TestTreeLastIndexSep(t *testing.T) {
+       cases := [...]struct {
+               path string
+               n    int
+       }{
+               {"github.com/rjeczalik/fakerpc", 20},
+               {"home/rjeczalik/src", 14},
+               {"Users/pknap/porn/gopher.avi", 16},
+               {"C:/Documents and Users", 2},
+               {"Documents and Users/pblaszczyk/wiertarka.exe", 30},
+               {"/home/(╯°□°)╯︵ ┻━┻", 5},
+       }
+       for i, cas := range cases {
+               if n := lastIndexSep(filepath.FromSlash(cas.path)); n != cas.n {
+                       t.Errorf("want n=%d; got %d (i=%d)", cas.n, n, i)
+               }
+       }
+}
+
+func TestCleanpath(t *testing.T) {
+       t.Skip("TODO(rjeczalik)")
+}
diff --git a/vendor/github.com/rjeczalik/notify/util_unix_test.go b/vendor/github.com/rjeczalik/notify/util_unix_test.go
new file mode 100644 (file)
index 0000000..b944db2
--- /dev/null
@@ -0,0 +1,125 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !windows
+
+package notify
+
+import (
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "testing"
+)
+
+func tmpfile(s string) (string, error) {
+       f, err := ioutil.TempFile(filepath.Split(s))
+       if err != nil {
+               return "", err
+       }
+       if err = nonil(f.Sync(), f.Close()); err != nil {
+               return "", err
+       }
+       return f.Name(), nil
+}
+
+func symlink(src, dst string) (string, error) {
+       name, err := tmpfile(dst)
+       if err != nil {
+               return "", err
+       }
+       if err = nonil(os.Remove(name), os.Symlink(src, name)); err != nil {
+               return "", err
+       }
+       return name, nil
+}
+
+func removeall(s ...string) {
+       for _, s := range s {
+               os.Remove(s)
+       }
+}
+
+func TestCanonical(t *testing.T) {
+       wd, err := os.Getwd()
+       if err != nil {
+               t.Fatalf("os.Getwd()=%v", err)
+       }
+       wdsym, err := symlink(wd, "")
+       if err != nil {
+               t.Fatalf(`symlink(%q, "")=%v`, wd, err)
+       }
+       td := filepath.Join(wd, "testdata")
+       tdsym, err := symlink(td, td)
+       if err != nil {
+               t.Errorf("symlink(%q, %q)=%v", td, td, nonil(err, os.Remove(wdsym)))
+       }
+       defer removeall(wdsym, tdsym)
+       vfstxt := filepath.Join(td, "vfs.txt")
+       cases := [...]caseCanonical{
+               {wdsym, wd},
+               {tdsym, td},
+               {filepath.Join(wdsym, "notify.go"), filepath.Join(wd, "notify.go")},
+               {filepath.Join(tdsym, "vfs.txt"), vfstxt},
+               {filepath.Join(wdsym, filepath.Base(tdsym), "vfs.txt"), vfstxt},
+       }
+       testCanonical(t, cases[:])
+}
+
+func TestCanonicalCircular(t *testing.T) {
+       tmp1, err := tmpfile("circular")
+       if err != nil {
+               t.Fatal(err)
+       }
+       tmp2, err := tmpfile("circular")
+       if err != nil {
+               t.Fatal(nonil(err, os.Remove(tmp1)))
+       }
+       defer removeall(tmp1, tmp2)
+       // Symlink tmp1 -> tmp2.
+       if err = nonil(os.Remove(tmp1), os.Symlink(tmp2, tmp1)); err != nil {
+               t.Fatal(err)
+       }
+       // Symlnik tmp2 -> tmp1.
+       if err = nonil(os.Remove(tmp2), os.Symlink(tmp1, tmp2)); err != nil {
+               t.Fatal(err)
+       }
+       if _, err = canonical(tmp1); err == nil {
+               t.Fatalf("want canonical(%q)!=nil", tmp1)
+       }
+       if _, ok := err.(*os.PathError); !ok {
+               t.Fatalf("want canonical(%q)=os.PathError; got %T", tmp1, err)
+       }
+}
+
+// issue #83
+func TestCanonical_RelativeSymlink(t *testing.T) {
+       dir, err := ioutil.TempDir(wd, "")
+       if err != nil {
+               t.Fatalf("TempDir()=%v", err)
+       }
+       var (
+               path     = filepath.Join(dir, filepath.FromSlash("a/b/c/d/e/f"))
+               realpath = filepath.Join(dir, filepath.FromSlash("a/b/x/y/z/d/e/f"))
+               rel      = filepath.FromSlash("x/y/z/../z/../z")
+               chdir    = filepath.Join(dir, filepath.FromSlash("a/b"))
+       )
+       defer os.RemoveAll(dir)
+       if err = os.MkdirAll(realpath, 0755); err != nil {
+               t.Fatalf("MkdirAll()=%v", err)
+       }
+       if err := os.Chdir(chdir); err != nil {
+               t.Fatalf("Chdir()=%v", err)
+       }
+       if err := nonil(os.Symlink(rel, "c"), os.Chdir(wd)); err != nil {
+               t.Fatalf("Symlink()=%v", err)
+       }
+       got, err := canonical(path)
+       if err != nil {
+               t.Fatalf("canonical(%s)=%v", path, err)
+       }
+       if got != realpath {
+               t.Fatalf("want canonical()=%s; got %s", realpath, got)
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher.go b/vendor/github.com/rjeczalik/notify/watcher.go
new file mode 100644 (file)
index 0000000..34148ef
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import "errors"
+
+var (
+       errAlreadyWatched  = errors.New("path is already watched")
+       errNotWatched      = errors.New("path is not being watched")
+       errInvalidEventSet = errors.New("invalid event set provided")
+)
+
+// Watcher is a intermediate interface for wrapping inotify, ReadDirChangesW,
+// FSEvents, kqueue and poller implementations.
+//
+// The watcher implementation is expected to do its own mapping between paths and
+// create watchers if underlying event notification does not support it. For
+// the ease of implementation it is guaranteed that paths provided via Watch and
+// Unwatch methods are absolute and clean.
+type watcher interface {
+       // Watch requests a watcher creation for the given path and given event set.
+       Watch(path string, event Event) error
+
+       // Unwatch requests a watcher deletion for the given path and given event set.
+       Unwatch(path string) error
+
+       // Rewatch provides a functionality for modifying existing watch-points, like
+       // expanding its event set.
+       //
+       // Rewatch modifies existing watch-point under for the given path. It passes
+       // the existing event set currently registered for the given path, and the
+       // new, requested event set.
+       //
+       // It is guaranteed that Tree will not pass to Rewatch zero value for any
+       // of its arguments. If old == new and watcher can be upgraded to
+       // recursiveWatcher interface, a watch for the corresponding path is expected
+       // to be changed from recursive to the non-recursive one.
+       Rewatch(path string, old, new Event) error
+
+       // Close unwatches all paths that are registered. When Close returns, it
+       // is expected it will report no more events.
+       Close() error
+}
+
+// RecursiveWatcher is an interface for a Watcher for those OS, which do support
+// recursive watching over directories.
+type recursiveWatcher interface {
+       RecursiveWatch(path string, event Event) error
+
+       // RecursiveUnwatch removes a recursive watch-point given by the path. For
+       // native recursive implementation there is no difference in functionality
+       // between Unwatch and RecursiveUnwatch, however for those platforms, that
+       // requires emulation for recursive watch-points, the implementation differs.
+       RecursiveUnwatch(path string) error
+
+       // RecursiveRewatcher provides a functionality for modifying and/or relocating
+       // existing recursive watch-points.
+       //
+       // To relocate a watch-point means to unwatch oldpath and set a watch-point on
+       // newpath.
+       //
+       // To modify a watch-point means either to expand or shrink its event set.
+       //
+       // Tree can want to either relocate, modify or relocate and modify a watch-point
+       // via single RecursiveRewatch call.
+       //
+       // If oldpath == newpath, the watch-point is expected to change its event set value
+       // from oldevent to newevent.
+       //
+       // If oldevent == newevent, the watch-point is expected to relocate from oldpath
+       // to the newpath.
+       //
+       // If oldpath != newpath and oldevent != newevent, the watch-point is expected
+       // to relocate from oldpath to the newpath first and then change its event set
+       // value from oldevent to the newevent. In other words the end result must be
+       // a watch-point set on newpath with newevent value of its event set.
+       //
+       // It is guaranteed that Tree will not pass to RecurisveRewatcha zero value
+       // for any of its arguments. If oldpath == newpath and oldevent == newevent,
+       // a watch for the corresponding path is expected to be changed for
+       // non-recursive to the recursive one.
+       RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_fen.go b/vendor/github.com/rjeczalik/notify/watcher_fen.go
new file mode 100644 (file)
index 0000000..dd069f2
--- /dev/null
@@ -0,0 +1,168 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build solaris
+
+package notify
+
+import (
+       "fmt"
+       "os"
+       "syscall"
+)
+
+// newTrigger returns implementation of trigger.
+func newTrigger(pthLkp map[string]*watched) trigger {
+       return &fen{
+               pthLkp: pthLkp,
+               cf:     newCfen(),
+       }
+}
+
+// fen is a structure implementing trigger for FEN.
+type fen struct {
+       // p is a FEN port identifier
+       p int
+       // pthLkp is a structure mapping monitored files/dir with data about them,
+       // shared with parent trg structure
+       pthLkp map[string]*watched
+       // cf wraps C operations for FEN
+       cf cfen
+}
+
+// watched is a data structure representing watched file/directory.
+type watched struct {
+       // p is a path to watched file/directory
+       p string
+       // fi provides information about watched file/dir
+       fi os.FileInfo
+       // eDir represents events watched directly
+       eDir Event
+       // eNonDir represents events watched indirectly
+       eNonDir Event
+}
+
+// Stop implements trigger.
+func (f *fen) Stop() error {
+       return f.cf.portAlert(f.p)
+}
+
+// Close implements trigger.
+func (f *fen) Close() (err error) {
+       return syscall.Close(f.p)
+}
+
+// NewWatched implements trigger.
+func (*fen) NewWatched(p string, fi os.FileInfo) (*watched, error) {
+       return &watched{p: p, fi: fi}, nil
+}
+
+// Record implements trigger.
+func (f *fen) Record(w *watched) {
+       f.pthLkp[w.p] = w
+}
+
+// Del implements trigger.
+func (f *fen) Del(w *watched) {
+       delete(f.pthLkp, w.p)
+}
+
+func inter2pe(n interface{}) PortEvent {
+       pe, ok := n.(PortEvent)
+       if !ok {
+               panic(fmt.Sprintf("fen: type should be PortEvent, %T instead", n))
+       }
+       return pe
+}
+
+// Watched implements trigger.
+func (f *fen) Watched(n interface{}) (*watched, int64, error) {
+       pe := inter2pe(n)
+       fo, ok := pe.PortevObject.(*FileObj)
+       if !ok || fo == nil {
+               panic(fmt.Sprintf("fen: type should be *FileObj, %T instead", fo))
+       }
+       w, ok := f.pthLkp[fo.Name]
+       if !ok {
+               return nil, 0, errNotWatched
+       }
+       return w, int64(pe.PortevEvents), nil
+}
+
+// init initializes FEN.
+func (f *fen) Init() (err error) {
+       f.p, err = f.cf.portCreate()
+       return
+}
+
+func fi2fo(fi os.FileInfo, p string) FileObj {
+       st, ok := fi.Sys().(*syscall.Stat_t)
+       if !ok {
+               panic(fmt.Sprintf("fen: type should be *syscall.Stat_t, %T instead", st))
+       }
+       return FileObj{Name: p, Atim: st.Atim, Mtim: st.Mtim, Ctim: st.Ctim}
+}
+
+// Unwatch implements trigger.
+func (f *fen) Unwatch(w *watched) error {
+       return f.cf.portDissociate(f.p, FileObj{Name: w.p})
+}
+
+// Watch implements trigger.
+func (f *fen) Watch(fi os.FileInfo, w *watched, e int64) error {
+       return f.cf.portAssociate(f.p, fi2fo(fi, w.p), int(e))
+}
+
+// Wait implements trigger.
+func (f *fen) Wait() (interface{}, error) {
+       var (
+               pe  PortEvent
+               err error
+       )
+       err = f.cf.portGet(f.p, &pe)
+       return pe, err
+}
+
+// IsStop implements trigger.
+func (f *fen) IsStop(n interface{}, err error) bool {
+       return err == syscall.EBADF || inter2pe(n).PortevSource == srcAlert
+}
+
+func init() {
+       encode = func(e Event, dir bool) (o int64) {
+               // Create event is not supported by FEN. Instead FileModified event will
+               // be registered. If this event will be reported on dir which is to be
+               // monitored for Create, dir will be rescanned and Create events will
+               // be generated and returned for new files. In case of files,
+               // if not requested FileModified event is reported, it will be ignored.
+               o = int64(e &^ Create)
+               if (e&Create != 0 && dir) || e&Write != 0 {
+                       o = (o &^ int64(Write)) | int64(FileModified)
+               }
+               // Following events are 'exception events' and as such cannot be requested
+               // explicitly for monitoring or filtered out. If the will be reported
+               // by FEN and not subscribed with by user, they will be filtered out by
+               // watcher's logic.
+               o &= int64(^Rename & ^Remove &^ FileDelete &^ FileRenameTo &^
+                       FileRenameFrom &^ Unmounted &^ MountedOver)
+               return
+       }
+       nat2not = map[Event]Event{
+               FileModified:   Write,
+               FileRenameFrom: Rename,
+               FileDelete:     Remove,
+               FileAccess:     Event(0),
+               FileAttrib:     Event(0),
+               FileRenameTo:   Event(0),
+               FileTrunc:      Event(0),
+               FileNoFollow:   Event(0),
+               Unmounted:      Event(0),
+               MountedOver:    Event(0),
+       }
+       not2nat = map[Event]Event{
+               Write:  FileModified,
+               Rename: FileRenameFrom,
+               Remove: FileDelete,
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go b/vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go
new file mode 100644 (file)
index 0000000..8ec8ead
--- /dev/null
@@ -0,0 +1,141 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build solaris
+
+package notify
+
+// #include <port.h>
+// #include <stdio.h>
+// #include <stdlib.h>
+// struct file_obj* newFo() { return (struct file_obj*) malloc(sizeof(struct file_obj)); }
+// port_event_t* newPe() { return (port_event_t*) malloc(sizeof(port_event_t)); }
+// uintptr_t conv(struct file_obj* fo) { return (uintptr_t) fo; }
+// struct file_obj* dconv(uintptr_t fo) { return (struct file_obj*) fo; }
+import "C"
+
+import (
+       "syscall"
+       "unsafe"
+)
+
+const (
+       fileAccess     = Event(C.FILE_ACCESS)
+       fileModified   = Event(C.FILE_MODIFIED)
+       fileAttrib     = Event(C.FILE_ATTRIB)
+       fileDelete     = Event(C.FILE_DELETE)
+       fileRenameTo   = Event(C.FILE_RENAME_TO)
+       fileRenameFrom = Event(C.FILE_RENAME_FROM)
+       fileTrunc      = Event(C.FILE_TRUNC)
+       fileNoFollow   = Event(C.FILE_NOFOLLOW)
+       unmounted      = Event(C.UNMOUNTED)
+       mountedOver    = Event(C.MOUNTEDOVER)
+)
+
+// PortEvent is a notify's equivalent of port_event_t.
+type PortEvent struct {
+       PortevEvents int         // PortevEvents is an equivalent of portev_events.
+       PortevSource uint8       // PortevSource is an equivalent of portev_source.
+       PortevPad    uint8       // Portevpad is an equivalent of portev_pad.
+       PortevObject interface{} // PortevObject is an equivalent of portev_object.
+       PortevUser   uintptr     // PortevUser is an equivalent of portev_user.
+}
+
+// FileObj is a notify's equivalent of file_obj.
+type FileObj struct {
+       Atim syscall.Timespec // Atim is an equivalent of fo_atime.
+       Mtim syscall.Timespec // Mtim is an equivalent of fo_mtime.
+       Ctim syscall.Timespec // Ctim is an equivalent of fo_ctime.
+       Pad  [3]uintptr       // Pad is an equivalent of fo_pad.
+       Name string           // Name is an equivalent of fo_name.
+}
+
+type cfen struct {
+       p2pe map[string]*C.port_event_t
+       p2fo map[string]*C.struct_file_obj
+}
+
+func newCfen() cfen {
+       return cfen{
+               p2pe: make(map[string]*C.port_event_t),
+               p2fo: make(map[string]*C.struct_file_obj),
+       }
+}
+
+func unix2C(sec int64, nsec int64) (C.time_t, C.long) {
+       return C.time_t(sec), C.long(nsec)
+}
+
+func (c *cfen) portAssociate(p int, fo FileObj, e int) (err error) {
+       cfo := C.newFo()
+       cfo.fo_atime.tv_sec, cfo.fo_atime.tv_nsec = unix2C(fo.Atim.Unix())
+       cfo.fo_mtime.tv_sec, cfo.fo_mtime.tv_nsec = unix2C(fo.Mtim.Unix())
+       cfo.fo_ctime.tv_sec, cfo.fo_ctime.tv_nsec = unix2C(fo.Ctim.Unix())
+       cfo.fo_name = C.CString(fo.Name)
+       c.p2fo[fo.Name] = cfo
+       _, err = C.port_associate(C.int(p), srcFile, C.conv(cfo), C.int(e), nil)
+       return
+}
+
+func (c *cfen) portDissociate(port int, fo FileObj) (err error) {
+       cfo, ok := c.p2fo[fo.Name]
+       if !ok {
+               return errNotWatched
+       }
+       _, err = C.port_dissociate(C.int(port), srcFile, C.conv(cfo))
+       C.free(unsafe.Pointer(cfo.fo_name))
+       C.free(unsafe.Pointer(cfo))
+       delete(c.p2fo, fo.Name)
+       return
+}
+
+const srcAlert = C.PORT_SOURCE_ALERT
+const srcFile = C.PORT_SOURCE_FILE
+const alertSet = C.PORT_ALERT_SET
+
+func cfo2fo(cfo *C.struct_file_obj) *FileObj {
+       // Currently remaining attributes are not used.
+       if cfo == nil {
+               return nil
+       }
+       var fo FileObj
+       fo.Name = C.GoString(cfo.fo_name)
+       return &fo
+}
+
+func (c *cfen) portGet(port int, pe *PortEvent) (err error) {
+       cpe := C.newPe()
+       if _, err = C.port_get(C.int(port), cpe, nil); err != nil {
+               C.free(unsafe.Pointer(cpe))
+               return
+       }
+       pe.PortevEvents, pe.PortevSource, pe.PortevPad =
+               int(cpe.portev_events), uint8(cpe.portev_source), uint8(cpe.portev_pad)
+       pe.PortevObject = cfo2fo(C.dconv(cpe.portev_object))
+       pe.PortevUser = uintptr(cpe.portev_user)
+       C.free(unsafe.Pointer(cpe))
+       return
+}
+
+func (c *cfen) portCreate() (int, error) {
+       p, err := C.port_create()
+       return int(p), err
+}
+
+func (c *cfen) portAlert(p int) (err error) {
+       _, err = C.port_alert(C.int(p), alertSet, C.int(666), nil)
+       return
+}
+
+func (c *cfen) free() {
+       for i := range c.p2fo {
+               C.free(unsafe.Pointer(c.p2fo[i].fo_name))
+               C.free(unsafe.Pointer(c.p2fo[i]))
+       }
+       for i := range c.p2pe {
+               C.free(unsafe.Pointer(c.p2pe[i]))
+       }
+       c.p2fo = make(map[string]*C.struct_file_obj)
+       c.p2pe = make(map[string]*C.port_event_t)
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_fen_test.go b/vendor/github.com/rjeczalik/notify/watcher_fen_test.go
new file mode 100644 (file)
index 0000000..7f811ae
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build solaris
+
+package notify
+
+import (
+       "os"
+       "path/filepath"
+       "testing"
+)
+
+func fremove(w *W, path string, files []string) WCase {
+       cas := remove(w, path)
+       cas.Events[0] = &Call{P: path, E: FileDelete}
+       for _, f := range files {
+               cas.Events = append(cas.Events, &Call{P: f, E: FileDelete})
+       }
+       return cas
+}
+
+func fwrite(w *W, path string, p []byte) WCase {
+       cas := write(w, path, p)
+       path = cas.Events[0].Path()
+       cas.Events[0] = &Call{P: path, E: FileModified}
+       return cas
+}
+
+func frename(w *W, path string, files []string) WCase {
+       const ext = ".notify"
+       cas := WCase{
+               Action: func() {
+                       file := filepath.Join(w.root, path)
+                       if err := os.Rename(file, file+ext); err != nil {
+                               w.Fatalf("Rename(%q, %q)=%v", path, path+ext, err)
+                       }
+               },
+               Events: []EventInfo{
+                       &Call{P: path + ext, E: osSpecificCreate},
+                       &Call{P: path, E: FileRenameFrom},
+               },
+       }
+       for _, f := range files {
+               cas.Events = append(cas.Events, &Call{P: f, E: FileRenameFrom})
+       }
+       return cas
+}
+
+var events = []Event{
+       FileModified,
+       FileAttrib,
+       FileRenameFrom,
+       osSpecificCreate,
+       FileDelete,
+}
+
+func TestWatcherFen(t *testing.T) {
+       w := NewWatcherTest(t, "testdata/vfs.txt", events...)
+       defer w.Close()
+
+       cases := [...]WCase{
+               fremove(w, "src/github.com/ppknap/link/include/coost/link", []string{
+                       "src/github.com/ppknap/link/include/coost/link/definitions.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/bundle.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/container_invoker.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/container_value_trait.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/dummy_type.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/function_trait.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/immediate_invoker.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/always_same.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/make_unique.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers",
+                       "src/github.com/ppknap/link/include/coost/link/detail/vertex.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/wire.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail",
+                       "src/github.com/ppknap/link/include/coost/link/link.hpp",
+               },
+               ),
+               fwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+               fremove(w, "src/github.com/ppknap/link/README.md", nil),
+               frename(w, "src/github.com/rjeczalik/fs/fs.go", nil),
+               frename(w, "src/github.com/rjeczalik/fs/cmd/gotree", []string{
+                       "src/github.com/rjeczalik/fs/cmd/gotree/go.go",
+                       "src/github.com/rjeczalik/fs/cmd/gotree/main.go",
+               },
+               ),
+       }
+
+       w.ExpectAll(cases[:])
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_fsevents.go b/vendor/github.com/rjeczalik/notify/watcher_fsevents.go
new file mode 100644 (file)
index 0000000..9062c17
--- /dev/null
@@ -0,0 +1,319 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify
+
+import (
+       "errors"
+       "strings"
+       "sync/atomic"
+)
+
+// TODO(rjeczalik): get rid of calls to canonical, it's tree responsibility
+
+const (
+       failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
+       filter  = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
+               FSEventsModified | FSEventsInodeMetaMod)
+)
+
+// FSEvent represents single file event. It is created out of values passed by
+// FSEvents to FSEventStreamCallback function.
+type FSEvent struct {
+       Path  string // real path of the file or directory
+       ID    uint64 // ID of the event (FSEventStreamEventId)
+       Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
+}
+
+// splitflags separates event flags from single set into slice of flags.
+func splitflags(set uint32) (e []uint32) {
+       for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
+               if (set & 1) != 0 {
+                       e = append(e, i)
+               }
+       }
+       return
+}
+
+// watch represents a filesystem watchpoint. It is a higher level abstraction
+// over FSEvents' stream, which implements filtering of file events based
+// on path and event set. It emulates non-recursive watch-point by filtering out
+// events which paths are more than 1 level deeper than the watched path.
+type watch struct {
+       // prev stores last event set  per path in order to filter out old flags
+       // for new events, which appratenly FSEvents likes to retain. It's a disgusting
+       // hack, it should be researched how to get rid of it.
+       prev    map[string]uint32
+       c       chan<- EventInfo
+       stream  *stream
+       path    string
+       events  uint32
+       isrec   int32
+       flushed bool
+}
+
+// Example format:
+//
+//   ~ $ (trigger command) # (event set) -> (effective event set)
+//
+// Heuristics:
+//
+// 1. Create event is removed when it was present in previous event set.
+// Example:
+//
+//   ~ $ echo > file # Create|Write -> Create|Write
+//   ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
+//
+// 2. Remove event is removed if it was present in previouse event set.
+// Example:
+//
+//   ~ $ touch file # Create -> Create
+//   ~ $ rm file    # Create|Remove -> Remove
+//   ~ $ touch file # Create|Remove -> Create
+//
+// 3. Write event is removed if not followed by InodeMetaMod on existing
+// file. Example:
+//
+//   ~ $ echo > file   # Create|Write -> Create|Write
+//   ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
+//
+// 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
+// Example:
+//
+//   ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
+//   ~ $ rm file     # Remove|Write|InodeMetaMod -> Remove
+//
+func (w *watch) strip(base string, set uint32) uint32 {
+       const (
+               write = FSEventsModified | FSEventsInodeMetaMod
+               both  = FSEventsCreated | FSEventsRemoved
+       )
+       switch w.prev[base] {
+       case FSEventsCreated:
+               set &^= FSEventsCreated
+               if set&FSEventsRemoved != 0 {
+                       w.prev[base] = FSEventsRemoved
+                       set &^= write
+               }
+       case FSEventsRemoved:
+               set &^= FSEventsRemoved
+               if set&FSEventsCreated != 0 {
+                       w.prev[base] = FSEventsCreated
+               }
+       default:
+               switch set & both {
+               case FSEventsCreated:
+                       w.prev[base] = FSEventsCreated
+               case FSEventsRemoved:
+                       w.prev[base] = FSEventsRemoved
+                       set &^= write
+               }
+       }
+       dbgprintf("split()=%v\n", Event(set))
+       return set
+}
+
+// Dispatch is a stream function which forwards given file events for the watched
+// path to underlying FileInfo channel.
+func (w *watch) Dispatch(ev []FSEvent) {
+       events := atomic.LoadUint32(&w.events)
+       isrec := (atomic.LoadInt32(&w.isrec) == 1)
+       for i := range ev {
+               if ev[i].Flags&FSEventsHistoryDone != 0 {
+                       w.flushed = true
+                       continue
+               }
+               if !w.flushed {
+                       continue
+               }
+               dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
+                       ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
+               if ev[i].Flags&failure != 0 {
+                       // TODO(rjeczalik): missing error handling
+                       continue
+               }
+               if !strings.HasPrefix(ev[i].Path, w.path) {
+                       continue
+               }
+               n := len(w.path)
+               base := ""
+               if len(ev[i].Path) > n {
+                       if ev[i].Path[n] != '/' {
+                               continue
+                       }
+                       base = ev[i].Path[n+1:]
+                       if !isrec && strings.IndexByte(base, '/') != -1 {
+                               continue
+                       }
+               }
+               // TODO(rjeczalik): get diff only from filtered events?
+               e := w.strip(string(base), ev[i].Flags) & events
+               if e == 0 {
+                       continue
+               }
+               for _, e := range splitflags(e) {
+                       dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
+                       w.c <- &event{
+                               fse:   ev[i],
+                               event: Event(e),
+                       }
+               }
+       }
+}
+
+// Stop closes underlying FSEvents stream and stops dispatching events.
+func (w *watch) Stop() {
+       w.stream.Stop()
+       // TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
+       // so the following hack can be removed. It should flush all the streams
+       // concurrently as we care not to block too much here.
+       atomic.StoreUint32(&w.events, 0)
+       atomic.StoreInt32(&w.isrec, 0)
+}
+
+// fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
+// framework.
+type fsevents struct {
+       watches map[string]*watch
+       c       chan<- EventInfo
+}
+
+func newWatcher(c chan<- EventInfo) watcher {
+       return &fsevents{
+               watches: make(map[string]*watch),
+               c:       c,
+       }
+}
+
+func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
+       if path, err = canonical(path); err != nil {
+               return err
+       }
+       if _, ok := fse.watches[path]; ok {
+               return errAlreadyWatched
+       }
+       w := &watch{
+               prev:   make(map[string]uint32),
+               c:      fse.c,
+               path:   path,
+               events: uint32(event),
+               isrec:  isrec,
+       }
+       w.stream = newStream(path, w.Dispatch)
+       if err = w.stream.Start(); err != nil {
+               return err
+       }
+       fse.watches[path] = w
+       return nil
+}
+
+func (fse *fsevents) unwatch(path string) (err error) {
+       if path, err = canonical(path); err != nil {
+               return
+       }
+       w, ok := fse.watches[path]
+       if !ok {
+               return errNotWatched
+       }
+       w.stream.Stop()
+       delete(fse.watches, path)
+       return nil
+}
+
+// Watch implements Watcher interface. It fails with non-nil error when setting
+// the watch-point by FSEvents fails or with errAlreadyWatched error when
+// the given path is already watched.
+func (fse *fsevents) Watch(path string, event Event) error {
+       return fse.watch(path, event, 0)
+}
+
+// Unwatch implements Watcher interface. It fails with errNotWatched when
+// the given path is not being watched.
+func (fse *fsevents) Unwatch(path string) error {
+       return fse.unwatch(path)
+}
+
+// Rewatch implements Watcher interface. It fails with errNotWatched when
+// the given path is not being watched or with errInvalidEventSet when oldevent
+// does not match event set the watch-point currently holds.
+func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
+       w, ok := fse.watches[path]
+       if !ok {
+               return errNotWatched
+       }
+       if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
+               return errInvalidEventSet
+       }
+       atomic.StoreInt32(&w.isrec, 0)
+       return nil
+}
+
+// RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
+// error when setting the watch-point by FSEvents fails or with errAlreadyWatched
+// error when the given path is already watched.
+func (fse *fsevents) RecursiveWatch(path string, event Event) error {
+       return fse.watch(path, event, 1)
+}
+
+// RecursiveUnwatch implements RecursiveWatcher interface. It fails with
+// errNotWatched when the given path is not being watched.
+//
+// TODO(rjeczalik): fail if w.isrec == 0?
+func (fse *fsevents) RecursiveUnwatch(path string) error {
+       return fse.unwatch(path)
+}
+
+// RecrusiveRewatch implements RecursiveWatcher interface. It fails:
+//
+//   * with errNotWatched when the given path is not being watched
+//   * with errInvalidEventSet when oldevent does not match the current event set
+//   * with errAlreadyWatched when watch-point given by the oldpath was meant to
+//     be relocated to newpath, but the newpath is already watched
+//   * a non-nil error when setting the watch-point with FSEvents fails
+//
+// TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
+// that follows.
+func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
+       switch [2]bool{oldpath == newpath, oldevent == newevent} {
+       case [2]bool{true, true}:
+               w, ok := fse.watches[oldpath]
+               if !ok {
+                       return errNotWatched
+               }
+               atomic.StoreInt32(&w.isrec, 1)
+               return nil
+       case [2]bool{true, false}:
+               w, ok := fse.watches[oldpath]
+               if !ok {
+                       return errNotWatched
+               }
+               if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
+                       return errors.New("invalid event state diff")
+               }
+               atomic.StoreInt32(&w.isrec, 1)
+               return nil
+       default:
+               // TODO(rjeczalik): rewatch newpath only if exists?
+               // TODO(rjeczalik): migrate w.prev to new watch?
+               if _, ok := fse.watches[newpath]; ok {
+                       return errAlreadyWatched
+               }
+               if err := fse.Unwatch(oldpath); err != nil {
+                       return err
+               }
+               // TODO(rjeczalik): revert unwatch if watch fails?
+               return fse.watch(newpath, newevent, 1)
+       }
+}
+
+// Close unwatches all watch-points.
+func (fse *fsevents) Close() error {
+       for _, w := range fse.watches {
+               w.Stop()
+       }
+       fse.watches = nil
+       return nil
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go b/vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go
new file mode 100644 (file)
index 0000000..5be6463
--- /dev/null
@@ -0,0 +1,190 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify
+
+/*
+#include <CoreServices/CoreServices.h>
+
+typedef void (*CFRunLoopPerformCallBack)(void*);
+
+void gosource(void *);
+void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t);
+
+static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) {
+       context->info = (void*) info;
+       return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags);
+}
+
+#cgo LDFLAGS: -framework CoreServices
+*/
+import "C"
+
+import (
+       "errors"
+       "os"
+       "sync"
+       "sync/atomic"
+       "time"
+       "unsafe"
+)
+
+var nilstream C.FSEventStreamRef
+
+// Default arguments for FSEventStreamCreate function.
+var (
+       latency C.CFTimeInterval
+       flags   = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer)
+       since   = uint64(C.FSEventsGetCurrentEventId())
+)
+
+var runloop C.CFRunLoopRef // global runloop which all streams are registered with
+var wg sync.WaitGroup      // used to wait until the runloop starts
+
+// source is used for synchronization purposes - it signals when runloop has
+// started and is ready via the wg. It also serves purpose of a dummy source,
+// thanks to it the runloop does not return as it also has at least one source
+// registered.
+var source = C.CFRunLoopSourceCreate(nil, 0, &C.CFRunLoopSourceContext{
+       perform: (C.CFRunLoopPerformCallBack)(C.gosource),
+})
+
+// Errors returned when FSEvents functions fail.
+var (
+       errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL"))
+       errStart  = os.NewSyscallError("FSEventStreamStart", errors.New("false"))
+)
+
+// initializes the global runloop and ensures any created stream awaits its
+// readiness.
+func init() {
+       wg.Add(1)
+       go func() {
+               runloop = C.CFRunLoopGetCurrent()
+               C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode)
+               C.CFRunLoopRun()
+               panic("runloop has just unexpectedly stopped")
+       }()
+       C.CFRunLoopSourceSignal(source)
+}
+
+//export gosource
+func gosource(unsafe.Pointer) {
+       time.Sleep(time.Second)
+       wg.Done()
+}
+
+//export gostream
+func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) {
+       const (
+               offchar = unsafe.Sizeof((*C.char)(nil))
+               offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0))
+               offid   = unsafe.Sizeof(C.FSEventStreamEventId(0))
+       )
+       if n == 0 {
+               return
+       }
+       ev := make([]FSEvent, 0, int(n))
+       for i := uintptr(0); i < uintptr(n); i++ {
+               switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
+               case flags&uint32(FSEventsEventIdsWrapped) != 0:
+                       atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
+               default:
+                       ev = append(ev, FSEvent{
+                               Path:  C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
+                               Flags: flags,
+                               ID:    *(*uint64)(unsafe.Pointer(ids + i*offid)),
+                       })
+               }
+
+       }
+       streamFuncs.get(info)(ev)
+}
+
+// StreamFunc is a callback called when stream receives file events.
+type streamFunc func([]FSEvent)
+
+var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}}
+
+type streamFuncRegistry struct {
+       mu sync.Mutex
+       m  map[uintptr]streamFunc
+       i  uintptr
+}
+
+func (r *streamFuncRegistry) get(id uintptr) streamFunc {
+       r.mu.Lock()
+       defer r.mu.Unlock()
+       return r.m[id]
+}
+
+func (r *streamFuncRegistry) add(fn streamFunc) uintptr {
+       r.mu.Lock()
+       defer r.mu.Unlock()
+       r.i++
+       r.m[r.i] = fn
+       return r.i
+}
+
+func (r *streamFuncRegistry) delete(id uintptr) {
+       r.mu.Lock()
+       defer r.mu.Unlock()
+       delete(r.m, id)
+}
+
+// Stream represents single watch-point which listens for events scheduled by
+// the global runloop.
+type stream struct {
+       path string
+       ref  C.FSEventStreamRef
+       info uintptr
+}
+
+// NewStream creates a stream for given path, listening for file events and
+// calling fn upon receiving any.
+func newStream(path string, fn streamFunc) *stream {
+       return &stream{
+               path: path,
+               info: streamFuncs.add(fn),
+       }
+}
+
+// Start creates a FSEventStream for the given path and schedules it with
+// global runloop. It's a nop if the stream was already started.
+func (s *stream) Start() error {
+       if s.ref != nilstream {
+               return nil
+       }
+       wg.Wait()
+       p := C.CFStringCreateWithCStringNoCopy(nil, C.CString(s.path), C.kCFStringEncodingUTF8, nil)
+       path := C.CFArrayCreate(nil, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
+       ctx := C.FSEventStreamContext{}
+       ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
+       if ref == nilstream {
+               return errCreate
+       }
+       C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
+       if C.FSEventStreamStart(ref) == C.Boolean(0) {
+               C.FSEventStreamInvalidate(ref)
+               return errStart
+       }
+       C.CFRunLoopWakeUp(runloop)
+       s.ref = ref
+       return nil
+}
+
+// Stop stops underlying FSEventStream and unregisters it from global runloop.
+func (s *stream) Stop() {
+       if s.ref == nilstream {
+               return
+       }
+       wg.Wait()
+       C.FSEventStreamStop(s.ref)
+       C.FSEventStreamInvalidate(s.ref)
+       C.CFRunLoopWakeUp(runloop)
+       s.ref = nilstream
+       streamFuncs.delete(s.info)
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_fsevents_test.go b/vendor/github.com/rjeczalik/notify/watcher_fsevents_test.go
new file mode 100644 (file)
index 0000000..1d60877
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify
+
+import (
+       "reflect"
+       "testing"
+)
+
+func TestSplitflags(t *testing.T) {
+       cases := [...]struct {
+               set   uint32
+               flags []uint32
+       }{
+               {0, nil},
+               {0xD, []uint32{0x1, 0x4, 0x8}},
+               {0x0010 | 0x0040 | 0x0080 | 0x01000, []uint32{0x0010, 0x0040, 0x0080, 0x01000}},
+               {0x40000 | 0x00100 | 0x00200, []uint32{0x00100, 0x00200, 0x40000}},
+       }
+       for i, cas := range cases {
+               if flags := splitflags(cas.set); !reflect.DeepEqual(flags, cas.flags) {
+                       t.Errorf("want flags=%v; got %v (i=%d)", cas.flags, flags, i)
+               }
+       }
+}
+
+func TestWatchStrip(t *testing.T) {
+       const (
+               create = uint32(FSEventsCreated)
+               remove = uint32(FSEventsRemoved)
+               rename = uint32(FSEventsRenamed)
+               write  = uint32(FSEventsModified)
+               inode  = uint32(FSEventsInodeMetaMod)
+               owner  = uint32(FSEventsChangeOwner)
+       )
+       cases := [...][]struct {
+               path string
+               flag uint32
+               diff uint32
+       }{
+               // 1.
+               {
+                       {"file", create | write, create | write},
+                       {"file", create | write | inode, write | inode},
+               },
+               // 2.
+               {
+                       {"file", create, create},
+                       {"file", create | remove, remove},
+                       {"file", create | remove, create},
+               },
+               // 3.
+               {
+                       {"file", create | write, create | write},
+                       {"file", create | write | owner, write | owner},
+               },
+               // 4.
+               {
+                       {"file", create | write, create | write},
+                       {"file", write | inode, write | inode},
+                       {"file", remove | write | inode, remove},
+               },
+               {
+                       {"file", remove | write | inode, remove},
+               },
+       }
+Test:
+       for i, cas := range cases {
+               if len(cas) == 0 {
+                       t.Log("skipped")
+                       continue
+               }
+               w := &watch{prev: make(map[string]uint32)}
+               for j, cas := range cas {
+                       if diff := w.strip(cas.path, cas.flag); diff != cas.diff {
+                               t.Errorf("want diff=%v; got %v (i=%d, j=%d)", Event(cas.diff),
+                                       Event(diff), i, j)
+                               continue Test
+                       }
+               }
+       }
+}
+
+// Test for cases 3) and 5) with shadowed write&create events.
+//
+// See comment for (flagdiff).diff method.
+func TestWatcherShadowedWriteCreate(t *testing.T) {
+       w := NewWatcherTest(t, "testdata/vfs.txt")
+       defer w.Close()
+
+       cases := [...]WCase{
+               // i=0
+               create(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
+               // i=1
+               write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
+               // i=2
+               write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
+               // i=3
+               remove(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
+               // i=4
+               create(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
+               // i=5
+               write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
+       }
+
+       w.ExpectAny(cases[:5]) // BUG(rjeczalik): #62
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_inotify.go b/vendor/github.com/rjeczalik/notify/watcher_inotify.go
new file mode 100644 (file)
index 0000000..da016f5
--- /dev/null
@@ -0,0 +1,397 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import (
+       "bytes"
+       "errors"
+       "path/filepath"
+       "runtime"
+       "sync"
+       "sync/atomic"
+       "unsafe"
+
+       "golang.org/x/sys/unix"
+)
+
+// eventBufferSize defines the size of the buffer given to read(2) function. One
+// should not depend on this value, since it was arbitrary chosen and may be
+// changed in the future.
+const eventBufferSize = 64 * (unix.SizeofInotifyEvent + unix.PathMax + 1)
+
+// consumersCount defines the number of consumers in producer-consumer based
+// implementation. Each consumer is run in a separate goroutine and has read
+// access to watched files map.
+const consumersCount = 2
+
+const invalidDescriptor = -1
+
+// watched is a pair of file path and inotify mask used as a value in
+// watched files map.
+type watched struct {
+       path string
+       mask uint32
+}
+
+// inotify implements Watcher interface.
+type inotify struct {
+       sync.RWMutex                       // protects inotify.m map
+       m            map[int32]*watched    // watch descriptor to watched object
+       fd           int32                 // inotify file descriptor
+       pipefd       []int                 // pipe's read and write descriptors
+       epfd         int                   // epoll descriptor
+       epes         []unix.EpollEvent     // epoll events
+       buffer       [eventBufferSize]byte // inotify event buffer
+       wg           sync.WaitGroup        // wait group used to close main loop
+       c            chan<- EventInfo      // event dispatcher channel
+}
+
+// NewWatcher creates new non-recursive inotify backed by inotify.
+func newWatcher(c chan<- EventInfo) watcher {
+       i := &inotify{
+               m:      make(map[int32]*watched),
+               fd:     invalidDescriptor,
+               pipefd: []int{invalidDescriptor, invalidDescriptor},
+               epfd:   invalidDescriptor,
+               epes:   make([]unix.EpollEvent, 0),
+               c:      c,
+       }
+       runtime.SetFinalizer(i, func(i *inotify) {
+               i.epollclose()
+               if i.fd != invalidDescriptor {
+                       unix.Close(int(i.fd))
+               }
+       })
+       return i
+}
+
+// Watch implements notify.watcher interface.
+func (i *inotify) Watch(path string, e Event) error {
+       return i.watch(path, e)
+}
+
+// Rewatch implements notify.watcher interface.
+func (i *inotify) Rewatch(path string, _, newevent Event) error {
+       return i.watch(path, newevent)
+}
+
+// watch adds a new watcher to the set of watched objects or modifies the existing
+// one. If called for the first time, this function initializes inotify filesystem
+// monitor and starts producer-consumers goroutines.
+func (i *inotify) watch(path string, e Event) (err error) {
+       if e&^(All|Event(unix.IN_ALL_EVENTS)) != 0 {
+               return errors.New("notify: unknown event")
+       }
+       if err = i.lazyinit(); err != nil {
+               return
+       }
+       iwd, err := unix.InotifyAddWatch(int(i.fd), path, encode(e))
+       if err != nil {
+               return
+       }
+       i.RLock()
+       wd := i.m[int32(iwd)]
+       i.RUnlock()
+       if wd == nil {
+               i.Lock()
+               if i.m[int32(iwd)] == nil {
+                       i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)}
+               }
+               i.Unlock()
+       } else {
+               i.Lock()
+               wd.mask = uint32(e)
+               i.Unlock()
+       }
+       return nil
+}
+
+// lazyinit sets up all required file descriptors and starts 1+consumersCount
+// goroutines. The producer goroutine blocks until file-system notifications
+// occur. Then, all events are read from system buffer and sent to consumer
+// goroutines which construct valid notify events. This method uses
+// Double-Checked Locking optimization.
+func (i *inotify) lazyinit() error {
+       if atomic.LoadInt32(&i.fd) == invalidDescriptor {
+               i.Lock()
+               defer i.Unlock()
+               if atomic.LoadInt32(&i.fd) == invalidDescriptor {
+                       fd, err := unix.InotifyInit1(unix.IN_CLOEXEC)
+                       if err != nil {
+                               return err
+                       }
+                       i.fd = int32(fd)
+                       if err = i.epollinit(); err != nil {
+                               _, _ = i.epollclose(), unix.Close(int(fd)) // Ignore errors.
+                               i.fd = invalidDescriptor
+                               return err
+                       }
+                       esch := make(chan []*event)
+                       go i.loop(esch)
+                       i.wg.Add(consumersCount)
+                       for n := 0; n < consumersCount; n++ {
+                               go i.send(esch)
+                       }
+               }
+       }
+       return nil
+}
+
+// epollinit opens an epoll file descriptor and creates a pipe which will be
+// used to wake up the epoll_wait(2) function. Then, file descriptor associated
+// with inotify event queue and the read end of the pipe are added to epoll set.
+// Note that `fd` member must be set before this function is called.
+func (i *inotify) epollinit() (err error) {
+       if i.epfd, err = unix.EpollCreate1(0); err != nil {
+               return
+       }
+       if err = unix.Pipe(i.pipefd); err != nil {
+               return
+       }
+       i.epes = []unix.EpollEvent{
+               {Events: unix.EPOLLIN, Fd: i.fd},
+               {Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])},
+       }
+       if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil {
+               return
+       }
+       return unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1])
+}
+
+// epollclose closes the file descriptor created by the call to epoll_create(2)
+// and two file descriptors opened by pipe(2) function.
+func (i *inotify) epollclose() (err error) {
+       if i.epfd != invalidDescriptor {
+               if err = unix.Close(i.epfd); err == nil {
+                       i.epfd = invalidDescriptor
+               }
+       }
+       for n, fd := range i.pipefd {
+               if fd != invalidDescriptor {
+                       switch e := unix.Close(fd); {
+                       case e != nil && err == nil:
+                               err = e
+                       case e == nil:
+                               i.pipefd[n] = invalidDescriptor
+                       }
+               }
+       }
+       return
+}
+
+// loop blocks until either inotify or pipe file descriptor is ready for I/O.
+// All read operations triggered by filesystem notifications are forwarded to
+// one of the event's consumers. If pipe fd became ready, loop function closes
+// all file descriptors opened by lazyinit method and returns afterwards.
+func (i *inotify) loop(esch chan<- []*event) {
+       epes := make([]unix.EpollEvent, 1)
+       fd := atomic.LoadInt32(&i.fd)
+       for {
+               switch _, err := unix.EpollWait(i.epfd, epes, -1); err {
+               case nil:
+                       switch epes[0].Fd {
+                       case fd:
+                               esch <- i.read()
+                               epes[0].Fd = 0
+                       case int32(i.pipefd[0]):
+                               i.Lock()
+                               defer i.Unlock()
+                               if err = unix.Close(int(fd)); err != nil && err != unix.EINTR {
+                                       panic("notify: close(2) error " + err.Error())
+                               }
+                               atomic.StoreInt32(&i.fd, invalidDescriptor)
+                               if err = i.epollclose(); err != nil && err != unix.EINTR {
+                                       panic("notify: epollclose error " + err.Error())
+                               }
+                               close(esch)
+                               return
+                       }
+               case unix.EINTR:
+                       continue
+               default: // We should never reach this line.
+                       panic("notify: epoll_wait(2) error " + err.Error())
+               }
+       }
+}
+
+// read reads events from an inotify file descriptor. It does not handle errors
+// returned from read(2) function since they are not critical to watcher logic.
+func (i *inotify) read() (es []*event) {
+       n, err := unix.Read(int(i.fd), i.buffer[:])
+       if err != nil || n < unix.SizeofInotifyEvent {
+               return
+       }
+       var sys *unix.InotifyEvent
+       nmin := n - unix.SizeofInotifyEvent
+       for pos, path := 0, ""; pos <= nmin; {
+               sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos]))
+               pos += unix.SizeofInotifyEvent
+               if path = ""; sys.Len > 0 {
+                       endpos := pos + int(sys.Len)
+                       path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00"))
+                       pos = endpos
+               }
+               es = append(es, &event{
+                       sys: unix.InotifyEvent{
+                               Wd:     sys.Wd,
+                               Mask:   sys.Mask,
+                               Cookie: sys.Cookie,
+                       },
+                       path: path,
+               })
+       }
+       return
+}
+
+// send is a consumer function which sends events to event dispatcher channel.
+// It is run in a separate goroutine in order to not block loop method when
+// possibly expensive write operations are performed on inotify map.
+func (i *inotify) send(esch <-chan []*event) {
+       for es := range esch {
+               for _, e := range i.transform(es) {
+                       if e != nil {
+                               i.c <- e
+                       }
+               }
+       }
+       i.wg.Done()
+}
+
+// transform prepares events read from inotify file descriptor for sending to
+// user. It removes invalid events and these which are no longer present in
+// inotify map. This method may also split one raw event into two different ones
+// when system-dependent result is required.
+func (i *inotify) transform(es []*event) []*event {
+       var multi []*event
+       i.RLock()
+       for idx, e := range es {
+               if e.sys.Mask&(unix.IN_IGNORED|unix.IN_Q_OVERFLOW) != 0 {
+                       es[idx] = nil
+                       continue
+               }
+               wd, ok := i.m[e.sys.Wd]
+               if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 {
+                       es[idx] = nil
+                       continue
+               }
+               if e.path == "" {
+                       e.path = wd.path
+               } else {
+                       e.path = filepath.Join(wd.path, e.path)
+               }
+               multi = append(multi, decode(Event(wd.mask), e))
+               if e.event == 0 {
+                       es[idx] = nil
+               }
+       }
+       i.RUnlock()
+       es = append(es, multi...)
+       return es
+}
+
+// encode converts notify system-independent events to valid inotify mask
+// which can be passed to inotify_add_watch(2) function.
+func encode(e Event) uint32 {
+       if e&Create != 0 {
+               e = (e ^ Create) | InCreate | InMovedTo
+       }
+       if e&Remove != 0 {
+               e = (e ^ Remove) | InDelete | InDeleteSelf
+       }
+       if e&Write != 0 {
+               e = (e ^ Write) | InModify
+       }
+       if e&Rename != 0 {
+               e = (e ^ Rename) | InMovedFrom | InMoveSelf
+       }
+       return uint32(e)
+}
+
+// decode uses internally stored mask to distinguish whether system-independent
+// or system-dependent event is requested. The first one is created by modifying
+// `e` argument. decode method sets e.event value to 0 when an event should be
+// skipped. System-dependent event is set as the function's return value which
+// can be nil when the event should not be passed on.
+func decode(mask Event, e *event) (syse *event) {
+       if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 {
+               syse = &event{sys: unix.InotifyEvent{
+                       Wd:     e.sys.Wd,
+                       Mask:   e.sys.Mask,
+                       Cookie: e.sys.Cookie,
+               }, event: Event(sysmask), path: e.path}
+       }
+       imask := encode(mask)
+       switch {
+       case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0:
+               e.event = Create
+       case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0:
+               e.event = Remove
+       case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0:
+               e.event = Write
+       case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0:
+               e.event = Rename
+       default:
+               e.event = 0
+       }
+       return
+}
+
+// Unwatch implements notify.watcher interface. It looks for watch descriptor
+// related to registered path and if found, calls inotify_rm_watch(2) function.
+// This method is allowed to return EINVAL error when concurrently requested to
+// delete identical path.
+func (i *inotify) Unwatch(path string) (err error) {
+       iwd := int32(invalidDescriptor)
+       i.RLock()
+       for iwdkey, wd := range i.m {
+               if wd.path == path {
+                       iwd = iwdkey
+                       break
+               }
+       }
+       i.RUnlock()
+       if iwd == invalidDescriptor {
+               return errors.New("notify: path " + path + " is already watched")
+       }
+       fd := atomic.LoadInt32(&i.fd)
+       if _, err = unix.InotifyRmWatch(int(fd), uint32(iwd)); err != nil {
+               return
+       }
+       i.Lock()
+       delete(i.m, iwd)
+       i.Unlock()
+       return nil
+}
+
+// Close implements notify.watcher interface. It removes all existing watch
+// descriptors and wakes up producer goroutine by sending data to the write end
+// of the pipe. The function waits for a signal from producer which means that
+// all operations on current monitoring instance are done.
+func (i *inotify) Close() (err error) {
+       i.Lock()
+       if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor {
+               i.Unlock()
+               return nil
+       }
+       for iwd := range i.m {
+               if _, e := unix.InotifyRmWatch(int(i.fd), uint32(iwd)); e != nil && err == nil {
+                       err = e
+               }
+               delete(i.m, iwd)
+       }
+       switch _, errwrite := unix.Write(i.pipefd[1], []byte{0x00}); {
+       case errwrite != nil && err == nil:
+               err = errwrite
+               fallthrough
+       case errwrite != nil:
+               i.Unlock()
+       default:
+               i.Unlock()
+               i.wg.Wait()
+       }
+       return
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_inotify_test.go b/vendor/github.com/rjeczalik/notify/watcher_inotify_test.go
new file mode 100644 (file)
index 0000000..5feadaf
--- /dev/null
@@ -0,0 +1,132 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import (
+       "os"
+       "path/filepath"
+       "testing"
+)
+
+func icreate(w *W, path string) WCase {
+       cas := create(w, path)
+       cas.Events = append(cas.Events,
+               &Call{P: path, E: InCreate},
+       )
+       return cas
+}
+
+func iremove(w *W, path string) WCase {
+       cas := remove(w, path)
+       cas.Events = append(cas.Events,
+               &Call{P: path, E: InDelete},
+       )
+       return cas
+}
+
+func iopen(w *W, path string) WCase {
+       return WCase{
+               Action: func() {
+                       f, err := os.OpenFile(filepath.Join(w.root, path), os.O_RDWR, 0644)
+                       if err != nil {
+                               w.Fatalf("OpenFile(%q)=%v", path, err)
+                       }
+                       if err := f.Close(); err != nil {
+                               w.Fatalf("Close(%q)=%v", path, err)
+                       }
+               },
+               Events: []EventInfo{
+                       &Call{P: path, E: InAccess},
+                       &Call{P: path, E: InOpen},
+                       &Call{P: path, E: InCloseNowrite},
+               },
+       }
+}
+
+func iread(w *W, path string, p []byte) WCase {
+       return WCase{
+               Action: func() {
+                       f, err := os.OpenFile(filepath.Join(w.root, path), os.O_RDWR, 0644)
+                       if err != nil {
+                               w.Fatalf("OpenFile(%q)=%v", path, err)
+                       }
+                       if _, err := f.Read(p); err != nil {
+                               w.Fatalf("Read(%q)=%v", path, err)
+                       }
+                       if err := f.Close(); err != nil {
+                               w.Fatalf("Close(%q)=%v", path, err)
+                       }
+               },
+               Events: []EventInfo{
+                       &Call{P: path, E: InAccess},
+                       &Call{P: path, E: InOpen},
+                       &Call{P: path, E: InModify},
+                       &Call{P: path, E: InCloseNowrite},
+               },
+       }
+}
+
+func iwrite(w *W, path string, p []byte) WCase {
+       cas := write(w, path, p)
+       path = cas.Events[0].Path()
+       cas.Events = append(cas.Events,
+               &Call{P: path, E: InAccess},
+               &Call{P: path, E: InOpen},
+               &Call{P: path, E: InModify},
+               &Call{P: path, E: InCloseWrite},
+       )
+       return cas
+}
+
+func irename(w *W, path string) WCase {
+       const ext = ".notify"
+       return WCase{
+               Action: func() {
+                       file := filepath.Join(w.root, path)
+                       if err := os.Rename(file, file+ext); err != nil {
+                               w.Fatalf("Rename(%q, %q)=%v", path, path+ext, err)
+                       }
+               },
+               Events: []EventInfo{
+                       &Call{P: path, E: InMovedFrom},
+                       &Call{P: path + ext, E: InMovedTo},
+                       &Call{P: path, E: InOpen},
+                       &Call{P: path, E: InAccess},
+                       &Call{P: path, E: InCreate},
+               },
+       }
+}
+
+var events = []Event{
+       InAccess,
+       InModify,
+       InAttrib,
+       InCloseWrite,
+       InCloseNowrite,
+       InOpen,
+       InMovedFrom,
+       InMovedTo,
+       InCreate,
+       InDelete,
+       InDeleteSelf,
+       InMoveSelf,
+}
+
+func TestWatcherInotify(t *testing.T) {
+       w := NewWatcherTest(t, "testdata/vfs.txt", events...)
+       defer w.Close()
+
+       cases := [...]WCase{
+               iopen(w, "src/github.com/rjeczalik/fs/fs.go"),
+               iwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+               iread(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+               iremove(w, "src/github.com/ppknap/link/README.md"),
+               irename(w, "src/github.com/rjeczalik/fs/LICENSE"),
+       }
+
+       w.ExpectAny(cases[:])
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_kqueue.go b/vendor/github.com/rjeczalik/notify/watcher_kqueue.go
new file mode 100644 (file)
index 0000000..6d500b7
--- /dev/null
@@ -0,0 +1,193 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,kqueue dragonfly freebsd netbsd openbsd
+
+package notify
+
+import (
+       "fmt"
+       "os"
+       "syscall"
+)
+
+// newTrigger returns implementation of trigger.
+func newTrigger(pthLkp map[string]*watched) trigger {
+       return &kq{
+               pthLkp: pthLkp,
+               idLkp:  make(map[int]*watched),
+       }
+}
+
+// kq is a structure implementing trigger for kqueue.
+type kq struct {
+       // fd is a kqueue file descriptor
+       fd int
+       // pipefds are file descriptors used to stop `Kevent` call.
+       pipefds [2]int
+       // idLkp is a data structure mapping file descriptors with data about watching
+       // represented by them files/directories.
+       idLkp map[int]*watched
+       // pthLkp is a structure mapping monitored files/dir with data about them,
+       // shared with parent trg structure
+       pthLkp map[string]*watched
+}
+
+// watched is a data structure representing watched file/directory.
+type watched struct {
+       // p is a path to watched file/directory.
+       p string
+       // fd is a file descriptor for watched file/directory.
+       fd int
+       // fi provides information about watched file/dir.
+       fi os.FileInfo
+       // eDir represents events watched directly.
+       eDir Event
+       // eNonDir represents events watched indirectly.
+       eNonDir Event
+}
+
+// Stop implements trigger.
+func (k *kq) Stop() (err error) {
+       // trigger event used to interrupt Kevent call.
+       _, err = syscall.Write(k.pipefds[1], []byte{0x00})
+       return
+}
+
+// Close implements trigger.
+func (k *kq) Close() error {
+       return syscall.Close(k.fd)
+}
+
+// NewWatched implements trigger.
+func (*kq) NewWatched(p string, fi os.FileInfo) (*watched, error) {
+       fd, err := syscall.Open(p, syscall.O_NONBLOCK|syscall.O_RDONLY, 0)
+       if err != nil {
+               return nil, err
+       }
+       return &watched{fd: fd, p: p, fi: fi}, nil
+}
+
+// Record implements trigger.
+func (k *kq) Record(w *watched) {
+       k.idLkp[w.fd], k.pthLkp[w.p] = w, w
+}
+
+// Del implements trigger.
+func (k *kq) Del(w *watched) {
+       syscall.Close(w.fd)
+       delete(k.idLkp, w.fd)
+       delete(k.pthLkp, w.p)
+}
+
+func inter2kq(n interface{}) syscall.Kevent_t {
+       kq, ok := n.(syscall.Kevent_t)
+       if !ok {
+               panic(fmt.Sprintf("kqueue: type should be Kevent_t, %T instead", n))
+       }
+       return kq
+}
+
+// Init implements trigger.
+func (k *kq) Init() (err error) {
+       if k.fd, err = syscall.Kqueue(); err != nil {
+               return
+       }
+       // Creates pipe used to stop `Kevent` call by registering it,
+       // watching read end and writing to other end of it.
+       if err = syscall.Pipe(k.pipefds[:]); err != nil {
+               return nonil(err, k.Close())
+       }
+       var kevn [1]syscall.Kevent_t
+       syscall.SetKevent(&kevn[0], k.pipefds[0], syscall.EVFILT_READ, syscall.EV_ADD)
+       if _, err = syscall.Kevent(k.fd, kevn[:], nil, nil); err != nil {
+               return nonil(err, k.Close())
+       }
+       return
+}
+
+// Unwatch implements trigger.
+func (k *kq) Unwatch(w *watched) (err error) {
+       var kevn [1]syscall.Kevent_t
+       syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
+
+       _, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
+       return
+}
+
+// Watch implements trigger.
+func (k *kq) Watch(fi os.FileInfo, w *watched, e int64) (err error) {
+       var kevn [1]syscall.Kevent_t
+       syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE,
+               syscall.EV_ADD|syscall.EV_CLEAR)
+       kevn[0].Fflags = uint32(e)
+
+       _, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
+       return
+}
+
+// Wait implements trigger.
+func (k *kq) Wait() (interface{}, error) {
+       var (
+               kevn [1]syscall.Kevent_t
+               err  error
+       )
+       kevn[0] = syscall.Kevent_t{}
+       _, err = syscall.Kevent(k.fd, nil, kevn[:], nil)
+
+       return kevn[0], err
+}
+
+// Watched implements trigger.
+func (k *kq) Watched(n interface{}) (*watched, int64, error) {
+       kevn, ok := n.(syscall.Kevent_t)
+       if !ok {
+               panic(fmt.Sprintf("kq: type should be syscall.Kevent_t, %T instead", kevn))
+       }
+       if _, ok = k.idLkp[int(kevn.Ident)]; !ok {
+               return nil, 0, errNotWatched
+       }
+       return k.idLkp[int(kevn.Ident)], int64(kevn.Fflags), nil
+}
+
+// IsStop implements trigger.
+func (k *kq) IsStop(n interface{}, err error) bool {
+       return int(inter2kq(n).Ident) == k.pipefds[0]
+}
+
+func init() {
+       encode = func(e Event, dir bool) (o int64) {
+               // Create event is not supported by kqueue. Instead NoteWrite event will
+               // be registered for a directory. If this event will be reported on dir
+               // which is to be monitored for Create, dir will be rescanned
+               // and Create events will be generated and returned for new files.
+               // In case of files, if not requested NoteRename event is reported,
+               // it will be ignored.
+               o = int64(e &^ Create)
+               if (e&Create != 0 && dir) || e&Write != 0 {
+                       o = (o &^ int64(Write)) | int64(NoteWrite)
+               }
+               if e&Rename != 0 {
+                       o = (o &^ int64(Rename)) | int64(NoteRename)
+               }
+               if e&Remove != 0 {
+                       o = (o &^ int64(Remove)) | int64(NoteDelete)
+               }
+               return
+       }
+       nat2not = map[Event]Event{
+               NoteWrite:  Write,
+               NoteRename: Rename,
+               NoteDelete: Remove,
+               NoteExtend: Event(0),
+               NoteAttrib: Event(0),
+               NoteRevoke: Event(0),
+               NoteLink:   Event(0),
+       }
+       not2nat = map[Event]Event{
+               Write:  NoteWrite,
+               Rename: NoteRename,
+               Remove: NoteDelete,
+       }
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_kqueue_test.go b/vendor/github.com/rjeczalik/notify/watcher_kqueue_test.go
new file mode 100644 (file)
index 0000000..8c07dc1
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,kqueue dragonfly freebsd netbsd openbsd
+
+package notify
+
+import (
+       "os"
+       "path/filepath"
+       "testing"
+)
+
+func kqremove(w *W, path string, files []string) WCase {
+       cas := remove(w, path)
+       cas.Events[0] = &Call{P: path, E: NoteDelete}
+       for _, f := range files {
+               cas.Events = append(cas.Events, &Call{P: f, E: NoteDelete})
+       }
+       return cas
+}
+
+func kqwrite(w *W, path string, p []byte) WCase {
+       cas := write(w, path, p)
+       path = cas.Events[0].Path()
+       cas.Events[0] = &Call{P: path, E: NoteExtend | NoteWrite}
+       return cas
+}
+
+func kqrename(w *W, path string, files []string) WCase {
+       const ext = ".notify"
+       cas := WCase{
+               Action: func() {
+                       file := filepath.Join(w.root, path)
+                       if err := os.Rename(file, file+ext); err != nil {
+                               w.Fatalf("Rename(%q, %q)=%v", path, path+ext, err)
+                       }
+               },
+               Events: []EventInfo{
+                       &Call{P: path + ext, E: osSpecificCreate},
+                       &Call{P: path, E: NoteRename},
+               },
+       }
+       for _, f := range files {
+               cas.Events = append(cas.Events, &Call{P: f, E: NoteRename})
+       }
+       return cas
+}
+
+func kqlink(w *W, path string) WCase {
+       const ext = ".notify"
+       return WCase{
+               Action: func() {
+                       file := filepath.Join(w.root, path)
+                       if err := os.Link(file, file+ext); err != nil {
+                               w.Fatalf("Link(%q, %q)=%v", path, path+ext, err)
+                       }
+               },
+               Events: []EventInfo{
+                       &Call{P: path, E: NoteLink},
+                       &Call{P: path + ext, E: osSpecificCreate},
+               },
+       }
+}
+
+var events = []Event{
+       NoteWrite,
+       NoteAttrib,
+       NoteRename,
+       osSpecificCreate,
+       NoteDelete,
+       NoteExtend,
+       NoteLink,
+}
+
+func TestWatcherKqueue(t *testing.T) {
+       w := NewWatcherTest(t, "testdata/vfs.txt", events...)
+       defer w.Close()
+
+       cases := [...]WCase{
+               kqremove(w, "src/github.com/ppknap/link/include/coost/link", []string{
+                       "src/github.com/ppknap/link/include/coost/link/definitions.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/bundle.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/container_invoker.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/container_value_trait.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/dummy_type.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/function_trait.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/immediate_invoker.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/always_same.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/make_unique.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers",
+                       "src/github.com/ppknap/link/include/coost/link/detail/vertex.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail/wire.hpp",
+                       "src/github.com/ppknap/link/include/coost/link/detail",
+                       "src/github.com/ppknap/link/include/coost/link/link.hpp",
+               },
+               ),
+               kqwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+               kqremove(w, "src/github.com/ppknap/link/README.md", nil),
+               kqlink(w, "src/github.com/rjeczalik/fs/LICENSE"),
+               kqrename(w, "src/github.com/rjeczalik/fs/fs.go", nil),
+               kqrename(w, "src/github.com/rjeczalik/fs/cmd/gotree", []string{
+                       "src/github.com/rjeczalik/fs/cmd/gotree/go.go",
+                       "src/github.com/rjeczalik/fs/cmd/gotree/main.go",
+               },
+               ),
+       }
+
+       w.ExpectAll(cases[:])
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_readdcw.go b/vendor/github.com/rjeczalik/notify/watcher_readdcw.go
new file mode 100644 (file)
index 0000000..5923bfd
--- /dev/null
@@ -0,0 +1,574 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import (
+       "errors"
+       "runtime"
+       "sync"
+       "sync/atomic"
+       "syscall"
+       "unsafe"
+)
+
+// readBufferSize defines the size of an array in which read statuses are stored.
+// The buffer have to be DWORD-aligned and, if notify is used in monitoring a
+// directory over the network, its size must not be greater than 64KB. Each of
+// watched directories uses its own buffer for storing events.
+const readBufferSize = 4096
+
+// Since all operations which go through the Windows completion routine are done
+// asynchronously, filter may set one of the constants belor. They were defined
+// in order to distinguish whether current folder should be re-registered in
+// ReadDirectoryChangesW function or some control operations need to be executed.
+const (
+       stateRewatch uint32 = 1 << (28 + iota)
+       stateUnwatch
+       stateCPClose
+)
+
+// Filter used in current implementation was split into four segments:
+//  - bits  0-11 store ReadDirectoryChangesW filters,
+//  - bits 12-19 store File notify actions,
+//  - bits 20-27 store notify specific events and flags,
+//  - bits 28-31 store states which are used in loop's FSM.
+// Constants below are used as masks to retrieve only specific filter parts.
+const (
+       onlyNotifyChanges uint32 = 0x00000FFF
+       onlyNGlobalEvents uint32 = 0x0FF00000
+       onlyMachineStates uint32 = 0xF0000000
+)
+
+// grip represents a single watched directory. It stores the data required by
+// ReadDirectoryChangesW function. Only the filter, recursive, and handle members
+// may by modified by watcher implementation. Rest of the them have to remain
+// constant since they are used by Windows completion routine. This indicates that
+// grip can be removed only when all operations on the file handle are finished.
+type grip struct {
+       handle    syscall.Handle
+       filter    uint32
+       recursive bool
+       pathw     []uint16
+       buffer    [readBufferSize]byte
+       parent    *watched
+       ovlapped  *overlappedEx
+}
+
+// overlappedEx stores information used in asynchronous input and output.
+// Additionally, overlappedEx contains a pointer to 'grip' item which is used in
+// order to gather the structure in which the overlappedEx object was created.
+type overlappedEx struct {
+       syscall.Overlapped
+       parent *grip
+}
+
+// newGrip creates a new file handle that can be used in overlapped operations.
+// Then, the handle is associated with I/O completion port 'cph' and its value
+// is stored in newly created 'grip' object.
+func newGrip(cph syscall.Handle, parent *watched, filter uint32) (*grip, error) {
+       g := &grip{
+               handle:    syscall.InvalidHandle,
+               filter:    filter,
+               recursive: parent.recursive,
+               pathw:     parent.pathw,
+               parent:    parent,
+               ovlapped:  &overlappedEx{},
+       }
+       if err := g.register(cph); err != nil {
+               return nil, err
+       }
+       g.ovlapped.parent = g
+       return g, nil
+}
+
+// NOTE : Thread safe
+func (g *grip) register(cph syscall.Handle) (err error) {
+       if g.handle, err = syscall.CreateFile(
+               &g.pathw[0],
+               syscall.FILE_LIST_DIRECTORY,
+               syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
+               nil,
+               syscall.OPEN_EXISTING,
+               syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED,
+               0,
+       ); err != nil {
+               return
+       }
+       if _, err = syscall.CreateIoCompletionPort(g.handle, cph, 0, 0); err != nil {
+               syscall.CloseHandle(g.handle)
+               return
+       }
+       return g.readDirChanges()
+}
+
+// readDirChanges tells the system to store file change information in grip's
+// buffer. Directory changes that occur between calls to this function are added
+// to the buffer and then, returned with the next call.
+func (g *grip) readDirChanges() error {
+       return syscall.ReadDirectoryChanges(
+               g.handle,
+               &g.buffer[0],
+               uint32(unsafe.Sizeof(g.buffer)),
+               g.recursive,
+               encode(g.filter),
+               nil,
+               (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)),
+               0,
+       )
+}
+
+// encode transforms a generic filter, which contains platform independent and
+// implementation specific bit fields, to value that can be used as NotifyFilter
+// parameter in ReadDirectoryChangesW function.
+func encode(filter uint32) uint32 {
+       e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges))
+       if e&dirmarker != 0 {
+               return uint32(FileNotifyChangeDirName)
+       }
+       if e&Create != 0 {
+               e = (e ^ Create) | FileNotifyChangeFileName
+       }
+       if e&Remove != 0 {
+               e = (e ^ Remove) | FileNotifyChangeFileName
+       }
+       if e&Write != 0 {
+               e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize |
+                       FileNotifyChangeCreation | FileNotifyChangeSecurity
+       }
+       if e&Rename != 0 {
+               e = (e ^ Rename) | FileNotifyChangeFileName
+       }
+       return uint32(e)
+}
+
+// watched is made in order to check whether an action comes from a directory or
+// file. This approach requires two file handlers per single monitored folder. The
+// second grip handles actions which include creating or deleting a directory. If
+// these processes are not monitored, only the first grip is created.
+type watched struct {
+       filter    uint32
+       recursive bool
+       count     uint8
+       pathw     []uint16
+       digrip    [2]*grip
+}
+
+// newWatched creates a new watched instance. It splits the filter variable into
+// two parts. The first part is responsible for watching all events which can be
+// created for a file in watched directory structure and the second one watches
+// only directory Create/Remove actions. If all operations succeed, the Create
+// message is sent to I/O completion port queue for further processing.
+func newWatched(cph syscall.Handle, filter uint32, recursive bool,
+       path string) (wd *watched, err error) {
+       wd = &watched{
+               filter:    filter,
+               recursive: recursive,
+       }
+       if wd.pathw, err = syscall.UTF16FromString(path); err != nil {
+               return
+       }
+       if err = wd.recreate(cph); err != nil {
+               return
+       }
+       return wd, nil
+}
+
+// TODO : doc
+func (wd *watched) recreate(cph syscall.Handle) (err error) {
+       filefilter := wd.filter &^ uint32(FileNotifyChangeDirName)
+       if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil {
+               return
+       }
+       dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove)
+       if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil {
+               return
+       }
+       wd.filter &^= onlyMachineStates
+       return
+}
+
+// TODO : doc
+func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool,
+       newflag uint32) (err error) {
+       if reset {
+               wd.digrip[idx] = nil
+       } else {
+               if wd.digrip[idx] == nil {
+                       if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil {
+                               wd.closeHandle()
+                               return
+                       }
+               } else {
+                       wd.digrip[idx].filter = newflag
+                       wd.digrip[idx].recursive = wd.recursive
+                       if err = wd.digrip[idx].register(cph); err != nil {
+                               wd.closeHandle()
+                               return
+                       }
+               }
+               wd.count++
+       }
+       return
+}
+
+// closeHandle closes handles that are stored in digrip array. Function always
+// tries to close all of the handlers before it exits, even when there are errors
+// returned from the operating system kernel.
+func (wd *watched) closeHandle() (err error) {
+       for _, g := range wd.digrip {
+               if g != nil && g.handle != syscall.InvalidHandle {
+                       switch suberr := syscall.CloseHandle(g.handle); {
+                       case suberr == nil:
+                               g.handle = syscall.InvalidHandle
+                       case err == nil:
+                               err = suberr
+                       }
+               }
+       }
+       return
+}
+
+// watcher implements Watcher interface. It stores a set of watched directories.
+// All operations which remove watched objects from map `m` must be performed in
+// loop goroutine since these structures are used internally by operating system.
+type readdcw struct {
+       sync.Mutex
+       m     map[string]*watched
+       cph   syscall.Handle
+       start bool
+       wg    sync.WaitGroup
+       c     chan<- EventInfo
+}
+
+// NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW.
+func newWatcher(c chan<- EventInfo) watcher {
+       r := &readdcw{
+               m:   make(map[string]*watched),
+               cph: syscall.InvalidHandle,
+               c:   c,
+       }
+       runtime.SetFinalizer(r, func(r *readdcw) {
+               if r.cph != syscall.InvalidHandle {
+                       syscall.CloseHandle(r.cph)
+               }
+       })
+       return r
+}
+
+// Watch implements notify.Watcher interface.
+func (r *readdcw) Watch(path string, event Event) error {
+       return r.watch(path, event, false)
+}
+
+// RecursiveWatch implements notify.RecursiveWatcher interface.
+func (r *readdcw) RecursiveWatch(path string, event Event) error {
+       return r.watch(path, event, true)
+}
+
+// watch inserts a directory to the group of watched folders. If watched folder
+// already exists, function tries to rewatch it with new filters(NOT VALID). Moreover,
+// watch starts the main event loop goroutine when called for the first time.
+func (r *readdcw) watch(path string, event Event, recursive bool) (err error) {
+       if event&^(All|fileNotifyChangeAll) != 0 {
+               return errors.New("notify: unknown event")
+       }
+       r.Lock()
+       wd, ok := r.m[path]
+       r.Unlock()
+       if !ok {
+               if err = r.lazyinit(); err != nil {
+                       return
+               }
+               r.Lock()
+               if wd, ok = r.m[path]; ok {
+                       r.Unlock()
+                       return
+               }
+               if wd, err = newWatched(r.cph, uint32(event), recursive, path); err != nil {
+                       r.Unlock()
+                       return
+               }
+               r.m[path] = wd
+               r.Unlock()
+       }
+       return nil
+}
+
+// lazyinit creates an I/O completion port and starts the main event processing
+// loop. This method uses Double-Checked Locking optimization.
+func (r *readdcw) lazyinit() (err error) {
+       invalid := uintptr(syscall.InvalidHandle)
+       if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
+               r.Lock()
+               defer r.Unlock()
+               if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
+                       cph := syscall.InvalidHandle
+                       if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil {
+                               return
+                       }
+                       r.cph, r.start = cph, true
+                       go r.loop()
+               }
+       }
+       return
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) loop() {
+       var n, key uint32
+       var overlapped *syscall.Overlapped
+       for {
+               err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE)
+               if key == stateCPClose {
+                       r.Lock()
+                       handle := r.cph
+                       r.cph = syscall.InvalidHandle
+                       r.Unlock()
+                       syscall.CloseHandle(handle)
+                       r.wg.Done()
+                       return
+               }
+               if overlapped == nil {
+                       // TODO: check key == rewatch delete or 0(panic)
+                       continue
+               }
+               overEx := (*overlappedEx)(unsafe.Pointer(overlapped))
+               if n == 0 {
+                       r.loopstate(overEx)
+               } else {
+                       r.loopevent(n, overEx)
+                       if err = overEx.parent.readDirChanges(); err != nil {
+                               // TODO: error handling
+                       }
+               }
+       }
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) loopstate(overEx *overlappedEx) {
+       filter := atomic.LoadUint32(&overEx.parent.parent.filter)
+       if filter&onlyMachineStates == 0 {
+               return
+       }
+       if overEx.parent.parent.count--; overEx.parent.parent.count == 0 {
+               switch filter & onlyMachineStates {
+               case stateRewatch:
+                       r.Lock()
+                       overEx.parent.parent.recreate(r.cph)
+                       r.Unlock()
+               case stateUnwatch:
+                       r.Lock()
+                       delete(r.m, syscall.UTF16ToString(overEx.parent.pathw))
+                       r.Unlock()
+               case stateCPClose:
+               default:
+                       panic(`notify: windows loopstate logic error`)
+               }
+       }
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) {
+       events := []*event{}
+       var currOffset uint32
+       for {
+               raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset]))
+               name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1])
+               events = append(events, &event{
+                       pathw:  overEx.parent.pathw,
+                       filter: overEx.parent.filter,
+                       action: raw.Action,
+                       name:   name,
+               })
+               if raw.NextEntryOffset == 0 {
+                       break
+               }
+               if currOffset += raw.NextEntryOffset; currOffset >= n {
+                       break
+               }
+       }
+       r.send(events)
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) send(es []*event) {
+       for _, e := range es {
+               var syse Event
+               if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 {
+                       continue
+               }
+               switch {
+               case e.action == syscall.FILE_ACTION_MODIFIED:
+                       e.ftype = fTypeUnknown
+               case e.filter&uint32(dirmarker) != 0:
+                       e.ftype = fTypeDirectory
+               default:
+                       e.ftype = fTypeFile
+               }
+               switch {
+               case e.e == 0:
+                       e.e = syse
+               case syse != 0:
+                       r.c <- &event{
+                               pathw:  e.pathw,
+                               name:   e.name,
+                               ftype:  e.ftype,
+                               action: e.action,
+                               filter: e.filter,
+                               e:      syse,
+                       }
+               }
+               r.c <- e
+       }
+}
+
+// Rewatch implements notify.Rewatcher interface.
+func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error {
+       return r.rewatch(path, uint32(oldevent), uint32(newevent), false)
+}
+
+// RecursiveRewatch implements notify.RecursiveRewatcher interface.
+func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent,
+       newevent Event) error {
+       if oldpath != newpath {
+               if err := r.unwatch(oldpath); err != nil {
+                       return err
+               }
+               return r.watch(newpath, newevent, true)
+       }
+       return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true)
+}
+
+// TODO : (pknap) doc.
+func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) {
+       if Event(newevent)&^(All|fileNotifyChangeAll) != 0 {
+               return errors.New("notify: unknown event")
+       }
+       var wd *watched
+       r.Lock()
+       if wd, err = r.nonStateWatched(path); err != nil {
+               r.Unlock()
+               return
+       }
+       if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent {
+               panic(`notify: windows re-watcher logic error`)
+       }
+       wd.filter = stateRewatch | newevent
+       wd.recursive, recursive = recursive, wd.recursive
+       if err = wd.closeHandle(); err != nil {
+               wd.filter = oldevent
+               wd.recursive = recursive
+               r.Unlock()
+               return
+       }
+       r.Unlock()
+       return
+}
+
+// TODO : pknap
+func (r *readdcw) nonStateWatched(path string) (wd *watched, err error) {
+       wd, ok := r.m[path]
+       if !ok || wd == nil {
+               err = errors.New(`notify: ` + path + ` path is unwatched`)
+               return
+       }
+       if filter := atomic.LoadUint32(&wd.filter); filter&onlyMachineStates != 0 {
+               err = errors.New(`notify: another re/unwatching operation in progress`)
+               return
+       }
+       return
+}
+
+// Unwatch implements notify.Watcher interface.
+func (r *readdcw) Unwatch(path string) error {
+       return r.unwatch(path)
+}
+
+// RecursiveUnwatch implements notify.RecursiveWatcher interface.
+func (r *readdcw) RecursiveUnwatch(path string) error {
+       return r.unwatch(path)
+}
+
+// TODO : pknap
+func (r *readdcw) unwatch(path string) (err error) {
+       var wd *watched
+       r.Lock()
+       if wd, err = r.nonStateWatched(path); err != nil {
+               r.Unlock()
+               return
+       }
+       wd.filter |= stateUnwatch
+       if err = wd.closeHandle(); err != nil {
+               wd.filter &^= stateUnwatch
+               r.Unlock()
+               return
+       }
+       r.Unlock()
+       return
+}
+
+// Close resets the whole watcher object, closes all existing file descriptors,
+// and sends stateCPClose state as completion key to the main watcher's loop.
+func (r *readdcw) Close() (err error) {
+       r.Lock()
+       if !r.start {
+               r.Unlock()
+               return nil
+       }
+       for _, wd := range r.m {
+               wd.filter &^= onlyMachineStates
+               wd.filter |= stateCPClose
+               if e := wd.closeHandle(); e != nil && err == nil {
+                       err = e
+               }
+       }
+       r.start = false
+       r.Unlock()
+       r.wg.Add(1)
+       if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil {
+               return e
+       }
+       r.wg.Wait()
+       return
+}
+
+// decode creates a notify event from both non-raw filter and action which was
+// returned from completion routine. Function may return Event(0) in case when
+// filter was replaced by a new value which does not contain fields that are
+// valid with passed action.
+func decode(filter, action uint32) (Event, Event) {
+       switch action {
+       case syscall.FILE_ACTION_ADDED:
+               return gensys(filter, Create, FileActionAdded)
+       case syscall.FILE_ACTION_REMOVED:
+               return gensys(filter, Remove, FileActionRemoved)
+       case syscall.FILE_ACTION_MODIFIED:
+               return gensys(filter, Write, FileActionModified)
+       case syscall.FILE_ACTION_RENAMED_OLD_NAME:
+               return gensys(filter, Rename, FileActionRenamedOldName)
+       case syscall.FILE_ACTION_RENAMED_NEW_NAME:
+               return gensys(filter, Rename, FileActionRenamedNewName)
+       }
+       panic(`notify: cannot decode internal mask`)
+}
+
+// gensys decides whether the Windows action, system-independent event or both
+// of them should be returned. Since the grip's filter may be atomically changed
+// during watcher lifetime, it is possible that neither Windows nor notify masks
+// are watched by the user when this function is called.
+func gensys(filter uint32, ge, se Event) (gene, syse Event) {
+       isdir := filter&uint32(dirmarker) != 0
+       if isdir && filter&uint32(FileNotifyChangeDirName) != 0 ||
+               !isdir && filter&uint32(FileNotifyChangeFileName) != 0 ||
+               filter&uint32(fileNotifyChangeModified) != 0 {
+               syse = se
+       }
+       if filter&uint32(ge) != 0 {
+               gene = ge
+       }
+       return
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_readdcw_test.go b/vendor/github.com/rjeczalik/notify/watcher_readdcw_test.go
new file mode 100644 (file)
index 0000000..ea15b4a
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import "testing"
+
+// TODO(ppknap) : remove notify.Create event.
+func rcreate(w *W, path string) WCase {
+       cas := create(w, path)
+       cas.Events = append(cas.Events,
+               &Call{P: path, E: FileActionAdded},
+       )
+       return cas
+}
+
+// TODO(ppknap) : remove notify.Remove event.
+func rremove(w *W, path string) WCase {
+       cas := remove(w, path)
+       cas.Events = append(cas.Events,
+               &Call{P: path, E: FileActionRemoved},
+       )
+       return cas
+}
+
+// TODO(ppknap) : remove notify.Rename event.
+func rrename(w *W, oldpath, newpath string) WCase {
+       cas := rename(w, oldpath, newpath)
+       cas.Events = append(cas.Events,
+               &Call{P: oldpath, E: FileActionRenamedOldName},
+               &Call{P: newpath, E: FileActionRenamedNewName},
+       )
+       return cas
+}
+
+// TODO(ppknap) : remove notify.Write event.
+func rwrite(w *W, path string, p []byte) WCase {
+       cas := write(w, path, p)
+       cas.Events = append(cas.Events,
+               &Call{P: path, E: FileActionModified},
+       )
+       return cas
+}
+
+var events = []Event{
+       FileNotifyChangeFileName,
+       FileNotifyChangeDirName,
+       FileNotifyChangeSize,
+}
+
+func TestWatcherReadDirectoryChangesW(t *testing.T) {
+       w := NewWatcherTest(t, "testdata/vfs.txt", events...)
+       defer w.Close()
+
+       cases := [...]WCase{
+               rcreate(w, "src/github.com/rjeczalik/fs/fs_windows.go"),
+               rcreate(w, "src/github.com/rjeczalik/fs/subdir/"),
+               rremove(w, "src/github.com/rjeczalik/fs/fs.go"),
+               rrename(w, "src/github.com/rjeczalik/fs/LICENSE", "src/github.com/rjeczalik/fs/COPYLEFT"),
+               rwrite(w, "src/github.com/rjeczalik/fs/cmd/gotree/go.go", []byte("XD")),
+       }
+
+       w.ExpectAny(cases[:])
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_recursive_test.go b/vendor/github.com/rjeczalik/notify/watcher_recursive_test.go
new file mode 100644 (file)
index 0000000..3b1f148
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue windows
+
+package notify
+
+import (
+       "fmt"
+       "testing"
+)
+
+// noevent stripts test-case from expected event list, used when action is not
+// expected to trigger any events.
+func noevent(cas WCase) WCase {
+       return WCase{Action: cas.Action}
+}
+
+func TestWatcherRecursiveRewatch(t *testing.T) {
+       w := newWatcherTest(t, "testdata/vfs.txt")
+       defer w.Close()
+
+       cases := []WCase{
+               create(w, "src/github.com/rjeczalik/file"),
+               create(w, "src/github.com/rjeczalik/dir/"),
+               noevent(create(w, "src/github.com/rjeczalik/fs/dir/")),
+               noevent(create(w, "src/github.com/dir/")),
+               noevent(write(w, "src/github.com/rjeczalik/file", []byte("XD"))),
+               noevent(rename(w, "src/github.com/rjeczalik/fs/LICENSE", "src/LICENSE")),
+       }
+
+       w.Watch("src/github.com/rjeczalik", Create)
+       w.ExpectAny(cases)
+
+       cases = []WCase{
+               create(w, "src/github.com/rjeczalik/fs/file"),
+               create(w, "src/github.com/rjeczalik/fs/cmd/gotree/file"),
+               create(w, "src/github.com/rjeczalik/fs/cmd/dir/"),
+               create(w, "src/github.com/rjeczalik/fs/cmd/gotree/dir/"),
+               noevent(write(w, "src/github.com/rjeczalik/fs/file", []byte("XD"))),
+               noevent(create(w, "src/github.com/anotherdir/")),
+       }
+
+       w.RecursiveRewatch("src/github.com/rjeczalik", "src/github.com/rjeczalik", Create, Create)
+       w.ExpectAny(cases)
+
+       cases = []WCase{
+               create(w, "src/github.com/rjeczalik/1"),
+               create(w, "src/github.com/rjeczalik/2/"),
+               noevent(create(w, "src/github.com/rjeczalik/fs/cmd/1")),
+               noevent(create(w, "src/github.com/rjeczalik/fs/1/")),
+               noevent(write(w, "src/github.com/rjeczalik/fs/file", []byte("XD"))),
+       }
+
+       w.Rewatch("src/github.com/rjeczalik", Create, Create)
+       w.ExpectAny(cases)
+}
+
+// TODO(rjeczalik): move to watcher_test.go after #5
+func TestIsDirCreateEvent(t *testing.T) {
+       w := NewWatcherTest(t, "testdata/vfs.txt")
+       defer w.Close()
+
+       cases := [...]WCase{
+               // i=0
+               create(w, "src/github.com/jszwec/"),
+               // i=1
+               create(w, "src/github.com/jszwec/gojunitxml/"),
+               // i=2
+               create(w, "src/github.com/jszwec/gojunitxml/README.md"),
+               // i=3
+               create(w, "src/github.com/jszwec/gojunitxml/LICENSE"),
+               // i=4
+               create(w, "src/github.com/jszwec/gojunitxml/cmd/"),
+       }
+
+       dirs := [...]bool{
+               true,  // i=0
+               true,  // i=1
+               false, // i=2
+               false, // i=3
+               true,  // i=4
+       }
+
+       fn := func(i int, _ WCase, ei EventInfo) error {
+               d, ok := ei.(isDirer)
+               if !ok {
+                       return fmt.Errorf("received EventInfo does not implement isDirer")
+               }
+               switch ok, err := d.isDir(); {
+               case err != nil:
+                       return err
+               case ok != dirs[i]:
+                       return fmt.Errorf("want ok=%v; got %v", dirs[i], ok)
+               default:
+                       return nil
+               }
+       }
+
+       w.ExpectAnyFunc(cases[:], fn)
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_stub.go b/vendor/github.com/rjeczalik/notify/watcher_stub.go
new file mode 100644 (file)
index 0000000..68b9c13
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows
+// +build !kqueue,!solaris
+
+package notify
+
+import "errors"
+
+type stub struct{ error }
+
+// newWatcher stub.
+func newWatcher(chan<- EventInfo) watcher {
+       return stub{errors.New("notify: not implemented")}
+}
+
+// Following methods implement notify.watcher interface.
+func (s stub) Watch(string, Event) error          { return s }
+func (s stub) Rewatch(string, Event, Event) error { return s }
+func (s stub) Unwatch(string) (err error)         { return s }
+func (s stub) Close() error                       { return s }
diff --git a/vendor/github.com/rjeczalik/notify/watcher_test.go b/vendor/github.com/rjeczalik/notify/watcher_test.go
new file mode 100644 (file)
index 0000000..911cba4
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin linux freebsd dragonfly netbsd openbsd windows solaris
+
+package notify
+
+import "testing"
+
+// NOTE Set DEBUG env var for extra debugging info.
+
+func TestWatcher(t *testing.T) {
+       w := NewWatcherTest(t, "testdata/vfs.txt")
+       defer w.Close()
+
+       cases := [...]WCase{
+               create(w, "src/github.com/ppknap/link/include/coost/.link.hpp.swp"),
+               create(w, "src/github.com/rjeczalik/fs/fs_test.go"),
+               create(w, "src/github.com/rjeczalik/fs/binfs/"),
+               create(w, "src/github.com/rjeczalik/fs/binfs.go"),
+               create(w, "src/github.com/rjeczalik/fs/binfs_test.go"),
+               remove(w, "src/github.com/rjeczalik/fs/binfs/"),
+               create(w, "src/github.com/rjeczalik/fs/binfs/"),
+               create(w, "src/github.com/rjeczalik/fs/virfs"),
+               remove(w, "src/github.com/rjeczalik/fs/virfs"),
+               create(w, "file"),
+               create(w, "dir/"),
+       }
+
+       w.ExpectAny(cases[:])
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_trigger.go b/vendor/github.com/rjeczalik/notify/watcher_trigger.go
new file mode 100644 (file)
index 0000000..d079d59
--- /dev/null
@@ -0,0 +1,432 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
+
+// watcher_trigger is used for FEN and kqueue which behave similarly:
+// only files and dirs can be watched directly, but not files inside dirs.
+// As a result Create events have to be generated by implementation when
+// after Write event is returned for watched dir, it is rescanned and Create
+// event is returned for new files and these are automatically added
+// to watchlist. In case of removal of watched directory, native system returns
+// events for all files, but for Rename, they also need to be generated.
+// As a result native system works as something like trigger for rescan,
+// but contains additional data about dir in which changes occurred. For files
+// detailed data is returned.
+// Usage of watcher_trigger requires:
+// - trigger implementation,
+// - encode func,
+// - not2nat, nat2not maps.
+// Required manual operations on filesystem can lead to loss of precision.
+
+package notify
+
+import (
+       "os"
+       "path/filepath"
+       "strings"
+       "sync"
+       "syscall"
+)
+
+// trigger is to be implemented by platform implementation like FEN or kqueue.
+type trigger interface {
+       // Close closes watcher's main native file descriptor.
+       Close() error
+       // Stop waiting for new events.
+       Stop() error
+       // Create new instance of watched.
+       NewWatched(string, os.FileInfo) (*watched, error)
+       // Record internally new *watched instance.
+       Record(*watched)
+       // Del removes internal copy of *watched instance.
+       Del(*watched)
+       // Watched returns *watched instance and native events for native type.
+       Watched(interface{}) (*watched, int64, error)
+       // Init initializes native watcher call.
+       Init() error
+       // Watch starts watching provided file/dir.
+       Watch(os.FileInfo, *watched, int64) error
+       // Unwatch stops watching provided file/dir.
+       Unwatch(*watched) error
+       // Wait for new events.
+       Wait() (interface{}, error)
+       // IsStop checks if Wait finished because of request watcher's stop.
+       IsStop(n interface{}, err error) bool
+}
+
+// encode Event to native representation. Implementation is to be provided by
+// platform specific implementation.
+var encode func(Event, bool) int64
+
+var (
+       // nat2not matches native events to notify's ones. To be initialized by
+       // platform dependent implementation.
+       nat2not map[Event]Event
+       // not2nat matches notify's events to native ones. To be initialized by
+       // platform dependent implementation.
+       not2nat map[Event]Event
+)
+
+// trg is a main structure implementing watcher.
+type trg struct {
+       sync.Mutex
+       // s is a channel used to stop monitoring.
+       s chan struct{}
+       // c is a channel used to pass events further.
+       c chan<- EventInfo
+       // pthLkp is a data structure mapping file names with data about watching
+       // represented by them files/directories.
+       pthLkp map[string]*watched
+       // t is a platform dependent implementation of trigger.
+       t trigger
+}
+
+// newWatcher returns new watcher's implementation.
+func newWatcher(c chan<- EventInfo) watcher {
+       t := &trg{
+               s:      make(chan struct{}, 1),
+               pthLkp: make(map[string]*watched, 0),
+               c:      c,
+       }
+       t.t = newTrigger(t.pthLkp)
+       if err := t.t.Init(); err != nil {
+               panic(err)
+       }
+       go t.monitor()
+       return t
+}
+
+// Close implements watcher.
+func (t *trg) Close() (err error) {
+       t.Lock()
+       if err = t.t.Stop(); err != nil {
+               t.Unlock()
+               return
+       }
+       <-t.s
+       var e error
+       for _, w := range t.pthLkp {
+               if e = t.unwatch(w.p, w.fi); e != nil {
+                       dbgprintf("trg: unwatch %q failed: %q\n", w.p, e)
+                       err = nonil(err, e)
+               }
+       }
+       if e = t.t.Close(); e != nil {
+               dbgprintf("trg: closing native watch failed: %q\n", e)
+               err = nonil(err, e)
+       }
+       t.Unlock()
+       return
+}
+
+// send reported events one by one through chan.
+func (t *trg) send(evn []event) {
+       for i := range evn {
+               t.c <- &evn[i]
+       }
+}
+
+// singlewatch starts to watch given p file/directory.
+func (t *trg) singlewatch(p string, e Event, direct mode, fi os.FileInfo) (err error) {
+       w, ok := t.pthLkp[p]
+       if !ok {
+               if w, err = t.t.NewWatched(p, fi); err != nil {
+                       return
+               }
+       }
+       switch direct {
+       case dir:
+               w.eDir |= e
+       case ndir:
+               w.eNonDir |= e
+       case both:
+               w.eDir |= e
+               w.eNonDir |= e
+       }
+       if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil {
+               return
+       }
+       if !ok {
+               t.t.Record(w)
+               return nil
+       }
+       return errAlreadyWatched
+}
+
+// decode converts event received from native to notify.Event
+// representation taking into account requested events (w).
+func decode(o int64, w Event) (e Event) {
+       for f, n := range nat2not {
+               if o&int64(f) != 0 {
+                       if w&f != 0 {
+                               e |= f
+                       }
+                       if w&n != 0 {
+                               e |= n
+                       }
+               }
+       }
+
+       return
+}
+
+func (t *trg) watch(p string, e Event, fi os.FileInfo) error {
+       if err := t.singlewatch(p, e, dir, fi); err != nil {
+               if err != errAlreadyWatched {
+                       return nil
+               }
+       }
+       if fi.IsDir() {
+               err := t.walk(p, func(fi os.FileInfo) (err error) {
+                       if err = t.singlewatch(filepath.Join(p, fi.Name()), e, ndir,
+                               fi); err != nil {
+                               if err != errAlreadyWatched {
+                                       return
+                               }
+                       }
+                       return nil
+               })
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// walk runs f func on each file/dir from p directory.
+func (t *trg) walk(p string, fn func(os.FileInfo) error) error {
+       fp, err := os.Open(p)
+       if err != nil {
+               return err
+       }
+       ls, err := fp.Readdir(0)
+       fp.Close()
+       if err != nil {
+               return err
+       }
+       for i := range ls {
+               if err := fn(ls[i]); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (t *trg) unwatch(p string, fi os.FileInfo) error {
+       if fi.IsDir() {
+               err := t.walk(p, func(fi os.FileInfo) error {
+                       err := t.singleunwatch(filepath.Join(p, fi.Name()), ndir)
+                       if err != errNotWatched {
+                               return err
+                       }
+                       return nil
+               })
+               if err != nil {
+                       return err
+               }
+       }
+       return t.singleunwatch(p, dir)
+}
+
+// Watch implements Watcher interface.
+func (t *trg) Watch(p string, e Event) error {
+       fi, err := os.Stat(p)
+       if err != nil {
+               return err
+       }
+       t.Lock()
+       err = t.watch(p, e, fi)
+       t.Unlock()
+       return err
+}
+
+// Unwatch implements Watcher interface.
+func (t *trg) Unwatch(p string) error {
+       fi, err := os.Stat(p)
+       if err != nil {
+               return err
+       }
+       t.Lock()
+       err = t.unwatch(p, fi)
+       t.Unlock()
+       return err
+}
+
+// Rewatch implements Watcher interface.
+//
+// TODO(rjeczalik): This is a naive hack. Rewrite might help.
+func (t *trg) Rewatch(p string, _, e Event) error {
+       fi, err := os.Stat(p)
+       if err != nil {
+               return err
+       }
+       t.Lock()
+       if err = t.unwatch(p, fi); err == nil {
+               // TODO(rjeczalik): If watch fails then we leave trigger in inconsistent
+               // state. Handle? Panic? Native version of rewatch?
+               err = t.watch(p, e, fi)
+       }
+       t.Unlock()
+       return nil
+}
+
+func (*trg) file(w *watched, n interface{}, e Event) (evn []event) {
+       evn = append(evn, event{w.p, e, w.fi.IsDir(), n})
+       return
+}
+
+func (t *trg) dir(w *watched, n interface{}, e, ge Event) (evn []event) {
+       // If it's dir and delete we have to send it and continue, because
+       // other processing relies on opening (in this case not existing) dir.
+       // Events for contents of this dir are reported by native impl.
+       // However events for rename must be generated for all monitored files
+       // inside of moved directory, because native impl does not report it independently
+       // for each file descriptor being moved in result of move action on
+       // parent directory.
+       if (ge & (not2nat[Rename] | not2nat[Remove])) != 0 {
+               // Write is reported also for Remove on directory. Because of that
+               // we have to filter it out explicitly.
+               evn = append(evn, event{w.p, e & ^Write & ^not2nat[Write], true, n})
+               if ge&not2nat[Rename] != 0 {
+                       for p := range t.pthLkp {
+                               if strings.HasPrefix(p, w.p+string(os.PathSeparator)) {
+                                       if err := t.singleunwatch(p, both); err != nil && err != errNotWatched &&
+                                               !os.IsNotExist(err) {
+                                               dbgprintf("trg: failed stop watching moved file (%q): %q\n",
+                                                       p, err)
+                                       }
+                                       if (w.eDir|w.eNonDir)&(not2nat[Rename]|Rename) != 0 {
+                                               evn = append(evn, event{
+                                                       p, (w.eDir | w.eNonDir) & e &^ Write &^ not2nat[Write],
+                                                       w.fi.IsDir(), nil,
+                                               })
+                                       }
+                               }
+                       }
+               }
+               t.t.Del(w)
+               return
+       }
+       if (ge & not2nat[Write]) != 0 {
+               switch err := t.walk(w.p, func(fi os.FileInfo) error {
+                       p := filepath.Join(w.p, fi.Name())
+                       switch err := t.singlewatch(p, w.eDir, ndir, fi); {
+                       case os.IsNotExist(err) && ((w.eDir & Remove) != 0):
+                               evn = append(evn, event{p, Remove, fi.IsDir(), n})
+                       case err == errAlreadyWatched:
+                       case err != nil:
+                               dbgprintf("trg: watching %q failed: %q", p, err)
+                       case (w.eDir & Create) != 0:
+                               evn = append(evn, event{p, Create, fi.IsDir(), n})
+                       default:
+                       }
+                       return nil
+               }); {
+               case os.IsNotExist(err):
+                       return
+               case err != nil:
+                       dbgprintf("trg: dir processing failed: %q", err)
+               default:
+               }
+       }
+       return
+}
+
+type mode uint
+
+const (
+       dir mode = iota
+       ndir
+       both
+)
+
+// unwatch stops watching p file/directory.
+func (t *trg) singleunwatch(p string, direct mode) error {
+       w, ok := t.pthLkp[p]
+       if !ok {
+               return errNotWatched
+       }
+       switch direct {
+       case dir:
+               w.eDir = 0
+       case ndir:
+               w.eNonDir = 0
+       case both:
+               w.eDir, w.eNonDir = 0, 0
+       }
+       if err := t.t.Unwatch(w); err != nil {
+               return err
+       }
+       if w.eNonDir|w.eDir != 0 {
+               mod := dir
+               if w.eNonDir == 0 {
+                       mod = ndir
+               }
+               if err := t.singlewatch(p, w.eNonDir|w.eDir, mod,
+                       w.fi); err != nil && err != errAlreadyWatched {
+                       return err
+               }
+       } else {
+               t.t.Del(w)
+       }
+       return nil
+}
+
+func (t *trg) monitor() {
+       var (
+               n   interface{}
+               err error
+       )
+       for {
+               switch n, err = t.t.Wait(); {
+               case err == syscall.EINTR:
+               case t.t.IsStop(n, err):
+                       t.s <- struct{}{}
+                       return
+               case err != nil:
+                       dbgprintf("trg: failed to read events: %q\n", err)
+               default:
+                       t.send(t.process(n))
+               }
+       }
+}
+
+// process event returned by native call.
+func (t *trg) process(n interface{}) (evn []event) {
+       t.Lock()
+       w, ge, err := t.t.Watched(n)
+       if err != nil {
+               t.Unlock()
+               dbgprintf("trg: %v event lookup failed: %q", Event(ge), err)
+               return
+       }
+
+       e := decode(ge, w.eDir|w.eNonDir)
+       if ge&int64(not2nat[Remove]|not2nat[Rename]) == 0 {
+               switch fi, err := os.Stat(w.p); {
+               case err != nil:
+               default:
+                       if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil {
+                               dbgprintf("trg: %q is no longer watched: %q", w.p, err)
+                               t.t.Del(w)
+                       }
+               }
+       }
+       if e == Event(0) && (!w.fi.IsDir() || (ge&int64(not2nat[Write])) == 0) {
+               t.Unlock()
+               return
+       }
+
+       if w.fi.IsDir() {
+               evn = append(evn, t.dir(w, n, e, Event(ge))...)
+       } else {
+               evn = append(evn, t.file(w, n, e)...)
+       }
+       if Event(ge)&(not2nat[Remove]|not2nat[Rename]) != 0 {
+               t.t.Del(w)
+       }
+       t.Unlock()
+       return
+}
diff --git a/vendor/github.com/rjeczalik/notify/watcher_trigger_test.go b/vendor/github.com/rjeczalik/notify/watcher_trigger_test.go
new file mode 100644 (file)
index 0000000..e59cbbc
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright (c) 2014-2017 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
+
+package notify
+
+import "testing"
+
+func TestWatcherCreateOnly(t *testing.T) {
+       w := NewWatcherTest(t, "testdata/vfs.txt", Create)
+       defer w.Close()
+
+       cases := [...]WCase{
+               create(w, "dir/"),
+               create(w, "dir2/"),
+       }
+
+       w.ExpectAny(cases[:])
+}
diff --git a/vendor/github.com/rjeczalik/notify/watchpoint.go b/vendor/github.com/rjeczalik/notify/watchpoint.go
new file mode 100644 (file)
index 0000000..5afc914
--- /dev/null
@@ -0,0 +1,103 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+// EventDiff describes a change to an event set - EventDiff[0] is an old state,
+// while EventDiff[1] is a new state. If event set has not changed (old == new),
+// functions typically return the None value.
+type eventDiff [2]Event
+
+func (diff eventDiff) Event() Event {
+       return diff[1] &^ diff[0]
+}
+
+// Watchpoint
+//
+// The nil key holds total event set - logical sum for all registered events.
+// It speeds up computing EventDiff for Add method.
+//
+// The rec key holds an event set for a watchpoints created by RecursiveWatch
+// for a Watcher implementation which is not natively recursive.
+type watchpoint map[chan<- EventInfo]Event
+
+// None is an empty event diff, think null object.
+var none eventDiff
+
+// rec is just a placeholder
+var rec = func() (ch chan<- EventInfo) {
+       ch = make(chan<- EventInfo)
+       close(ch)
+       return
+}()
+
+func (wp watchpoint) dryAdd(ch chan<- EventInfo, e Event) eventDiff {
+       if e &^= internal; wp[ch]&e == e {
+               return none
+       }
+       total := wp[ch] &^ internal
+       return eventDiff{total, total | e}
+}
+
+// Add assumes neither c nor e are nil or zero values.
+func (wp watchpoint) Add(c chan<- EventInfo, e Event) (diff eventDiff) {
+       wp[c] |= e
+       diff[0] = wp[nil]
+       diff[1] = diff[0] | e
+       wp[nil] = diff[1] &^ omit
+       // Strip diff from internal events.
+       diff[0] &^= internal
+       diff[1] &^= internal
+       if diff[0] == diff[1] {
+               return none
+       }
+       return
+}
+
+func (wp watchpoint) Del(c chan<- EventInfo, e Event) (diff eventDiff) {
+       wp[c] &^= e
+       if wp[c] == 0 {
+               delete(wp, c)
+       }
+       diff[0] = wp[nil]
+       delete(wp, nil)
+       if len(wp) != 0 {
+               // Recalculate total event set.
+               for _, e := range wp {
+                       diff[1] |= e
+               }
+               wp[nil] = diff[1] &^ omit
+       }
+       // Strip diff from internal events.
+       diff[0] &^= internal
+       diff[1] &^= internal
+       if diff[0] == diff[1] {
+               return none
+       }
+       return
+}
+
+func (wp watchpoint) Dispatch(ei EventInfo, extra Event) {
+       e := eventmask(ei, extra)
+       if !matches(wp[nil], e) {
+               return
+       }
+       for ch, eset := range wp {
+               if ch != nil && matches(eset, e) {
+                       select {
+                       case ch <- ei:
+                       default: // Drop event if receiver is too slow
+                               dbgprintf("dropped %s on %q: receiver too slow", ei.Event(), ei.Path())
+                       }
+               }
+       }
+}
+
+func (wp watchpoint) Total() Event {
+       return wp[nil] &^ internal
+}
+
+func (wp watchpoint) IsRecursive() bool {
+       return wp[nil]&recursive != 0
+}
diff --git a/vendor/github.com/rjeczalik/notify/watchpoint_other.go b/vendor/github.com/rjeczalik/notify/watchpoint_other.go
new file mode 100644 (file)
index 0000000..9bb381d
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !windows
+
+package notify
+
+// eventmask uses ei to create a new event which contains internal flags used by
+// notify package logic.
+func eventmask(ei EventInfo, extra Event) Event {
+       return ei.Event() | extra
+}
+
+// matches reports a match only when:
+//
+//   - for user events, when event is present in the given set
+//   - for internal events, when additionally both event and set have omit bit set
+//
+// Internal events must not be sent to user channels and vice versa.
+func matches(set, event Event) bool {
+       return (set&omit)^(event&omit) == 0 && set&event == event
+}
diff --git a/vendor/github.com/rjeczalik/notify/watchpoint_readdcw.go b/vendor/github.com/rjeczalik/notify/watchpoint_readdcw.go
new file mode 100644 (file)
index 0000000..9fd1e1d
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+// eventmask uses ei to create a new event which contains internal flags used by
+// notify package logic. If one of FileAction* masks is detected, this function
+// adds corresponding FileNotifyChange* values. This allows non registered
+// FileAction* events to be passed on.
+func eventmask(ei EventInfo, extra Event) (e Event) {
+       if e = ei.Event() | extra; e&fileActionAll != 0 {
+               if ev, ok := ei.(*event); ok {
+                       switch ev.ftype {
+                       case fTypeFile:
+                               e |= FileNotifyChangeFileName
+                       case fTypeDirectory:
+                               e |= FileNotifyChangeDirName
+                       case fTypeUnknown:
+                               e |= fileNotifyChangeModified
+                       }
+                       return e &^ fileActionAll
+               }
+       }
+       return
+}
+
+// matches reports a match only when:
+//
+//   - for user events, when event is present in the given set
+//   - for internal events, when additionally both event and set have omit bit set
+//
+// Internal events must not be sent to user channels and vice versa.
+func matches(set, event Event) bool {
+       return (set&omit)^(event&omit) == 0 && (set&event == event || set&fileNotifyChangeModified&event != 0)
+}
diff --git a/vendor/github.com/rjeczalik/notify/watchpoint_test.go b/vendor/github.com/rjeczalik/notify/watchpoint_test.go
new file mode 100644 (file)
index 0000000..e35483d
--- /dev/null
@@ -0,0 +1,162 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+       "fmt"
+       "reflect"
+       "testing"
+)
+
+func call(wp watchpoint, fn interface{}, args []interface{}) eventDiff {
+       vals := []reflect.Value{reflect.ValueOf(wp)}
+       for _, arg := range args {
+               vals = append(vals, reflect.ValueOf(arg))
+       }
+       res := reflect.ValueOf(fn).Call(vals)
+       if n := len(res); n != 1 {
+               panic(fmt.Sprintf("unexpected len(res)=%d", n))
+       }
+       diff, ok := res[0].Interface().(eventDiff)
+       if !ok {
+               panic(fmt.Sprintf("want typeof(diff)=EventDiff; got %T", res[0].Interface()))
+       }
+       return diff
+}
+
+func TestWatchpoint(t *testing.T) {
+       ch := NewChans(5)
+       all := All | recursive
+       cases := [...]struct {
+               fn    interface{}
+               args  []interface{}
+               diff  eventDiff
+               total Event
+       }{
+               // i=0
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[0], Remove},
+                       eventDiff{0, Remove},
+                       Remove,
+               },
+               // i=1
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[1], Create | Remove | recursive},
+                       eventDiff{Remove, Remove | Create},
+                       Create | Remove | recursive,
+               },
+               // i=2
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[2], Create | Rename},
+                       eventDiff{Create | Remove, Create | Remove | Rename},
+                       Create | Remove | Rename | recursive,
+               },
+               // i=3
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[0], Write | recursive},
+                       eventDiff{Create | Remove | Rename, Create | Remove | Rename | Write},
+                       Create | Remove | Rename | Write | recursive,
+               },
+               // i=4
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[2], Remove | recursive},
+                       none,
+                       Create | Remove | Rename | Write | recursive,
+               },
+               // i=5
+               {
+                       watchpoint.Del,
+                       []interface{}{ch[0], all},
+                       eventDiff{Create | Remove | Rename | Write, Create | Remove | Rename},
+                       Create | Remove | Rename | recursive,
+               },
+               // i=6
+               {
+                       watchpoint.Del,
+                       []interface{}{ch[2], all},
+                       eventDiff{Create | Remove | Rename, Create | Remove},
+                       Create | Remove | recursive,
+               },
+               // i=7
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[3], Create | Remove},
+                       none,
+                       Create | Remove | recursive,
+               },
+               // i=8
+               {
+                       watchpoint.Del,
+                       []interface{}{ch[1], all},
+                       none,
+                       Create | Remove,
+               },
+               // i=9
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[3], recursive | Write},
+                       eventDiff{Create | Remove, Create | Remove | Write},
+                       Create | Remove | Write | recursive,
+               },
+               // i=10
+               {
+                       watchpoint.Del,
+                       []interface{}{ch[3], Create},
+                       eventDiff{Create | Remove | Write, Remove | Write},
+                       Remove | Write | recursive,
+               },
+               // i=11
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[3], Create | Rename},
+                       eventDiff{Remove | Write, Create | Remove | Rename | Write},
+                       Create | Remove | Rename | Write | recursive,
+               },
+               // i=12
+               {
+                       watchpoint.Add,
+                       []interface{}{ch[2], Remove | Write},
+                       none,
+                       Create | Remove | Rename | Write | recursive,
+               },
+               // i=13
+               {
+                       watchpoint.Del,
+                       []interface{}{ch[3], Create | Remove | Write},
+                       eventDiff{Create | Remove | Rename | Write, Remove | Rename | Write},
+                       Remove | Rename | Write | recursive,
+               },
+               // i=14
+               {
+                       watchpoint.Del,
+                       []interface{}{ch[2], Remove},
+                       eventDiff{Remove | Rename | Write, Rename | Write},
+                       Rename | Write | recursive,
+               },
+               // i=15
+               {
+                       watchpoint.Del,
+                       []interface{}{ch[3], Rename | recursive},
+                       eventDiff{Rename | Write, Write},
+                       Write,
+               },
+       }
+       wp := watchpoint{}
+       for i, cas := range cases {
+               if diff := call(wp, cas.fn, cas.args); diff != cas.diff {
+                       t.Errorf("want diff=%v; got %v (i=%d)", cas.diff, diff, i)
+                       continue
+               }
+               if total := wp[nil]; total != cas.total {
+                       t.Errorf("want total=%v; got %v (i=%d)", cas.total, total, i)
+                       continue
+               }
+       }
+}