fast_ignore 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|