diff -Nru golang-fsnotify-1.2.1/AUTHORS golang-fsnotify-1.2.9/AUTHORS --- golang-fsnotify-1.2.1/AUTHORS 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/AUTHORS 2016-01-14 02:10:36.000000000 +0000 @@ -11,18 +11,23 @@ Adrien Bustany Caleb Spare Case Nelson -Chris Howey +Chris Howey Christoffer Buchholz +Daniel Wagner-Hall Dave Cheney +Evan Phoenix Francisco Souza Hari haran John C Barstow Kelvin Fo +Ken-ichirou MATSUZAWA Matt Layher Nathan Youngman Paul Hammond +Pawel Knap Pieter Droogendijk Pursuit92 +Riku Voipio Rob Figueiredo Soge Zhang Tilak Sharma @@ -32,3 +37,4 @@ bronze1man debrando henrikedwards +铁哥 diff -Nru golang-fsnotify-1.2.1/CHANGELOG.md golang-fsnotify-1.2.9/CHANGELOG.md --- golang-fsnotify-1.2.1/CHANGELOG.md 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/CHANGELOG.md 2016-01-14 02:10:36.000000000 +0000 @@ -1,5 +1,22 @@ # Changelog +## v1.2.9 / 2016-01-13 + +kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/go-fsnotify/fsnotify/pull/111) (thanks @bep) + +## v1.2.8 / 2015-12-17 + +* kqueue: fix race condition in Close [#105](https://github.com/go-fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test) +* inotify: fix race in test +* enable race detection for continuous integration (Linux, Mac, Windows) + +## v1.2.5 / 2015-10-17 + +* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/go-fsnotify/fsnotify/pull/100) (thanks @suihkulokki) +* inotify: fix path leaks [#73](https://github.com/go-fsnotify/fsnotify/pull/73) (thanks @chamaken) +* kqueue: watch for rename events on subdirectories [#83](https://github.com/go-fsnotify/fsnotify/pull/83) (thanks @guotie) +* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/go-fsnotify/fsnotify/pull/101) (thanks @illicitonion) + ## v1.2.1 / 2015-10-14 * kqueue: don't watch named pipes [#98](https://github.com/go-fsnotify/fsnotify/pull/98) (thanks @evanphx) @@ -51,7 +68,7 @@ * Moved to [github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify). * Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno) - + ## dev / 2014-07-04 * kqueue: fix incorrect mutex used in Close() @@ -264,4 +281,3 @@ [#25]: https://github.com/howeyc/fsnotify/issues/25 [#24]: https://github.com/howeyc/fsnotify/issues/24 [#21]: https://github.com/howeyc/fsnotify/issues/21 - diff -Nru golang-fsnotify-1.2.1/debian/changelog golang-fsnotify-1.2.9/debian/changelog --- golang-fsnotify-1.2.1/debian/changelog 2015-11-04 09:36:27.000000000 +0000 +++ golang-fsnotify-1.2.9/debian/changelog 2016-01-14 09:28:45.000000000 +0000 @@ -1,3 +1,11 @@ +golang-fsnotify (1.2.9-1) unstable; urgency=medium + + * Update to 1.2.9 upstream release + * Build-Depends on gccgo where golang-go is not available + * Add myself to the list of Uploaders + + -- Anthony Fok Thu, 14 Jan 2016 02:28:11 -0700 + golang-fsnotify (1.2.1-1) unstable; urgency=medium * Update to 1.2.1 upstream release diff -Nru golang-fsnotify-1.2.1/debian/control golang-fsnotify-1.2.9/debian/control --- golang-fsnotify-1.2.1/debian/control 2015-11-04 09:36:18.000000000 +0000 +++ golang-fsnotify-1.2.9/debian/control 2016-01-14 09:23:29.000000000 +0000 @@ -2,8 +2,11 @@ Section: devel Priority: extra Maintainer: Debian Go Packaging Team -Uploaders: Tianon Gravi -Build-Depends: debhelper (>= 9), dh-golang, golang-go +Uploaders: Tianon Gravi , Anthony Fok +Build-Depends: debhelper (>= 9), + dh-golang, + golang-go [amd64 arm64 armel armhf i386 ppc64 ppc64el], + gccgo [!amd64 !arm64 !armel !armhf !i386 !ppc64 !ppc64el] Standards-Version: 3.9.6 Homepage: https://github.com/go-fsnotify/fsnotify Vcs-Git: git://anonscm.debian.org/pkg-go/packages/golang-fsnotify.git diff -Nru golang-fsnotify-1.2.1/inotify.go golang-fsnotify-1.2.9/inotify.go --- golang-fsnotify-1.2.1/inotify.go 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/inotify.go 2016-01-14 02:10:36.000000000 +0000 @@ -23,6 +23,7 @@ Events chan Event Errors chan error mu sync.Mutex // Map access + cv *sync.Cond // sync removing on rm_watch with IN_IGNORE fd int poller *fdPoller watches map[string]*watch // Map of inotify watches (key: path) @@ -54,6 +55,7 @@ done: make(chan struct{}), doneResp: make(chan struct{}), } + w.cv = sync.NewCond(&w.mu) go w.readEvents() return w, nil @@ -134,8 +136,10 @@ } // inotify_rm_watch will return EINVAL if the file has been deleted; // the inotify will already have been removed. - // That means we can safely delete it from our watches, whatever inotify_rm_watch does. - delete(w.watches, name) + // watches and pathes are deleted in ignoreLinux() implicitly and asynchronously + // by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE + // so that EINVAL means that the wd is being rm_watch()ed or its file removed + // by another thread and we have not received IN_IGNORE event. success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) if success == -1 { // TODO: Perhaps it's not helpful to return an error here in every case. @@ -146,6 +150,14 @@ // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. return errno } + + // wait until ignoreLinux() deleting maps + exists := true + for exists { + w.cv.Wait() + _, exists = w.watches[name] + } + return nil } @@ -249,7 +261,7 @@ event := newEvent(name, mask) // Send the events that are not ignored on the events channel - if !event.ignoreLinux(mask) { + if !event.ignoreLinux(w, raw.Wd, mask) { select { case w.Events <- event: case <-w.done: @@ -266,9 +278,15 @@ // Certain types of events can be "ignored" and not sent over the Events // channel. Such as events marked ignore by the kernel, or MODIFY events // against files that do not exist. -func (e *Event) ignoreLinux(mask uint32) bool { +func (e *Event) ignoreLinux(w *Watcher, wd int32, mask uint32) bool { // Ignore anything the inotify API says to ignore if mask&syscall.IN_IGNORED == syscall.IN_IGNORED { + w.mu.Lock() + defer w.mu.Unlock() + name := w.paths[int(wd)] + delete(w.paths, int(wd)) + delete(w.watches, name) + w.cv.Broadcast() return true } diff -Nru golang-fsnotify-1.2.1/inotify_poller.go golang-fsnotify-1.2.9/inotify_poller.go --- golang-fsnotify-1.2.1/inotify_poller.go 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/inotify_poller.go 2016-01-14 02:10:36.000000000 +0000 @@ -39,7 +39,7 @@ poller.fd = fd // Create epoll fd - poller.epfd, errno = syscall.EpollCreate(1) + poller.epfd, errno = syscall.EpollCreate1(0) if poller.epfd == -1 { return nil, errno } diff -Nru golang-fsnotify-1.2.1/inotify_test.go golang-fsnotify-1.2.9/inotify_test.go --- golang-fsnotify-1.2.1/inotify_test.go 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/inotify_test.go 2016-01-14 02:10:36.000000000 +0000 @@ -7,6 +7,7 @@ package fsnotify import ( + "fmt" "os" "path/filepath" "syscall" @@ -281,12 +282,62 @@ } err = w.Remove(testFile) - if err != syscall.EINVAL { - t.Fatalf("Expected EINVAL from Remove, got: %v", err) + if err == nil { + t.Fatalf("no error on removing invalid file") } + s1 := fmt.Sprintf("%s", err) err = w.Remove(testFile) - if err == syscall.EINVAL { - t.Fatalf("Got EINVAL again, watch was not removed") + if err == nil { + t.Fatalf("no error on removing invalid file") + } + s2 := fmt.Sprintf("%s", err) + + if s1 != s2 { + t.Fatalf("receive different error - %s / %s", s1, s2) + } +} + +func TestInotifyInnerMapLength(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + testFile := filepath.Join(testDir, "testfile") + + handle, err := os.Create(testFile) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + handle.Close() + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testFile) + if err != nil { + t.Fatalf("Failed to add testFile: %v", err) + } + go func() { + for err := range w.Errors { + t.Fatalf("error received: %s", err) + } + }() + + err = os.Remove(testFile) + if err != nil { + t.Fatalf("Failed to remove testFile: %v", err) + } + _ = <-w.Events // consume Remove event + <-time.After(50 * time.Millisecond) // wait IN_IGNORE propagated + + w.mu.Lock() + defer w.mu.Unlock() + if len(w.watches) != 0 { + t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches) + } + if len(w.paths) != 0 { + t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths) } } diff -Nru golang-fsnotify-1.2.1/integration_darwin_test.go golang-fsnotify-1.2.9/integration_darwin_test.go --- golang-fsnotify-1.2.1/integration_darwin_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-fsnotify-1.2.9/integration_darwin_test.go 2016-01-14 02:10:36.000000000 +0000 @@ -0,0 +1,146 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fsnotify + +import ( + "os" + "path/filepath" + "syscall" + "testing" + "time" +) + +// testExchangedataForWatcher tests the watcher with the exchangedata operation on OS X. +// +// This is widely used for atomic saves on OS X, e.g. TextMate and in Apple's NSDocument. +// +// See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html +// Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20 +func testExchangedataForWatcher(t *testing.T, watchDir bool) { + // Create directory to watch + testDir1 := tempMkdir(t) + + // For the intermediate file + testDir2 := tempMkdir(t) + + defer os.RemoveAll(testDir1) + defer os.RemoveAll(testDir2) + + resolvedFilename := "TestFsnotifyEvents.file" + + // TextMate does: + // + // 1. exchangedata (intermediate, resolved) + // 2. unlink intermediate + // + // Let's try to simulate that: + resolved := filepath.Join(testDir1, resolvedFilename) + intermediate := filepath.Join(testDir2, resolvedFilename+"~") + + // Make sure we create the file before we start watching + createAndSyncFile(t, resolved) + + watcher := newWatcher(t) + + // Test both variants in isolation + if watchDir { + addWatch(t, watcher, testDir1) + } else { + addWatch(t, watcher, resolved) + } + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var removeReceived counter + var createReceived counter + + done := make(chan bool) + + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(resolved) { + if event.Op&Remove == Remove { + removeReceived.increment() + } + if event.Op&Create == Create { + createReceived.increment() + } + } + t.Logf("event received: %s", event) + } + done <- true + }() + + // Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop. + for i := 1; i <= 3; i++ { + // The intermediate file is created in a folder outside the watcher + createAndSyncFile(t, intermediate) + + // 1. Swap + if err := syscall.Exchangedata(intermediate, resolved, 0); err != nil { + t.Fatalf("[%d] exchangedata failed: %s", i, err) + } + + time.Sleep(50 * time.Millisecond) + + // 2. Delete the intermediate file + err := os.Remove(intermediate) + + if err != nil { + t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err) + } + + time.Sleep(50 * time.Millisecond) + + } + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + + // The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two: + if removeReceived.value() < 3 { + t.Fatal("fsnotify remove events have not been received after 500 ms") + } + + if createReceived.value() < 3 { + t.Fatal("fsnotify create events have not been received after 500 ms") + } + + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } +} + +// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir. +func TestExchangedataInWatchedDir(t *testing.T) { + testExchangedataForWatcher(t, true) +} + +// TestExchangedataInWatchedDir test exchangedata operation on watched file. +func TestExchangedataInWatchedFile(t *testing.T) { + testExchangedataForWatcher(t, false) +} + +func createAndSyncFile(t *testing.T, filepath string) { + f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating %s failed: %s", filepath, err) + } + f1.Sync() + f1.Close() +} diff -Nru golang-fsnotify-1.2.1/integration_test.go golang-fsnotify-1.2.9/integration_test.go --- golang-fsnotify-1.2.1/integration_test.go 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/integration_test.go 2016-01-14 02:10:36.000000000 +0000 @@ -10,6 +10,7 @@ "io/ioutil" "os" "os/exec" + "path" "path/filepath" "runtime" "sync/atomic" @@ -43,6 +44,16 @@ return dir } +// tempMkFile makes a temporary file. +func tempMkFile(t *testing.T, dir string) string { + f, err := ioutil.TempFile(dir, "fsnotify") + if err != nil { + t.Fatalf("failed to create test file: %v", err) + } + defer f.Close() + return f.Name() +} + // newWatcher initializes an fsnotify Watcher instance. func newWatcher(t *testing.T) *Watcher { watcher, err := NewWatcher() @@ -1065,6 +1076,53 @@ watcher.Close() } +func TestCyclicSymlink(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("symlinks don't work on Windows.") + } + + watcher := newWatcher(t) + + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + link := path.Join(testDir, "link") + if err := os.Symlink(".", link); err != nil { + t.Fatalf("could not make symlink: %v", err) + } + addWatch(t, watcher, testDir) + + var createEventsReceived counter + go func() { + for ev := range watcher.Events { + if ev.Op&Create == Create { + createEventsReceived.increment() + } + } + }() + + if err := os.Remove(link); err != nil { + t.Fatalf("Error removing link: %v", err) + } + + // It would be nice to be able to expect a delete event here, but kqueue has + // no way for us to get events on symlinks themselves, because opening them + // opens an fd to the file to which they point. + + if err := ioutil.WriteFile(link, []byte("foo"), 0700); err != nil { + t.Fatalf("could not make symlink: %v", err) + } + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + + if got := createEventsReceived.value(); got == 0 { + t.Errorf("want at least 1 create event got %v", got) + } + + watcher.Close() +} + // TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race. // See https://codereview.appspot.com/103300045/ // go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race @@ -1123,6 +1181,50 @@ t.Fatalf("Expected no error on Close, got %v.", err) } } + +// TestRemoveWithClose tests if one can handle Remove events and, at the same +// time, close Watcher object without any data races. +func TestRemoveWithClose(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + const fileN = 200 + tempFiles := make([]string, 0, fileN) + for i := 0; i < fileN; i++ { + tempFiles = append(tempFiles, tempMkFile(t, testDir)) + } + watcher := newWatcher(t) + if err := watcher.Add(testDir); err != nil { + t.Fatalf("Expected no error on Add, got %v", err) + } + startC, stopC := make(chan struct{}), make(chan struct{}) + errC := make(chan error) + go func() { + for { + select { + case <-watcher.Errors: + case <-watcher.Events: + case <-stopC: + return + } + } + }() + go func() { + <-startC + for _, fileName := range tempFiles { + os.Remove(fileName) + } + }() + go func() { + <-startC + errC <- watcher.Close() + }() + close(startC) + defer close(stopC) + if err := <-errC; err != nil { + t.Fatalf("Expected no error on Close, got %v.", err) + } +} func testRename(file1, file2 string) error { switch runtime.GOOS { diff -Nru golang-fsnotify-1.2.1/kqueue.go golang-fsnotify-1.2.9/kqueue.go --- golang-fsnotify-1.2.1/kqueue.go 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/kqueue.go 2016-01-14 02:10:36.000000000 +0000 @@ -72,12 +72,17 @@ w.isClosed = true w.mu.Unlock() + // copy paths to remove while locked w.mu.Lock() - ws := w.watches + var pathsToRemove = make([]string, 0, len(w.watches)) + for name := range w.watches { + pathsToRemove = append(pathsToRemove, name) + } w.mu.Unlock() + // unlock before calling Remove, which also locks var err error - for name := range ws { + for _, name := range pathsToRemove { if e := w.Remove(name); e != nil && err == nil { err = e } @@ -94,7 +99,8 @@ w.mu.Lock() w.externalWatches[name] = true w.mu.Unlock() - return w.addWatch(name, noteAllEvents) + _, err := w.addWatch(name, noteAllEvents) + return err } // Remove stops watching the the named file or directory (non-recursively). @@ -153,7 +159,8 @@ // addWatch adds name to the watched file set. // The flags are interpreted as described in kevent(2). -func (w *Watcher) addWatch(name string, flags uint32) error { +// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks. +func (w *Watcher) addWatch(name string, flags uint32) (string, error) { var isDir bool // Make ./name and name equivalent name = filepath.Clean(name) @@ -161,7 +168,7 @@ w.mu.Lock() if w.isClosed { w.mu.Unlock() - return errors.New("kevent instance already closed") + return "", errors.New("kevent instance already closed") } watchfd, alreadyWatching := w.watches[name] // We already have a watch, but we can still override flags. @@ -173,17 +180,17 @@ if !alreadyWatching { fi, err := os.Lstat(name) if err != nil { - return err + return "", err } // Don't watch sockets. if fi.Mode()&os.ModeSocket == os.ModeSocket { - return nil + return "", nil } // Don't watch named pipes. if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { - return nil + return "", nil } // Follow Symlinks @@ -195,18 +202,26 @@ if fi.Mode()&os.ModeSymlink == os.ModeSymlink { name, err = filepath.EvalSymlinks(name) if err != nil { - return nil + return "", nil + } + + w.mu.Lock() + _, alreadyWatching = w.watches[name] + w.mu.Unlock() + + if alreadyWatching { + return name, nil } fi, err = os.Lstat(name) if err != nil { - return nil + return "", nil } } watchfd, err = syscall.Open(name, openMode, 0700) if watchfd == -1 { - return err + return "", err } isDir = fi.IsDir() @@ -215,7 +230,7 @@ const registerAdd = syscall.EV_ADD | syscall.EV_CLEAR | syscall.EV_ENABLE if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { syscall.Close(watchfd) - return err + return "", err } if !alreadyWatching { @@ -229,6 +244,7 @@ // Watch the directory if it has not been watched before, // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) w.mu.Lock() + watchDir := (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE && (!alreadyWatching || (w.dirFlags[name]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE) // Store flags so this watch can be updated later @@ -237,11 +253,11 @@ if watchDir { if err := w.watchDirectoryFiles(name); err != nil { - return err + return "", err } } } - return nil + return name, nil } // readEvents reads from kqueue and converts the received kevents into @@ -309,19 +325,24 @@ if event.Op&Remove == Remove { // Look for a file that may have overwritten this. // For example, mv f1 f2 will delete f2, then create f2. - fileDir, _ := filepath.Split(event.Name) - fileDir = filepath.Clean(fileDir) - w.mu.Lock() - _, found := w.watches[fileDir] - w.mu.Unlock() - if found { - // make sure the directory exists before we watch for changes. When we - // do a recursive watch and perform rm -fr, the parent directory might - // have gone missing, ignore the missing directory and let the - // upcoming delete event remove the watch from the parent directory. - if _, err := os.Lstat(fileDir); os.IsExist(err) { - w.sendDirectoryChangeEvents(fileDir) - // FIXME: should this be for events on files or just isDir? + if path.isDir { + fileDir := filepath.Clean(event.Name) + w.mu.Lock() + _, found := w.watches[fileDir] + w.mu.Unlock() + if found { + // make sure the directory exists before we watch for changes. When we + // do a recursive watch and perform rm -fr, the parent directory might + // have gone missing, ignore the missing directory and let the + // upcoming delete event remove the watch from the parent directory. + if _, err := os.Lstat(fileDir); err == nil { + w.sendDirectoryChangeEvents(fileDir) + } + } + } else { + filePath := filepath.Clean(event.Name) + if fileInfo, err := os.Lstat(filePath); err == nil { + w.sendFileCreatedEventIfNew(filePath, fileInfo) } } } @@ -364,7 +385,8 @@ for _, fileInfo := range files { filePath := filepath.Join(dirPath, fileInfo.Name()) - if err := w.internalWatch(filePath, fileInfo); err != nil { + filePath, err = w.internalWatch(filePath, fileInfo) + if err != nil { return err } @@ -390,26 +412,38 @@ // Search for new files for _, fileInfo := range files { filePath := filepath.Join(dirPath, fileInfo.Name()) - w.mu.Lock() - _, doesExist := w.fileExists[filePath] - w.mu.Unlock() - if !doesExist { - // Send create event - w.Events <- newCreateEvent(filePath) - } + err := w.sendFileCreatedEventIfNew(filePath, fileInfo) - // like watchDirectoryFiles (but without doing another ReadDir) - if err := w.internalWatch(filePath, fileInfo); err != nil { + if err != nil { return } + } +} - w.mu.Lock() - w.fileExists[filePath] = true - w.mu.Unlock() +// sendFileCreatedEvent sends a create event if the file isn't already being tracked. +func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) { + w.mu.Lock() + _, doesExist := w.fileExists[filePath] + w.mu.Unlock() + if !doesExist { + // Send create event + w.Events <- newCreateEvent(filePath) } + + // like watchDirectoryFiles (but without doing another ReadDir) + filePath, err = w.internalWatch(filePath, fileInfo) + if err != nil { + return err + } + + w.mu.Lock() + w.fileExists[filePath] = true + w.mu.Unlock() + + return nil } -func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) error { +func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) { if fileInfo.IsDir() { // mimic Linux providing delete events for subdirectories // but preserve the flags used if currently watching subdirectory @@ -417,7 +451,7 @@ flags := w.dirFlags[name] w.mu.Unlock() - flags |= syscall.NOTE_DELETE + flags |= syscall.NOTE_DELETE | syscall.NOTE_RENAME return w.addWatch(name, flags) } diff -Nru golang-fsnotify-1.2.1/README.md golang-fsnotify-1.2.9/README.md --- golang-fsnotify-1.2.1/README.md 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/README.md 2016-01-14 02:10:36.000000000 +0000 @@ -8,7 +8,7 @@ |Adapter |OS |Status | |----------|----------|----------| -|inotify |Linux, Android\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)| +|inotify |Linux 2.6.27 or later, Android\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)| |kqueue |BSD, OS X, iOS\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)| |ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)| |FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)| diff -Nru golang-fsnotify-1.2.1/.travis.yml golang-fsnotify-1.2.9/.travis.yml --- golang-fsnotify-1.2.1/.travis.yml 2015-10-14 23:43:03.000000000 +0000 +++ golang-fsnotify-1.2.9/.travis.yml 2016-01-14 02:10:36.000000000 +0000 @@ -2,11 +2,14 @@ language: go go: - - 1.5.1 + - 1.5.2 before_script: - go get -u github.com/golang/lint/golint +script: + - go test -v --race ./... + after_script: - test -z "$(gofmt -s -l -w . | tee /dev/stderr)" - test -z "$(golint ./... | tee /dev/stderr)"