listen 2.7.12 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 65b3168861b5ccc57809689d8ad5849a631db822
4
- data.tar.gz: 3640593ce18fa45435a2ff0184e9c7fda77acfce
3
+ metadata.gz: 5a859a235a28efe17024969a47542ebec79836f9
4
+ data.tar.gz: 54b831e943a6b7b773aa9da8684041ee55e9a8fe
5
5
  SHA512:
6
- metadata.gz: 1ba0723ef31cfc026fb71bc89f0d1f951cbd276270bb57329fef1ffa5aae8e982f645a69a2a7bbf39591ae6d8160950fbb71f356417874948c39d010559cca31
7
- data.tar.gz: 1203e42ab3497f1c2d1348330398880f92b1932038f60b7f8f2283e67bfc4e04e034f10b35389bb00a0775db352d91228af98243951f702137cbc19a3ffe2ad5
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
- * ~~Non-recursive directory scanning~~ [#111](https://github.com/guard/listen/issues/111)
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
 
@@ -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
- left = Queue.new
106
- left << '.'
107
-
108
- until left.empty?
109
- dirname = left.pop
110
- add_dir(root, dirname)
111
-
112
- path = ::File.join(root, dirname)
113
- current = Dir.entries(path.to_s) - %w(. ..)
114
-
115
- current.each do |entry|
116
- full_path = ::File.join(path, entry)
117
-
118
- if Dir.exist?(full_path)
119
- left << (dirname == '.' ? entry : ::File.join(dirname, entry))
120
- else
121
- begin
122
- lstat = ::File.lstat(full_path)
123
- data = { mtime: lstat.mtime.to_f, mode: lstat.mode }
124
- _fast_update_file(root, dirname, entry, data)
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
@@ -1,3 +1,3 @@
1
1
  module Listen
2
- VERSION = '2.7.12'
2
+ VERSION = '2.8.0'
3
3
  end
@@ -193,21 +193,24 @@ describe Listen::Record do
193
193
  before do
194
194
  allow(listener).to receive(:directories) { directories }
195
195
 
196
- allow(::File).to receive(:lstat) do |path|
197
- fail "::File.lstat stub called with: #{path.inspect}"
198
- end
199
-
200
- allow(::Dir).to receive(:entries) do |path|
201
- fail "::Dir.entries stub called with: #{path.inspect}"
202
- end
203
-
204
- allow(::Dir).to receive(:exist?) do |path|
205
- fail "::Dir.exist? stub called with: #{path.inspect}"
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
- expect(::Dir).to receive(:entries).with('/dir1/.') { %w(foo bar) }
225
- expect(::Dir).to receive(:exist?).with('/dir1/./foo') { false }
226
- expect(::Dir).to receive(:exist?).with('/dir1/./bar') { false }
227
- expect(::File).to receive(:lstat).with('/dir1/./foo') { foo_stat }
228
- expect(::File).to receive(:lstat).with('/dir1/./bar') { bar_stat }
229
-
230
- expect(::Dir).to receive(:entries).with('/dir2/.') { [] }
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' do
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
- expect(::Dir).to receive(:entries).with('/dir1/.') { %w(foo) }
246
- expect(::Dir).to receive(:exist?).with('/dir1/./foo') { true }
247
-
248
- expect(::Dir).to receive(:entries).with('/dir1/foo') { %w(bar) }
249
-
250
- expect(::Dir).to receive(:exist?).with('/dir1/foo/bar') { false }
251
- expect(::File).to receive(:lstat).with('/dir1/foo/bar') { bar_stat }
252
-
253
- expect(::Dir).to receive(:entries).with('/dir2/.') { [] }
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
- expect(::Dir).to receive(:entries).with('/dir1/.') { %w(foo) }
268
- expect(::Dir).to receive(:exist?).with('/dir1/./foo') { true }
269
-
270
- expect(::Dir).to receive(:entries).with('/dir1/foo') { %w(bar baz) }
271
-
272
- expect(::Dir).to receive(:exist?).with('/dir1/foo/bar') { true }
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.7.12
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-13 00:00:00.000000000 Z
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