rabbitt-githooks 1.2.7 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|