overcommit 0.31.0 → 0.32.0.rc1
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 +4 -4
- data/config/default.yml +7 -0
- data/lib/overcommit/configuration.rb +24 -1
- data/lib/overcommit/configuration_validator.rb +28 -1
- data/lib/overcommit/hook/base.rb +8 -0
- data/lib/overcommit/hook_runner.rb +87 -41
- data/lib/overcommit/printer.rb +32 -8
- data/lib/overcommit/utils.rb +36 -0
- data/lib/overcommit/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b45a46c1290a6ddd0fd418f28012e7138f01470e
|
4
|
+
data.tar.gz: 1443470dae06fb17b84fe99823d8e2c655bdb792
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 133dc22840e178a2473670780a385fb0b4c46a48d175c5ab7ad391eccfb0fc1517a1f4318f8ad91e1ef53ca40cd2925d3e3912fb203b91e8aeeb8a9285892df1
|
7
|
+
data.tar.gz: 0847b5f3045de5b413be8704a76fbd6fdef908a34e11b64414a58628eadc3f961d77efa1e2257ef0a7b77b6e861b2496a1ec97f5752a02cbe1f9a17fe1cd1adb
|
data/config/default.yml
CHANGED
@@ -39,6 +39,13 @@ gemfile: false
|
|
39
39
|
# to the root of the repository.
|
40
40
|
plugin_directory: '.git-hooks'
|
41
41
|
|
42
|
+
# Number of hooks that can be run concurrently. Typically this won't need to be
|
43
|
+
# adjusted, but if you know that some of your hooks themselves use multiple
|
44
|
+
# processors you can lower this value accordingly. You can define
|
45
|
+
# single-operator mathematical expressions, e.g. '%{processors} * 2', or
|
46
|
+
# '%{processors} / 2'.
|
47
|
+
concurrency: '%{processors}'
|
48
|
+
|
42
49
|
# Whether to check if a hook plugin has changed since Overcommit last ran it.
|
43
50
|
# This is a defense mechanism when working with repositories which can contain
|
44
51
|
# untrusted code (e.g. when you fetch a pull request from a third party).
|
@@ -13,7 +13,10 @@ module Overcommit
|
|
13
13
|
def initialize(hash, options = {})
|
14
14
|
@options = options.dup
|
15
15
|
@options[:logger] ||= Overcommit::Logger.silent
|
16
|
-
@hash =
|
16
|
+
@hash = hash # Assign so validator can read original values
|
17
|
+
unless options[:validate] == false
|
18
|
+
@hash = Overcommit::ConfigurationValidator.new.validate(self, hash, options)
|
19
|
+
end
|
17
20
|
end
|
18
21
|
|
19
22
|
def ==(other)
|
@@ -34,6 +37,26 @@ module Overcommit
|
|
34
37
|
File.join(Overcommit::Utils.repo_root, @hash['plugin_directory'] || '.git-hooks')
|
35
38
|
end
|
36
39
|
|
40
|
+
def concurrency
|
41
|
+
@concurrency ||=
|
42
|
+
begin
|
43
|
+
cores = Overcommit::Utils.processor_count
|
44
|
+
content = @hash.fetch('concurrency', '%{processors}')
|
45
|
+
if content.is_a?(String)
|
46
|
+
concurrency_expr = content % { processors: cores }
|
47
|
+
|
48
|
+
a, op, b = concurrency_expr.scan(%r{(\d+)\s*([+\-*\/])\s*(\d+)})[0]
|
49
|
+
if a
|
50
|
+
a.to_i.send(op, b.to_i)
|
51
|
+
else
|
52
|
+
concurrency_expr.to_i
|
53
|
+
end
|
54
|
+
else
|
55
|
+
content.to_i
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
37
60
|
# Returns configuration for all hooks in each hook type.
|
38
61
|
#
|
39
62
|
# @return [Hash]
|
@@ -3,12 +3,13 @@ module Overcommit
|
|
3
3
|
class ConfigurationValidator
|
4
4
|
# Validates hash for any invalid options, normalizing where possible.
|
5
5
|
#
|
6
|
+
# @param config [Overcommit::Configuration]
|
6
7
|
# @param hash [Hash] hash representation of YAML config
|
7
8
|
# @param options[Hash]
|
8
9
|
# @option default [Boolean] whether hash represents the default built-in config
|
9
10
|
# @option logger [Overcommit::Logger] logger to output warnings to
|
10
11
|
# @return [Hash] validated hash (potentially modified)
|
11
|
-
def validate(hash, options)
|
12
|
+
def validate(config, hash, options)
|
12
13
|
@options = options.dup
|
13
14
|
@log = options[:logger]
|
14
15
|
|
@@ -16,6 +17,7 @@ module Overcommit
|
|
16
17
|
ensure_hook_type_sections_exist(hash)
|
17
18
|
check_hook_name_format(hash)
|
18
19
|
check_for_missing_enabled_option(hash) unless @options[:default]
|
20
|
+
check_for_too_many_processors(config, hash)
|
19
21
|
check_for_verify_plugin_signatures_option(hash)
|
20
22
|
|
21
23
|
hash
|
@@ -96,6 +98,31 @@ module Overcommit
|
|
96
98
|
@log.newline if any_warnings
|
97
99
|
end
|
98
100
|
|
101
|
+
# Prints a warning if any hook has a number of processors larger than the
|
102
|
+
# global `concurrency` setting.
|
103
|
+
def check_for_too_many_processors(config, hash)
|
104
|
+
concurrency = config.concurrency
|
105
|
+
|
106
|
+
errors = []
|
107
|
+
Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
|
108
|
+
hash.fetch(hook_type, {}).each do |hook_name, hook_config|
|
109
|
+
processors = hook_config.fetch('processors', 1)
|
110
|
+
if processors > concurrency
|
111
|
+
errors << "#{hook_type}::#{hook_name} `processors` value " \
|
112
|
+
"(#{processors}) is larger than the global `concurrency` " \
|
113
|
+
"option (#{concurrency})"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if errors.any?
|
119
|
+
@log.error errors.join("\n") if @log
|
120
|
+
@log.newline if @log
|
121
|
+
raise Overcommit::Exceptions::ConfigurationError,
|
122
|
+
'One or more hooks had invalid `processor` value configured'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
99
126
|
# Prints a warning if the `verify_plugin_signatures` option is used instead
|
100
127
|
# of the new `verify_signatures` option.
|
101
128
|
def check_for_verify_plugin_signatures_option(hash)
|
data/lib/overcommit/hook/base.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Overcommit
|
2
2
|
# Responsible for loading the hooks the repository has configured and running
|
3
3
|
# them, collecting and displaying the results.
|
4
|
-
class HookRunner
|
4
|
+
class HookRunner # rubocop:disable Metrics/ClassLength
|
5
5
|
# @param config [Overcommit::Configuration]
|
6
6
|
# @param logger [Overcommit::Logger]
|
7
7
|
# @param context [Overcommit::HookContext]
|
@@ -12,6 +12,10 @@ module Overcommit
|
|
12
12
|
@context = context
|
13
13
|
@printer = printer
|
14
14
|
@hooks = []
|
15
|
+
|
16
|
+
@lock = Mutex.new
|
17
|
+
@resource = ConditionVariable.new
|
18
|
+
@slots_available = @config.concurrency
|
15
19
|
end
|
16
20
|
|
17
21
|
# Loads and runs the hooks registered for this {HookRunner}.
|
@@ -51,78 +55,120 @@ module Overcommit
|
|
51
55
|
if @hooks.any?(&:enabled?)
|
52
56
|
@printer.start_run
|
53
57
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
@hooks.each do |hook|
|
59
|
-
hook_status = run_hook(hook)
|
58
|
+
# Sort so hooks requiring fewer processors get queued first. This
|
59
|
+
# ensures we make better use of our available processors
|
60
|
+
@hooks_left = @hooks.sort_by { |hook| processors_for_hook(hook) }
|
61
|
+
@threads = Array.new(@config.concurrency) { Thread.new(&method(:consume)) }
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
if hook_status == :interrupt
|
65
|
-
# Stop running any more hooks and assume a bad result
|
66
|
-
interrupted = true
|
67
|
-
break
|
63
|
+
begin
|
64
|
+
InterruptHandler.disable_until_finished_or_interrupted do
|
65
|
+
@threads.each(&:join)
|
68
66
|
end
|
67
|
+
rescue Interrupt
|
68
|
+
@printer.interrupt_triggered
|
69
|
+
# We received an interrupt on the main thread, so alert the
|
70
|
+
# remaining workers that an exception occurred
|
71
|
+
@interrupted = true
|
72
|
+
@threads.each { |thread| thread.raise Interrupt }
|
69
73
|
end
|
70
74
|
|
71
|
-
print_results
|
75
|
+
print_results
|
72
76
|
|
73
|
-
!(
|
77
|
+
!(@failed || @interrupted)
|
74
78
|
else
|
75
79
|
@printer.nothing_to_run
|
76
80
|
true # Run was successful
|
77
81
|
end
|
78
82
|
end
|
79
83
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
84
|
+
def consume
|
85
|
+
loop do
|
86
|
+
hook = @lock.synchronize { @hooks_left.pop }
|
87
|
+
break unless hook
|
88
|
+
run_hook(hook)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def wait_for_slot(hook)
|
93
|
+
@lock.synchronize do
|
94
|
+
slots_needed = processors_for_hook(hook)
|
95
|
+
|
96
|
+
loop do
|
97
|
+
if @slots_available >= slots_needed
|
98
|
+
@slots_available -= slots_needed
|
99
|
+
|
100
|
+
# Give another thread a chance since there are still slots available
|
101
|
+
@resource.signal if @slots_available > 0
|
102
|
+
break
|
103
|
+
elsif @slots_available > 0
|
104
|
+
# It's possible that another hook that requires fewer slots can be
|
105
|
+
# served, so give another a chance
|
106
|
+
@resource.signal
|
107
|
+
|
108
|
+
# Wait for a signal from another thread to try again
|
109
|
+
@resource.wait(@lock)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def release_slot(hook)
|
116
|
+
@lock.synchronize do
|
117
|
+
slots_released = processors_for_hook(hook)
|
118
|
+
@slots_available += slots_released
|
119
|
+
|
120
|
+
if @hooks_left.any?
|
121
|
+
# Signal once. `wait_for_slot` will perform additional signals if
|
122
|
+
# there are still slots available. This prevents us from sending out
|
123
|
+
# useless signals
|
124
|
+
@resource.signal
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def processors_for_hook(hook)
|
130
|
+
hook.parallelize? ? hook.processors : @config.concurrency
|
131
|
+
end
|
132
|
+
|
133
|
+
def print_results
|
134
|
+
if @interrupted
|
85
135
|
@printer.run_interrupted
|
86
|
-
elsif failed
|
136
|
+
elsif @failed
|
87
137
|
@printer.run_failed
|
88
|
-
elsif warned
|
138
|
+
elsif @warned
|
89
139
|
@printer.run_warned
|
90
140
|
else
|
91
141
|
@printer.run_succeeded
|
92
142
|
end
|
93
143
|
end
|
94
144
|
|
95
|
-
def run_hook(hook)
|
96
|
-
return if should_skip?(hook)
|
97
|
-
|
98
|
-
@printer.start_hook(hook)
|
99
|
-
|
145
|
+
def run_hook(hook) # rubocop:disable Metrics/CyclomaticComplexity
|
100
146
|
status, output = nil, nil
|
101
147
|
|
102
148
|
begin
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
status, output = hook.run_and_transform
|
108
|
-
end
|
149
|
+
wait_for_slot(hook)
|
150
|
+
return if should_skip?(hook)
|
151
|
+
|
152
|
+
status, output = hook.run_and_transform
|
109
153
|
rescue => ex
|
110
154
|
status = :fail
|
111
155
|
output = "Hook raised unexpected error\n#{ex.message}\n#{ex.backtrace.join("\n")}"
|
112
|
-
rescue Interrupt
|
113
|
-
# At this point, interrupt has been handled and protection is back in
|
114
|
-
# effect thanks to the InterruptHandler.
|
115
|
-
status = :interrupt
|
116
|
-
output = 'Hook was interrupted by Ctrl-C; restoring repo state...'
|
117
156
|
end
|
118
157
|
|
119
|
-
@
|
158
|
+
@failed = true if status == :fail
|
159
|
+
@warned = true if status == :warn
|
160
|
+
|
161
|
+
@printer.end_hook(hook, status, output) unless @interrupted
|
120
162
|
|
121
163
|
status
|
164
|
+
rescue Interrupt
|
165
|
+
@interrupted = true
|
166
|
+
ensure
|
167
|
+
release_slot(hook)
|
122
168
|
end
|
123
169
|
|
124
170
|
def should_skip?(hook)
|
125
|
-
return true
|
171
|
+
return true if @interrupted || !hook.enabled?
|
126
172
|
|
127
173
|
if hook.skip?
|
128
174
|
if hook.required?
|
data/lib/overcommit/printer.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require 'monitor'
|
4
|
+
|
3
5
|
module Overcommit
|
4
6
|
# Provide a set of callbacks which can be executed as events occur during the
|
5
7
|
# course of {HookRunner#run}.
|
@@ -9,6 +11,8 @@ module Overcommit
|
|
9
11
|
def initialize(logger, context)
|
10
12
|
@log = logger
|
11
13
|
@context = context
|
14
|
+
@lock = Monitor.new # Need to use monitor so we can have re-entrant locks
|
15
|
+
synchronize_all_methods
|
12
16
|
end
|
13
17
|
|
14
18
|
# Executed at the very beginning of running the collection of hooks.
|
@@ -20,13 +24,6 @@ module Overcommit
|
|
20
24
|
log.debug "✓ No applicable #{hook_script_name} hooks to run"
|
21
25
|
end
|
22
26
|
|
23
|
-
# Executed at the start of an individual hook run.
|
24
|
-
def start_hook(hook)
|
25
|
-
unless hook.quiet?
|
26
|
-
print_header(hook)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
27
|
def hook_skipped(hook)
|
31
28
|
log.warning "Skipping #{hook.name}"
|
32
29
|
end
|
@@ -39,11 +36,16 @@ module Overcommit
|
|
39
36
|
def end_hook(hook, status, output)
|
40
37
|
# Want to print the header for quiet hooks only if the result wasn't good
|
41
38
|
# so that the user knows what failed
|
42
|
-
print_header(hook) if hook.quiet?
|
39
|
+
print_header(hook) if !hook.quiet? || status != :pass
|
43
40
|
|
44
41
|
print_result(hook, status, output)
|
45
42
|
end
|
46
43
|
|
44
|
+
def interrupt_triggered
|
45
|
+
log.newline
|
46
|
+
log.error 'Interrupt signal received. Stopping hooks...'
|
47
|
+
end
|
48
|
+
|
47
49
|
# Executed when a hook run was interrupted/cancelled by user.
|
48
50
|
def run_interrupted
|
49
51
|
log.newline
|
@@ -108,5 +110,27 @@ module Overcommit
|
|
108
110
|
def hook_script_name
|
109
111
|
@context.hook_script_name
|
110
112
|
end
|
113
|
+
|
114
|
+
# Get all public methods that were defined on this class and wrap them with
|
115
|
+
# synchronization locks so we ensure the output isn't interleaved amongst
|
116
|
+
# the various threads.
|
117
|
+
def synchronize_all_methods
|
118
|
+
methods = self.class.instance_methods - self.class.superclass.instance_methods
|
119
|
+
|
120
|
+
methods.each do |method_name|
|
121
|
+
old_method = :"old_#{method_name}"
|
122
|
+
new_method = :"synchronized_#{method_name}"
|
123
|
+
|
124
|
+
self.class.__send__(:alias_method, old_method, method_name)
|
125
|
+
|
126
|
+
self.class.send(:define_method, new_method) do |*args|
|
127
|
+
@lock.synchronize do
|
128
|
+
__send__(old_method, *args)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
self.class.__send__(:alias_method, method_name, new_method)
|
133
|
+
end
|
134
|
+
end
|
111
135
|
end
|
112
136
|
end
|
data/lib/overcommit/utils.rb
CHANGED
@@ -211,6 +211,42 @@ module Overcommit
|
|
211
211
|
Subprocess.spawn_detached(args)
|
212
212
|
end
|
213
213
|
|
214
|
+
# Return the number of processors used by the OS for process scheduling.
|
215
|
+
#
|
216
|
+
# @see https://github.com/grosser/parallel/blob/v1.6.1/lib/parallel/processor_count.rb#L17-L51
|
217
|
+
def processor_count # rubocop:disable all
|
218
|
+
@processor_count ||=
|
219
|
+
begin
|
220
|
+
if Overcommit::OS.windows?
|
221
|
+
require 'win32ole'
|
222
|
+
result = WIN32OLE.connect('winmgmts://').ExecQuery(
|
223
|
+
'select NumberOfLogicalProcessors from Win32_Processor')
|
224
|
+
result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
|
225
|
+
elsif File.readable?('/proc/cpuinfo')
|
226
|
+
IO.read('/proc/cpuinfo').scan(/^processor/).size
|
227
|
+
elsif File.executable?('/usr/bin/hwprefs')
|
228
|
+
IO.popen('/usr/bin/hwprefs thread_count').read.to_i
|
229
|
+
elsif File.executable?('/usr/sbin/psrinfo')
|
230
|
+
IO.popen('/usr/sbin/psrinfo').read.scan(/^.*on-*line/).size
|
231
|
+
elsif File.executable?('/usr/sbin/ioscan')
|
232
|
+
IO.popen('/usr/sbin/ioscan -kC processor') do |out|
|
233
|
+
out.read.scan(/^.*processor/).size
|
234
|
+
end
|
235
|
+
elsif File.executable?('/usr/sbin/pmcycles')
|
236
|
+
IO.popen('/usr/sbin/pmcycles -m').read.count("\n")
|
237
|
+
elsif File.executable?('/usr/sbin/lsdev')
|
238
|
+
IO.popen('/usr/sbin/lsdev -Cc processor -S 1').read.count("\n")
|
239
|
+
elsif File.executable?('/usr/sbin/sysctl')
|
240
|
+
IO.popen('/usr/sbin/sysctl -n hw.ncpu').read.to_i
|
241
|
+
elsif File.executable?('/sbin/sysctl')
|
242
|
+
IO.popen('/sbin/sysctl -n hw.ncpu').read.to_i
|
243
|
+
else
|
244
|
+
# Unknown platform; assume 1 processor
|
245
|
+
1
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
214
250
|
# Calls a block of code with a modified set of environment variables,
|
215
251
|
# restoring them once the code has executed.
|
216
252
|
def with_environment(env)
|
data/lib/overcommit/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: overcommit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.32.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brigade Engineering
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-01-
|
12
|
+
date: 2016-01-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: childprocess
|
@@ -263,9 +263,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
263
263
|
version: 1.9.3
|
264
264
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
265
265
|
requirements:
|
266
|
-
- - "
|
266
|
+
- - ">"
|
267
267
|
- !ruby/object:Gem::Version
|
268
|
-
version:
|
268
|
+
version: 1.3.1
|
269
269
|
requirements: []
|
270
270
|
rubyforge_project:
|
271
271
|
rubygems_version: 2.4.5.1
|