fast_ignore 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +3 -1
- data/lib/fast_ignore.rb +4 -0
- data/lib/fast_ignore/file_root.rb +39 -0
- data/lib/fast_ignore/gitignore_include_rule_builder.rb +102 -0
- data/lib/fast_ignore/gitignore_rule_builder.rb +125 -155
- data/lib/fast_ignore/gitignore_rule_regexp_builder.rb +76 -0
- data/lib/fast_ignore/gitignore_rule_scanner.rb +73 -0
- data/lib/fast_ignore/global_gitignore.rb +13 -5
- data/lib/fast_ignore/rule.rb +7 -2
- data/lib/fast_ignore/rule_builder.rb +19 -50
- data/lib/fast_ignore/rule_set.rb +16 -6
- data/lib/fast_ignore/rule_sets.rb +13 -12
- data/lib/fast_ignore/shebang_rule.rb +15 -5
- data/lib/fast_ignore/unmatchable_rule.rb +4 -0
- data/lib/fast_ignore/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f96030c8670a5d709139689212b3a78a0eb0b5266cbd0057954863ae89e11745
|
4
|
+
data.tar.gz: a2815ec68f6c367a4450efce8799aa359adeee1ad1aaa971b589cd8612ddbb61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c22c6c79a6605473156a041d5ddb5b81f943605dc4360dd8577acd56718c928c106e044169b13bc73c7e884ffcc5516c7a6b5d6134fee9aaa4aa73d0178b791
|
7
|
+
data.tar.gz: 1d165e44b50d9b1c301c5cad8ff3c443bfb2f9134be09e28a4e90539c960a269083485f6ff30bc6df6e2ec82347630187ebf0082cd2d06ba7416e9c70f37ea6f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
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
|
+
|
1
8
|
# v0.14.0
|
2
9
|
- significant performance improvements ~50% faster
|
3
10
|
- add `FastIgnore#to_proc` for no good reason
|
data/README.md
CHANGED
@@ -309,11 +309,13 @@ This is not required, and if FastIgnore does have to go to the filesystem for th
|
|
309
309
|
(It does handle changing the current working directory between [`FastIgnore#allowed?`](#allowed) calls)
|
310
310
|
- FastIgnore always matches patterns case-insensitively. (git varies by filesystem).
|
311
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`
|
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
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
314
|
- Untracked files will be returned by FastIgnore, but not by `git ls-files`
|
315
315
|
- Deleted files whose deletions haven't been committed will be returned by `git ls-files`, but not by FastIgnore
|
316
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`.
|
317
319
|
|
318
320
|
## Contributing
|
319
321
|
|
data/lib/fast_ignore.rb
CHANGED
@@ -9,6 +9,10 @@ require_relative './fast_ignore/rule_set'
|
|
9
9
|
require_relative './fast_ignore/global_gitignore'
|
10
10
|
require_relative './fast_ignore/rule_builder'
|
11
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'
|
12
16
|
require_relative './fast_ignore/rule'
|
13
17
|
require_relative './fast_ignore/unmatchable_rule'
|
14
18
|
require_relative './fast_ignore/shebang_rule'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FastIgnore
|
4
|
+
class FileRoot
|
5
|
+
# :nocov:
|
6
|
+
using ::FastIgnore::Backports::DeletePrefixSuffix if defined?(::FastIgnore::Backports::DeletePrefixSuffix)
|
7
|
+
# :nocov:
|
8
|
+
|
9
|
+
def self.build(file_path, project_root)
|
10
|
+
file_root = "#{::File.dirname(file_path)}/".delete_prefix(project_root)
|
11
|
+
|
12
|
+
new(file_root) unless file_root.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(file_root)
|
16
|
+
@file_root = file_root
|
17
|
+
end
|
18
|
+
|
19
|
+
def shebang_path_pattern
|
20
|
+
@shebang_path_pattern ||= /\A#{escaped}./
|
21
|
+
end
|
22
|
+
|
23
|
+
def escaped
|
24
|
+
@escaped ||= ::Regexp.escape(@file_root)
|
25
|
+
end
|
26
|
+
|
27
|
+
def escaped_segments
|
28
|
+
@escaped_segments ||= escaped.split('/')
|
29
|
+
end
|
30
|
+
|
31
|
+
def escaped_segments_length
|
32
|
+
@escaped_segments_length ||= escaped_segments.length
|
33
|
+
end
|
34
|
+
|
35
|
+
def escaped_segments_joined
|
36
|
+
@escaped_segments_joined ||= escaped_segments.join('(?:/') + '(?:/'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FastIgnore
|
4
|
+
class GitignoreIncludeRuleBuilder < GitignoreRuleBuilder
|
5
|
+
# :nocov:
|
6
|
+
using ::FastIgnore::Backports::DeletePrefixSuffix if defined?(::FastIgnore::Backports::DeletePrefixSuffix)
|
7
|
+
# :nocov:
|
8
|
+
|
9
|
+
def initialize(rule, file_path, expand_path_from = nil)
|
10
|
+
super(rule, file_path)
|
11
|
+
|
12
|
+
@parent_segments = []
|
13
|
+
@negation = true
|
14
|
+
@expand_path_from = expand_path_from
|
15
|
+
end
|
16
|
+
|
17
|
+
def expand_rule_path
|
18
|
+
anchored! unless @s.match?(/\*/)
|
19
|
+
return unless @s.match?(%r{(?:[~/]|\.{1,2}/|.*/\.\./)})
|
20
|
+
|
21
|
+
dir_only! if @s.match?(%r{.*/\s*\z})
|
22
|
+
@s.string.replace(::File.expand_path(@s.rest))
|
23
|
+
@s.string.delete_prefix!(@expand_path_from)
|
24
|
+
@s.pos = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def negated!
|
28
|
+
@negation = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def unmatchable_rule!
|
32
|
+
throw :abort_build, ::FastIgnore::UnmatchableRule
|
33
|
+
end
|
34
|
+
|
35
|
+
def nothing_emitted?
|
36
|
+
@re.empty? && @parent_segments.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def emit_dir
|
40
|
+
anchored!
|
41
|
+
|
42
|
+
@parent_segments << @re
|
43
|
+
@re = ::FastIgnore::GitignoreRuleRegexpBuilder.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def emit_end
|
47
|
+
@dir_only || @re.append_end_dir_or_anchor
|
48
|
+
break!
|
49
|
+
end
|
50
|
+
|
51
|
+
def parent_dir_re # rubocop:disable Metrics/MethodLength
|
52
|
+
segment_joins_count = @parent_segments.length
|
53
|
+
parent_prefix = if @file_path
|
54
|
+
segment_joins_count += @file_path.escaped_segments_length
|
55
|
+
|
56
|
+
if @anchored
|
57
|
+
"\\A#{@file_path.escaped_segments_joined}"
|
58
|
+
else
|
59
|
+
"\\A#{@file_path.escaped_segments_joined}(?:.*/)?"
|
60
|
+
end
|
61
|
+
else
|
62
|
+
prefix
|
63
|
+
end
|
64
|
+
|
65
|
+
out = parent_prefix.dup
|
66
|
+
unless @parent_segments.empty?
|
67
|
+
out << '(?:'
|
68
|
+
out << @parent_segments.join('/(?:')
|
69
|
+
out << '/'
|
70
|
+
end
|
71
|
+
out << (')?' * segment_joins_count)
|
72
|
+
out
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_parent_dir_rule
|
76
|
+
# Regexp::IGNORECASE = 1
|
77
|
+
::FastIgnore::Rule.new(::Regexp.new(parent_dir_re, 1), true, anchored_or_file_path, true)
|
78
|
+
end
|
79
|
+
|
80
|
+
def build_child_file_rule
|
81
|
+
# Regexp::IGNORECASE = 1
|
82
|
+
::FastIgnore::Rule.new(@re.append_dir.to_regexp, @negation, anchored_or_file_path, false)
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_rule
|
86
|
+
joined_re = ::FastIgnore::GitignoreRuleRegexpBuilder.new
|
87
|
+
joined_re.append(@parent_segments.join('/'))
|
88
|
+
joined_re.append_dir unless @parent_segments.empty?
|
89
|
+
joined_re.append(@re)
|
90
|
+
@re = joined_re
|
91
|
+
|
92
|
+
rules = [super, build_parent_dir_rule]
|
93
|
+
(rules << build_child_file_rule) if @dir_only
|
94
|
+
rules
|
95
|
+
end
|
96
|
+
|
97
|
+
def process_rule
|
98
|
+
expand_rule_path if @expand_path_from
|
99
|
+
super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -2,210 +2,180 @@
|
|
2
2
|
|
3
3
|
class FastIgnore
|
4
4
|
class GitignoreRuleBuilder # rubocop:disable Metrics/ClassLength
|
5
|
-
def initialize(rule,
|
6
|
-
@re = ::
|
7
|
-
@
|
8
|
-
@allow = allow
|
9
|
-
if @allow
|
10
|
-
@segments = 0
|
11
|
-
@parent_re = ::String.new
|
12
|
-
end
|
13
|
-
|
14
|
-
@s = ::StringScanner.new(rule)
|
5
|
+
def initialize(rule, file_path)
|
6
|
+
@re = ::FastIgnore::GitignoreRuleRegexpBuilder.new
|
7
|
+
@s = ::FastIgnore::GitignoreRuleScanner.new(rule)
|
15
8
|
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@negation = negation
|
9
|
+
@file_path = file_path
|
10
|
+
@negation = false
|
19
11
|
@anchored = false
|
20
|
-
@
|
12
|
+
@dir_only = false
|
21
13
|
end
|
22
14
|
|
23
|
-
def
|
24
|
-
|
15
|
+
def break!
|
16
|
+
throw :break
|
25
17
|
end
|
26
18
|
|
27
|
-
def
|
28
|
-
|
19
|
+
def blank!
|
20
|
+
throw :abort_build, []
|
21
|
+
end
|
29
22
|
|
30
|
-
|
31
|
-
|
23
|
+
def unmatchable_rule!
|
24
|
+
throw :abort_build, []
|
32
25
|
end
|
33
26
|
|
34
|
-
def
|
35
|
-
|
27
|
+
def negated!
|
28
|
+
@negation = true
|
29
|
+
end
|
36
30
|
|
37
|
-
|
38
|
-
|
31
|
+
def anchored!
|
32
|
+
@anchored ||= true
|
39
33
|
end
|
40
34
|
|
41
|
-
def
|
42
|
-
|
43
|
-
@allow ? ::FastIgnore::UnmatchableRule : []
|
44
|
-
)
|
35
|
+
def never_anchored!
|
36
|
+
@anchored = :never
|
45
37
|
end
|
46
38
|
|
47
|
-
def
|
48
|
-
|
39
|
+
def dir_only!
|
40
|
+
@dir_only = true
|
41
|
+
end
|
49
42
|
|
50
|
-
|
43
|
+
def nothing_emitted?
|
44
|
+
@re.empty?
|
45
|
+
end
|
51
46
|
|
52
|
-
|
47
|
+
def emit_dir
|
48
|
+
anchored!
|
49
|
+
@re.append_dir
|
53
50
|
end
|
54
51
|
|
55
|
-
def
|
56
|
-
@
|
57
|
-
|
58
|
-
if @s.eos?
|
59
|
-
unmatchable_rule!
|
60
|
-
elsif process_escaped_char
|
61
|
-
@has_characters_in_group = true
|
62
|
-
elsif @s.skip(%r{/})
|
63
|
-
next unless negated_class
|
64
|
-
|
65
|
-
@has_characters_in_group = true
|
66
|
-
@segment_re << '/'
|
67
|
-
elsif @s.skip(/-/)
|
68
|
-
@has_characters_in_group = true
|
69
|
-
@segment_re << '-'
|
70
|
-
else @s.scan(%r{[^/\]\-]+})
|
71
|
-
@has_characters_in_group = true
|
72
|
-
@segment_re << ::Regexp.escape(@s.matched)
|
73
|
-
end
|
74
|
-
end
|
52
|
+
def emit_end
|
53
|
+
@re.append_end_anchor
|
54
|
+
break!
|
75
55
|
end
|
76
56
|
|
77
|
-
def
|
78
|
-
return unless @s.
|
57
|
+
def process_backslash
|
58
|
+
return unless @s.backslash?
|
79
59
|
|
80
|
-
|
81
|
-
if @segment_re.empty?
|
82
|
-
@parent_re << '.*'
|
83
|
-
else
|
84
|
-
process_slash_allow('.*')
|
85
|
-
end
|
86
|
-
end
|
87
|
-
process_slash('(?:.*/)?')
|
60
|
+
@re.append_escaped(@s.next_character) || unmatchable_rule!
|
88
61
|
end
|
89
62
|
|
90
|
-
def
|
91
|
-
return unless @s.
|
63
|
+
def process_star_end_after_slash
|
64
|
+
return true unless @s.star_end?
|
92
65
|
|
93
|
-
|
94
|
-
|
66
|
+
@re.append_many_non_dir
|
67
|
+
emit_end
|
95
68
|
end
|
96
69
|
|
97
|
-
def
|
98
|
-
return unless @s.
|
70
|
+
def process_slash
|
71
|
+
return unless @s.slash?
|
72
|
+
return dir_only! if @s.end?
|
73
|
+
return unmatchable_rule! if @s.slash?
|
99
74
|
|
100
|
-
|
101
|
-
|
75
|
+
emit_dir
|
76
|
+
process_star_end_after_slash
|
102
77
|
end
|
103
78
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
107
|
-
@segment_re.clear
|
108
|
-
@anchored = true
|
109
|
-
end
|
79
|
+
def process_two_stars # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
80
|
+
return unless @s.two_stars?
|
81
|
+
return break! if @s.end?
|
110
82
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
83
|
+
if @s.slash?
|
84
|
+
if @s.end?
|
85
|
+
@re.append_any_non_dir
|
86
|
+
dir_only!
|
87
|
+
elsif @s.slash?
|
88
|
+
unmatchable_rule!
|
89
|
+
else
|
90
|
+
if nothing_emitted?
|
91
|
+
never_anchored!
|
92
|
+
else
|
93
|
+
@re.append_any_dir
|
94
|
+
anchored!
|
95
|
+
end
|
96
|
+
process_star_end_after_slash
|
97
|
+
end
|
98
|
+
else
|
99
|
+
@re.append_any_non_dir
|
100
|
+
end
|
116
101
|
end
|
117
102
|
|
118
|
-
def
|
119
|
-
|
120
|
-
end
|
103
|
+
def process_character_class # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
104
|
+
return unless @s.character_class_start?
|
121
105
|
|
122
|
-
|
123
|
-
|
124
|
-
|
106
|
+
@re.append_character_class_open
|
107
|
+
@re.append_character_class_negation if @s.character_class_negation?
|
108
|
+
unmatchable_rule! if @s.character_class_end?
|
109
|
+
|
110
|
+
until @s.character_class_end?
|
111
|
+
next if process_backslash
|
112
|
+
next @re.append_character_class_dash if @s.dash?
|
113
|
+
next if @re.append_escaped(@s.character_class_literal)
|
125
114
|
|
126
|
-
|
127
|
-
|
115
|
+
unmatchable_rule!
|
116
|
+
end
|
117
|
+
|
118
|
+
@re.append_character_class_close
|
128
119
|
end
|
129
120
|
|
130
121
|
def process_end
|
131
|
-
|
122
|
+
blank! if nothing_emitted?
|
132
123
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
124
|
+
emit_end
|
125
|
+
end
|
126
|
+
|
127
|
+
def process_rule # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
128
|
+
anchored! if @s.slash?
|
129
|
+
|
130
|
+
catch :break do
|
131
|
+
loop do
|
132
|
+
next if process_backslash
|
133
|
+
next if process_slash
|
134
|
+
next if process_two_stars
|
135
|
+
next @re.append_any_non_dir if @s.star?
|
136
|
+
next @re.append_one_non_dir if @s.question_mark?
|
137
|
+
next if process_character_class
|
138
|
+
next if @re.append_escaped(@s.literal)
|
139
|
+
next if @re.append_escaped(@s.significant_whitespace)
|
140
|
+
|
141
|
+
process_end
|
138
142
|
end
|
139
143
|
end
|
140
|
-
@trailing_stars = true
|
141
144
|
end
|
142
145
|
|
143
|
-
def
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
146
|
+
def prefix # rubocop:disable Metrics/MethodLength
|
147
|
+
out = ::FastIgnore::GitignoreRuleRegexpBuilder.new
|
148
|
+
if @file_path
|
149
|
+
out.append_start_anchor.append(@file_path.escaped)
|
150
|
+
out.append_any_dir unless @anchored
|
151
|
+
else
|
152
|
+
if @anchored
|
153
|
+
out.append_start_anchor
|
154
|
+
else
|
155
|
+
out.append_start_dir_or_anchor
|
156
|
+
end
|
150
157
|
end
|
158
|
+
out
|
151
159
|
end
|
152
160
|
|
153
|
-
def
|
154
|
-
@
|
155
|
-
|
156
|
-
|
157
|
-
process_rule
|
161
|
+
def build_rule
|
162
|
+
@re.prepend(prefix)
|
163
|
+
::FastIgnore::Rule.new(@re.to_regexp, @negation, anchored_or_file_path, @dir_only)
|
164
|
+
end
|
158
165
|
|
159
|
-
|
166
|
+
def anchored_or_file_path
|
167
|
+
@anchored || @file_path
|
168
|
+
end
|
160
169
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
"\\A#{escaped_file_path}(?:.*/)?"
|
167
|
-
end
|
168
|
-
else
|
169
|
-
if @anchored
|
170
|
-
'\\A'
|
171
|
-
else
|
172
|
-
'(?:\\A|/)'
|
173
|
-
end
|
174
|
-
end
|
170
|
+
def build
|
171
|
+
catch :abort_build do
|
172
|
+
blank! if @s.hash?
|
173
|
+
negated! if @s.exclamation_mark?
|
174
|
+
process_rule
|
175
175
|
|
176
|
-
@
|
177
|
-
anchored_or_file_path = @anchored || @file_path
|
178
|
-
if @allow
|
179
|
-
if @file_path
|
180
|
-
allow_escaped_file_path = escaped_file_path.gsub(%r{(?<!\\)(?:\\\\)*/}) do |e|
|
181
|
-
@segments += 1
|
182
|
-
"#{e[0..-2]}(?:/"
|
183
|
-
end
|
184
|
-
|
185
|
-
prefix = if @anchored
|
186
|
-
"\\A#{allow_escaped_file_path}"
|
187
|
-
else
|
188
|
-
"\\A#{allow_escaped_file_path}(?:.*/)?"
|
189
|
-
end
|
190
|
-
end
|
191
|
-
@parent_re.prepend(prefix)
|
192
|
-
@parent_re << (')?' * @segments)
|
193
|
-
(@re << '(/|\\z)') unless @dir_only || @trailing_stars
|
194
|
-
rules = [
|
195
|
-
# Regexp::IGNORECASE = 1
|
196
|
-
::FastIgnore::Rule.new(::Regexp.new(@re, 1), @negation, anchored_or_file_path, @dir_only),
|
197
|
-
::FastIgnore::Rule.new(::Regexp.new(@parent_re, 1), true, anchored_or_file_path, true)
|
198
|
-
]
|
199
|
-
if @dir_only
|
200
|
-
(rules << ::FastIgnore::Rule.new(::Regexp.new((@re << '/.*'), 1), @negation, anchored_or_file_path, false))
|
201
|
-
end
|
202
|
-
rules
|
203
|
-
else
|
204
|
-
(@re << '\\z') unless @trailing_stars
|
176
|
+
@anchored = false if @anchored == :never
|
205
177
|
|
206
|
-
|
207
|
-
::FastIgnore::Rule.new(::Regexp.new(@re, 1), @negation, anchored_or_file_path, @dir_only)
|
208
|
-
end
|
178
|
+
build_rule
|
209
179
|
end
|
210
180
|
end
|
211
181
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FastIgnore
|
4
|
+
class GitignoreRuleRegexpBuilder < String
|
5
|
+
def to_regexp
|
6
|
+
# Regexp::IGNORECASE = 1
|
7
|
+
::Regexp.new(self, 1)
|
8
|
+
end
|
9
|
+
|
10
|
+
def append(value)
|
11
|
+
self.<<(value)
|
12
|
+
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def append_escaped(value)
|
17
|
+
return unless value
|
18
|
+
|
19
|
+
append(::Regexp.escape(value))
|
20
|
+
end
|
21
|
+
|
22
|
+
def append_dir
|
23
|
+
append('/')
|
24
|
+
end
|
25
|
+
|
26
|
+
def append_any_dir
|
27
|
+
append('(?:.*/)?')
|
28
|
+
end
|
29
|
+
|
30
|
+
def append_one_non_dir
|
31
|
+
append('[^/]')
|
32
|
+
end
|
33
|
+
|
34
|
+
def append_any_non_dir
|
35
|
+
append_one_non_dir
|
36
|
+
append('*')
|
37
|
+
end
|
38
|
+
|
39
|
+
def append_many_non_dir
|
40
|
+
append_one_non_dir
|
41
|
+
append('+')
|
42
|
+
end
|
43
|
+
|
44
|
+
def append_end_anchor
|
45
|
+
append('\\z')
|
46
|
+
end
|
47
|
+
|
48
|
+
def append_start_anchor
|
49
|
+
append('\\A')
|
50
|
+
end
|
51
|
+
|
52
|
+
def append_start_dir_or_anchor
|
53
|
+
append('(?:\\A|/)')
|
54
|
+
end
|
55
|
+
|
56
|
+
def append_end_dir_or_anchor
|
57
|
+
append('(?:/|\\z)')
|
58
|
+
end
|
59
|
+
|
60
|
+
def append_character_class_open
|
61
|
+
append('(?!/)[')
|
62
|
+
end
|
63
|
+
|
64
|
+
def append_character_class_negation
|
65
|
+
append('^')
|
66
|
+
end
|
67
|
+
|
68
|
+
def append_character_class_dash
|
69
|
+
append('-')
|
70
|
+
end
|
71
|
+
|
72
|
+
def append_character_class_close
|
73
|
+
append(']')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FastIgnore
|
4
|
+
class GitignoreRuleScanner < StringScanner
|
5
|
+
def character_class_end?
|
6
|
+
skip(/\]/)
|
7
|
+
end
|
8
|
+
|
9
|
+
def character_class_start?
|
10
|
+
skip(/\[/)
|
11
|
+
end
|
12
|
+
|
13
|
+
def character_class_negation?
|
14
|
+
skip(/\^|!/)
|
15
|
+
end
|
16
|
+
|
17
|
+
def end?
|
18
|
+
skip(/\s*\z/)
|
19
|
+
end
|
20
|
+
|
21
|
+
def slash?
|
22
|
+
skip(%r{/})
|
23
|
+
end
|
24
|
+
|
25
|
+
def backslash?
|
26
|
+
skip(/\\/)
|
27
|
+
end
|
28
|
+
|
29
|
+
def two_stars?
|
30
|
+
skip(/\*{2,}/)
|
31
|
+
end
|
32
|
+
|
33
|
+
def star?
|
34
|
+
skip(/\*/)
|
35
|
+
end
|
36
|
+
|
37
|
+
def next_character
|
38
|
+
matched if scan(/./)
|
39
|
+
end
|
40
|
+
|
41
|
+
def star_end?
|
42
|
+
skip(/\*\s*\z/)
|
43
|
+
end
|
44
|
+
|
45
|
+
def question_mark?
|
46
|
+
skip(/\?/)
|
47
|
+
end
|
48
|
+
|
49
|
+
def dash?
|
50
|
+
skip(/-/)
|
51
|
+
end
|
52
|
+
|
53
|
+
def character_class_literal
|
54
|
+
matched if scan(/[^\]\-\\]+/)
|
55
|
+
end
|
56
|
+
|
57
|
+
def literal
|
58
|
+
matched if scan(%r{[^*/?\[\\\s]+})
|
59
|
+
end
|
60
|
+
|
61
|
+
def significant_whitespace
|
62
|
+
matched if scan(/\s+(?!\s|\z)/)
|
63
|
+
end
|
64
|
+
|
65
|
+
def exclamation_mark?
|
66
|
+
skip(/!/)
|
67
|
+
end
|
68
|
+
|
69
|
+
def hash?
|
70
|
+
skip(/#/)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -11,6 +11,8 @@ class FastIgnore
|
|
11
11
|
default_global_gitignore_path
|
12
12
|
end
|
13
13
|
|
14
|
+
private
|
15
|
+
|
14
16
|
def gitconfig_gitignore_path(config_path)
|
15
17
|
return unless config_path
|
16
18
|
return unless ::File.exist?(config_path)
|
@@ -25,18 +27,24 @@ class FastIgnore
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def xdg_config_path
|
28
|
-
|
29
|
-
|
30
|
-
::File.expand_path('git/config', ::ENV['XDG_CONFIG_HOME'])
|
30
|
+
xdg_config_home? && ::File.expand_path('git/config', xdg_config_home)
|
31
31
|
end
|
32
32
|
|
33
33
|
def default_global_gitignore_path
|
34
|
-
if
|
35
|
-
::File.expand_path('git/ignore',
|
34
|
+
if xdg_config_home?
|
35
|
+
::File.expand_path('git/ignore', xdg_config_home)
|
36
36
|
else
|
37
37
|
::File.expand_path('~/.config/git/ignore')
|
38
38
|
end
|
39
39
|
end
|
40
|
+
|
41
|
+
def xdg_config_home
|
42
|
+
::ENV['XDG_CONFIG_HOME']
|
43
|
+
end
|
44
|
+
|
45
|
+
def xdg_config_home?
|
46
|
+
xdg_config_home && (not xdg_config_home.empty?)
|
47
|
+
end
|
40
48
|
end
|
41
49
|
end
|
42
50
|
end
|
data/lib/fast_ignore/rule.rb
CHANGED
@@ -7,6 +7,7 @@ class FastIgnore
|
|
7
7
|
undef :negation
|
8
8
|
|
9
9
|
attr_reader :component_rules
|
10
|
+
attr_reader :component_rules_count
|
10
11
|
|
11
12
|
attr_reader :dir_only
|
12
13
|
alias_method :dir_only?, :dir_only
|
@@ -17,8 +18,11 @@ class FastIgnore
|
|
17
18
|
|
18
19
|
def squash(rules)
|
19
20
|
# component rules is to improve the performance of repos with many .gitignore files. e.g. linux.
|
20
|
-
|
21
|
-
::FastIgnore::Rule.new(
|
21
|
+
component_rules = rules.flat_map(&:component_rules)
|
22
|
+
::FastIgnore::Rule.new(
|
23
|
+
::Regexp.union(component_rules.map(&:rule)).freeze,
|
24
|
+
@negation, @anchored, @dir_only, component_rules
|
25
|
+
)
|
22
26
|
end
|
23
27
|
|
24
28
|
def initialize(rule, negation, anchored, dir_only, component_rules = self) # rubocop:disable Metrics/MethodLength
|
@@ -27,6 +31,7 @@ class FastIgnore
|
|
27
31
|
@dir_only = dir_only
|
28
32
|
@negation = negation
|
29
33
|
@component_rules = component_rules
|
34
|
+
@component_rules_count = component_rules == self ? 1 : component_rules.length
|
30
35
|
|
31
36
|
@squashable_type = if anchored && negation
|
32
37
|
1
|
@@ -8,64 +8,33 @@ class FastIgnore
|
|
8
8
|
# :nocov:
|
9
9
|
|
10
10
|
def build(rule, allow, expand_path_with, file_root)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
gitignore_rules(rule, allow, expand_path_with, file_root)
|
11
|
+
if rule.delete_prefix!('#!:')
|
12
|
+
shebang_rules(rule, allow, file_root)
|
13
|
+
else
|
14
|
+
gitignore_rules(rule, allow, file_root, expand_path_with)
|
15
|
+
end
|
17
16
|
end
|
18
17
|
|
19
18
|
private
|
20
19
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def remove_shebang(rule)
|
27
|
-
return unless rule.delete_prefix!('#!:')
|
28
|
-
|
29
|
-
rule.strip!
|
30
|
-
|
31
|
-
true
|
32
|
-
end
|
33
|
-
|
34
|
-
def shebang_rules(rule, allow)
|
35
|
-
rule = ::FastIgnore::ShebangRule.new(/\A#!.*\b#{::Regexp.escape(rule)}\b/i, allow)
|
20
|
+
def shebang_rules(shebang, allow, file_root)
|
21
|
+
shebang.strip!
|
22
|
+
pattern = /\A#!.*\b#{::Regexp.escape(shebang)}\b/i
|
23
|
+
rule = ::FastIgnore::ShebangRule.new(pattern, allow, file_root&.shebang_path_pattern)
|
36
24
|
return rule unless allow
|
37
25
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
rule.empty? || rule.start_with?('#')
|
43
|
-
end
|
44
|
-
|
45
|
-
def gitignore_rules(rule, allow, expand_path_with, file_root)
|
46
|
-
dir_only = extract_dir_only(rule)
|
47
|
-
negation = extract_negation(rule, allow)
|
48
|
-
|
49
|
-
expand_rule_path(rule, expand_path_with) if expand_path_with
|
50
|
-
|
51
|
-
::FastIgnore::GitignoreRuleBuilder.new(rule, negation, dir_only, file_root, allow).build
|
52
|
-
end
|
53
|
-
|
54
|
-
def extract_dir_only(rule)
|
55
|
-
rule.delete_suffix!('/')
|
56
|
-
end
|
57
|
-
|
58
|
-
def extract_negation(rule, allow)
|
59
|
-
return allow unless rule.delete_prefix!('!')
|
60
|
-
|
61
|
-
not allow
|
26
|
+
rules = gitignore_rules('*/'.dup, allow, file_root)
|
27
|
+
rules.pop # don't want the include all children one.
|
28
|
+
rules << rule
|
29
|
+
rules
|
62
30
|
end
|
63
31
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
32
|
+
def gitignore_rules(rule, allow, file_root, expand_path_with = nil)
|
33
|
+
if allow
|
34
|
+
::FastIgnore::GitignoreIncludeRuleBuilder.new(rule, file_root, expand_path_with).build
|
35
|
+
else
|
36
|
+
::FastIgnore::GitignoreRuleBuilder.new(rule, file_root).build
|
37
|
+
end
|
69
38
|
end
|
70
39
|
end
|
71
40
|
end
|
data/lib/fast_ignore/rule_set.rb
CHANGED
@@ -6,9 +6,9 @@ class FastIgnore
|
|
6
6
|
alias_method :gitignore?, :gitignore
|
7
7
|
undef :gitignore
|
8
8
|
|
9
|
-
def initialize(rules, allow, gitignore)
|
10
|
-
@dir_rules = squash_rules(rules.reject(&:file_only?)).freeze
|
11
|
-
@file_rules = squash_rules(rules.reject(&:dir_only?)).freeze
|
9
|
+
def initialize(rules, allow, gitignore, squash = true)
|
10
|
+
@dir_rules = (squash ? squash_rules(rules.reject(&:file_only?)) : rules.reject(&:file_only?)).freeze
|
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
14
|
@allowed_recursive = { '.' => true }
|
@@ -42,8 +42,16 @@ class FastIgnore
|
|
42
42
|
not @allow
|
43
43
|
end
|
44
44
|
|
45
|
-
def squash_rules(rules)
|
46
|
-
|
45
|
+
def squash_rules(rules) # rubocop:disable Metrics/MethodLength
|
46
|
+
running_component_rule_size = rules.first&.component_rules_count || 0
|
47
|
+
rules.chunk_while do |a, b|
|
48
|
+
# a.squashable_type == b.squashable_type
|
49
|
+
next true if a.squashable_type == b.squashable_type &&
|
50
|
+
(running_component_rule_size + b.component_rules_count <= 40)
|
51
|
+
|
52
|
+
running_component_rule_size = b.component_rules_count
|
53
|
+
false
|
54
|
+
end.map do |chunk| # rubocop:disable Style/MultilineBlockChain
|
47
55
|
first = chunk.first
|
48
56
|
next first if chunk.length == 1
|
49
57
|
|
@@ -61,6 +69,8 @@ class FastIgnore
|
|
61
69
|
|
62
70
|
protected
|
63
71
|
|
64
|
-
attr_reader :dir_rules
|
72
|
+
attr_reader :dir_rules
|
73
|
+
attr_reader :file_rules
|
74
|
+
attr_reader :has_shebang_rules
|
65
75
|
end
|
66
76
|
end
|
@@ -35,13 +35,16 @@ class FastIgnore
|
|
35
35
|
@array.all? { |r| r.allowed_unrecursive?(relative_path, dir, full_path, filename, nil) }
|
36
36
|
end
|
37
37
|
|
38
|
-
def append_subdir_gitignore(relative_path:, check_exists: true)
|
39
|
-
new_gitignore = build_set_from_file(relative_path, gitignore: true, check_exists: check_exists)
|
40
|
-
return if !new_gitignore || new_gitignore.empty?
|
41
|
-
|
38
|
+
def append_subdir_gitignore(relative_path:, check_exists: true) # rubocop:disable Metrics/MethodLength
|
42
39
|
if @gitignore_rule_set
|
40
|
+
new_gitignore = build_set_from_file(relative_path, gitignore: true, check_exists: check_exists, squash: false)
|
41
|
+
return if !new_gitignore || new_gitignore.empty?
|
42
|
+
|
43
43
|
@gitignore_rule_set << new_gitignore
|
44
44
|
else
|
45
|
+
new_gitignore = build_set_from_file(relative_path, gitignore: true, check_exists: check_exists)
|
46
|
+
return if !new_gitignore || new_gitignore.empty?
|
47
|
+
|
45
48
|
@array << new_gitignore
|
46
49
|
@gitignore_rule_set = new_gitignore
|
47
50
|
@array.sort_by!(&:weight) && @array.freeze
|
@@ -75,23 +78,21 @@ class FastIgnore
|
|
75
78
|
build_rule_set(::File.readlines(path), false, gitignore: true)
|
76
79
|
end
|
77
80
|
|
78
|
-
def build_rule_set(rules, allow, expand_path_with: nil, file_root: nil, gitignore: false)
|
81
|
+
def build_rule_set(rules, allow, expand_path_with: nil, file_root: nil, gitignore: false, squash: true) # rubocop:disable Metrics/ParameterLists
|
79
82
|
rules = rules.flat_map do |rule|
|
80
83
|
::FastIgnore::RuleBuilder.build(rule, allow, expand_path_with, file_root)
|
81
84
|
end
|
82
85
|
|
83
|
-
::FastIgnore::RuleSet.new(rules, allow, gitignore)
|
86
|
+
::FastIgnore::RuleSet.new(rules, allow, gitignore, squash)
|
84
87
|
end
|
85
88
|
|
86
|
-
def build_set_from_file(filename, allow: false,
|
89
|
+
def build_set_from_file(filename, allow: false, gitignore: false, check_exists: false, squash: true)
|
87
90
|
filename = ::File.expand_path(filename, @project_root)
|
88
91
|
return if check_exists && !::File.exist?(filename)
|
89
|
-
|
90
|
-
raise ::FastIgnore::Error, "#{filename} is not within #{@project_root}"
|
91
|
-
end
|
92
|
+
raise ::FastIgnore::Error, "#{filename} is not within #{@project_root}" unless filename.start_with?(@project_root)
|
92
93
|
|
93
|
-
file_root
|
94
|
-
build_rule_set(::File.readlines(filename), allow, file_root: file_root, gitignore: gitignore)
|
94
|
+
file_root = ::FastIgnore::FileRoot.build(filename, @project_root)
|
95
|
+
build_rule_set(::File.readlines(filename), allow, file_root: file_root, gitignore: gitignore, squash: squash)
|
95
96
|
end
|
96
97
|
|
97
98
|
def append_sets_from_files(files, allow: false)
|
@@ -8,17 +8,24 @@ class FastIgnore
|
|
8
8
|
|
9
9
|
attr_reader :rule
|
10
10
|
|
11
|
+
attr_reader :file_path_pattern
|
12
|
+
|
11
13
|
attr_reader :squashable_type
|
12
14
|
|
13
15
|
def squash(rules)
|
14
|
-
::FastIgnore::ShebangRule.new(::Regexp.union(rules.map(&:rule)).freeze, negation
|
16
|
+
::FastIgnore::ShebangRule.new(::Regexp.union(rules.map(&:rule)).freeze, negation?, file_path_pattern)
|
17
|
+
end
|
18
|
+
|
19
|
+
def component_rules_count
|
20
|
+
1
|
15
21
|
end
|
16
22
|
|
17
|
-
def initialize(rule, negation)
|
23
|
+
def initialize(rule, negation, file_path_pattern)
|
18
24
|
@rule = rule
|
19
25
|
@negation = negation
|
26
|
+
@file_path_pattern = file_path_pattern
|
20
27
|
|
21
|
-
@squashable_type = negation ?
|
28
|
+
@squashable_type = (negation ? 13 : 12) + file_path_pattern.object_id
|
22
29
|
|
23
30
|
freeze
|
24
31
|
end
|
@@ -33,12 +40,15 @@ class FastIgnore
|
|
33
40
|
|
34
41
|
# :nocov:
|
35
42
|
def inspect
|
36
|
-
|
43
|
+
allow_fragment = 'allow ' if @negation
|
44
|
+
in_fragment = " in #{@file_path_pattern}" if @file_path_pattern
|
45
|
+
"#<ShebangRule #{allow_fragment}#!:#{@rule.to_s[15..-4]}#{in_fragment}>"
|
37
46
|
end
|
38
47
|
# :nocov:
|
39
48
|
|
40
|
-
def match?(
|
49
|
+
def match?(path, full_path, filename, content)
|
41
50
|
return false if filename.include?('.')
|
51
|
+
return false unless (not @file_path_pattern) || @file_path_pattern.match?(path)
|
42
52
|
|
43
53
|
(content || first_line(full_path))&.match?(@rule)
|
44
54
|
end
|
data/lib/fast_ignore/version.rb
CHANGED
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.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dana Sherson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -148,7 +148,11 @@ files:
|
|
148
148
|
- README.md
|
149
149
|
- lib/fast_ignore.rb
|
150
150
|
- lib/fast_ignore/backports.rb
|
151
|
+
- lib/fast_ignore/file_root.rb
|
152
|
+
- lib/fast_ignore/gitignore_include_rule_builder.rb
|
151
153
|
- lib/fast_ignore/gitignore_rule_builder.rb
|
154
|
+
- lib/fast_ignore/gitignore_rule_regexp_builder.rb
|
155
|
+
- lib/fast_ignore/gitignore_rule_scanner.rb
|
152
156
|
- lib/fast_ignore/global_gitignore.rb
|
153
157
|
- lib/fast_ignore/rule.rb
|
154
158
|
- lib/fast_ignore/rule_builder.rb
|