fast_ignore 0.11.0 → 0.15.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: f467113b0ee03faf028758ac176c59b3e51465684f8ffe144d432ba75133c10f
4
- data.tar.gz: 2f6f0ce0babec00977a55595baa7ec806d7a9a3f1fec86df46f29475b41238cc
3
+ metadata.gz: f96030c8670a5d709139689212b3a78a0eb0b5266cbd0057954863ae89e11745
4
+ data.tar.gz: a2815ec68f6c367a4450efce8799aa359adeee1ad1aaa971b589cd8612ddbb61
5
5
  SHA512:
6
- metadata.gz: 3a7698e54ab0897b6b50c8638d41f3243bd3ba4ced559a5c7e01f6d365b6fb875b906a1924e96641572815748479eb793dfabc91da6f3bec62115f98ff39a392
7
- data.tar.gz: 7e8b85ff838d04d1c16cd3eca5a480d6948342a07667760782840b0e36a706c02db2bb4e09e3370b3ccc1cdc2f38f9e893707c9f2128652a8a4aae4cdb8dd3d0
6
+ metadata.gz: 5c22c6c79a6605473156a041d5ddb5b81f943605dc4360dd8577acd56718c928c106e044169b13bc73c7e884ffcc5516c7a6b5d6134fee9aaa4aa73d0178b791
7
+ data.tar.gz: 1d165e44b50d9b1c301c5cad8ff3c443bfb2f9134be09e28a4e90539c960a269083485f6ff30bc6df6e2ec82347630187ebf0082cd2d06ba7416e9c70f37ea6f
@@ -1,3 +1,25 @@
1
+ # v0.15.0
2
+ - fixed a handful of character class edge cases to match git behavior
3
+ - mostly ranges with - or / as one end of the range
4
+ - major refactoring of the regexp builder that shouldn't have any behaviour implications but should make development easier (e.g. seeing those unhandled edge cases).
5
+ - improved speed of repos with many sub-gitignore files
6
+ - mentioned submodules & sparse checkout in the readme as yet another thing git does that this project doesn't because submodule details are hidden in the git index.
7
+
8
+ # v0.14.0
9
+ - significant performance improvements ~50% faster
10
+ - add `FastIgnore#to_proc` for no good reason
11
+
12
+ # v0.13.0
13
+ - Attempt to improve documentation structure
14
+ - 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.
15
+ - Don't ignore `.git` if `gitignore: false`.
16
+
17
+ # v0.12.1
18
+ - Reads all relevant git config files when finding a global .gitignore
19
+
20
+ # v0.12.0
21
+ - Reads all relevant gitignore files (nested .gitignore files, global .gitignore referred to in .gitconfig, and .git/info/exclude)
22
+
1
23
  # v0.11.0
2
24
  - major performance improvement (use regexp rather than fnmatch)
3
25
  - optionally pass directory: and content: into allowed? if these are already loaded.
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.
@@ -14,12 +15,15 @@ FastIgnore.new(relative: true).sort == `git ls-files`.split("\n").sort
14
15
  ## Features
15
16
 
16
17
  - Fast (faster than using `` `git ls-files`.split("\n") `` for small repos (because it avoids the overhead of ``` `` ```))
17
- - Supports ruby 2.4 - 2.7
18
- - supports all gitignore rule patterns
18
+ - Supports ruby 2.4-2.7 & jruby
19
+ - supports all [gitignore rule patterns](https://git-scm.com/docs/gitignore#_pattern_format)
19
20
  - doesn't require git to be installed
20
- - supports a gitignore-esque "include" patterns. (`include_rules:`/`include_files:`)
21
- - supports an expansion of include patterns, matching expanded paths (`argv_rules:`)
22
- - supports matching by shebang rather than filename for extensionless files: `#!:`
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
23
27
 
24
28
  ## Installation
25
29
 
@@ -46,22 +50,26 @@ FastIgnore.new.each { |file| puts "#{file} is not ignored by the .gitignore file
46
50
 
47
51
  ### `#each`, `#map` etc
48
52
 
49
- 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:
50
56
 
51
57
  ```ruby
52
58
  FastIgnore.new.to_a
53
59
  FastIgnore.new.map { |file| file.upcase }
54
60
  ```
55
61
 
56
- Like other enumerables, `FastIgnore#each` can return an enumerator
62
+ Like other enumerables, `FastIgnore#each` can return an enumerator:
57
63
 
58
64
  ```ruby
59
65
  FastIgnore.new.each.with_index { |file, index| puts "#{file}#{index}" }
60
66
  ```
61
67
 
68
+ **Warning: Do not change directory (e.g. `Dir.chdir`) in the block.**
69
+
62
70
  ### `#allowed?`
63
71
 
64
- To check if a single file is allowed, use
72
+ To check if a single path is allowed, use
65
73
  ```ruby
66
74
  FastIgnore.new.allowed?('relative/path')
67
75
  FastIgnore.new.allowed?('./relative/path')
@@ -69,7 +77,9 @@ FastIgnore.new.allowed?('/absolute/path')
69
77
  FastIgnore.new.allowed?('~/home/path')
70
78
  ```
71
79
 
72
- 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.
73
83
  ```ruby
74
84
  case my_path
75
85
  when FastIgnore.new
@@ -77,18 +87,34 @@ when FastIgnore.new
77
87
  end
78
88
  ```
79
89
 
80
- 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
+
81
98
 
82
99
  ### `relative: true`
83
- 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
84
105
 
85
106
  ```ruby
86
107
  FastIgnore.new(relative: true).to_a
87
108
  ```
88
109
 
89
110
  ### `follow_symlinks: true`
90
- By default, FastIgnore will match git's behaviour and not follow symbolic links.
91
- 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.**
92
118
 
93
119
  ```ruby
94
120
  FastIgnore.new(follow_symlinks: true).to_a
@@ -96,12 +122,16 @@ FastIgnore.new(follow_symlinks: true).to_a
96
122
 
97
123
  ### `root:`
98
124
 
99
- By default, root is PWD (the current working directory)
125
+ **Default: Dir.pwd ($PWD, the current working directory)**
126
+
100
127
  This directory is used for:
101
- - looking for .gitignore files
102
- - as the root directory for array rules starting with `/` or ending with `/**`
103
- - and the path that relative is relative to
104
- - 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)
105
135
 
106
136
  To use a different directory:
107
137
  ```ruby
@@ -109,36 +139,49 @@ FastIgnore.new(root: '/absolute/path/to/root').to_a
109
139
  FastIgnore.new(root: '../relative/path/to/root').to_a
110
140
  ```
111
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
+
112
146
  ### `gitignore:`
113
147
 
114
- By default, the .gitignore file in root directory is loaded.
115
- To not do this use
116
- ```ruby
117
- FastIgnore.new(gitignore: false).to_a
118
- ```
148
+ **Default: true**
119
149
 
120
- To raise an `Errno::ENOENT` error if the .gitignore file is not found use:
121
- ```ruby
122
- FastIgnore.new(gitignore: true).to_a
123
- ```
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.
124
152
 
125
- If the gitignore file is somewhere else
126
153
  ```ruby
127
- FastIgnore.new(ignore_file: '/absolute/path/to/.gitignore', gitignore: false).to_a
154
+ FastIgnore.new(gitignore: false).to_a
128
155
  ```
129
- Note that the location of the .gitignore file will affect rules beginning with `/` or ending in `/**`
130
156
 
131
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
+
132
163
  You can specify other gitignore-style files to ignore as well.
133
164
  Missing files will raise an `Errno::ENOENT` error.
134
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
+
135
170
  ```ruby
136
- 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
137
172
  FastIgnore.new(ignore_files: ['/absolute/path/to/my/ignore/file', '/and/another']).to_a
138
173
  ```
139
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
+
140
182
  ### `ignore_rules:`
141
- 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.
142
185
 
143
186
  ```ruby
144
187
  FastIgnore.new(ignore_rules: '.DS_Store').to_a
@@ -146,84 +189,110 @@ FastIgnore.new(ignore_rules: ['.git', '.gitkeep']).to_a
146
189
  FastIgnore.new(ignore_rules: ".git\n.gitkeep").to_a
147
190
  ```
148
191
 
149
- ### `include_files:` and `include_rules:`
192
+ These rules use the [`root:`](#root) argument to resolve rules containing `/`.
150
193
 
151
- Building on the gitignore format, FastIgnore also accepts a list of allowed or included files.
194
+ ### `include_files:`
152
195
 
153
- ```gitignore
154
- # a line like this means any files named foo will be included
155
- # as well as any files within directories named foo
156
- foo
157
- # a line beginning with a slash will be anything in a directory that is a child of the $PWD
158
- /foo
159
- # a line ending in a slash will will include any files in any directories named foo
160
- # but not any files named foo
161
- foo/
162
- fo*
163
- !foe
164
- # otherwise this format deals with !'s, *'s and ?'s and etc as you'd expect from gitignore.
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
165
215
  ```
166
216
 
167
- These can be passed either as files or as an array or string rules
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.
168
225
  ```ruby
169
- FastIgnore.new(include_files: '/absolute/path/to/my/include/file', gitignore: false).to_a
170
226
  FastIgnore.new(include_rules: %w{my*rule /and/another !rule}, gitignore: false).to_a
171
227
  ```
172
228
 
173
- 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.
174
242
 
175
243
  ```ruby
176
244
  FastIgnore.new(argv_rules: ['./a/pasted/path', '/or/a/path/from/stdin', 'an/argument', '*.txt']).to_a
177
245
  ```
178
246
 
179
- It resolves absolute paths, and paths beginning with `~`, `../` and `./` (with and without `!`)
180
- 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 `~`,`../`,`./`,`/`.**
181
248
 
182
- 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.**
183
250
 
184
251
  ### shebang rules
185
252
 
186
- Sometimes you need to match files by their shebang rather than their path or filename
187
-
188
- To match extensionless files by shebang/hashbang/etc:
253
+ Sometimes you need to match files by their shebang/hashbang/etc rather than their path or filename
189
254
 
190
- 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.
191
256
  e.g.
192
257
  ```gitignore
193
258
  #!:ruby
194
259
  ```
195
260
  will match shebang lines: `#!/usr/bin/env ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
261
+
196
262
  e.g.
197
263
  ```gitignore
198
264
  #!:bin/ruby
199
265
  ```
200
266
  will match `#!/bin/ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
201
- 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.
202
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)
203
270
  ```ruby
204
271
  FastIgnore.new(include_rules: ['*.rb', '#!:ruby']).to_a
205
272
  FastIgnore.new(ignore_rules: ['*.sh', '#!:sh', '#!:bash', '#!:zsh']).to_a
206
273
  ```
207
274
 
275
+ **Note: git considers rules like this as a comment and will ignore them.**
276
+
208
277
  ## Combinations
209
278
 
210
- 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`.
211
280
 
212
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:
213
- 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)
214
283
  ```ruby
215
284
  FastIgnore.new(include_rules: [File.read('/my/path'), File.read('/another/path')])).to_a
216
285
  ```
217
- This does unfortunately lose the file path as the root for `/` and `/**` rules.
218
- 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.
219
288
 
220
- 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.
221
290
 
222
291
  ```ruby
223
292
  FastIgnore.new(argv_rules: ["my/rule", File.read('/my/path')]).to_a
224
293
  ```
225
294
 
226
- This does unfortunately lose the file path as the root for `/` and `/**` rules.
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.
227
296
 
228
297
  ### optimising #allowed?
229
298
 
@@ -233,29 +302,39 @@ FastIgnore.new.allowed?('relative/path', directory: false, content: "#!/usr/bin/
233
302
  ```
234
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.
235
304
 
236
-
237
- ## Known issues
238
- - Doesn't take into account project excludes in `.git/info/exclude`
239
- - Doesn't take into account globally ignored files in `git config core.excludesFile`.
240
- - Doesn't know what to do if you change the current working directory inside the `FastIgnore#each` block.
305
+ ## Limitations
306
+ - Doesn't know what to do if you change the current working directory inside the [`FastIgnore#each`](#each_map_etc) block.
241
307
  So don't do that.
242
308
 
243
- (It does handle changing the current working directory between `FastIgnore#allowed?` calls.)
244
-
245
- ## Development
246
-
247
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and linters.
248
-
249
- You can run `bin/console` for an interactive prompt that will allow you to experiment.
250
- `bin/ls [argv_rules]` will return something equivalent to `git ls-files` and `bin/time [argv_rules]` will give you the average time for 30 runs.
251
- 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.
252
-
253
- To install this gem onto your local machine, run `bundle exec rake install`.
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`. To avoid these differences you may want to use the [`git_ls`](https://github.com/robotdana/git_ls) gem instead
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.
317
+ - FastIgnore is unaware of submodules and just treats them like regular directories. For example: `git ls-files --recurse-submodules` won't use the parent repo's gitignore on a submodule, while FastIgnore doesn't know it's a submodule and will.
318
+ - FastIgnore will only return the files actually on the file system when using `git sparse-checkout`.
254
319
 
255
320
  ## Contributing
256
321
 
257
322
  Bug reports and pull requests are welcome on GitHub at https://github.com/robotdana/fast_ignore.
258
323
 
324
+ Some tools that may help:
325
+
326
+ - `bin/setup`: install development dependencies
327
+ - `bundle exec rspec`: run all tests
328
+ - `bundle exec rake`: run all tests and linters
329
+ - `bin/console`: open a `pry` console with everything required for experimenting
330
+ - `bin/ls [argv_rules]`: the equivalent of `git ls-files`
331
+ - `bin/prof/ls [argv_rules]`: ruby-prof report for `bin/ls`
332
+ - `bin/prof/parse [argv_rules]`: ruby-prof report for parsing root and global gitignore files and any arguments.
333
+ - `bin/time [argv_rules]`: the average time for 30 runs of `bin/ls`<br>
334
+ 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.
335
+ - `bin/compare`: compare the speed and output of FastIgnore and `git ls-files`.
336
+ (suppressing differences that are because of known [limitations](#limitations))
337
+
259
338
  ## License
260
339
 
261
340
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -2,12 +2,20 @@
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'
12
+ require_relative './fast_ignore/gitignore_include_rule_builder'
13
+ require_relative './fast_ignore/gitignore_rule_regexp_builder'
14
+ require_relative './fast_ignore/gitignore_rule_scanner'
15
+ require_relative './fast_ignore/file_root'
8
16
  require_relative './fast_ignore/rule'
17
+ require_relative './fast_ignore/unmatchable_rule'
9
18
  require_relative './fast_ignore/shebang_rule'
10
- require_relative './fast_ignore/fn_match_to_re'
11
19
 
12
20
  class FastIgnore
13
21
  class Error < StandardError; end
@@ -19,11 +27,13 @@ class FastIgnore
19
27
  using ::FastIgnore::Backports::DirEachChild if defined?(::FastIgnore::Backports::DirEachChild)
20
28
  # :nocov:
21
29
 
22
- def initialize(relative: false, root: nil, follow_symlinks: false, **rule_set_builder_args)
30
+ def initialize(relative: false, root: nil, gitignore: :auto, follow_symlinks: false, **rule_set_builder_args)
23
31
  @relative = relative
24
- @follow_symlinks = follow_symlinks
32
+ @follow_symlinks_method = ::File.method(follow_symlinks ? :stat : :lstat)
33
+ @gitignore_enabled = gitignore
34
+ @loaded_gitignore_files = ::Set[''] if gitignore
25
35
  @root = "#{::File.expand_path(root.to_s, Dir.pwd)}/"
26
- @rule_sets = ::FastIgnore::RuleSetBuilder.build(root: @root, **rule_set_builder_args)
36
+ @rule_sets = ::FastIgnore::RuleSets.new(root: @root, gitignore: gitignore, **rule_set_builder_args)
27
37
 
28
38
  freeze
29
39
  end
@@ -31,7 +41,7 @@ class FastIgnore
31
41
  def each(&block)
32
42
  return enum_for(:each) unless block_given?
33
43
 
34
- dir_pwd = Dir.pwd
44
+ dir_pwd = ::Dir.pwd
35
45
  root_from_pwd = @root.start_with?(dir_pwd) ? ".#{@root.delete_prefix(dir_pwd)}" : @root
36
46
 
37
47
  each_recursive(root_from_pwd, '', &block)
@@ -40,35 +50,53 @@ class FastIgnore
40
50
  def allowed?(path, directory: nil, content: nil)
41
51
  full_path = ::File.expand_path(path, @root)
42
52
  return false unless full_path.start_with?(@root)
43
- return false if directory.nil? ? directory?(full_path) : directory
53
+ return false if directory.nil? ? @follow_symlinks_method.call(full_path).directory? : directory
44
54
 
45
55
  relative_path = full_path.delete_prefix(@root)
56
+ load_gitignore_recursive(relative_path) if @gitignore_enabled
57
+
46
58
  filename = ::File.basename(relative_path)
47
59
 
48
- @rule_sets.all? { |r| r.allowed_recursive?(relative_path, false, full_path, filename, content) }
60
+ @rule_sets.allowed_recursive?(relative_path, full_path, filename, content)
49
61
  rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
50
62
  false
51
63
  end
52
64
  alias_method :===, :allowed?
53
65
 
66
+ def to_proc
67
+ method(:allowed?).to_proc
68
+ end
69
+
54
70
  private
55
71
 
56
- def directory?(path)
57
- if @follow_symlinks
58
- ::File.stat(path).directory?
59
- else
60
- ::File.lstat(path).directory?
72
+ def load_gitignore_recursive(path)
73
+ paths = []
74
+ while (path = ::File.dirname(path)) != '.'
75
+ paths << path
61
76
  end
77
+
78
+ paths.reverse_each(&method(:load_gitignore))
62
79
  end
63
80
 
64
- def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
65
- ::Dir.each_child(parent_full_path) do |filename|
81
+ def load_gitignore(parent_path, check_exists: true)
82
+ return if @loaded_gitignore_files.include?(parent_path)
83
+
84
+ @rule_sets.append_subdir_gitignore(relative_path: parent_path + '.gitignore', check_exists: check_exists)
85
+
86
+ @loaded_gitignore_files << parent_path
87
+ end
88
+
89
+ def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
90
+ children = ::Dir.children(parent_full_path)
91
+ load_gitignore(parent_relative_path, check_exists: false) if @gitignore_enabled && children.include?('.gitignore')
92
+
93
+ children.each do |filename|
66
94
  begin
67
95
  full_path = parent_full_path + filename
68
96
  relative_path = parent_relative_path + filename
69
- dir = directory?(full_path)
97
+ dir = @follow_symlinks_method.call(full_path).directory?
70
98
 
71
- next unless @rule_sets.all? { |r| r.allowed_unrecursive?(relative_path, dir, full_path, filename, nil) }
99
+ next unless @rule_sets.allowed_unrecursive?(relative_path, dir, full_path, filename)
72
100
 
73
101
  if dir
74
102
  each_recursive(full_path + '/', relative_path + '/', &block)