rabbitt-githooks 1.2.7 → 1.3.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 +13 -5
- data/{hooks → .hooks}/commit-messages.rb +0 -0
- data/{hooks → .hooks}/formatting.rb +6 -15
- data/.rubocop.yml +18 -4
- data/Gemfile.lock +20 -14
- data/README.md +109 -3
- data/bin/githooks +1 -1
- data/bin/githooks-runner +1 -0
- data/lib/githooks.rb +11 -11
- data/lib/githooks/action.rb +20 -11
- data/lib/githooks/cli.rb +15 -6
- data/lib/githooks/core_ext/process.rb +7 -5
- data/lib/githooks/hook.rb +24 -7
- data/lib/githooks/repository.rb +35 -18
- data/lib/githooks/repository/config.rb +122 -115
- data/lib/githooks/repository/diff_index_entry.rb +60 -62
- data/lib/githooks/repository/file.rb +77 -75
- data/lib/githooks/repository/limiter.rb +34 -25
- data/lib/githooks/runner.rb +17 -19
- data/lib/githooks/section.rb +17 -4
- data/lib/githooks/system_utils.rb +1 -1
- data/lib/githooks/terminal_colors.rb +5 -6
- data/lib/githooks/version.rb +1 -1
- data/rabbitt-githooks.gemspec +2 -1
- metadata +35 -21
@@ -1,79 +1,77 @@
|
|
1
1
|
require 'ostruct'
|
2
2
|
require 'pathname'
|
3
|
+
require 'githooks/repository/file'
|
3
4
|
|
4
5
|
module GitHooks
|
5
|
-
class Repository
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
class Repository
|
7
|
+
class DiffIndexEntry < OpenStruct
|
8
|
+
DIFF_STRUCTURE_REGEXP = %r{
|
9
|
+
^:
|
10
|
+
(?<original_mode>\d+)\s
|
11
|
+
(?<new_mode>\d+)\s
|
12
|
+
(?<original_sha>[a-f\d]+)\.*\s
|
13
|
+
(?<new_sha>[a-f\d]+)\.*\s
|
14
|
+
(?<change_type>.)
|
15
|
+
(?:(?<score>\d+)?)\s
|
16
|
+
(?<file_path>\S+)\s?
|
17
|
+
(?<rename_path>\S+)?
|
18
|
+
}xi unless defined? DIFF_STRUCTURE_REGEXP
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
0x0,
|
25
|
-
0x0,
|
26
|
-
'?',
|
27
|
-
file_path.to_s
|
28
|
-
].join(' ').prepend(':')
|
29
|
-
)
|
30
|
-
end
|
20
|
+
def self.from_file_path(path, tracked = false)
|
21
|
+
path = Pathname.new(path)
|
22
|
+
entry_line = sprintf(":%06o %06o %040x %040x %s\t%s",
|
23
|
+
0, path.stat.mode, 0, 0, (tracked ? '^' : '?'), path.to_s)
|
24
|
+
new(entry_line)
|
25
|
+
end
|
31
26
|
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
def initialize(entry)
|
28
|
+
unless entry =~ DIFF_STRUCTURE_REGEXP
|
29
|
+
fail ArgumentError, "Unable to parse incoming diff entry data: #{entry}"
|
30
|
+
end
|
31
|
+
super parse_data(entry)
|
35
32
|
end
|
36
|
-
super parse_data(entry)
|
37
|
-
end
|
38
33
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
34
|
+
def parse_data(entry) # rubocop:disable MethodLength
|
35
|
+
data = Hash[
|
36
|
+
DIFF_STRUCTURE_REGEXP.names.collect(&:to_sym).zip(
|
37
|
+
entry.match(DIFF_STRUCTURE_REGEXP).captures
|
38
|
+
)
|
39
|
+
]
|
43
40
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
41
|
+
{
|
42
|
+
from: FileState.new(
|
43
|
+
data[:original_mode].to_i(8),
|
44
|
+
data[:original_sha],
|
45
|
+
data[:file_path].nil? ? nil : Pathname.new(data[:file_path])
|
46
|
+
),
|
47
|
+
to: FileState.new(
|
48
|
+
data[:new_mode].to_i(8),
|
49
|
+
data[:new_sha],
|
50
|
+
data[:rename_path].nil? ? nil : Pathname.new(data[:rename_path])
|
51
|
+
),
|
52
|
+
type: Repository::CHANGE_TYPES[data[:change_type]],
|
53
|
+
score: data[:score].to_i
|
54
|
+
}
|
55
|
+
end
|
59
56
|
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
def to_repo_file
|
58
|
+
Repository::File.new(self)
|
59
|
+
end
|
63
60
|
|
64
|
-
|
65
|
-
|
61
|
+
class FileState
|
62
|
+
attr_reader :mode, :sha, :path
|
66
63
|
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
def initialize(mode, sha, path)
|
65
|
+
@mode, @sha, @path = mode, sha, path
|
66
|
+
end
|
70
67
|
|
71
|
-
|
72
|
-
|
73
|
-
|
68
|
+
def inspect
|
69
|
+
"#<#{self.class.name.split('::').last} mode=#{mode.to_s(8)} path=#{path.to_s.inspect} sha=#{sha.inspect}>"
|
70
|
+
end
|
74
71
|
|
75
|
-
|
76
|
-
|
72
|
+
def to_path
|
73
|
+
Pathname.new(@path)
|
74
|
+
end
|
77
75
|
end
|
78
76
|
end
|
79
77
|
end
|
@@ -26,100 +26,102 @@ unless defined? DiffIndexEntryDelegateClass
|
|
26
26
|
end
|
27
27
|
|
28
28
|
module GitHooks
|
29
|
-
class Repository
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
class Repository
|
30
|
+
class File < DiffIndexEntryDelegateClass
|
31
|
+
def initialize(entry)
|
32
|
+
unless entry.is_a? Repository::DiffIndexEntry
|
33
|
+
fail ArgumentError, "Expected a Repository::DiffIndexEntry but got a '#{entry.class.name}'"
|
34
|
+
end
|
35
|
+
@file = entry
|
33
36
|
end
|
34
|
-
@file = entry
|
35
|
-
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def __getobj__ # rubocop:disable TrivialAccessors
|
39
|
+
@file
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
def inspect
|
43
|
+
attributes = [:name, :path, :type, :mode, :sha, :score].collect do |name|
|
44
|
+
"#{name}=#{attribute_value(name).inspect}"
|
45
|
+
end
|
46
|
+
"#<#{self.class.name} #{attributes.join(' ')} >"
|
44
47
|
end
|
45
|
-
"#<#{self.class.name} #{attributes.join(' ')} >"
|
46
|
-
end
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
def path
|
50
|
+
to.path || from.path
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
53
|
+
def name
|
54
|
+
path.basename.to_s
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
57
|
+
# rubocop:disable CyclomaticComplexity
|
58
|
+
def attribute_value(attribute)
|
59
|
+
case attribute
|
60
|
+
when :name then name
|
61
|
+
when :path then path.to_s
|
62
|
+
when :type then type
|
63
|
+
when :mode then to.mode
|
64
|
+
when :sha then to.sha
|
65
|
+
when :score then score
|
66
|
+
else fail ArgumentError,
|
67
|
+
"Invalid attribute type '#{attribute}' - expected: :name, :path, :type, :mode, :sha, or :score"
|
68
|
+
end
|
67
69
|
end
|
68
|
-
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
71
|
+
def match(type, selector)
|
72
|
+
value = attribute_value(type)
|
73
|
+
return selector.call(value) if selector.respond_to? :call
|
74
|
+
|
75
|
+
case type
|
76
|
+
when :name then selector.is_a?(Regexp) ? value =~ selector : value == selector
|
77
|
+
when :path then selector.is_a?(Regexp) ? value =~ selector : value == selector
|
78
|
+
when :type then [*selector].include?(:any) ? true : [*selector].include?(value)
|
79
|
+
when :mode then selector & value == selector
|
80
|
+
when :sha then selector == value
|
81
|
+
when :score then selector == value
|
82
|
+
end
|
81
83
|
end
|
82
|
-
|
83
|
-
# rubocop:enable CyclomaticComplexity
|
84
|
+
# rubocop:enable CyclomaticComplexity
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
86
|
+
def fd
|
87
|
+
case type
|
88
|
+
when :deleted, :deletion then nil
|
89
|
+
else path.open
|
90
|
+
end
|
89
91
|
end
|
90
|
-
end
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
def realpath
|
94
|
+
case type
|
95
|
+
when :deleted, :deletion then path
|
96
|
+
else path.realpath
|
97
|
+
end
|
96
98
|
end
|
97
|
-
end
|
98
99
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
100
|
+
def contains?(string_or_regexp)
|
101
|
+
if string_or_regexp.is_a?(Regexp)
|
102
|
+
contents =~ string_or_regexp
|
103
|
+
else
|
104
|
+
contents.include? string_or_regexp
|
105
|
+
end
|
104
106
|
end
|
105
|
-
end
|
106
107
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
108
|
+
def grep(regexp)
|
109
|
+
lines(true).select_with_index { |line|
|
110
|
+
line =~ regexp
|
111
|
+
}.collect { |num, line|
|
112
|
+
[num + 1, line] # line numbers start from 1, not 0
|
113
|
+
}
|
114
|
+
end
|
114
115
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
116
|
+
def contents
|
117
|
+
return unless fd
|
118
|
+
fd.read
|
119
|
+
end
|
119
120
|
|
120
|
-
|
121
|
-
|
122
|
-
|
121
|
+
def lines(strip_newlines = false)
|
122
|
+
return [] unless fd
|
123
|
+
strip_newlines ? fd.readlines.collect(&:chomp!) : fd.readlines
|
124
|
+
end
|
123
125
|
end
|
124
126
|
end
|
125
127
|
end
|
@@ -17,38 +17,47 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|
17
17
|
=end
|
18
18
|
|
19
19
|
module GitHooks
|
20
|
-
class Repository
|
21
|
-
|
20
|
+
class Repository
|
21
|
+
class Limiter
|
22
|
+
attr_reader :type, :only
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
def initialize(type, options = {})
|
25
|
+
@type = type
|
26
|
+
@only = options.delete(:only) || options.delete(:to)
|
27
|
+
@inverted = false
|
28
|
+
end
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
30
|
+
def only(*args)
|
31
|
+
return @only if args.empty?
|
32
|
+
@only = args.flatten
|
33
|
+
self
|
34
|
+
end
|
35
|
+
alias_method :to, :only
|
36
|
+
|
37
|
+
def inverted
|
38
|
+
@inverted = true
|
39
|
+
end
|
40
|
+
alias_method :invert, :inverted
|
41
|
+
|
42
|
+
def limit(files)
|
43
|
+
files.select! do |file|
|
44
|
+
match_file(file, @only).tap do |result|
|
45
|
+
if GitHooks.debug?
|
46
|
+
result = (result ? 'success' : 'failure')
|
47
|
+
puts " #{file.path} (#{file.attribute_value(@type).inspect}) was a #{result}"
|
48
|
+
end
|
40
49
|
end
|
41
50
|
end
|
42
51
|
end
|
43
|
-
end
|
44
52
|
|
45
|
-
|
53
|
+
private
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
55
|
+
def match_file(file, match_value)
|
56
|
+
if @inverted
|
57
|
+
[*match_value].none? { |value| file.match(@type, value) }
|
58
|
+
else
|
59
|
+
[*match_value].any? { |value| file.match(@type, value) }
|
60
|
+
end
|
52
61
|
end
|
53
62
|
end
|
54
63
|
end
|
data/lib/githooks/runner.rb
CHANGED
@@ -49,7 +49,7 @@ module GitHooks
|
|
49
49
|
end
|
50
50
|
|
51
51
|
if script && !(options['ignore-script'] || GitHooks.ignore_script)
|
52
|
-
command = "#{script} #{Pathname.new($0)
|
52
|
+
command = "#{script} #{Pathname.new($0)} #{Shellwords.join(ARGV)};"
|
53
53
|
puts "Kernel#exec(#{command.inspect})" if GitHooks.verbose
|
54
54
|
exec(command)
|
55
55
|
elsif libpath
|
@@ -106,7 +106,7 @@ module GitHooks
|
|
106
106
|
|
107
107
|
hook_phases.each do |hook|
|
108
108
|
hook = (repo_hooks + hook).to_s
|
109
|
-
puts "Linking #{gitrunner
|
109
|
+
puts "Linking #{gitrunner} -> #{hook}" if GitHooks.verbose
|
110
110
|
FileUtils.ln_sf gitrunner.to_s, hook
|
111
111
|
end
|
112
112
|
end
|
@@ -120,10 +120,9 @@ module GitHooks
|
|
120
120
|
repo = Repository.instance(repo_path)
|
121
121
|
|
122
122
|
hook_phases.each do |hook|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
123
|
+
next unless (repo_hook = (repo_hooks + hook)).symlink?
|
124
|
+
puts "Removing hook '#{hook}' from repository at: #{repo_path}" if GitHooks.verbose
|
125
|
+
FileUtils.rm_f repo_hook
|
127
126
|
end
|
128
127
|
|
129
128
|
active_hooks = Hook::VALID_PHASES.select { |hook| (repo_hooks + hook).exist? }
|
@@ -137,7 +136,7 @@ module GitHooks
|
|
137
136
|
end
|
138
137
|
module_function :detach
|
139
138
|
|
140
|
-
def list(repo_path)
|
139
|
+
def list(repo_path) # rubocop:disable Style/CyclomaticComplexity, Style/MethodLength
|
141
140
|
repo_path ||= Pathname.new(Repository.root_path)
|
142
141
|
|
143
142
|
repo = Repository.instance(repo_path)
|
@@ -223,14 +222,15 @@ module GitHooks
|
|
223
222
|
end
|
224
223
|
module_function :run_externals
|
225
224
|
|
226
|
-
def start(options = {}) # rubocop:disable MethodLength
|
225
|
+
def start(options = {}) # rubocop:disable Style/CyclomaticComplexity, Style/MethodLength
|
227
226
|
phase = options[:hook] || GitHooks.hook_name || 'pre-commit'
|
228
227
|
puts "PHASE: #{phase}" if GitHooks.debug
|
229
228
|
|
230
|
-
if active_hook = Hook.phases[phase]
|
229
|
+
if (active_hook = Hook.phases[phase])
|
231
230
|
active_hook.args = options.delete(:args)
|
232
|
-
active_hook.
|
231
|
+
active_hook.staged = options.delete(:staged)
|
233
232
|
active_hook.untracked = options.delete(:untracked)
|
233
|
+
active_hook.tracked = options.delete(:tracked)
|
234
234
|
active_hook.repository_path = options.delete(:repo)
|
235
235
|
else
|
236
236
|
fail Error::InvalidPhase, "Hook '#{phase}' is not defined - have you registered any tests for this hook yet?"
|
@@ -242,10 +242,10 @@ module GitHooks
|
|
242
242
|
|
243
243
|
sections.each do |section|
|
244
244
|
hash_tail_length = (section_length - section.title.length)
|
245
|
-
printf "===== %s %s
|
245
|
+
printf "===== %s %s===== (%ds)\n", section.colored_name(phase), ('=' * hash_tail_length), section.benchmark
|
246
246
|
|
247
247
|
section.actions.each_with_index do |action, index|
|
248
|
-
printf " %d. [ %s ] %s\n", (index + 1), action.state_symbol, action.colored_title
|
248
|
+
printf " %d. [ %s ] %s (%ds)\n", (index + 1), action.state_symbol, action.colored_title, action.benchmark
|
249
249
|
|
250
250
|
action.errors.each do |error|
|
251
251
|
printf " %s %s\n", color_bright_red(MARK_FAILURE), error
|
@@ -270,9 +270,9 @@ module GitHooks
|
|
270
270
|
end
|
271
271
|
module_function :start
|
272
272
|
|
273
|
-
def load_tests(path, skip_bundler = false) # rubocop:disable MethodLength
|
273
|
+
def load_tests(path, skip_bundler = false) # rubocop:disable MethodLength,Style/CyclomaticComplexity
|
274
274
|
hooks_root = Pathname.new(path).realpath
|
275
|
-
hooks_path = hooks_root + 'hooks'
|
275
|
+
hooks_path = (p = (hooks_root + 'hooks')).exist? ? p : (hooks_root + '.hooks')
|
276
276
|
hooks_libs = hooks_root + 'libs'
|
277
277
|
gemfile = hooks_root + 'Gemfile'
|
278
278
|
|
@@ -285,18 +285,15 @@ module GitHooks
|
|
285
285
|
# stupid RVM polluting my environment without asking via it's
|
286
286
|
# executable-hooks gem preloading bundler. hence the following ...
|
287
287
|
if defined? Bundler
|
288
|
-
[:@bundle_path, :@configured, :@definition, :@load].each do |var|
|
288
|
+
[:@settings, :@bundle_path, :@configured, :@definition, :@load].each do |var|
|
289
289
|
Bundler.instance_variable_set(var, nil)
|
290
290
|
end
|
291
|
-
# bundler tests for @settings using defined? - which means we need
|
292
|
-
# to forcibly remove it.
|
293
|
-
Bundler.send(:remove_instance_variable, :@settings)
|
294
291
|
else
|
295
292
|
require 'bundler'
|
296
293
|
end
|
297
294
|
Bundler.require(:default)
|
298
295
|
rescue LoadError
|
299
|
-
puts
|
296
|
+
puts %Q|Unable to load bundler - please make sure it's installed.|
|
300
297
|
raise # rubocop:disable SignalException
|
301
298
|
rescue Bundler::GemNotFound => e
|
302
299
|
puts "Error: #{e.message}"
|
@@ -306,6 +303,7 @@ module GitHooks
|
|
306
303
|
end
|
307
304
|
|
308
305
|
$LOAD_PATH.unshift hooks_libs.to_s
|
306
|
+
|
309
307
|
Dir["#{hooks_path}/**/*.rb"].each do |lib|
|
310
308
|
lib.gsub!('.rb', '')
|
311
309
|
puts "Loading: #{lib}" if GitHooks.verbose
|