rabbitt-githooks 1.2.7
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 +7 -0
- data/.gitignore +34 -0
- data/.rubocop.yml +73 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +339 -0
- data/README.md +4 -0
- data/Rakefile +19 -0
- data/bin/githooks +15 -0
- data/bin/githooks-runner +17 -0
- data/hooks/commit-messages.rb +29 -0
- data/hooks/formatting.rb +53 -0
- data/lib/githooks.rb +89 -0
- data/lib/githooks/action.rb +150 -0
- data/lib/githooks/cli.rb +93 -0
- data/lib/githooks/commands/config.rb +107 -0
- data/lib/githooks/core_ext.rb +23 -0
- data/lib/githooks/core_ext/array.rb +3 -0
- data/lib/githooks/core_ext/array/extract_options.rb +5 -0
- data/lib/githooks/core_ext/array/min_max.rb +37 -0
- data/lib/githooks/core_ext/array/select_with_index.rb +13 -0
- data/lib/githooks/core_ext/numbers.rb +1 -0
- data/lib/githooks/core_ext/numbers/infinity.rb +19 -0
- data/lib/githooks/core_ext/pathname.rb +27 -0
- data/lib/githooks/core_ext/process.rb +7 -0
- data/lib/githooks/core_ext/string.rb +3 -0
- data/lib/githooks/core_ext/string/git_option_path_split.rb +6 -0
- data/lib/githooks/core_ext/string/inflections.rb +67 -0
- data/lib/githooks/core_ext/string/strip_empty_lines.rb +9 -0
- data/lib/githooks/error.rb +10 -0
- data/lib/githooks/hook.rb +159 -0
- data/lib/githooks/repository.rb +152 -0
- data/lib/githooks/repository/config.rb +170 -0
- data/lib/githooks/repository/diff_index_entry.rb +80 -0
- data/lib/githooks/repository/file.rb +125 -0
- data/lib/githooks/repository/limiter.rb +55 -0
- data/lib/githooks/runner.rb +317 -0
- data/lib/githooks/section.rb +98 -0
- data/lib/githooks/system_utils.rb +109 -0
- data/lib/githooks/terminal_colors.rb +63 -0
- data/lib/githooks/version.rb +22 -0
- data/rabbitt-githooks.gemspec +49 -0
- data/thoughts.txt +56 -0
- metadata +175 -0
@@ -0,0 +1,152 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright (C) 2013 Carl P. Corliss
|
4
|
+
|
5
|
+
This program is free software; you can redistribute it and/or modify
|
6
|
+
it under the terms of the GNU General Public License as published by
|
7
|
+
the Free Software Foundation; either version 2 of the License, or
|
8
|
+
(at your option) any later version.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU General Public License along
|
16
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
17
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
18
|
+
=end
|
19
|
+
|
20
|
+
require 'ostruct'
|
21
|
+
require 'singleton'
|
22
|
+
require 'open3'
|
23
|
+
|
24
|
+
module GitHooks
|
25
|
+
class Repository
|
26
|
+
autoload :Config, 'githooks/repository/config'
|
27
|
+
autoload :File, 'githooks/repository/file'
|
28
|
+
autoload :Limiter, 'githooks/repository/limiter'
|
29
|
+
autoload :DiffIndexEntry, 'githooks/repository/diff_index_entry'
|
30
|
+
|
31
|
+
CHANGE_TYPE_SYMBOLS = {
|
32
|
+
added: 'A', copied: 'C',
|
33
|
+
deleted: 'D', modified: 'M',
|
34
|
+
renamed: 'R', retyped: 'T',
|
35
|
+
unknown: 'U', unmerged: 'X',
|
36
|
+
broken: 'B', untracked: '?',
|
37
|
+
any: '*'
|
38
|
+
}.freeze unless defined? CHANGE_TYPE_SYMBOLS
|
39
|
+
|
40
|
+
CHANGE_TYPES = CHANGE_TYPE_SYMBOLS.invert.freeze unless defined? CHANGE_TYPES
|
41
|
+
|
42
|
+
DEFAULT_DIFF_INDEX_OPTIONS = { staged: true, ref: 'HEAD' } unless defined? DEFAULT_DIFF_INDEX_OPTIONS
|
43
|
+
|
44
|
+
@__instance__ = {}
|
45
|
+
@__mutex__ = Mutex.new
|
46
|
+
def self.instance(path = Dir.getwd)
|
47
|
+
path = Pathname.new(path).realpath
|
48
|
+
strpath = path.to_s
|
49
|
+
return @__instance__[strpath] if @__instance__[strpath]
|
50
|
+
|
51
|
+
@__mutex__.synchronize do
|
52
|
+
return @__instance__[strpath] if @__instance__[strpath]
|
53
|
+
@__instance__[strpath] = new(path)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.method_missing(method, *args, &block)
|
58
|
+
return super unless instance.public_methods.include? method
|
59
|
+
instance.public_send(method, *args, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :root_path
|
63
|
+
|
64
|
+
def initialize(path = Dir.getwd)
|
65
|
+
@root_path = get_root_path(path)
|
66
|
+
end
|
67
|
+
protected :initialize
|
68
|
+
|
69
|
+
def config
|
70
|
+
@config ||= Repository::Config.new(root_path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def git_command(*args)
|
74
|
+
git.execute(*args.flatten)
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_root_path(path)
|
78
|
+
git_command('rev-parse', '--show-toplevel', path: path).tap do |result|
|
79
|
+
unless result.status.success? && result.output !~ /not a git repository/i
|
80
|
+
fail Error::NotAGitRepo, "Unable to find a valid git repo in #{path}"
|
81
|
+
end
|
82
|
+
end.output.strip
|
83
|
+
end
|
84
|
+
|
85
|
+
def stash
|
86
|
+
git_command(%w( stash -q --keep-index -a)).status.success?
|
87
|
+
end
|
88
|
+
|
89
|
+
def unstash
|
90
|
+
git_command(%w(stash pop -q)).status.success?
|
91
|
+
end
|
92
|
+
|
93
|
+
def manifest(options = {})
|
94
|
+
ref = options.delete(:ref) || 'HEAD'
|
95
|
+
unstaged = options.delete(:unstaged)
|
96
|
+
untracked = options.delete(:untracked)
|
97
|
+
|
98
|
+
return staged_manifest(ref: ref) unless unstaged || untracked
|
99
|
+
|
100
|
+
[].tap do |files|
|
101
|
+
files.push(*unstaged_manifest(ref: ref)) if unstaged
|
102
|
+
files.push(*untracked_manifest) if untracked
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def staged_manifest(options = {})
|
107
|
+
diff_index(options.merge(unstaged: false))
|
108
|
+
end
|
109
|
+
alias_method :commit_manifest, :staged_manifest
|
110
|
+
|
111
|
+
def unstaged_manifest(options = {})
|
112
|
+
diff_index(options.merge(unstaged: true))
|
113
|
+
end
|
114
|
+
|
115
|
+
def untracked_manifest
|
116
|
+
files = git_command('ls-files', '--others', '--exclude-standard').output.strip.split(/\s*\n\s*/)
|
117
|
+
files.collect { |path| DiffIndexEntry.from_file_path(path).to_repo_file }
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def diff_index(options = {})
|
123
|
+
options = DEFAULT_DIFF_INDEX_OPTIONS.merge(options)
|
124
|
+
|
125
|
+
cmd = %w(diff-index -C -M -B)
|
126
|
+
cmd << '--cached' unless options[:unstaged]
|
127
|
+
cmd << options.delete(:ref) || 'HEAD'
|
128
|
+
|
129
|
+
raw_output = git_command(*cmd).output.strip
|
130
|
+
raw_output.split(/\n/).collect { |data| DiffIndexEntry.new(data).to_repo_file }
|
131
|
+
end
|
132
|
+
|
133
|
+
def git
|
134
|
+
@git ||= SystemUtils::Command.new('git')
|
135
|
+
end
|
136
|
+
|
137
|
+
def while_stashed(&block)
|
138
|
+
fail ArgumentError, 'Missing required block' unless block_given?
|
139
|
+
begin
|
140
|
+
stash
|
141
|
+
yield
|
142
|
+
ensure
|
143
|
+
unstash
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def run_while_stashed(cmd)
|
148
|
+
while_stashed { system(cmd) }
|
149
|
+
$? == 0
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright (C) 2013 Carl P. Corliss
|
4
|
+
|
5
|
+
This program is free software; you can redistribute it and/or modify
|
6
|
+
it under the terms of the GNU General Public License as published by
|
7
|
+
the Free Software Foundation; either version 2 of the License, or
|
8
|
+
(at your option) any later version.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU General Public License along
|
16
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
17
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
18
|
+
=end
|
19
|
+
|
20
|
+
require 'ostruct'
|
21
|
+
require 'singleton'
|
22
|
+
require 'open3'
|
23
|
+
|
24
|
+
module GitHooks
|
25
|
+
class Repository::Config # rubocop:disable ClassLength
|
26
|
+
OPTIONS = {
|
27
|
+
'path' => { type: :path, multiple: false },
|
28
|
+
'script' => { type: :path, multiple: false },
|
29
|
+
'pre-run-execute' => { type: :path, multiple: true },
|
30
|
+
'post-run-execute' => { type: :path, multiple: true }
|
31
|
+
}
|
32
|
+
|
33
|
+
def initialize(path = Dir.getwd)
|
34
|
+
@repository = Repository.instance(path)
|
35
|
+
end
|
36
|
+
|
37
|
+
OPTIONS.keys.each do |name|
|
38
|
+
method_name = name.gsub(/-/, '_')
|
39
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
40
|
+
def #{method_name}(options = {})
|
41
|
+
result = get('#{name}', options)
|
42
|
+
OPTIONS['#{name}'][:multiple] ? [result].flatten.compact : result
|
43
|
+
end
|
44
|
+
EOS
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](option)
|
48
|
+
send(option.gsub('-', '_'))
|
49
|
+
end
|
50
|
+
|
51
|
+
def set(option, value, options = {}) # rubocop:disable CyclomaticComplexity, MethodLength
|
52
|
+
unless OPTIONS.keys.include? option
|
53
|
+
fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
|
54
|
+
end
|
55
|
+
|
56
|
+
repo = options.delete(:repo_path) || repo_path
|
57
|
+
global = (opt = options.delete(:global)).nil? ? false : opt
|
58
|
+
var_type = "--#{OPTIONS[option][:type]}"
|
59
|
+
add_type = OPTIONS[option][:multiple] ? '--add' : '--replace-all'
|
60
|
+
overwrite = !!options.delete(:overwrite)
|
61
|
+
|
62
|
+
if option == 'path'
|
63
|
+
new_path = Pathname.new(value)
|
64
|
+
errors = []
|
65
|
+
errors << 'path must be a real location' unless new_path.exist?
|
66
|
+
errors << 'path must be a directory' unless new_path.directory?
|
67
|
+
errors << 'path must have a hooks directory in it' unless (new_path + 'hooks').exist?
|
68
|
+
|
69
|
+
if errors.size > 0
|
70
|
+
puts "Unable to change githooks path for [#{repo}]:"
|
71
|
+
errors.each { |error| puts " #{error}" }
|
72
|
+
fail ArgumentError
|
73
|
+
end
|
74
|
+
else
|
75
|
+
fail ArgumentError unless Pathname.new(value).executable?
|
76
|
+
end
|
77
|
+
|
78
|
+
value = Pathname.new(value).realpath.to_s
|
79
|
+
|
80
|
+
if overwrite && !self[option].nil? && !self[option].empty?
|
81
|
+
puts "Overwrite requested for option '#{option}'" if GitHooks.verbose
|
82
|
+
unset(option, repo_path: repo, global: global)
|
83
|
+
end
|
84
|
+
|
85
|
+
option = "githooks.#{repo}.#{option}"
|
86
|
+
git_command(global ? '--global' : '--local', var_type, add_type, option, value, path: repo).tap do |result|
|
87
|
+
puts "Added option #{option} with value #{value}" if result.status.success?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def remove_section(options = {})
|
92
|
+
repo = options.delete(:repo_path) || repo_path
|
93
|
+
global = (opt = options.delete(:global)).nil? ? false : opt
|
94
|
+
option = "githooks.#{repo}"
|
95
|
+
git_command(global ? '--global' : '--local', '--remove-section', option, path: repo)
|
96
|
+
end
|
97
|
+
|
98
|
+
def unset(option, *args)
|
99
|
+
unless OPTIONS.keys.include? option
|
100
|
+
fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
|
101
|
+
end
|
102
|
+
|
103
|
+
options = args.extract_options
|
104
|
+
repo = options.delete(:repo_path) || repo_path
|
105
|
+
global = (opt = options.delete(:global)).nil? ? false : opt
|
106
|
+
option = "githooks.#{repo}.#{option}"
|
107
|
+
|
108
|
+
value_regex = args.first
|
109
|
+
|
110
|
+
if options.delete(:all) || value_regex.nil?
|
111
|
+
git_command(global ? '--global' : '--local', '--unset-all', option, path: repo)
|
112
|
+
else
|
113
|
+
git_command(global ? '--global' : '--local', '--unset', option, value_regex, path: repo)
|
114
|
+
end.tap do |result|
|
115
|
+
puts "Unset option #{option.git_option_path_split.last}" if result.status.success?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def get(option, options = {})
|
120
|
+
unless OPTIONS.keys.include? option
|
121
|
+
fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
|
122
|
+
end
|
123
|
+
|
124
|
+
repo = options[:repo_path] || repo_path
|
125
|
+
githooks = list(options)['githooks']
|
126
|
+
|
127
|
+
githooks[repo][option] if githooks && githooks[repo] && githooks[repo][option]
|
128
|
+
end
|
129
|
+
|
130
|
+
def list(options = {}) # rubocop:disable MethodLength, CyclomaticComplexity
|
131
|
+
repo = options.delete(:repo_path) || repo_path
|
132
|
+
global = (opt = options.delete(:global)).nil? ? false : opt
|
133
|
+
|
134
|
+
config_list = git_command('--list', global ? '--global' : '--local', path: repo).output.split(/\n/)
|
135
|
+
config_list.inject({}) do |hash, line|
|
136
|
+
key, value = line.split(/\s*=\s*/)
|
137
|
+
key_parts = key.git_option_path_split
|
138
|
+
|
139
|
+
ptr = hash[key_parts.shift] ||= {} # rubocop:disable IndentationWidth
|
140
|
+
while key_parts.size > 1 && (part = key_parts.shift)
|
141
|
+
ptr = ptr[part] ||= {} # rubocop:disable IndentationWidth
|
142
|
+
end
|
143
|
+
|
144
|
+
key = key_parts.shift
|
145
|
+
case ptr[key]
|
146
|
+
when nil then ptr[key] = value
|
147
|
+
when Array then ptr[key] << value
|
148
|
+
else ptr[key] = [ptr[key], value].flatten
|
149
|
+
end
|
150
|
+
|
151
|
+
hash
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def repo_path
|
158
|
+
@repository.root_path
|
159
|
+
end
|
160
|
+
|
161
|
+
def git_command(*args)
|
162
|
+
args = ['config', *args].flatten
|
163
|
+
@repository.git_command(*args)
|
164
|
+
end
|
165
|
+
|
166
|
+
def git
|
167
|
+
@repository.git
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module GitHooks
|
5
|
+
class Repository::DiffIndexEntry < OpenStruct
|
6
|
+
DIFF_STRUCTURE_REGEXP = %r{
|
7
|
+
^:
|
8
|
+
(?<original_mode>\d+)\s
|
9
|
+
(?<new_mode>\d+)\s
|
10
|
+
(?<original_sha>[a-f\d]+)\s
|
11
|
+
(?<new_sha>[a-f\d]+)\s
|
12
|
+
(?<change_type>.)
|
13
|
+
(?:(?<score>\d+)?)\s
|
14
|
+
(?<file_path>\S+)\s?
|
15
|
+
(?<rename_path>\S+)?
|
16
|
+
}xi unless defined? DIFF_STRUCTURE_REGEXP
|
17
|
+
|
18
|
+
def self.from_file_path(file_path)
|
19
|
+
file_path = Pathname.new(file_path)
|
20
|
+
new(
|
21
|
+
[
|
22
|
+
0,
|
23
|
+
file_path.stat.mode.to_s(8),
|
24
|
+
0x0,
|
25
|
+
0x0,
|
26
|
+
'?',
|
27
|
+
file_path.to_s
|
28
|
+
].join(' ').prepend(':')
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(entry)
|
33
|
+
unless entry =~ DIFF_STRUCTURE_REGEXP
|
34
|
+
fail ArgumentError, 'Unable to parse incoming diff entry data: #{entry}'
|
35
|
+
end
|
36
|
+
super parse_data(entry)
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_data(entry) # rubocop:disable MethodLength
|
40
|
+
data = Hash[DIFF_STRUCTURE_REGEXP.names.collect(&:to_sym).zip(
|
41
|
+
entry.match(DIFF_STRUCTURE_REGEXP).captures
|
42
|
+
)]
|
43
|
+
|
44
|
+
{
|
45
|
+
from: FileState.new(
|
46
|
+
data[:original_mode].to_i(8),
|
47
|
+
data[:original_sha],
|
48
|
+
data[:file_path].nil? ? nil : Pathname.new(data[:file_path])
|
49
|
+
),
|
50
|
+
to: FileState.new(
|
51
|
+
data[:new_mode].to_i(8),
|
52
|
+
data[:new_sha],
|
53
|
+
data[:rename_path].nil? ? nil : Pathname.new(data[:rename_path])
|
54
|
+
),
|
55
|
+
type: Repository::CHANGE_TYPES[data[:change_type]],
|
56
|
+
score: data[:score].to_i
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_repo_file
|
61
|
+
Repository::File.new(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
class FileState
|
65
|
+
attr_reader :mode, :sha, :path
|
66
|
+
|
67
|
+
def initialize(mode, sha, path)
|
68
|
+
@mode, @sha, @path = mode, sha, path
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
"#<#{self.class.name.split('::').last} mode=#{mode.to_s(8)} path=#{path.to_s.inspect} sha=#{sha.inspect}>"
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_path
|
76
|
+
Pathname.new(@path)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright (C) 2013 Carl P. Corliss
|
4
|
+
|
5
|
+
This program is free software; you can redistribute it and/or modify
|
6
|
+
it under the terms of the GNU General Public License as published by
|
7
|
+
the Free Software Foundation; either version 2 of the License, or
|
8
|
+
(at your option) any later version.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU General Public License along
|
16
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
17
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
18
|
+
=end
|
19
|
+
|
20
|
+
require 'ostruct'
|
21
|
+
require 'delegate'
|
22
|
+
|
23
|
+
# allow for reloading of class
|
24
|
+
unless defined? DiffIndexEntryDelegateClass
|
25
|
+
DiffIndexEntryDelegateClass = DelegateClass(GitHooks::Repository::DiffIndexEntry)
|
26
|
+
end
|
27
|
+
|
28
|
+
module GitHooks
|
29
|
+
class Repository::File < DiffIndexEntryDelegateClass
|
30
|
+
def initialize(entry)
|
31
|
+
unless entry.is_a? Repository::DiffIndexEntry
|
32
|
+
fail ArgumentError, "Expected a Repository::DiffIndexEntry but got a '#{entry.class.name}'"
|
33
|
+
end
|
34
|
+
@file = entry
|
35
|
+
end
|
36
|
+
|
37
|
+
def __getobj__ # rubocop:disable TrivialAccessors
|
38
|
+
@file
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
attributes = [:name, :path, :type, :mode, :sha, :score].collect do |name|
|
43
|
+
"#{name}=#{attribute_value(name).inspect}"
|
44
|
+
end
|
45
|
+
"#<#{self.class.name} #{attributes.join(' ')} >"
|
46
|
+
end
|
47
|
+
|
48
|
+
def path
|
49
|
+
to.path || from.path
|
50
|
+
end
|
51
|
+
|
52
|
+
def name
|
53
|
+
path.basename.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
# rubocop:disable CyclomaticComplexity
|
57
|
+
def attribute_value(attribute)
|
58
|
+
case attribute
|
59
|
+
when :name then name
|
60
|
+
when :path then path.to_s
|
61
|
+
when :type then type
|
62
|
+
when :mode then to.mode
|
63
|
+
when :sha then to.sha
|
64
|
+
when :score then score
|
65
|
+
else fail ArgumentError,
|
66
|
+
"Invalid attribute type '#{attribute}' - expected: :name, :path, :type, :mode, :sha, or :score"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def match(type, _match)
|
71
|
+
value = attribute_value(type)
|
72
|
+
return _match.call(value) if _match.respond_to? :call
|
73
|
+
|
74
|
+
case type
|
75
|
+
when :name then _match.is_a?(Regexp) ? value =~ _match : value == _match
|
76
|
+
when :path then _match.is_a?(Regexp) ? value =~ _match : value == _match
|
77
|
+
when :type then _match.is_a?(Array) ? _match.include?(value) : _match == value
|
78
|
+
when :mode then _match & value == _match
|
79
|
+
when :sha then _match == value
|
80
|
+
when :score then _match == value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
# rubocop:enable CyclomaticComplexity
|
84
|
+
|
85
|
+
def fd
|
86
|
+
case type
|
87
|
+
when :deleted, :deletion then nil
|
88
|
+
else path.open
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def realpath
|
93
|
+
case type
|
94
|
+
when :deleted, :deletion then path
|
95
|
+
else path.realpath
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def contains?(string_or_regexp)
|
100
|
+
if string_or_regexp.is_a?(Regexp)
|
101
|
+
contents =~ string_or_regexp
|
102
|
+
else
|
103
|
+
contents.include? string_or_regexp
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def grep(regexp)
|
108
|
+
lines(true).select_with_index { |line|
|
109
|
+
line =~ regexp
|
110
|
+
}.collect { |num, line|
|
111
|
+
[num + 1, line] # line numbers start from 1, not 0
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def contents
|
116
|
+
return unless fd
|
117
|
+
fd.read
|
118
|
+
end
|
119
|
+
|
120
|
+
def lines(strip_newlines = false)
|
121
|
+
return [] unless fd
|
122
|
+
strip_newlines ? fd.readlines.collect(&:chomp!) : fd.readlines
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|