fast_ignore 0.16.0 → 0.17.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: 88d6249cc842976d1b5c0e984816cfc260cc3edcbe4774ea4580e934bedcf925
4
- data.tar.gz: 3ab13efc019bee9b5cadcb45f485021132f07787bce73d66bb9ef63b76bc51ca
3
+ metadata.gz: 88853f2613fe610772a13666845d500a4b377bbf5606e3fba3bd8842d66c17a2
4
+ data.tar.gz: c158117c0234816a067b3e68d7686e247bf18ea81e39b3c339fcf06ecc4026ae
5
5
  SHA512:
6
- metadata.gz: 853124ae1ddf1fc599e8a422580ee84ff705907a70c7ece17db72502a4468ca0d5479f918c175f91dc1915c7cccb5ad4cd7eaa956adf2d7b0a5e2fa024821c3d
7
- data.tar.gz: b90c827caa6c24c3491237ec5fb538b828b2daea879a4d94d6bc3ac4368807c7040bc0d6ea6405b8e9712483d00fddfb6d43e1a970786f374144a4d6165f59ee
6
+ metadata.gz: a409a69b31021dedca95858ebed16abb4a36efbb758067dcab41bd6cbeb988acf0a62d4324f31cbb68ba4787efca4564cfc51721a50dc710f3357923775cc6e2
7
+ data.tar.gz: 4f0e0a8f900d6c0d008850eb8899c1c4446914149697539b9f708620054b614b310305e83df8743a3d5fb558a82ac08f0d48e44450486ad5de966943916a6ac2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # v0.17.1
2
+ - fix handling of backward character classes `[z-a]`
3
+ previously this raised a RegexpError, but git just considered it to be identical to `[z]`, now we match the git behaviour (but why would you ever do this?, i only found it because of the fuzz spec in the `leftovers` gem)
4
+
5
+ # v0.17.0
6
+ - allow overriding `exists:` in `allowed?`
7
+ - allow setting `include_directories: true` in `allowed?`
8
+ - subsequent calls to `allowed?` with the same path but different `directory:` or `content:` arguments won't potentially mess up the cache
9
+ - slight performance improvements of the shebang rule matcher loading the first line
10
+ - drop support for ruby 2.4
11
+ - add ruby 3.1 to the test matrix
12
+
13
+ # v0.16.1
14
+ - respect GIT_CONFIG_SYSTEM, GIT_CONFIG_NOSYSTEM and GIT_CONFIG_GLOBAL env vars the same way git does
15
+ - make the tests more resilient to whatever global config is going on.
16
+
1
17
  # v0.16.0
2
18
  - Entirely rewrite the way that git config files are read. previously it was just a regexp. now we actually parse git config files according to the same rules as git.
3
19
  - Add ruby 3 to the test matrix
data/README.md CHANGED
@@ -15,7 +15,7 @@ FastIgnore.new(relative: true).sort == `git ls-files`.split("\n").sort
15
15
  ## Features
16
16
 
17
17
  - Fast (faster than using `` `git ls-files`.split("\n") `` for small repos (because it avoids the overhead of ``` `` ```))
18
- - Supports ruby 2.4-3.0.x & jruby
18
+ - Supports ruby 2.5-3.1.x & jruby
19
19
  - supports all [gitignore rule patterns](https://git-scm.com/docs/gitignore#_pattern_format)
20
20
  - doesn't require git to be installed
21
21
  - supports a gitignore-esque "include" patterns. ([`include_rules:`](#include_rules)/[`include_files:`](#include_files))
@@ -81,20 +81,79 @@ Relative paths will be considered relative to the [`root:`](#root) directory, no
81
81
 
82
82
  This is aliased as `===` so you can use a FastIgnore instance in case statements.
83
83
  ```ruby
84
+ @path_matcher ||= FastIgnore.new
85
+
84
86
  case my_path
85
- when FastIgnore.new
87
+ when @path_matcher
86
88
  puts(my_path)
87
89
  end
88
90
  ```
89
91
 
90
92
  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
93
 
92
- See [Optimising allowed](#optimising_allowed) for ways to make this even faster
94
+ #### directory: true/false/nil
95
+
96
+ If your code already knows the path to test is/not a directory or wants to lie about whether it is/is not a directory, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)
97
+
98
+ ```
99
+ FastIgnore.new.allowed?('relative/path', directory: false) # matches `path` as a file
100
+ FastIgnore.new.allowed?('relative/path', directory: true) # matches `path` as a directory
101
+ FastIgnore.new.allowed?('relative/path', directory: nil) # matches path as whatever it is on the filesystem
102
+ FastIgnore.new.allowed?('relative/path) # or as a file if it doesn't exist on the file system
103
+ ```
104
+
105
+ #### content: true/false/nil
106
+
107
+ default: `nil`
108
+
109
+ If your code already knows the path to test is has a particular text content or wants to lie about the content, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)
110
+
111
+ ```
112
+ FastIgnore.new.allowed?('relative/path', content: "#!/usr/bin/env ruby\n\nputs 'hello'") # matches ruby shebang
113
+ FastIgnore.new.allowed?('relative/path', content: "#!/usr/bin/env bash\n\necho 'hello'") # matches bash shebang
114
+ FastIgnore.new.allowed?('relative/path', content: nil) # matches path as whatever content is on the filesystem
115
+ FastIgnore.new.allowed?('relative/path) # or as an empty file if it doesn't actually exist
116
+ ```
117
+
118
+ #### content: true/false/nil
119
+
120
+ default: `nil`
121
+
122
+ If your code already knows the path to test is has a particular text content or wants to lie about the content, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)
123
+
124
+ ```
125
+ FastIgnore.new.allowed?('relative/path', content: "#!/usr/bin/env ruby\n\nputs 'hello'") # matches ruby shebang
126
+ FastIgnore.new.allowed?('relative/path', content: "#!/usr/bin/env bash\n\necho 'hello'") # matches bash shebang
127
+ FastIgnore.new.allowed?('relative/path', content: nil) # matches path as whatever content is on the filesystem
128
+ FastIgnore.new.allowed?('relative/path) # or as an empty file if it doesn't actually exist
129
+ ```
93
130
 
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.
131
+ #### exist: true/false/nil
97
132
 
133
+ default: `nil`
134
+
135
+ If your code already knows the path to test exists or wants to lie about its existence, you can pass `exists: true` or `exists: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `exists: nil` or nothing)
136
+
137
+ ```
138
+ FastIgnore.new.allowed?('relative/path', exists: true) # will check the path regardless of whether it actually truly exists
139
+ FastIgnore.new.allowed?('relative/path', exists: false) # will always return false
140
+ FastIgnore.new.allowed?('relative/path', exists: nil) # asks the filesystem
141
+ FastIgnore.new.allowed?('relative/path) # asks the filesystem
142
+ ```
143
+
144
+ #### include_directories: true/false
145
+
146
+ default: `false`
147
+
148
+ By default a file must not be a directory for it to be considered allowed. This is intended to match the behaviour of `git ls-files` which only lists files.
149
+
150
+ To match directories you can pass `include_directories: true` to `allowed?`
151
+
152
+ ```
153
+ FastIgnore.new.allowed?('relative/path', include_directories: true) # will test the path even if it's a directory
154
+ FastIgnore.new.allowed?('relative/path', include_directories: false) # will always return false if the path is a directory
155
+ FastIgnore.new.allowed?('relative/path) # will always return false if the path is a directory
156
+ ```
98
157
 
99
158
  ### `relative: true`
100
159
 
@@ -293,15 +352,6 @@ FastIgnore.new(argv_rules: ["my/rule", File.read('/my/path')]).to_a
293
352
  ```
294
353
 
295
354
  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.
296
-
297
- ### optimising #allowed?
298
-
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.
304
-
305
355
  ## Limitations
306
356
  - Doesn't know what to do if you change the current working directory inside the [`FastIgnore#each`](#each_map_etc) block.
307
357
  So don't do that.
@@ -309,6 +359,7 @@ This is not required, and if FastIgnore does have to go to the filesystem for th
309
359
  (It does handle changing the current working directory between [`FastIgnore#allowed?`](#allowed) calls)
310
360
  - FastIgnore always matches patterns case-insensitively. (git varies by filesystem).
311
361
  - 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).
362
+ - git has a system-wide config file installed at `$(prefix)/etc/gitconfig`, where `prefix` is defined for git at install time. FastIgnore assumes that it will always be `/usr/local/etc/gitconfig`. if it's important your system config file is looked at, as that's where you have the core.excludesfile defined, use git's built-in way to override this by adding `export GIT_CONFIG_SYSTEM='/the/actual/location'` to your shell profile.
312
363
  - 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
364
  - Tracked files that were committed before the matching ignore rule was committed will be returned by `git ls-files`, but not by FastIgnore.
314
365
  - Untracked files will be returned by FastIgnore, but not by `git ls-files`
@@ -108,8 +108,8 @@ class FastIgnore
108
108
  unmatchable_rule! if @s.character_class_end?
109
109
 
110
110
  until @s.character_class_end?
111
+ next if process_character_class_range
111
112
  next if process_backslash
112
- next @re.append_character_class_dash if @s.dash?
113
113
  next if @re.append_escaped(@s.character_class_literal)
114
114
 
115
115
  unmatchable_rule!
@@ -118,6 +118,22 @@ class FastIgnore
118
118
  @re.append_character_class_close
119
119
  end
120
120
 
121
+ def process_character_class_range
122
+ start = @s.character_class_range_start
123
+ return unless start
124
+
125
+ start = start.delete_prefix('\\')
126
+
127
+ @re.append_escaped(start)
128
+
129
+ finish = @s.character_class_range_end.delete_prefix('\\')
130
+
131
+ return true unless start < finish
132
+
133
+ @re.append_character_class_dash
134
+ @re.append_escaped(finish)
135
+ end
136
+
121
137
  def process_end
122
138
  blank! if nothing_emitted?
123
139
 
@@ -46,12 +46,20 @@ class FastIgnore
46
46
  skip(/\?/)
47
47
  end
48
48
 
49
- def dash?
50
- skip(/-/)
49
+ def character_class_literal
50
+ matched if scan(/[^\]\\][^\]\\-]*(?!-)/)
51
51
  end
52
52
 
53
- def character_class_literal
54
- matched if scan(/[^\]\-\\]+/)
53
+ def character_class_range_start
54
+ matched if scan(/(\\.|[^\\\]])(?=-(\\.|[^\\\]]))/)
55
+ end
56
+
57
+ def character_class_range_end
58
+ # we already confirmed this was going to match
59
+ # with the lookahead in character_class_range_start
60
+ skip(/-/)
61
+ scan(/(\\.|[^\\\]])/)
62
+ matched
55
63
  end
56
64
 
57
65
  def literal
@@ -4,15 +4,23 @@ class FastIgnore
4
4
  module GlobalGitignore
5
5
  class << self
6
6
  def path(root:)
7
- gitconfig_gitignore_path(::File.expand_path('.git/config', root)) ||
8
- gitconfig_gitignore_path(::File.expand_path('~/.gitconfig')) ||
9
- gitconfig_gitignore_path(xdg_config_path) ||
10
- gitconfig_gitignore_path('/etc/gitconfig') ||
7
+ ignore_path = gitconfigs_gitignore_path(root) ||
11
8
  default_global_gitignore_path
9
+
10
+ ignore_path unless ignore_path.empty?
12
11
  end
13
12
 
14
13
  private
15
14
 
15
+ def gitconfigs_gitignore_path(root)
16
+ gitconfig_gitignore_path(repo_config_path(root)) ||
17
+ gitconfig_gitignore_path(global_config_path) ||
18
+ gitconfig_gitignore_path(default_user_config_path) ||
19
+ gitconfig_gitignore_path(system_config_path)
20
+ rescue ::FastIgnore::GitconfigParseError
21
+ ''
22
+ end
23
+
16
24
  def gitconfig_gitignore_path(config_path)
17
25
  return unless config_path
18
26
  return unless ::File.readable?(config_path)
@@ -26,24 +34,54 @@ class FastIgnore
26
34
  ::File.expand_path(ignore_path)
27
35
  end
28
36
 
29
- def xdg_config_path
30
- xdg_config_home? && ::File.expand_path('git/config', xdg_config_home)
37
+ def default_user_config_path
38
+ return if env('GIT_CONFIG_GLOBAL')
39
+
40
+ ::File.expand_path('git/config', default_config_home)
31
41
  end
32
42
 
33
43
  def default_global_gitignore_path
34
- if xdg_config_home?
35
- ::File.expand_path('git/ignore', xdg_config_home)
44
+ ::File.expand_path('git/ignore', default_config_home)
45
+ end
46
+
47
+ def repo_config_path(root)
48
+ ::File.expand_path('.git/config', root)
49
+ end
50
+
51
+ def global_config_path
52
+ ::File.expand_path(env('GIT_CONFIG_GLOBAL', '~/.gitconfig'))
53
+ end
54
+
55
+ def system_config_path
56
+ return if env?('GIT_CONFIG_NOSYSTEM')
57
+
58
+ ::File.expand_path(env('GIT_CONFIG_SYSTEM', '/usr/local/etc/gitconfig'))
59
+ end
60
+
61
+ def default_config_home
62
+ env('XDG_CONFIG_HOME', '~/.config')
63
+ end
64
+
65
+ def env(env_var, default = nil)
66
+ value = ::ENV[env_var]
67
+
68
+ if value && (not value.empty?)
69
+ value
36
70
  else
37
- ::File.expand_path('~/.config/git/ignore')
71
+ default
38
72
  end
39
73
  end
40
74
 
41
- def xdg_config_home
42
- ::ENV['XDG_CONFIG_HOME']
43
- end
75
+ def env?(env_var)
76
+ value = ::ENV[env_var]
44
77
 
45
- def xdg_config_home?
46
- xdg_config_home && (not xdg_config_home.empty?)
78
+ if value&.match?(/\A(yes|on|true|1)\z/i)
79
+ true
80
+ elsif !value || value.match?(/\A(no|off|false|0|)\z/i)
81
+ false
82
+ else
83
+ raise ::FastIgnore::GitconfigParseError
84
+ end
47
85
  end
48
86
  end
49
87
  end
@@ -11,7 +11,7 @@ class FastIgnore
11
11
  @file_rules = (squash ? squash_rules(rules.reject(&:dir_only?)) : rules.reject(&:dir_only?)).freeze
12
12
  @has_shebang_rules = rules.any?(&:shebang?)
13
13
 
14
- @allowed_recursive = { '.' => true }
14
+ @allowed_recursive = { ['.', true, nil] => true }
15
15
  @allow = allow
16
16
  @gitignore = gitignore
17
17
 
@@ -27,8 +27,8 @@ class FastIgnore
27
27
  end
28
28
 
29
29
  def allowed_recursive?(relative_path, dir, full_path, filename, content = nil)
30
- @allowed_recursive.fetch(relative_path) do
31
- @allowed_recursive[relative_path] =
30
+ @allowed_recursive.fetch([relative_path, dir, content]) do |key|
31
+ @allowed_recursive[key] =
32
32
  allowed_recursive?(::File.dirname(relative_path), true, nil, nil, nil) &&
33
33
  allowed_unrecursive?(relative_path, dir, full_path, filename, content)
34
34
  end
@@ -27,8 +27,8 @@ class FastIgnore
27
27
  @array.freeze if @gitignore_rule_set
28
28
  end
29
29
 
30
- def allowed_recursive?(relative_path, full_path, filename, content)
31
- @array.all? { |r| r.allowed_recursive?(relative_path, false, full_path, filename, content) }
30
+ def allowed_recursive?(relative_path, dir, full_path, filename, content)
31
+ @array.all? { |r| r.allowed_recursive?(relative_path, dir, full_path, filename, content) }
32
32
  end
33
33
 
34
34
  def allowed_unrecursive?(relative_path, dir, full_path, filename)
@@ -73,7 +73,7 @@ class FastIgnore
73
73
  end
74
74
 
75
75
  def build_from_root_gitignore_file(path)
76
- return unless ::File.exist?(path)
76
+ return unless path && ::File.exist?(path)
77
77
 
78
78
  build_rule_set(::File.readlines(path), false, gitignore: true)
79
79
  end
@@ -61,12 +61,9 @@ class FastIgnore
61
61
 
62
62
  def first_line(path) # rubocop:disable Metrics/MethodLength
63
63
  file = ::File.new(path)
64
- first_line = new_fragment = file.sysread(64)
64
+ first_line = file.sysread(64)
65
65
  if first_line.start_with?('#!')
66
- until new_fragment.include?("\n")
67
- new_fragment = file.sysread(64)
68
- first_line += new_fragment
69
- end
66
+ first_line += file.readline unless first_line.match?(/\n/)
70
67
  else
71
68
  file.close
72
69
  return
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FastIgnore
4
- VERSION = '0.16.0'
4
+ VERSION = '0.17.1'
5
5
  end
data/lib/fast_ignore.rb CHANGED
@@ -49,19 +49,30 @@ class FastIgnore
49
49
  each_recursive(root_from_pwd, '', &block)
50
50
  end
51
51
 
52
- def allowed?(path, directory: nil, content: nil)
52
+ def allowed?(path, directory: nil, content: nil, exists: nil, include_directories: false) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
53
53
  full_path = ::File.expand_path(path, @root)
54
54
  return false unless full_path.start_with?(@root)
55
- return false if directory.nil? ? @follow_symlinks_method.call(full_path).directory? : directory
55
+
56
+ begin
57
+ directory = directory.nil? ? @follow_symlinks_method.call(full_path).directory? : directory
58
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
59
+ exists = false if exists.nil?
60
+ directory = false
61
+ end
62
+
63
+ return false if !include_directories && directory
64
+
65
+ exists = exists.nil? ? ::File.exist?(full_path) : exists
66
+
67
+ return false unless exists
56
68
 
57
69
  relative_path = full_path.delete_prefix(@root)
58
70
  load_gitignore_recursive(relative_path) if @gitignore_enabled
59
71
 
60
72
  filename = ::File.basename(relative_path)
73
+ content = content.slice(/.*/) if content # we only care about the first line
61
74
 
62
- @rule_sets.allowed_recursive?(relative_path, full_path, filename, content)
63
- rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
64
- false
75
+ @rule_sets.allowed_recursive?(relative_path, directory, full_path, filename, content)
65
76
  end
66
77
  alias_method :===, :allowed?
67
78
 
@@ -77,7 +88,7 @@ class FastIgnore
77
88
  paths << path
78
89
  end
79
90
 
80
- paths.reverse_each(&method(:load_gitignore))
91
+ paths.reverse_each { |p| load_gitignore(p) }
81
92
  end
82
93
 
83
94
  def load_gitignore(parent_path, check_exists: true)
@@ -93,21 +104,19 @@ class FastIgnore
93
104
  load_gitignore(parent_relative_path, check_exists: false) if @gitignore_enabled && children.include?('.gitignore')
94
105
 
95
106
  children.each do |filename|
96
- begin
97
- full_path = parent_full_path + filename
98
- relative_path = parent_relative_path + filename
99
- dir = @follow_symlinks_method.call(full_path).directory?
100
-
101
- next unless @rule_sets.allowed_unrecursive?(relative_path, dir, full_path, filename)
102
-
103
- if dir
104
- each_recursive(full_path + '/', relative_path + '/', &block)
105
- else
106
- yield(@relative ? relative_path : @root + relative_path)
107
- end
108
- rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
109
- nil
107
+ full_path = parent_full_path + filename
108
+ relative_path = parent_relative_path + filename
109
+ dir = @follow_symlinks_method.call(full_path).directory?
110
+
111
+ next unless @rule_sets.allowed_unrecursive?(relative_path, dir, full_path, filename)
112
+
113
+ if dir
114
+ each_recursive(full_path + '/', relative_path + '/', &block)
115
+ else
116
+ yield(@relative ? relative_path : @root + relative_path)
110
117
  end
118
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
119
+ nil
111
120
  end
112
121
  end
113
122
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_ignore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dana Sherson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-09 00:00:00.000000000 Z
11
+ date: 2022-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -100,6 +100,20 @@ dependencies:
100
100
  - - "<"
101
101
  - !ruby/object:Gem::Version
102
102
  version: '1.12'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rubocop-rake
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
103
117
  - !ruby/object:Gem::Dependency
104
118
  name: rubocop-rspec
105
119
  requirement: !ruby/object:Gem::Requirement
@@ -197,14 +211,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
197
211
  requirements:
198
212
  - - ">="
199
213
  - !ruby/object:Gem::Version
200
- version: 2.4.0
214
+ version: 2.5.0
201
215
  required_rubygems_version: !ruby/object:Gem::Requirement
202
216
  requirements:
203
217
  - - ">="
204
218
  - !ruby/object:Gem::Version
205
219
  version: '0'
206
220
  requirements: []
207
- rubygems_version: 3.2.15
221
+ rubygems_version: 3.1.6
208
222
  signing_key:
209
223
  specification_version: 4
210
224
  summary: Parse gitignore files, quickly