fast_ignore 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f467113b0ee03faf028758ac176c59b3e51465684f8ffe144d432ba75133c10f
4
- data.tar.gz: 2f6f0ce0babec00977a55595baa7ec806d7a9a3f1fec86df46f29475b41238cc
3
+ metadata.gz: 45d98b9329a3cf9935ad9bda95f89ffcea7776c19acde46b4f758cf03bad654b
4
+ data.tar.gz: ec33228d9d25f5e82a8deed045c5dc2f1885e7fcf05779e079ab4a1460f981f2
5
5
  SHA512:
6
- metadata.gz: 3a7698e54ab0897b6b50c8638d41f3243bd3ba4ced559a5c7e01f6d365b6fb875b906a1924e96641572815748479eb793dfabc91da6f3bec62115f98ff39a392
7
- data.tar.gz: 7e8b85ff838d04d1c16cd3eca5a480d6948342a07667760782840b0e36a706c02db2bb4e09e3370b3ccc1cdc2f38f9e893707c9f2128652a8a4aae4cdb8dd3d0
6
+ metadata.gz: 6d03cc0337b2b5bb4b7f90adaeedf645e6fcc01a299c873cbdec743f2d13a7a6820c2269a4193d1723907e3f7de00ec4aa76d90a52efba9c022462edb5e129fe
7
+ data.tar.gz: 9771665b209531cd704c8603b9791755e054f5a3f72214ef43d23dfb403263f5c00f5ebe9be27b94a63503c85e35dc3d6c4293822cf93bd8ef4dc60b6b2b7715
@@ -11,18 +11,21 @@ enumerables
11
11
  env
12
12
  errno
13
13
  esque
14
+ excludesfile
14
15
  extensionless
15
16
  fancyignore
16
17
  filesystem
17
18
  fnmatch
18
19
  frotz
19
20
  gemfile
21
+ gitconfig
20
22
  github
21
23
  gitignore
22
24
  gitkeep
23
25
  hashbang
24
26
  includefile
25
27
  janky
28
+ jruby
26
29
  klass
27
30
  llo
28
31
  memoize
@@ -43,6 +46,7 @@ rvm
43
46
  sherson
44
47
  simplecov
45
48
  stdin
49
+ subdir
46
50
  substring
47
51
  sudo
48
52
  symlinks
@@ -53,4 +57,6 @@ unrecursive
53
57
  upcase
54
58
  usr
55
59
  webpack
60
+ xconfig
61
+ xdg
56
62
  zsh
data/.travis.yml CHANGED
@@ -8,3 +8,4 @@ rvm:
8
8
  - 2.5
9
9
  - 2.6
10
10
  - 2.7
11
+ - jruby
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ # v0.12.0
2
+ - Reads all relevant gitignore files (nested .gitignore files, global .gitignore referred to in .gitconfig, and .git/info/exclude)
3
+
1
4
  # v0.11.0
2
5
  - major performance improvement (use regexp rather than fnmatch)
3
6
  - optionally pass directory: and content: into allowed? if these are already loaded.
data/README.md CHANGED
@@ -14,12 +14,13 @@ FastIgnore.new(relative: true).sort == `git ls-files`.split("\n").sort
14
14
  ## Features
15
15
 
16
16
  - 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
17
+ - Supports ruby 2.4 - 2.7 & jruby
18
18
  - supports all gitignore rule patterns
19
19
  - doesn't require git to be installed
20
20
  - supports a gitignore-esque "include" patterns. (`include_rules:`/`include_files:`)
21
21
  - supports an expansion of include patterns, matching expanded paths (`argv_rules:`)
22
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
23
24
 
24
25
  ## Installation
25
26
 
@@ -98,10 +99,12 @@ FastIgnore.new(follow_symlinks: true).to_a
98
99
 
99
100
  By default, root is PWD (the current working directory)
100
101
  This directory is used for:
101
- - looking for .gitignore files
102
- - as the root directory for array rules starting with `/` or ending with `/**`
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 `/**`
103
105
  - and the path that relative is relative to
104
- - which files get checked
106
+ - the ancestor of which files get checked
107
+ - the ancestor of all include_files and ignore_files
105
108
 
106
109
  To use a different directory:
107
110
  ```ruby
@@ -111,7 +114,8 @@ FastIgnore.new(root: '../relative/path/to/root').to_a
111
114
 
112
115
  ### `gitignore:`
113
116
 
114
- By default, the .gitignore file in root directory is loaded.
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.
118
+
115
119
  To not do this use
116
120
  ```ruby
117
121
  FastIgnore.new(gitignore: false).to_a
@@ -124,7 +128,7 @@ FastIgnore.new(gitignore: true).to_a
124
128
 
125
129
  If the gitignore file is somewhere else
126
130
  ```ruby
127
- FastIgnore.new(ignore_file: '/absolute/path/to/.gitignore', gitignore: false).to_a
131
+ FastIgnore.new(ignore_file: '/absolute/path/to/.gitignore').to_a
128
132
  ```
129
133
  Note that the location of the .gitignore file will affect rules beginning with `/` or ending in `/**`
130
134
 
@@ -133,7 +137,7 @@ You can specify other gitignore-style files to ignore as well.
133
137
  Missing files will raise an `Errno::ENOENT` error.
134
138
 
135
139
  ```ruby
136
- FastIgnore.new(ignore_files: '/absolute/path/to/my/ignore/file').to_a
140
+ FastIgnore.new(ignore_files: 'relative/path/to/my/ignore/file').to_a
137
141
  FastIgnore.new(ignore_files: ['/absolute/path/to/my/ignore/file', '/and/another']).to_a
138
142
  ```
139
143
 
@@ -235,8 +239,6 @@ This is not required, and if FastIgnore does have to go to the filesystem for th
235
239
 
236
240
 
237
241
  ## Known issues
238
- - Doesn't take into account project excludes in `.git/info/exclude`
239
- - Doesn't take into account globally ignored files in `git config core.excludesFile`.
240
242
  - Doesn't know what to do if you change the current working directory inside the `FastIgnore#each` block.
241
243
  So don't do that.
242
244
 
data/Rakefile CHANGED
@@ -11,4 +11,8 @@ RSpec::Core::RakeTask.new(:spec)
11
11
  Spellr::RakeTask.generate_task
12
12
  Leftovers::RakeTask.generate_task
13
13
 
14
- task default: [:spec, :rubocop, :spellr, :leftovers]
14
+ if RUBY_PLATFORM == 'java'
15
+ task default: :spec
16
+ else
17
+ task default: [:spec, :rubocop, :spellr, :leftovers]
18
+ end
data/bin/ls_seconds ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby --disable-all
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/fast_ignore'
5
+
6
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
7
+ new_a = FastIgnore.new(relative: true, argv_rules: ARGV).to_a
8
+ t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
9
+ puts "new #{new_a.count}: #{t2 - t1}"
10
+ fi = FastIgnore.new(relative: true, argv_rules: ARGV)
11
+ fi.to_a
12
+ t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
13
+ cached_a = fi.to_a
14
+ t4 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
15
+ puts "cached #{cached_a.count}: #{t4 - t3}"
16
+ t5 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
17
+ git_a = `git ls-files`.split("\n")
18
+ t6 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
+ puts "git ls-files #{git_a.count}: #{t6 - t5}"
20
+
21
+ exit 1 unless new_a.length == cached_a.length && new_a.length == git_a.length
data/lib/fast_ignore.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative './fast_ignore/backports'
4
4
 
5
+ require 'set'
5
6
  require_relative './fast_ignore/rule_set_builder'
6
7
  require_relative './fast_ignore/rule_builder'
7
8
  require_relative './fast_ignore/rule_set'
@@ -19,11 +20,13 @@ class FastIgnore
19
20
  using ::FastIgnore::Backports::DirEachChild if defined?(::FastIgnore::Backports::DirEachChild)
20
21
  # :nocov:
21
22
 
22
- def initialize(relative: false, root: nil, follow_symlinks: false, **rule_set_builder_args)
23
+ def initialize(relative: false, root: nil, gitignore: :auto, follow_symlinks: false, **rule_set_builder_args)
23
24
  @relative = relative
24
25
  @follow_symlinks = follow_symlinks
26
+ @gitignore_enabled = gitignore
27
+ @loaded_gitignore_files = Set[''] if gitignore
25
28
  @root = "#{::File.expand_path(root.to_s, Dir.pwd)}/"
26
- @rule_sets = ::FastIgnore::RuleSetBuilder.build(root: @root, **rule_set_builder_args)
29
+ @rule_sets = ::FastIgnore::RuleSetBuilder.build(root: @root, gitignore: gitignore, **rule_set_builder_args)
27
30
 
28
31
  freeze
29
32
  end
@@ -43,6 +46,8 @@ class FastIgnore
43
46
  return false if directory.nil? ? directory?(full_path) : directory
44
47
 
45
48
  relative_path = full_path.delete_prefix(@root)
49
+ load_gitignore_recursive(relative_path) if @gitignore_enabled
50
+
46
51
  filename = ::File.basename(relative_path)
47
52
 
48
53
  @rule_sets.all? { |r| r.allowed_recursive?(relative_path, false, full_path, filename, content) }
@@ -61,8 +66,30 @@ class FastIgnore
61
66
  end
62
67
  end
63
68
 
64
- def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
65
- ::Dir.each_child(parent_full_path) do |filename|
69
+ def load_gitignore_recursive(path)
70
+ paths = []
71
+ while (path = ::File.dirname(path)) != '.'
72
+ paths << path
73
+ end
74
+
75
+ paths.reverse_each(&method(:load_gitignore))
76
+ end
77
+
78
+ def load_gitignore(parent_path, soft: true)
79
+ return if @loaded_gitignore_files.include?(parent_path)
80
+
81
+ ::FastIgnore::RuleSetBuilder.append_gitignore(
82
+ @rule_sets, project_root: @root, relative_path: parent_path + '.gitignore', soft: soft
83
+ )
84
+ @loaded_gitignore_files << parent_path
85
+ end
86
+
87
+ def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
88
+ children = ::Dir.children(parent_full_path)
89
+
90
+ load_gitignore(parent_relative_path, soft: false) if @gitignore_enabled && children.include?('.gitignore')
91
+
92
+ children.each do |filename|
66
93
  begin
67
94
  full_path = parent_full_path + filename
68
95
  relative_path = parent_relative_path + filename
@@ -6,12 +6,8 @@ class FastIgnore
6
6
  unless ruby_major.to_i > 2 || ruby_major.to_i == 2 && ruby_minor.to_i > 5
7
7
  module DirEachChild
8
8
  refine ::Dir.singleton_class do
9
- def each_child(path, &block)
10
- Dir.entries(path).each do |entry|
11
- next if entry == '.' || entry == '..'
12
-
13
- block.call entry
14
- end
9
+ def children(path)
10
+ Dir.entries(path) - ['.', '..']
15
11
  end
16
12
  end
17
13
  end
@@ -2,7 +2,11 @@
2
2
 
3
3
  class FastIgnore
4
4
  class RuleSet
5
- def initialize(rules, allow)
5
+ attr_reader :gitignore
6
+ alias_method :gitignore?, :gitignore
7
+ undef :gitignore
8
+
9
+ def initialize(rules, allow, gitignore)
6
10
  @dir_rules = squash_rules(rules.reject(&:file_only?)).freeze
7
11
  @file_rules = squash_rules(rules.reject(&:dir_only?)).freeze
8
12
  @any_not_anchored = rules.any?(&:unanchored?)
@@ -10,8 +14,18 @@ class FastIgnore
10
14
 
11
15
  @allowed_recursive = { '.' => true }
12
16
  @allow = allow
17
+ @gitignore = gitignore
18
+
19
+ freeze unless gitignore?
20
+ end
13
21
 
14
- freeze
22
+ def <<(other)
23
+ return unless other
24
+
25
+ @any_not_anchored ||= other.any_not_anchored
26
+ @has_shebang_rules ||= other.has_shebang_rules
27
+ @dir_rules += other.dir_rules
28
+ @file_rules += other.file_rules
15
29
  end
16
30
 
17
31
  def allowed_recursive?(relative_path, dir, full_path, filename, content = nil)
@@ -47,5 +61,9 @@ class FastIgnore
47
61
  def empty?
48
62
  @dir_rules.empty? && @file_rules.empty?
49
63
  end
64
+
65
+ protected
66
+
67
+ attr_reader :dir_rules, :file_rules, :any_not_anchored, :has_shebang_rules
50
68
  end
51
69
  end
@@ -27,6 +27,19 @@ class FastIgnore
27
27
  ]
28
28
  end
29
29
 
30
+ def append_gitignore(rule_sets, project_root:, relative_path:, soft: true)
31
+ new_gitignore = from_file(relative_path, project_root: project_root, gitignore: true, soft: soft)
32
+ return unless new_gitignore
33
+
34
+ base_gitignore = rule_sets.find(&:gitignore?)
35
+ if base_gitignore
36
+ base_gitignore << new_gitignore
37
+ else
38
+ rule_sets << new_gitignore
39
+ prepare(rule_sets)
40
+ end
41
+ end
42
+
30
43
  private
31
44
 
32
45
  def prepare(rule_sets)
@@ -36,12 +49,15 @@ class FastIgnore
36
49
  rule_sets
37
50
  end
38
51
 
39
- def from_file(filename, project_root:, allow: false)
52
+ def from_file(filename, project_root:, allow: false, file_root: nil, gitignore: false, soft: false) # rubocop:disable Metrics/ParameterLists
40
53
  filename = ::File.expand_path(filename, project_root)
41
- raise FastIgnore::Error, "#{filename} is not within #{project_root}" unless filename.start_with?(project_root)
54
+ return if soft && !::File.exist?(filename)
55
+ unless file_root || filename.start_with?(project_root)
56
+ raise FastIgnore::Error, "#{filename} is not within #{project_root}"
57
+ end
42
58
 
43
- file_root = "#{::File.dirname(filename)}/".delete_prefix(project_root)
44
- build_rule_set(::File.readlines(filename), allow, file_root: file_root)
59
+ file_root ||= "#{::File.dirname(filename)}/".delete_prefix(project_root)
60
+ build_rule_set(::File.readlines(filename), allow, file_root: file_root, gitignore: gitignore)
45
61
  end
46
62
 
47
63
  def from_files(files, project_root:, allow: false)
@@ -51,12 +67,38 @@ class FastIgnore
51
67
  end
52
68
 
53
69
  def from_gitignore_arg(gitignore, project_root:)
54
- default_path = ::File.join(project_root, '.gitignore')
55
- case gitignore
56
- when :auto
57
- from_file(default_path, project_root: project_root) if ::File.exist?(default_path)
58
- when true
59
- from_file(default_path, project_root: project_root)
70
+ return unless gitignore
71
+
72
+ gi = ::FastIgnore::RuleSet.new([], false, true)
73
+ gi << from_gitignore_file(gitconfig_global_gitignore_path || default_global_gitignore_path)
74
+ gi << from_gitignore_file(::File.join(project_root, '.git/info/exclude'))
75
+ gi << from_gitignore_file(::File.join(project_root, '.gitignore'), soft: gitignore == :auto)
76
+ gi
77
+ end
78
+
79
+ def from_gitignore_file(path, soft: true)
80
+ return if soft && !::File.exist?(path)
81
+
82
+ build_rule_set(::File.readlines(path), false, file_root: '', gitignore: true)
83
+ end
84
+
85
+ def gitconfig_global_gitignore_path
86
+ config_path = ::File.expand_path('~/.gitconfig')
87
+ return unless ::File.exist?(config_path)
88
+
89
+ ignore_path = ::File.readlines(config_path).find { |l| l.start_with?("\texcludesfile = ") }
90
+ return unless ignore_path
91
+
92
+ ignore_path.delete_prefix!("\texcludesfile = ")
93
+ ignore_path.strip!
94
+ ::File.expand_path(ignore_path)
95
+ end
96
+
97
+ def default_global_gitignore_path
98
+ if ENV['XDG_CONFIG_HOME'] && !ENV['XDG_CONFIG_HOME'].empty?
99
+ ::File.expand_path('git/ignore', ENV['XDG_CONFIG_HOME'])
100
+ else
101
+ ::File.expand_path('~/.config/git/ignore')
60
102
  end
61
103
  end
62
104
 
@@ -72,12 +114,12 @@ class FastIgnore
72
114
  build_rule_set(rules, allow, expand_path: expand_path)
73
115
  end
74
116
 
75
- def build_rule_set(rules, allow, expand_path: false, file_root: nil)
117
+ def build_rule_set(rules, allow, expand_path: false, file_root: nil, gitignore: false)
76
118
  rules = rules.flat_map do |rule|
77
119
  ::FastIgnore::RuleBuilder.build(rule, allow, expand_path, file_root)
78
120
  end
79
121
 
80
- ::FastIgnore::RuleSet.new(rules, allow).freeze
122
+ ::FastIgnore::RuleSet.new(rules, allow, gitignore)
81
123
  end
82
124
  end
83
125
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FastIgnore
4
- VERSION = '0.11.0'
4
+ VERSION = '0.12.0'
5
5
  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.11.0
4
+ version: 0.12.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-05-02 00:00:00.000000000 Z
11
+ date: 2020-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -160,6 +160,7 @@ files:
160
160
  - Rakefile
161
161
  - bin/console
162
162
  - bin/ls
163
+ - bin/ls_seconds
163
164
  - bin/setup
164
165
  - bin/time
165
166
  - fast_ignore.gemspec