fast_ignore 0.10.2 → 0.14.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
  SHA256:
3
- metadata.gz: 2dd945c76bf226766ac86086faf839eac44c4c0cce452e2a3a44e7c47964b7f5
4
- data.tar.gz: a72de31878713db71c94aa8067c87b807f19833eb008a1a6d6e673fcdeff5a7a
3
+ metadata.gz: 05f6139deefedc14e0c58acd66909aa25af5240a83e67a6463eacedd6cec9eb3
4
+ data.tar.gz: c6becf91ea8bc9ccf0524309c5cad1e01485c4e2324222f231ade33855c65528
5
5
  SHA512:
6
- metadata.gz: cff75a6418ecc958f2a839b9617554e3857763611248063aef27ec244dd0d1b433d61b81bfdb85a74dd8497fea8599f146ae723fd76023aef72285f53a620ecd
7
- data.tar.gz: 39aeceb2cd078c8b873019b9763eccb01450e408a24c3fb04005f9451879e1a125bcd73f26a1c4e67e6c999e7d6d0f78b4a1b41abbc0a2650ec36bb1ac7adde6
6
+ metadata.gz: cca040e6401d0c412498dff47b460ba7a19d5c9cb810cc205151a56b9db959a88ea3c4137610537bda43c5520611b1711abc8124b0ab98dd6bda261de9638588
7
+ data.tar.gz: a3fe257c77f8350c2dde1bcfcde01293b5af87b5d6b627b4e44555b3b6a45154c3d567d1e68f8f03520d21e28e78eda439e92b7a8b5b2d0b43b6f2a23a2f5ae4
@@ -1,3 +1,22 @@
1
+ # v0.14.0
2
+ - significant performance improvements ~50% faster
3
+ - add `FastIgnore#to_proc` for no good reason
4
+
5
+ # v0.13.0
6
+ - Attempt to improve documentation structure
7
+ - Remove `gitignore: true` raising `Errno::ENOENT` if root:/.gitignore didn't exist. I can't think of a use. Now `gitignore: true` is just the default behaviour.
8
+ - Don't ignore `.git` if `gitignore: false`.
9
+
10
+ # v0.12.1
11
+ - Reads all relevant git config files when finding a global .gitignore
12
+
13
+ # v0.12.0
14
+ - Reads all relevant gitignore files (nested .gitignore files, global .gitignore referred to in .gitconfig, and .git/info/exclude)
15
+
16
+ # v0.11.0
17
+ - major performance improvement (use regexp rather than fnmatch)
18
+ - optionally pass directory: and content: into allowed? if these are already loaded.
19
+
1
20
  # v0.10.2
2
21
  - add FastIgnore#=== as an alias for FastIgnore#allowed? so that FastIgnore objects can be used for case statements.
3
22
  - Fix shebangs in non-pwd-root situations
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # FastIgnore
2
2
 
3
- [![travis](https://travis-ci.org/robotdana/fast_ignore.svg?branch=master)](https://travis-ci.org/robotdana/fast_ignore)
3
+ [![travis](https://travis-ci.com/robotdana/fast_ignore.svg?branch=master)](https://travis-ci.com/robotdana/fast_ignore)
4
+ [![Gem Version](https://badge.fury.io/rb/fast_ignore.svg)](https://rubygems.org/gems/fast_ignore)
4
5
 
5
6
  This started as a way to quickly and natively ruby-ly parse gitignore files and find matching files.
6
7
  It's now gained an equivalent includes file functionality, ARGV awareness, and some shebang matching, while still being extremely fast, to be a one-stop file-list for your linter.
@@ -11,6 +12,19 @@ Filter a directory tree using a .gitignore file. Recognises all of the [gitignor
11
12
  FastIgnore.new(relative: true).sort == `git ls-files`.split("\n").sort
12
13
  ```
13
14
 
15
+ ## Features
16
+
17
+ - Fast (faster than using `` `git ls-files`.split("\n") `` for small repos (because it avoids the overhead of ``` `` ```))
18
+ - Supports ruby 2.4-2.7 & jruby
19
+ - supports all [gitignore rule patterns](https://git-scm.com/docs/gitignore#_pattern_format)
20
+ - doesn't require git to be installed
21
+ - supports a gitignore-esque "include" patterns. ([`include_rules:`](#include_rules)/[`include_files:`](#include_files))
22
+ - supports an expansion of include patterns, expanding and anchoring paths ([`argv_rules:`](#argv_rules))
23
+ - supports [matching by shebang](#shebang_rules) rather than filename for extensionless files: `#!:`
24
+ - reads .gitignore in all subdirectories
25
+ - reads .git/info/excludes
26
+ - reads the global gitignore file mentioned in your git config
27
+
14
28
  ## Installation
15
29
 
16
30
  Add this line to your application's Gemfile:
@@ -36,22 +50,26 @@ FastIgnore.new.each { |file| puts "#{file} is not ignored by the .gitignore file
36
50
 
37
51
  ### `#each`, `#map` etc
38
52
 
39
- The FastIgnore object is an enumerable and responds to all Enumerable methods
53
+ This yields paths that are _not_ ignored by the gitignore, i.e. the paths that would be returned by `git ls-files`.
54
+
55
+ A FastIgnore instance is an Enumerable and responds to all Enumerable methods:
40
56
 
41
57
  ```ruby
42
58
  FastIgnore.new.to_a
43
59
  FastIgnore.new.map { |file| file.upcase }
44
60
  ```
45
61
 
46
- Like other enumerables, `FastIgnore#each` can return an enumerator
62
+ Like other enumerables, `FastIgnore#each` can return an enumerator:
47
63
 
48
64
  ```ruby
49
65
  FastIgnore.new.each.with_index { |file, index| puts "#{file}#{index}" }
50
66
  ```
51
67
 
68
+ **Warning: Do not change directory (e.g. `Dir.chdir`) in the block.**
69
+
52
70
  ### `#allowed?`
53
71
 
54
- To check if a single file is allowed, use
72
+ To check if a single path is allowed, use
55
73
  ```ruby
56
74
  FastIgnore.new.allowed?('relative/path')
57
75
  FastIgnore.new.allowed?('./relative/path')
@@ -59,25 +77,44 @@ FastIgnore.new.allowed?('/absolute/path')
59
77
  FastIgnore.new.allowed?('~/home/path')
60
78
  ```
61
79
 
62
- This is aliased as `===` so you can use the FastIgnore object in case statements.
80
+ Relative paths will be considered relative to the [`root:`](#root) directory, not the current directory.
81
+
82
+ This is aliased as `===` so you can use a FastIgnore instance in case statements.
63
83
  ```ruby
64
84
  case my_path
65
- when FastIgnore.new then puts my_path
85
+ when FastIgnore.new
86
+ puts(my_path)
66
87
  end
67
88
  ```
68
89
 
69
- It's recommended to memoize the FastIgnore.new object somehow to avoid having to parse the gitignore file repeatedly.
90
+ It's recommended to save the FastIgnore instance to a variable to avoid having to read and parse the gitignore file and gitconfig files repeatedly.
91
+
92
+ See [Optimising allowed](#optimising_allowed) for ways to make this even faster
93
+
94
+ **Note: A file must exist at that path and not be a directory for it to be considered allowed.**
95
+ Essentially it can be thought of as `` `git ls-files`.include?(path) `` but much faster.
96
+ This excludes all directories and all possible path names that don't exist.
97
+
70
98
 
71
99
  ### `relative: true`
72
- By default, FastIgnore.each will yield full paths. To yield paths relative to the current working directory, or if supplied, [`root:`](#root), use:
100
+
101
+ **Default: false**
102
+
103
+ When `relative: false`: FastIgnore#each will yield full paths.
104
+ When `relative: true`: FastIgnore#each will yield paths relative to the [`root:`](#root) directory
73
105
 
74
106
  ```ruby
75
107
  FastIgnore.new(relative: true).to_a
76
108
  ```
77
109
 
78
110
  ### `follow_symlinks: true`
79
- By default, FastIgnore will match git's behaviour and not follow symbolic links.
80
- To make it follow symlinks, use:
111
+
112
+ **Default: false**
113
+
114
+ When `follow_symlinks: false`: FastIgnore#each will match git's behaviour and not follow symbolic links.
115
+ When `follow_symlinks: true`: FastIgnore#each will check if a symlink points to a directory, and files in linked directories must also match rules using the symlink path as the directory location, not the real directory location.
116
+
117
+ **This doesn't use the real path for matching or yield or return it.**
81
118
 
82
119
  ```ruby
83
120
  FastIgnore.new(follow_symlinks: true).to_a
@@ -85,12 +122,16 @@ FastIgnore.new(follow_symlinks: true).to_a
85
122
 
86
123
  ### `root:`
87
124
 
88
- By default, root is PWD (the current working directory)
125
+ **Default: Dir.pwd ($PWD, the current working directory)**
126
+
89
127
  This directory is used for:
90
- - looking for .gitignore files
91
- - as the root directory for array rules starting with `/` or ending with `/**`
92
- - and the path that relative is relative to
93
- - which files get checked
128
+ - the location of `.git/core/exclude`
129
+ - the ancestor of all non-global [automatically loaded `.gitignore` files](#gitignore_false)
130
+ - the root directory for array rules ([`ignore_rules:`](#ignore_rules), [`include_rules:`](#include_rules), [`argv_rules:`](#argv_rules)) containing `/`
131
+ - the path that [`relative:`](#relative_true) is relative to
132
+ - the ancestor of all paths yielded by [`#each`](#each_map_etc)
133
+ - the path that [`#allowed?`](#allowed) considers relative paths relative to
134
+ - the ancestor of all [`include_files:`](#include_files) and [`ignore_files:`](#ignore_files)
94
135
 
95
136
  To use a different directory:
96
137
  ```ruby
@@ -98,36 +139,49 @@ FastIgnore.new(root: '/absolute/path/to/root').to_a
98
139
  FastIgnore.new(root: '../relative/path/to/root').to_a
99
140
  ```
100
141
 
142
+ A relative root will be found relative to the current working directory when the FastIgnore instance is initialized, and that will be the last time the current working directory is relevant.
143
+
144
+ **Note: Changes to the current working directory (e.g. with `Dir.chdir`), after initialising a FastIgnore instance, will _not_ affect the FastIgnore instance. `root:` will always be what it was when the instance was initialized, even as a default value.**
145
+
101
146
  ### `gitignore:`
102
147
 
103
- By default, the .gitignore file in root directory is loaded.
104
- To not do this use
105
- ```ruby
106
- FastIgnore.new(gitignore: false).to_a
107
- ```
148
+ **Default: true**
108
149
 
109
- To raise an `Errno::ENOENT` error if the .gitignore file is not found use:
110
- ```ruby
111
- FastIgnore.new(gitignore: true).to_a
112
- ```
150
+ When `gitignore: true`: the .gitignore file in the [`root:`](#root) directory is loaded, plus any .gitignore files in its subdirectories, the global git ignore file as described in git config, and .git/info/exclude. `.git` directories are also excluded to match the behaviour of `git ls-files`.
151
+ When `gitignore: false`: no ignore files or git config files are automatically read, and `.git` will not be automatically excluded.
113
152
 
114
- If the gitignore file is somewhere else
115
153
  ```ruby
116
- FastIgnore.new(ignore_file: '/absolute/path/to/.gitignore', gitignore: false).to_a
154
+ FastIgnore.new(gitignore: false).to_a
117
155
  ```
118
- Note that the location of the .gitignore file will affect rules beginning with `/` or ending in `/**`
119
156
 
120
157
  ### `ignore_files:`
158
+
159
+ **This is a list of files in the gitignore format to parse and match paths against, not a list of files to ignore** If you want an array of files use [`ignore_rules:`](#ignore_rules)
160
+
161
+ Additional gitignore-style files, either as a path or an array of paths.
162
+
121
163
  You can specify other gitignore-style files to ignore as well.
122
164
  Missing files will raise an `Errno::ENOENT` error.
123
165
 
166
+ Relative paths are relative to the [`root:`](#root) directory.
167
+ Absolute paths also need to be within the [`root:`](#root) directory.
168
+
169
+
124
170
  ```ruby
125
- FastIgnore.new(ignore_files: '/absolute/path/to/my/ignore/file').to_a
171
+ FastIgnore.new(ignore_files: 'relative/path/to/my/ignore/file').to_a
126
172
  FastIgnore.new(ignore_files: ['/absolute/path/to/my/ignore/file', '/and/another']).to_a
127
173
  ```
128
174
 
175
+ Note: the location of the files will affect rules beginning with or containing `/`.
176
+
177
+ To avoid raising `Errno::ENOENT` when the file doesn't exist:
178
+ ```ruby
179
+ FastIgnore.new(ignore_files: ['/ignore/file'].select { |f| File.exist?(f) }).to_a
180
+ ```
181
+
129
182
  ### `ignore_rules:`
130
- You can also supply an array of rule strings.
183
+
184
+ This can be a string, or an array of strings, and multiline strings can be used with one rule per line.
131
185
 
132
186
  ```ruby
133
187
  FastIgnore.new(ignore_rules: '.DS_Store').to_a
@@ -135,103 +189,150 @@ FastIgnore.new(ignore_rules: ['.git', '.gitkeep']).to_a
135
189
  FastIgnore.new(ignore_rules: ".git\n.gitkeep").to_a
136
190
  ```
137
191
 
138
- ### `include_files:` and `include_rules:`
192
+ These rules use the [`root:`](#root) argument to resolve rules containing `/`.
139
193
 
140
- Building on the gitignore format, FastIgnore also accepts a list of allowed or included files.
194
+ ### `include_files:`
141
195
 
142
- ```gitignore
143
- # a line like this means any files named foo will be included
144
- # as well as any files within directories named foo
145
- foo
146
- # a line beginning with a slash will be anything in a directory that is a child of the $PWD
147
- /foo
148
- # a line ending in a slash will will include any files in any directories named foo
149
- # but not any files named foo
150
- foo/
151
- fo*
152
- !foe
153
- # otherwise this format deals with !'s, *'s and ?'s and etc as you'd expect from gitignore.
154
- ```
155
-
156
- These can be passed either as files or as an array or string rules
157
- ```ruby
158
- FastIgnore.new(include_files: '/absolute/path/to/my/include/file', gitignore: false).to_a
196
+ **This is an array of files in the gitignore format to parse and match paths against, not a list of files to include.** If you want an array of files use [`include_rules:`](#include_rules).
197
+
198
+ Building on the gitignore format, FastIgnore also accepts rules to include matching paths (rather than ignoring them).
199
+ A rule matching a directory will include all descendants of that directory.
200
+
201
+ These rules can be provided in files either as absolute or relative paths, or an array of paths.
202
+ Relative paths are relative to the [`root:`](#root) directory.
203
+ Absolute paths also need to be within the [`root:`](#root) directory.
204
+
205
+ ```ruby
206
+ FastIgnore.new(include_files: 'my_include_file').to_a
207
+ FastIgnore.new(include_files: ['/absolute/include/file', './relative/include/file']).to_a
208
+ ```
209
+
210
+ Missing files will raise an `Errno::ENOENT` error.
211
+
212
+ To avoid raising `Errno::ENOENT` when the file doesn't exist:
213
+ ```ruby
214
+ FastIgnore.new(include_files: ['include/file'].select { |f| File.exist?(f) }).to_a
215
+ ```
216
+
217
+ **Note: All paths checked must not be excluded by any ignore files AND each included by include file separately AND the [`include_rules:`](#include_rules) AND the [`argv_rules:`](#argv_rules). see [Combinations](#combinations) for solutions to using OR.**
218
+
219
+ ### `include_rules:`
220
+
221
+ Building on the gitignore format, FastIgnore also accepts rules to include matching paths (rather than ignoring them).
222
+ A rule matching a directory will include all descendants of that directory.
223
+
224
+ This can be a string, or an array of strings, and multiline strings can be used with one rule per line.
225
+ ```ruby
159
226
  FastIgnore.new(include_rules: %w{my*rule /and/another !rule}, gitignore: false).to_a
160
227
  ```
161
228
 
162
- There is an additional argument meant for dealing with humans and `ARGV` values.
229
+ Rules use the [`root:`](#root) argument to resolve rules containing `/`.
230
+
231
+ **Note: All paths checked must not be excluded by any ignore files AND each included by [include file](#include_files) separately AND the `include_rules:` AND the [`argv_rules:`](#argv_rules). see [Combinations](#combinations) for solutions to using OR.**
232
+
233
+ ### `argv_rules:`
234
+ This is like [`include_rules:`](#include_rules) with additional features meant for dealing with humans and `ARGV` values.
235
+
236
+ It expands rules that are absolute paths, and paths beginning with `~`, `../` and `./` (with and without `!`).
237
+ This means rules beginning with `/` are absolute. Not relative to [`root:`](#root).
238
+
239
+ Additionally it assumes all rules are relative to the [`root:`](#root) directory (after resolving absolute paths) unless they begin with `*` (or `!*`).
240
+
241
+ This can be a string, or an array of strings, and multiline strings can be used with one rule per line.
163
242
 
164
243
  ```ruby
165
244
  FastIgnore.new(argv_rules: ['./a/pasted/path', '/or/a/path/from/stdin', 'an/argument', '*.txt']).to_a
166
245
  ```
167
246
 
168
- It resolves absolute paths, and paths beginning with `~`, `../` and `./` (with and without `!`)
169
- It assumes all rules are anchored unless they begin with `*` or `!*`.
247
+ **Warning: it will *not* expand e.g. `/../` in the middle of a rule that doesn't begin with any of `~`,`../`,`./`,`/`.**
170
248
 
171
- Note: it will *not* resolve e.g. `/../` in the middle of a rule that doesn't begin with any of `~`,`../`,`./`,`/`.
249
+ **Note: All paths checked must not be excluded by any ignore files AND each included by [include file](#include_files) separately AND the [`include_rules:`](#include_rules) AND the `argv_rules:`. see [Combinations](#combinations) for solutions to using OR.**
172
250
 
173
251
  ### shebang rules
174
252
 
175
- Sometimes you need to match files by their shebang rather than their path or filename
253
+ Sometimes you need to match files by their shebang/hashbang/etc rather than their path or filename
176
254
 
177
- To match extensionless files by shebang/hashbang/etc:
178
-
179
- Lines beginning with `#!:` will match whole words in the shebang line of extensionless files.
255
+ Rules beginning with `#!:` will match whole words in the shebang line of extensionless files.
180
256
  e.g.
181
257
  ```gitignore
182
258
  #!:ruby
183
259
  ```
184
260
  will match shebang lines: `#!/usr/bin/env ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
261
+
185
262
  e.g.
186
263
  ```gitignore
187
264
  #!:bin/ruby
188
265
  ```
189
266
  will match `#!/bin/ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
190
- Currently only exact substring matches are available, There's no special handling of * or / or etc.
267
+ Only exact substring matches are available, There's no special handling of * or / or etc.
191
268
 
269
+ These rules can be supplied any way regular rules are, whether in a .gitignore file or files mentioned in [`include_files:`](#include_files) or [`ignore_files:`](#ignore_files) or [`include_rules:`](#include_rules) or [`ignore_rules:`](#ignore_rules) or [`argv_rules:`](#argv_rules)
192
270
  ```ruby
193
271
  FastIgnore.new(include_rules: ['*.rb', '#!:ruby']).to_a
194
272
  FastIgnore.new(ignore_rules: ['*.sh', '#!:sh', '#!:bash', '#!:zsh']).to_a
195
273
  ```
196
274
 
275
+ **Note: git considers rules like this as a comment and will ignore them.**
276
+
197
277
  ## Combinations
198
278
 
199
- In the simplest case a file must be allowed by each ignore file, each include file, and each array of rules. That is, they are combined using AND.
279
+ In the simplest case a file must be allowed by each ignore file, each include file, and each array of rules. That is, they are combined using `AND`.
200
280
 
201
281
  To combine files using `OR`, that is, a file may be matched by either file it doesn't have to be referred to in both:
202
- provide the files as strings to `include_rules:` or `ignore_rules:`
282
+ provide the files as strings to [`include_rules:`](#include_rules) or [`ignore_rules:`](#ignore_rules)
203
283
  ```ruby
204
284
  FastIgnore.new(include_rules: [File.read('/my/path'), File.read('/another/path')])).to_a
205
285
  ```
206
- This does unfortunately lose the file path as the root for `/` and `/**` rules.
207
- If that's important, combine the files in the file system and use `include_files:` or `ignore_files:` as normal.
286
+ This does unfortunately lose the file path as the root for rules containing `/`.
287
+ If that's important, combine the files in the file system and use [`include_files:`](#include_files) or [`ignore_files:`](#ignore_files) as normal.
208
288
 
209
- To use the additional ARGV handling rules mentioned above for files, read the file into the array as a string.
289
+ To use the additional `ARGV` handling of [`argv_rules:`](#argv_rules) on a file, read the file into the array.
210
290
 
211
291
  ```ruby
212
292
  FastIgnore.new(argv_rules: ["my/rule", File.read('/my/path')]).to_a
213
293
  ```
214
294
 
215
- This does unfortunately lose the file path as the root for `/` and `/**` rules.
216
-
217
- ## Known issues
218
- - Doesn't take into account project excludes in `.git/info/exclude`
219
- - Doesn't take into account globally ignored files in `git config core.excludesFile`.
220
- - Doesn't know what to do if you change the current working directory inside the `FastIgnore#each` block.
221
- So don't do that.
295
+ This does unfortunately lose the file path as the root `/` and there is no workaround except setting the [`root:`](#root) for the whole FastIgnore instance.
222
296
 
223
- (It does handle changing the current working directory between `FastIgnore#allowed?` calls.)
297
+ ### optimising #allowed?
224
298
 
225
- ## Development
299
+ To avoid unnecessary calls to the filesystem, if your code already knows whether or not it's a directory, or if you're checking shebangs and you have already read the content of the file: use
300
+ ```ruby
301
+ FastIgnore.new.allowed?('relative/path', directory: false, content: "#!/usr/bin/ruby\n\nputs 'ok'\n")
302
+ ```
303
+ This is not required, and if FastIgnore does have to go to the filesystem for this information it's well optimised to only read what is necessary.
226
304
 
227
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
305
+ ## Limitations
306
+ - Doesn't know what to do if you change the current working directory inside the [`FastIgnore#each`](#each_map_etc) block.
307
+ So don't do that.
228
308
 
229
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
309
+ (It does handle changing the current working directory between [`FastIgnore#allowed?`](#allowed) calls)
310
+ - FastIgnore always matches patterns case-insensitively. (git varies by filesystem).
311
+ - FastIgnore always outputs paths as literal UTF-8 characters. (git depends on your core.quotepath setting but by default outputs non ascii paths with octal escapes surrounded by quotes).
312
+ - Because git looks at its own index objects and FastIgnore looks at the file system there may be some differences between FastIgnore and `git ls-files`
313
+ - Tracked files that were committed before the matching ignore rule was committed will be returned by `git ls-files`, but not by FastIgnore.
314
+ - Untracked files will be returned by FastIgnore, but not by `git ls-files`
315
+ - Deleted files whose deletions haven't been committed will be returned by `git ls-files`, but not by FastIgnore
316
+ - On a case insensitive file system, with files that differ only by case, `git ls-files` will include all case variations, while FastIgnore will only include whichever variation git placed in the file system.
230
317
 
231
318
  ## Contributing
232
319
 
233
320
  Bug reports and pull requests are welcome on GitHub at https://github.com/robotdana/fast_ignore.
234
321
 
322
+ Some tools that may help:
323
+
324
+ - `bin/setup`: install development dependencies
325
+ - `bundle exec rspec`: run all tests
326
+ - `bundle exec rake`: run all tests and linters
327
+ - `bin/console`: open a `pry` console with everything required for experimenting
328
+ - `bin/ls [argv_rules]`: the equivalent of `git ls-files`
329
+ - `bin/prof/ls [argv_rules]`: ruby-prof report for `bin/ls`
330
+ - `bin/prof/parse [argv_rules]`: ruby-prof report for parsing root and global gitignore files and any arguments.
331
+ - `bin/time [argv_rules]`: the average time for 30 runs of `bin/ls`<br>
332
+ This repo is too small to stress bin/time more than 0.01s, switch to a large repo and find the average time before and after changes.
333
+ - `bin/compare`: compare the speed and output of FastIgnore and `git ls-files`.
334
+ (suppressing differences that are because of known [limitations](#limitations))
335
+
235
336
  ## License
236
337
 
237
338
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -2,10 +2,15 @@
2
2
 
3
3
  require_relative './fast_ignore/backports'
4
4
 
5
- require_relative './fast_ignore/rule_set_builder'
6
- require_relative './fast_ignore/rule_builder'
5
+ require 'set'
6
+ require 'strscan'
7
+ require_relative './fast_ignore/rule_sets'
7
8
  require_relative './fast_ignore/rule_set'
9
+ require_relative './fast_ignore/global_gitignore'
10
+ require_relative './fast_ignore/rule_builder'
11
+ require_relative './fast_ignore/gitignore_rule_builder'
8
12
  require_relative './fast_ignore/rule'
13
+ require_relative './fast_ignore/unmatchable_rule'
9
14
  require_relative './fast_ignore/shebang_rule'
10
15
 
11
16
  class FastIgnore
@@ -14,20 +19,17 @@ class FastIgnore
14
19
  include ::Enumerable
15
20
 
16
21
  # :nocov:
17
- if ::FastIgnore::Backports.ruby_version_less_than?(2, 5)
18
- require_relative 'fast_ignore/backports/delete_prefix_suffix'
19
- using ::FastIgnore::Backports::DeletePrefixSuffix
20
-
21
- require_relative 'fast_ignore/backports/dir_each_child'
22
- using ::FastIgnore::Backports::DirEachChild
23
- end
22
+ using ::FastIgnore::Backports::DeletePrefixSuffix if defined?(::FastIgnore::Backports::DeletePrefixSuffix)
23
+ using ::FastIgnore::Backports::DirEachChild if defined?(::FastIgnore::Backports::DirEachChild)
24
24
  # :nocov:
25
25
 
26
- def initialize(relative: false, root: nil, follow_symlinks: false, **rule_set_builder_args)
26
+ def initialize(relative: false, root: nil, gitignore: :auto, follow_symlinks: false, **rule_set_builder_args)
27
27
  @relative = relative
28
- @follow_symlinks = follow_symlinks
28
+ @follow_symlinks_method = ::File.method(follow_symlinks ? :stat : :lstat)
29
+ @gitignore_enabled = gitignore
30
+ @loaded_gitignore_files = ::Set[''] if gitignore
29
31
  @root = "#{::File.expand_path(root.to_s, Dir.pwd)}/"
30
- @rule_sets = ::FastIgnore::RuleSetBuilder.build(root: @root, **rule_set_builder_args)
32
+ @rule_sets = ::FastIgnore::RuleSets.new(root: @root, gitignore: gitignore, **rule_set_builder_args)
31
33
 
32
34
  freeze
33
35
  end
@@ -35,44 +37,62 @@ class FastIgnore
35
37
  def each(&block)
36
38
  return enum_for(:each) unless block_given?
37
39
 
38
- dir_pwd = Dir.pwd
40
+ dir_pwd = ::Dir.pwd
39
41
  root_from_pwd = @root.start_with?(dir_pwd) ? ".#{@root.delete_prefix(dir_pwd)}" : @root
40
42
 
41
43
  each_recursive(root_from_pwd, '', &block)
42
44
  end
43
45
 
44
- def directory?(path)
45
- if @follow_symlinks
46
- ::File.stat(path).directory?
47
- else
48
- ::File.lstat(path).directory?
49
- end
50
- end
51
-
52
- def allowed?(path)
46
+ def allowed?(path, directory: nil, content: nil)
53
47
  full_path = ::File.expand_path(path, @root)
54
48
  return false unless full_path.start_with?(@root)
55
- return false if directory?(full_path)
49
+ return false if directory.nil? ? @follow_symlinks_method.call(full_path).directory? : directory
56
50
 
57
51
  relative_path = full_path.delete_prefix(@root)
52
+ load_gitignore_recursive(relative_path) if @gitignore_enabled
53
+
58
54
  filename = ::File.basename(relative_path)
59
55
 
60
- @rule_sets.all? { |r| r.allowed_recursive?(relative_path, false, full_path, filename) }
56
+ @rule_sets.allowed_recursive?(relative_path, full_path, filename, content)
61
57
  rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
62
58
  false
63
59
  end
64
60
  alias_method :===, :allowed?
65
61
 
62
+ def to_proc
63
+ method(:allowed?).to_proc
64
+ end
65
+
66
66
  private
67
67
 
68
- def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
69
- ::Dir.each_child(parent_full_path) do |filename|
68
+ def load_gitignore_recursive(path)
69
+ paths = []
70
+ while (path = ::File.dirname(path)) != '.'
71
+ paths << path
72
+ end
73
+
74
+ paths.reverse_each(&method(:load_gitignore))
75
+ end
76
+
77
+ def load_gitignore(parent_path, check_exists: true)
78
+ return if @loaded_gitignore_files.include?(parent_path)
79
+
80
+ @rule_sets.append_subdir_gitignore(relative_path: parent_path + '.gitignore', check_exists: check_exists)
81
+
82
+ @loaded_gitignore_files << parent_path
83
+ end
84
+
85
+ def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
86
+ children = ::Dir.children(parent_full_path)
87
+ load_gitignore(parent_relative_path, check_exists: false) if @gitignore_enabled && children.include?('.gitignore')
88
+
89
+ children.each do |filename|
70
90
  begin
71
91
  full_path = parent_full_path + filename
72
92
  relative_path = parent_relative_path + filename
73
- dir = directory?(full_path)
93
+ dir = @follow_symlinks_method.call(full_path).directory?
74
94
 
75
- next unless @rule_sets.all? { |r| r.allowed_unrecursive?(relative_path, dir, full_path, filename) }
95
+ next unless @rule_sets.allowed_unrecursive?(relative_path, dir, full_path, filename)
76
96
 
77
97
  if dir
78
98
  each_recursive(full_path + '/', relative_path + '/', &block)