listen 2.7.12 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -2
- data/lib/listen/record.rb +27 -27
- data/lib/listen/record/entry.rb +51 -0
- data/lib/listen/record/symlink_detector.rb +59 -0
- data/lib/listen/version.rb +1 -1
- data/spec/lib/listen/record_spec.rb +57 -41
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a859a235a28efe17024969a47542ebec79836f9
|
|
4
|
+
data.tar.gz: 54b831e943a6b7b773aa9da8684041ee55e9a8fe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7058ec4f00876391e3179d8566b6fcce5aa9741f37da3ac32b5f68f08e655520278320f16237e080113a4774a580b7c391f2f08569b4bf4cb3f709787b6334fc
|
|
7
|
+
data.tar.gz: 8eede032561181f9d5add525343992098c4216a59953d519eafaac11df294d0fc98bdc9dd31a0da6fb10a8c3fced456c57ee260a2ef620d57951051546342d91
|
data/README.md
CHANGED
|
@@ -6,6 +6,21 @@
|
|
|
6
6
|
|
|
7
7
|
The Listen gem listens to file modifications and notifies you about the changes.
|
|
8
8
|
|
|
9
|
+
## IMPORTANT NOTE
|
|
10
|
+
|
|
11
|
+
Got an issue with Listen and you're "in a hurry"? First, try Polling mode as a workaround (described below).
|
|
12
|
+
|
|
13
|
+
Otherwise ...
|
|
14
|
+
|
|
15
|
+
Output from using `LISTEN_GEM_DEBUGGING` (debug mode) is often *crucial* to quickly diagnosing and fixing issues.
|
|
16
|
+
|
|
17
|
+
There are TOO MANY possible and surprising reasons why Listen "doesn't work as expected" (e.g. Dropbox folders, editor settings, system limits) - and the best way to find out is by going through these 3 steps first:
|
|
18
|
+
|
|
19
|
+
See [TROUBLESHOOTING](https://github.com/guard/listen/blob/master/TROUBLESHOOTING.md)
|
|
20
|
+
|
|
21
|
+
Once you've reproduced the problem in debug mode (`LISTEN_GEM_DEBUGGING`), paste the output it into a Gist and link it to your issue.
|
|
22
|
+
|
|
23
|
+
|
|
9
24
|
## Features
|
|
10
25
|
|
|
11
26
|
* Supports watching multiple directories from a single listener.
|
|
@@ -25,8 +40,7 @@ Please note that:
|
|
|
25
40
|
|
|
26
41
|
## Pending features / issues
|
|
27
42
|
|
|
28
|
-
*
|
|
29
|
-
* Symlinks support. [#25](https://github.com/guard/listen/issues/25)
|
|
43
|
+
* Ignored directories are actually still scanned [#274](https://github.com/guard/listen/issues/274) (Except when polling)
|
|
30
44
|
* Directory/adapter specific configuration options
|
|
31
45
|
* Support for plugins
|
|
32
46
|
|
data/lib/listen/record.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
require 'listen/record/entry'
|
|
2
|
+
require 'listen/record/symlink_detector'
|
|
3
|
+
|
|
1
4
|
module Listen
|
|
2
5
|
class Record
|
|
3
6
|
include Celluloid
|
|
4
|
-
|
|
5
7
|
# TODO: one Record object per watched directory?
|
|
6
8
|
|
|
7
9
|
# TODO: deprecate
|
|
@@ -100,34 +102,32 @@ module Listen
|
|
|
100
102
|
end
|
|
101
103
|
end
|
|
102
104
|
|
|
105
|
+
# TODO: test with a file name given
|
|
106
|
+
# TODO: test other permissions
|
|
107
|
+
# TODO: test with mixed encoding
|
|
103
108
|
def _fast_build(root)
|
|
109
|
+
symlink_detector = SymlinkDetector.new
|
|
104
110
|
@paths[root] = _auto_hash
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
rescue SystemCallError
|
|
126
|
-
_fast_unset_path(root, dirname, entry)
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
111
|
+
remaining = Queue.new
|
|
112
|
+
remaining << Entry.new(root, nil, nil)
|
|
113
|
+
_fast_build_dir(remaining, symlink_detector) until remaining.empty?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def _fast_build_dir(remaining, symlink_detector)
|
|
117
|
+
entry = remaining.pop
|
|
118
|
+
entry.children.each { |child| remaining << child }
|
|
119
|
+
symlink_detector.verify_unwatched!(entry)
|
|
120
|
+
add_dir(entry.root, entry.record_dir_key)
|
|
121
|
+
rescue Errno::ENOTDIR
|
|
122
|
+
_fast_try_file(entry)
|
|
123
|
+
rescue SystemCallError
|
|
124
|
+
_fast_unset_path(entry.root, entry.relative, entry.name)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def _fast_try_file(entry)
|
|
128
|
+
_fast_update_file(entry.root, entry.relative, entry.name, entry.meta)
|
|
129
|
+
rescue SystemCallError
|
|
130
|
+
_fast_unset_path(entry.root, entry.relative, entry.name)
|
|
131
131
|
end
|
|
132
132
|
end
|
|
133
133
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Listen
|
|
2
|
+
# @private api
|
|
3
|
+
class Record
|
|
4
|
+
# Represents a directory entry (dir or file)
|
|
5
|
+
class Entry
|
|
6
|
+
# file: "/home/me/watched_dir", "app/models", "foo.rb"
|
|
7
|
+
# dir, "/home/me/watched_dir", "."
|
|
8
|
+
def initialize(root, relative, name = nil)
|
|
9
|
+
@root, @relative, @name = root, relative, name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :root, :relative, :name
|
|
13
|
+
|
|
14
|
+
def children
|
|
15
|
+
child_relative = _join
|
|
16
|
+
(Dir.entries(sys_path) - %w(. ..)).map do |name|
|
|
17
|
+
Entry.new(@root, child_relative, name)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def meta
|
|
22
|
+
lstat = ::File.lstat(sys_path)
|
|
23
|
+
{ mtime: lstat.mtime.to_f, mode: lstat.mode }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# record hash is e.g.
|
|
27
|
+
# if @record["/home/me/watched_dir"]["project/app/models"]["foo.rb"]
|
|
28
|
+
# if @record["/home/me/watched_dir"]["project/app"]["models"]
|
|
29
|
+
# record_dir_key is "project/app/models"
|
|
30
|
+
def record_dir_key
|
|
31
|
+
::File.join(*[@relative, @name].compact)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def sys_path
|
|
35
|
+
# Use full path in case someone uses chdir
|
|
36
|
+
::File.join(*[@root, @relative, @name].compact)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def real_path
|
|
40
|
+
@real_path ||= ::File.realpath(sys_path)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def _join
|
|
46
|
+
args = [@relative, @name].compact
|
|
47
|
+
args.empty? ? nil : ::File.join(*args)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module Listen
|
|
4
|
+
# @private api
|
|
5
|
+
class Record
|
|
6
|
+
class SymlinkDetector
|
|
7
|
+
SYMLINK_LOOP_ERROR = <<-EOS
|
|
8
|
+
** ERROR: Listen detected a duplicate directory being watched! **
|
|
9
|
+
|
|
10
|
+
(This may be due to symlinks pointing to parent directories).
|
|
11
|
+
|
|
12
|
+
Duplicate: %s
|
|
13
|
+
|
|
14
|
+
which already is added as: %s
|
|
15
|
+
|
|
16
|
+
Listen is refusing to continue, because this may likely result in
|
|
17
|
+
an infinite loop.
|
|
18
|
+
|
|
19
|
+
Suggestions:
|
|
20
|
+
|
|
21
|
+
1) (best option) watch only directories you care about, e.g.
|
|
22
|
+
either symlinked folders or folders with the real directories,
|
|
23
|
+
but not both.
|
|
24
|
+
|
|
25
|
+
2) reorganize your project so that watched directories do not
|
|
26
|
+
contain symlinked directories
|
|
27
|
+
|
|
28
|
+
3) submit patches so that Listen can reliably and quickly (!)
|
|
29
|
+
detect symlinks to already watched read directories, skip
|
|
30
|
+
them, and then reasonably choose which symlinked paths to
|
|
31
|
+
report as changed (if any)
|
|
32
|
+
|
|
33
|
+
4) (not worth it) help implement a "reverse symlink lookup"
|
|
34
|
+
function in Listen, which - given a real directory - would
|
|
35
|
+
return all the symlinks pointing to that directory
|
|
36
|
+
|
|
37
|
+
Issue: https://github.com/guard/listen/issues/259
|
|
38
|
+
EOS
|
|
39
|
+
|
|
40
|
+
def initialize
|
|
41
|
+
@real_dirs = Set.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def verify_unwatched!(entry)
|
|
45
|
+
real_path = entry.real_path
|
|
46
|
+
@real_dirs.add?(real_path) || _fail(entry.sys_path, real_path)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def _fail(symlinked, real_path)
|
|
52
|
+
STDERR.puts format(SYMLINK_LOOP_ERROR, symlinked, real_path)
|
|
53
|
+
|
|
54
|
+
# Note Celluloid eats up abort message anyway
|
|
55
|
+
fail 'Failed due to looped symlinks'
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/listen/version.rb
CHANGED
|
@@ -193,21 +193,24 @@ describe Listen::Record do
|
|
|
193
193
|
before do
|
|
194
194
|
allow(listener).to receive(:directories) { directories }
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
196
|
+
stubs = {
|
|
197
|
+
::File => %w(lstat realpath),
|
|
198
|
+
::Dir => %w(entries exist?)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
stubs.each do |klass, meths|
|
|
202
|
+
meths.each do |meth|
|
|
203
|
+
allow(klass).to receive(meth.to_sym) do |*args|
|
|
204
|
+
fail "stub called: #{klass}.#{meth}(#{args.map(&:inspect) * ', '})"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
206
207
|
end
|
|
207
208
|
end
|
|
208
209
|
|
|
209
210
|
it 're-inits paths' do
|
|
210
|
-
allow(::Dir).to receive(:entries)
|
|
211
|
+
allow(::Dir).to receive(:entries).and_return([])
|
|
212
|
+
allow(::File).to receive(:realpath).with('/dir1').and_return('/dir1')
|
|
213
|
+
allow(::File).to receive(:realpath).with('/dir2').and_return('/dir2')
|
|
211
214
|
|
|
212
215
|
record.update_file(dir, 'path/file.rb', mtime: 1.1)
|
|
213
216
|
record.build
|
|
@@ -219,18 +222,19 @@ describe Listen::Record do
|
|
|
219
222
|
let(:bar_stat) { instance_double(::File::Stat, mtime: 2.3, mode: 0755) }
|
|
220
223
|
|
|
221
224
|
context 'with no subdirs' do
|
|
222
|
-
|
|
223
225
|
before do
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
allow(::Dir).to receive(:entries).with('/dir1') { %w(foo bar) }
|
|
227
|
+
allow(::Dir).to receive(:entries).with('/dir1/foo').and_raise(Errno::ENOTDIR)
|
|
228
|
+
allow(::Dir).to receive(:entries).with('/dir1/bar').and_raise(Errno::ENOTDIR)
|
|
229
|
+
allow(::File).to receive(:lstat).with('/dir1/foo') { foo_stat }
|
|
230
|
+
allow(::File).to receive(:lstat).with('/dir1/bar') { bar_stat }
|
|
231
|
+
allow(::Dir).to receive(:entries).with('/dir2') { [] }
|
|
232
|
+
|
|
233
|
+
allow(::File).to receive(:realpath).with('/dir1').and_return('/dir1')
|
|
234
|
+
allow(::File).to receive(:realpath).with('/dir2').and_return('/dir2')
|
|
231
235
|
end
|
|
232
236
|
|
|
233
|
-
it 'builds record'
|
|
237
|
+
it 'builds record' do
|
|
234
238
|
record.build
|
|
235
239
|
expect(record.paths.keys).to eq %w( /dir1 /dir2 )
|
|
236
240
|
expect(record.paths['/dir1']).
|
|
@@ -242,15 +246,16 @@ describe Listen::Record do
|
|
|
242
246
|
|
|
243
247
|
context 'with subdir containing files' do
|
|
244
248
|
before do
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
249
|
+
allow(::Dir).to receive(:entries).with('/dir1') { %w(foo) }
|
|
250
|
+
allow(::Dir).to receive(:entries).with('/dir1/foo') { %w(bar) }
|
|
251
|
+
allow(::Dir).to receive(:entries).with('/dir1/foo/bar').and_raise(Errno::ENOTDIR)
|
|
252
|
+
allow(::File).to receive(:lstat).with('/dir1/foo/bar') { bar_stat }
|
|
253
|
+
allow(::Dir).to receive(:entries).with('/dir2') { [] }
|
|
254
|
+
|
|
255
|
+
allow(::File).to receive(:realpath).with('/dir1').and_return('/dir1')
|
|
256
|
+
allow(::File).to receive(:realpath).with('/dir2').and_return('/dir2')
|
|
257
|
+
allow(::File).to receive(:realpath).with('/dir1/foo').
|
|
258
|
+
and_return('/dir1/foo')
|
|
254
259
|
end
|
|
255
260
|
|
|
256
261
|
it 'builds record' do
|
|
@@ -264,18 +269,12 @@ describe Listen::Record do
|
|
|
264
269
|
|
|
265
270
|
context 'with subdir containing dirs' do
|
|
266
271
|
before do
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
expect(::Dir).to receive(:entries).with('/dir1/foo/bar') { [] }
|
|
274
|
-
|
|
275
|
-
expect(::Dir).to receive(:exist?).with('/dir1/foo/baz') { true }
|
|
276
|
-
expect(::Dir).to receive(:entries).with('/dir1/foo/baz') { [] }
|
|
277
|
-
|
|
278
|
-
expect(::Dir).to receive(:entries).with('/dir2/.') { [] }
|
|
272
|
+
allow(::File).to receive(:realpath) { |path| path }
|
|
273
|
+
allow(::Dir).to receive(:entries).with('/dir1') { %w(foo) }
|
|
274
|
+
allow(::Dir).to receive(:entries).with('/dir1/foo') { %w(bar baz) }
|
|
275
|
+
allow(::Dir).to receive(:entries).with('/dir1/foo/bar') { [] }
|
|
276
|
+
allow(::Dir).to receive(:entries).with('/dir1/foo/baz') { [] }
|
|
277
|
+
allow(::Dir).to receive(:entries).with('/dir2') { [] }
|
|
279
278
|
end
|
|
280
279
|
|
|
281
280
|
it 'builds record' do
|
|
@@ -290,5 +289,22 @@ describe Listen::Record do
|
|
|
290
289
|
expect(record.paths['/dir2']).to eq({})
|
|
291
290
|
end
|
|
292
291
|
end
|
|
292
|
+
|
|
293
|
+
context 'with subdir containing symlink to parent' do
|
|
294
|
+
subject { record.paths }
|
|
295
|
+
before do
|
|
296
|
+
allow(::Dir).to receive(:entries).with('/dir1') { %w(foo) }
|
|
297
|
+
allow(::Dir).to receive(:entries).with('/dir1/foo') { %w(foo) }
|
|
298
|
+
allow(::File).to receive(:realpath).with('/dir1').and_return('/bar')
|
|
299
|
+
allow(::File).to receive(:realpath).with('/dir1/foo').and_return('/bar')
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it 'shows message and aborts with error' do
|
|
303
|
+
expect(STDERR).to receive(:puts).with(/detected a duplicate directory/)
|
|
304
|
+
|
|
305
|
+
expect { record.build }.to raise_error(RuntimeError,
|
|
306
|
+
/Failed due to looped symlinks/)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
293
309
|
end
|
|
294
310
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: listen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thibaud Guillaume-Gentil
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2014-11-
|
|
11
|
+
date: 2014-11-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: celluloid
|
|
@@ -165,6 +165,8 @@ files:
|
|
|
165
165
|
- lib/listen/options.rb
|
|
166
166
|
- lib/listen/queue_optimizer.rb
|
|
167
167
|
- lib/listen/record.rb
|
|
168
|
+
- lib/listen/record/entry.rb
|
|
169
|
+
- lib/listen/record/symlink_detector.rb
|
|
168
170
|
- lib/listen/silencer.rb
|
|
169
171
|
- lib/listen/tcp.rb
|
|
170
172
|
- lib/listen/tcp/broadcaster.rb
|