fast_ignore 0.11.0 → 0.12.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 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