fast_ignore 0.12.0 → 0.15.1

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: 45d98b9329a3cf9935ad9bda95f89ffcea7776c19acde46b4f758cf03bad654b
4
- data.tar.gz: ec33228d9d25f5e82a8deed045c5dc2f1885e7fcf05779e079ab4a1460f981f2
3
+ metadata.gz: 4899762ee54c59acb1fa001e7f36a3aada9299dd3896814def76605f1af88bb0
4
+ data.tar.gz: adec5396184fa9a6012c7292726783ea7c823866f7223894de6eec8c199b9679
5
5
  SHA512:
6
- metadata.gz: 6d03cc0337b2b5bb4b7f90adaeedf645e6fcc01a299c873cbdec743f2d13a7a6820c2269a4193d1723907e3f7de00ec4aa76d90a52efba9c022462edb5e129fe
7
- data.tar.gz: 9771665b209531cd704c8603b9791755e054f5a3f72214ef43d23dfb403263f5c00f5ebe9be27b94a63503c85e35dc3d6c4293822cf93bd8ef4dc60b6b2b7715
6
+ metadata.gz: 664b6792d6205c3f15b3f0e045d01cff9540a6bf2b88e76ea10ea3b606cb9ba7d8be1ee48873bf96c162e8f1446fc93d491d01888410da8fb5d55fc26ae74290
7
+ data.tar.gz: 01fec165029777a3f0065a2362b1acba9ef9b745d53cb06c26760184597a1155a535e8c60c3b3879a5b09635ec6670bbfa49504285e740909d61e249d2556bcc
@@ -1,3 +1,25 @@
1
+ # v0.15.1
2
+ - Updated dependencies to allow running on ruby 3.0.0.preview1
3
+
4
+ # v0.15.0
5
+ - fixed a handful of character class edge cases to match git behavior
6
+ - mostly ranges with - or / as one end of the range
7
+ - 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).
8
+ - improved speed of repos with many sub-gitignore files
9
+ - 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.
10
+
11
+ # v0.14.0
12
+ - significant performance improvements ~50% faster
13
+ - add `FastIgnore#to_proc` for no good reason
14
+
15
+ # v0.13.0
16
+ - Attempt to improve documentation structure
17
+ - 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.
18
+ - Don't ignore `.git` if `gitignore: false`.
19
+
20
+ # v0.12.1
21
+ - Reads all relevant git config files when finding a global .gitignore
22
+
1
23
  # v0.12.0
2
24
  - Reads all relevant gitignore files (nested .gitignore files, global .gitignore referred to in .gitconfig, and .git/info/exclude)
3
25
 
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=main)](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,13 +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 & jruby
18
- - supports all gitignore rule patterns
18
+ - Supports ruby 2.4-3.0.0.preview1 & 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: `#!:`
23
- - in addition to .gitignore, reads .git/info/excludes and the ignore file mentioned in your ~/.gitconfig
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
24
27
 
25
28
  ## Installation
26
29
 
@@ -47,22 +50,26 @@ FastIgnore.new.each { |file| puts "#{file} is not ignored by the .gitignore file
47
50
 
48
51
  ### `#each`, `#map` etc
49
52
 
50
- 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:
51
56
 
52
57
  ```ruby
53
58
  FastIgnore.new.to_a
54
59
  FastIgnore.new.map { |file| file.upcase }
55
60
  ```
56
61
 
57
- Like other enumerables, `FastIgnore#each` can return an enumerator
62
+ Like other enumerables, `FastIgnore#each` can return an enumerator:
58
63
 
59
64
  ```ruby
60
65
  FastIgnore.new.each.with_index { |file, index| puts "#{file}#{index}" }
61
66
  ```
62
67
 
68
+ **Warning: Do not change directory (e.g. `Dir.chdir`) in the block.**
69
+
63
70
  ### `#allowed?`
64
71
 
65
- To check if a single file is allowed, use
72
+ To check if a single path is allowed, use
66
73
  ```ruby
67
74
  FastIgnore.new.allowed?('relative/path')
68
75
  FastIgnore.new.allowed?('./relative/path')
@@ -70,7 +77,9 @@ FastIgnore.new.allowed?('/absolute/path')
70
77
  FastIgnore.new.allowed?('~/home/path')
71
78
  ```
72
79
 
73
- 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.
74
83
  ```ruby
75
84
  case my_path
76
85
  when FastIgnore.new
@@ -78,18 +87,34 @@ when FastIgnore.new
78
87
  end
79
88
  ```
80
89
 
81
- 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
+
82
98
 
83
99
  ### `relative: true`
84
- 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
85
105
 
86
106
  ```ruby
87
107
  FastIgnore.new(relative: true).to_a
88
108
  ```
89
109
 
90
110
  ### `follow_symlinks: true`
91
- By default, FastIgnore will match git's behaviour and not follow symbolic links.
92
- 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.**
93
118
 
94
119
  ```ruby
95
120
  FastIgnore.new(follow_symlinks: true).to_a
@@ -97,14 +122,16 @@ FastIgnore.new(follow_symlinks: true).to_a
97
122
 
98
123
  ### `root:`
99
124
 
100
- By default, root is PWD (the current working directory)
125
+ **Default: Dir.pwd ($PWD, the current working directory)**
126
+
101
127
  This directory is used for:
102
- - the location of .git/core/exclude
103
- - the outermost directory that project .gitignore files are looked for
104
- - the root directory for array rules starting with `/` or ending with `/**`
105
- - and the path that relative is relative to
106
- - the ancestor of which files get checked
107
- - the ancestor of all include_files and ignore_files
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)
108
135
 
109
136
  To use a different directory:
110
137
  ```ruby
@@ -112,37 +139,49 @@ FastIgnore.new(root: '/absolute/path/to/root').to_a
112
139
  FastIgnore.new(root: '../relative/path/to/root').to_a
113
140
  ```
114
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
+
115
146
  ### `gitignore:`
116
147
 
117
- By default, the .gitignore file in the root directory is loaded, plus any .gitignore files in subdirectories, the global ignore file, and .git/info/exclude.
148
+ **Default: true**
149
+
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.
118
152
 
119
- To not do this use
120
153
  ```ruby
121
154
  FastIgnore.new(gitignore: false).to_a
122
155
  ```
123
156
 
124
- To raise an `Errno::ENOENT` error if the .gitignore file is not found use:
125
- ```ruby
126
- FastIgnore.new(gitignore: true).to_a
127
- ```
157
+ ### `ignore_files:`
128
158
 
129
- If the gitignore file is somewhere else
130
- ```ruby
131
- FastIgnore.new(ignore_file: '/absolute/path/to/.gitignore').to_a
132
- ```
133
- Note that the location of the .gitignore file will affect rules beginning with `/` or ending in `/**`
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.
134
162
 
135
- ### `ignore_files:`
136
163
  You can specify other gitignore-style files to ignore as well.
137
164
  Missing files will raise an `Errno::ENOENT` error.
138
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
+
139
170
  ```ruby
140
171
  FastIgnore.new(ignore_files: 'relative/path/to/my/ignore/file').to_a
141
172
  FastIgnore.new(ignore_files: ['/absolute/path/to/my/ignore/file', '/and/another']).to_a
142
173
  ```
143
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
+
144
182
  ### `ignore_rules:`
145
- 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.
146
185
 
147
186
  ```ruby
148
187
  FastIgnore.new(ignore_rules: '.DS_Store').to_a
@@ -150,84 +189,110 @@ FastIgnore.new(ignore_rules: ['.git', '.gitkeep']).to_a
150
189
  FastIgnore.new(ignore_rules: ".git\n.gitkeep").to_a
151
190
  ```
152
191
 
153
- ### `include_files:` and `include_rules:`
192
+ These rules use the [`root:`](#root) argument to resolve rules containing `/`.
154
193
 
155
- Building on the gitignore format, FastIgnore also accepts a list of allowed or included files.
194
+ ### `include_files:`
156
195
 
157
- ```gitignore
158
- # a line like this means any files named foo will be included
159
- # as well as any files within directories named foo
160
- foo
161
- # a line beginning with a slash will be anything in a directory that is a child of the $PWD
162
- /foo
163
- # a line ending in a slash will will include any files in any directories named foo
164
- # but not any files named foo
165
- foo/
166
- fo*
167
- !foe
168
- # 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
169
215
  ```
170
216
 
171
- 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.
172
225
  ```ruby
173
- FastIgnore.new(include_files: '/absolute/path/to/my/include/file', gitignore: false).to_a
174
226
  FastIgnore.new(include_rules: %w{my*rule /and/another !rule}, gitignore: false).to_a
175
227
  ```
176
228
 
177
- 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.
178
242
 
179
243
  ```ruby
180
244
  FastIgnore.new(argv_rules: ['./a/pasted/path', '/or/a/path/from/stdin', 'an/argument', '*.txt']).to_a
181
245
  ```
182
246
 
183
- It resolves absolute paths, and paths beginning with `~`, `../` and `./` (with and without `!`)
184
- 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 `~`,`../`,`./`,`/`.**
185
248
 
186
- 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.**
187
250
 
188
251
  ### shebang rules
189
252
 
190
- Sometimes you need to match files by their shebang rather than their path or filename
191
-
192
- 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
193
254
 
194
- 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.
195
256
  e.g.
196
257
  ```gitignore
197
258
  #!:ruby
198
259
  ```
199
260
  will match shebang lines: `#!/usr/bin/env ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
261
+
200
262
  e.g.
201
263
  ```gitignore
202
264
  #!:bin/ruby
203
265
  ```
204
266
  will match `#!/bin/ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
205
- 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.
206
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)
207
270
  ```ruby
208
271
  FastIgnore.new(include_rules: ['*.rb', '#!:ruby']).to_a
209
272
  FastIgnore.new(ignore_rules: ['*.sh', '#!:sh', '#!:bash', '#!:zsh']).to_a
210
273
  ```
211
274
 
275
+ **Note: git considers rules like this as a comment and will ignore them.**
276
+
212
277
  ## Combinations
213
278
 
214
- 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`.
215
280
 
216
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:
217
- 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)
218
283
  ```ruby
219
284
  FastIgnore.new(include_rules: [File.read('/my/path'), File.read('/another/path')])).to_a
220
285
  ```
221
- This does unfortunately lose the file path as the root for `/` and `/**` rules.
222
- 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.
223
288
 
224
- 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.
225
290
 
226
291
  ```ruby
227
292
  FastIgnore.new(argv_rules: ["my/rule", File.read('/my/path')]).to_a
228
293
  ```
229
294
 
230
- 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.
231
296
 
232
297
  ### optimising #allowed?
233
298
 
@@ -237,27 +302,39 @@ FastIgnore.new.allowed?('relative/path', directory: false, content: "#!/usr/bin/
237
302
  ```
238
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.
239
304
 
240
-
241
- ## Known issues
242
- - 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.
243
307
  So don't do that.
244
308
 
245
- (It does handle changing the current working directory between `FastIgnore#allowed?` calls.)
246
-
247
- ## Development
248
-
249
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and linters.
250
-
251
- You can run `bin/console` for an interactive prompt that will allow you to experiment.
252
- `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.
253
- 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.
254
-
255
- 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`.
256
319
 
257
320
  ## Contributing
258
321
 
259
322
  Bug reports and pull requests are welcome on GitHub at https://github.com/robotdana/fast_ignore.
260
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
+
261
338
  ## License
262
339
 
263
340
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -3,12 +3,19 @@
3
3
  require_relative './fast_ignore/backports'
4
4
 
5
5
  require 'set'
6
- require_relative './fast_ignore/rule_set_builder'
7
- require_relative './fast_ignore/rule_builder'
6
+ require 'strscan'
7
+ require_relative './fast_ignore/rule_sets'
8
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'
9
16
  require_relative './fast_ignore/rule'
17
+ require_relative './fast_ignore/unmatchable_rule'
10
18
  require_relative './fast_ignore/shebang_rule'
11
- require_relative './fast_ignore/fn_match_to_re'
12
19
 
13
20
  class FastIgnore
14
21
  class Error < StandardError; end
@@ -22,11 +29,11 @@ class FastIgnore
22
29
 
23
30
  def initialize(relative: false, root: nil, gitignore: :auto, follow_symlinks: false, **rule_set_builder_args)
24
31
  @relative = relative
25
- @follow_symlinks = follow_symlinks
32
+ @follow_symlinks_method = ::File.method(follow_symlinks ? :stat : :lstat)
26
33
  @gitignore_enabled = gitignore
27
- @loaded_gitignore_files = Set[''] if gitignore
34
+ @loaded_gitignore_files = ::Set[''] if gitignore
28
35
  @root = "#{::File.expand_path(root.to_s, Dir.pwd)}/"
29
- @rule_sets = ::FastIgnore::RuleSetBuilder.build(root: @root, gitignore: gitignore, **rule_set_builder_args)
36
+ @rule_sets = ::FastIgnore::RuleSets.new(root: @root, gitignore: gitignore, **rule_set_builder_args)
30
37
 
31
38
  freeze
32
39
  end
@@ -34,7 +41,7 @@ class FastIgnore
34
41
  def each(&block)
35
42
  return enum_for(:each) unless block_given?
36
43
 
37
- dir_pwd = Dir.pwd
44
+ dir_pwd = ::Dir.pwd
38
45
  root_from_pwd = @root.start_with?(dir_pwd) ? ".#{@root.delete_prefix(dir_pwd)}" : @root
39
46
 
40
47
  each_recursive(root_from_pwd, '', &block)
@@ -43,29 +50,25 @@ class FastIgnore
43
50
  def allowed?(path, directory: nil, content: nil)
44
51
  full_path = ::File.expand_path(path, @root)
45
52
  return false unless full_path.start_with?(@root)
46
- return false if directory.nil? ? directory?(full_path) : directory
53
+ return false if directory.nil? ? @follow_symlinks_method.call(full_path).directory? : directory
47
54
 
48
55
  relative_path = full_path.delete_prefix(@root)
49
56
  load_gitignore_recursive(relative_path) if @gitignore_enabled
50
57
 
51
58
  filename = ::File.basename(relative_path)
52
59
 
53
- @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)
54
61
  rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
55
62
  false
56
63
  end
57
64
  alias_method :===, :allowed?
58
65
 
59
- private
60
-
61
- def directory?(path)
62
- if @follow_symlinks
63
- ::File.stat(path).directory?
64
- else
65
- ::File.lstat(path).directory?
66
- end
66
+ def to_proc
67
+ method(:allowed?).to_proc
67
68
  end
68
69
 
70
+ private
71
+
69
72
  def load_gitignore_recursive(path)
70
73
  paths = []
71
74
  while (path = ::File.dirname(path)) != '.'
@@ -75,27 +78,25 @@ class FastIgnore
75
78
  paths.reverse_each(&method(:load_gitignore))
76
79
  end
77
80
 
78
- def load_gitignore(parent_path, soft: true)
81
+ def load_gitignore(parent_path, check_exists: true)
79
82
  return if @loaded_gitignore_files.include?(parent_path)
80
83
 
81
- ::FastIgnore::RuleSetBuilder.append_gitignore(
82
- @rule_sets, project_root: @root, relative_path: parent_path + '.gitignore', soft: soft
83
- )
84
+ @rule_sets.append_subdir_gitignore(relative_path: parent_path + '.gitignore', check_exists: check_exists)
85
+
84
86
  @loaded_gitignore_files << parent_path
85
87
  end
86
88
 
87
- def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
89
+ def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
88
90
  children = ::Dir.children(parent_full_path)
89
-
90
- load_gitignore(parent_relative_path, soft: false) if @gitignore_enabled && children.include?('.gitignore')
91
+ load_gitignore(parent_relative_path, check_exists: false) if @gitignore_enabled && children.include?('.gitignore')
91
92
 
92
93
  children.each do |filename|
93
94
  begin
94
95
  full_path = parent_full_path + filename
95
96
  relative_path = parent_relative_path + filename
96
- dir = directory?(full_path)
97
+ dir = @follow_symlinks_method.call(full_path).directory?
97
98
 
98
- 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)
99
100
 
100
101
  if dir
101
102
  each_recursive(full_path + '/', relative_path + '/', &block)