overcommit 0.30.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6b041ebaa80bcfe3013027b461fd01453c6b9206
4
- data.tar.gz: aa64ece4843d4d6153850d9e8e2a3bc14399257c
3
+ metadata.gz: b45a46c1290a6ddd0fd418f28012e7138f01470e
4
+ data.tar.gz: 1443470dae06fb17b84fe99823d8e2c655bdb792
5
5
  SHA512:
6
- metadata.gz: c2c6d61a99022f38a43bcaa286bb5907fda839325d0334112450e45226b52b51b3b52841121ef767dbabaed8bec3d595a8129c9fabbee2691eff8f994fea9a68
7
- data.tar.gz: c36a4c528cb64b8387caeeedee2dda72207da0d42fc4c92b849fd9d9c0e7ee5bfa2a3a998165fdea66a4d7c91632dcc5521b2e7d47cffc3550910480e99ad446
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).
@@ -324,6 +331,13 @@ PreCommit:
324
331
  flags: ['-IHnE', "^[^#]*((\\bpath:)|(:path[ \t]*=>))"]
325
332
  include: '**/Gemfile'
326
333
 
334
+ Mdl:
335
+ enabled: false
336
+ description: 'Analyzing with mdl'
337
+ required_executable: 'mdl'
338
+ install_command: 'gem install mdl'
339
+ include: '**/*.md'
340
+
327
341
  MergeConflicts:
328
342
  enabled: true
329
343
  description: 'Checking for merge conflicts'
@@ -392,6 +406,7 @@ PreCommit:
392
406
  enabled: false
393
407
  description: 'Analyzing with RailsBestPractices'
394
408
  required_executable: 'rails_best_practices'
409
+ flags: ['--without-color']
395
410
  install_command: 'gem install rails_best_practices'
396
411
 
397
412
  RailsSchemaUpToDate:
@@ -13,13 +13,15 @@ module Overcommit
13
13
  def initialize(hash, options = {})
14
14
  @options = options.dup
15
15
  @options[:logger] ||= Overcommit::Logger.silent
16
- @hash = Overcommit::ConfigurationValidator.new.validate(hash, options)
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)
20
23
  super || @hash == other.hash
21
24
  end
22
- alias_method :eql?, :==
23
25
 
24
26
  # Access the configuration as if it were a hash.
25
27
  #
@@ -35,6 +37,26 @@ module Overcommit
35
37
  File.join(Overcommit::Utils.repo_root, @hash['plugin_directory'] || '.git-hooks')
36
38
  end
37
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
+
38
60
  # Returns configuration for all hooks in each hook type.
39
61
  #
40
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)
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Global application constants.
2
4
  module Overcommit
3
- HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
4
- CONFIG_FILE_NAME = '.overcommit.yml'
5
+ HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
6
+ CONFIG_FILE_NAME = '.overcommit.yml'.freeze
5
7
 
6
- HOOK_DIRECTORY = File.join(HOME, 'lib', 'overcommit', 'hook')
8
+ HOOK_DIRECTORY = File.join(HOME, 'lib', 'overcommit', 'hook').freeze
7
9
 
8
- REPO_URL = 'https://github.com/brigade/overcommit'
9
- BUG_REPORT_URL = "#{REPO_URL}/issues"
10
+ REPO_URL = 'https://github.com/brigade/overcommit'.freeze
11
+ BUG_REPORT_URL = "#{REPO_URL}/issues".freeze
10
12
  end
@@ -11,6 +11,9 @@ module Overcommit::Exceptions
11
11
  # Raised when there was a problem reading submodule information for a repo.
12
12
  class GitSubmoduleError < StandardError; end
13
13
 
14
+ # Raised when there was a problem reading git revision information with `rev-list`.
15
+ class GitRevListError < StandardError; end
16
+
14
17
  # Raised when a {HookContext} is unable to setup the environment before a run.
15
18
  class HookSetupFailed < StandardError; end
16
19
 
@@ -11,7 +11,7 @@ module Overcommit::Hook
11
11
  end
12
12
 
13
13
  # Possible types of messages.
14
- MESSAGE_TYPES = [:error, :warning]
14
+ MESSAGE_TYPES = [:error, :warning].freeze
15
15
 
16
16
  # Functionality common to all hooks.
17
17
  class Base # rubocop:disable Metrics/ClassLength
@@ -61,6 +61,14 @@ module Overcommit::Hook
61
61
  @config['required']
62
62
  end
63
63
 
64
+ def parallelize?
65
+ @config['parallelize'] != false
66
+ end
67
+
68
+ def processors
69
+ @config.fetch('processors', 1)
70
+ end
71
+
64
72
  def quiet?
65
73
  @config['quiet']
66
74
  end
@@ -5,7 +5,7 @@ module Overcommit::Hook::CommitMsg
5
5
  return :pass if empty_message?
6
6
 
7
7
  first_letter = commit_message_lines[0].to_s.match(/^[[:punct:]]*(.)/)[1]
8
- unless first_letter.match(/[[:upper:]]/)
8
+ unless first_letter =~ /[[:upper:]]/
9
9
  return :warn, 'Subject should start with a capital letter'
10
10
  end
11
11
 
@@ -40,7 +40,9 @@ module Overcommit::Hook::PreCommit
40
40
  end
41
41
 
42
42
  def extract_file(match, message)
43
- if match[:file].nil? || match[:file].empty?
43
+ return unless match.names.include?('file')
44
+
45
+ if match[:file].to_s.empty?
44
46
  raise "Unexpected output: no file found in '#{message}'"
45
47
  end
46
48
 
@@ -48,6 +50,7 @@ module Overcommit::Hook::PreCommit
48
50
  end
49
51
 
50
52
  def extract_line(match, message)
53
+ return unless match.names.include?('line')
51
54
  Integer(match[:line])
52
55
  rescue ArgumentError, TypeError
53
56
  raise "Unexpected output: invalid line number found in '#{message}'"
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Overcommit::Hook::PreCommit
2
4
  # Check if local Berksfile.lock matches Berksfile when either changes, unless
3
5
  # Berksfile.lock is ignored by git.
4
6
  #
5
7
  # @see http://berkshelf.com/
6
8
  class BerksfileCheck < Base
7
- LOCK_FILE = 'Berksfile.lock'
9
+ LOCK_FILE = 'Berksfile.lock'.freeze
8
10
 
9
11
  def run
10
12
  # Ignore if Berksfile.lock is not tracked by git
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Overcommit::Hook::PreCommit
2
4
  # Check if local Gemfile.lock matches Gemfile when either changes, unless
3
5
  # Gemfile.lock is ignored by git.
4
6
  #
5
7
  # @see http://bundler.io/
6
8
  class BundleCheck < Base
7
- LOCK_FILE = 'Gemfile.lock'
9
+ LOCK_FILE = 'Gemfile.lock'.freeze
8
10
 
9
11
  def run
10
12
  # Ignore if Gemfile.lock is not tracked by git
@@ -0,0 +1,23 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `mdl` against any modified Markdown files
3
+ #
4
+ # @see https://github.com/mivok/markdownlint
5
+ class Mdl < Base
6
+ MESSAGE_REGEX = /^(?<file>(?:\w:)?[^:]+):(?<line>\d+)/
7
+
8
+ def run
9
+ result = execute(command, args: applicable_files)
10
+ output = result.stdout.chomp
11
+
12
+ return :pass if result.success?
13
+ return [:fail, result.stderr] unless result.stderr.empty?
14
+
15
+ # example message:
16
+ # path/to/file.md:1: MD001 Error message
17
+ extract_messages(
18
+ output.split("\n"),
19
+ MESSAGE_REGEX
20
+ )
21
+ end
22
+ end
23
+ end
@@ -3,20 +3,31 @@ module Overcommit::Hook::PreCommit
3
3
  #
4
4
  # @see http://batsov.com/rubocop/
5
5
  class RuboCop < Base
6
- MESSAGE_TYPE_CATEGORIZER = lambda do |type|
6
+ GENERIC_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
7
+ type =~ /^warn/ ? :warning : :error
8
+ end
9
+
10
+ COP_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
7
11
  type.include?('W') ? :warning : :error
8
12
  end
9
13
 
10
14
  def run
11
15
  result = execute(command, args: applicable_files)
12
16
  return :pass if result.success?
13
- return [:fail, result.stderr] unless result.stderr.empty?
14
17
 
15
- extract_messages(
18
+ generic_messages = extract_messages(
19
+ result.stderr.split("\n"),
20
+ /^(?<type>[a-z]+)/i,
21
+ GENERIC_MESSAGE_TYPE_CATEGORIZER,
22
+ )
23
+
24
+ cop_messages = extract_messages(
16
25
  result.stdout.split("\n"),
17
26
  /^(?<file>(?:\w:)?[^:]+):(?<line>\d+):[^ ]+ (?<type>[^ ]+)/,
18
- MESSAGE_TYPE_CATEGORIZER,
27
+ COP_MESSAGE_TYPE_CATEGORIZER,
19
28
  )
29
+
30
+ generic_messages + cop_messages
20
31
  end
21
32
  end
22
33
  end
@@ -13,15 +13,22 @@ module Overcommit::Hook::PrePush
13
13
 
14
14
  private
15
15
 
16
- def branches
17
- @branches ||= config['branches']
18
- end
19
-
20
16
  def illegal_pushes
21
17
  @illegal_pushes ||= pushed_refs.select do |pushed_ref|
22
- (pushed_ref.deleted? || pushed_ref.forced?) &&
23
- branches.any? { |branch| pushed_ref.remote_ref == "refs/heads/#{branch}" }
18
+ protected?(pushed_ref.remote_ref) && pushed_ref.destructive?
24
19
  end
25
20
  end
21
+
22
+ def protected?(remote_ref)
23
+ ref_name = remote_ref[%r{refs/heads/(.*)}, 1]
24
+ protected_branch_patterns.any? do |pattern|
25
+ File.fnmatch(pattern, ref_name)
26
+ end
27
+ end
28
+
29
+ def protected_branch_patterns
30
+ @protected_branch_patterns ||= Array(config['branches']).
31
+ concat(Array(config['branch_patterns']))
32
+ end
26
33
  end
27
34
  end
@@ -30,6 +30,10 @@ module Overcommit::HookContext
30
30
  local_sha1 == '0' * 40
31
31
  end
32
32
 
33
+ def destructive?
34
+ deleted? || forced?
35
+ end
36
+
33
37
  def to_s
34
38
  "#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}"
35
39
  end
@@ -37,7 +41,14 @@ module Overcommit::HookContext
37
41
  private
38
42
 
39
43
  def overwritten_commits
40
- `git rev-list #{remote_sha1} ^#{local_sha1}`.split("\n")
44
+ return @overwritten_commits if defined? @overwritten_commits
45
+ result = Overcommit::Subprocess.spawn(%W[git rev-list #{remote_sha1} ^#{local_sha1}])
46
+ if result.success?
47
+ result.stdout.split("\n")
48
+ else
49
+ raise Overcommit::Exceptions::GitRevListError,
50
+ "Unable to check if commits on the remote ref will be overwritten: #{result.stderr}"
51
+ end
41
52
  end
42
53
  end
43
54
  end
@@ -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
- interrupted = false
55
- run_failed = false
56
- run_warned = false
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
- run_failed = true if hook_status == :fail
62
- run_warned = true if hook_status == :warn
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(run_failed, run_warned, interrupted)
75
+ print_results
72
76
 
73
- !(run_failed || interrupted)
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
- # @param failed [Boolean]
81
- # @param warned [Boolean]
82
- # @param interrupted [Boolean]
83
- def print_results(failed, warned, interrupted)
84
- if interrupted
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
- # Disable the interrupt handler during individual hook run so that
104
- # Ctrl-C actually stops the current hook from being run, but doesn't
105
- # halt the entire process.
106
- InterruptHandler.disable_until_finished_or_interrupted do
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
- @printer.end_hook(hook, status, output)
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 unless hook.enabled?
171
+ return true if @interrupted || !hook.enabled?
126
172
 
127
173
  if hook.skip?
128
174
  if hook.required?
@@ -5,7 +5,7 @@ module Overcommit
5
5
 
6
6
  # We don't want to include the skip setting as it is set by Overcommit
7
7
  # itself
8
- IGNORED_CONFIG_KEYS = %w[skip]
8
+ IGNORED_CONFIG_KEYS = %w[skip].freeze
9
9
 
10
10
  # @param hook_name [String] name of the hook
11
11
  # @param config [Overcommit::Configuration]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Overcommit
2
4
  # Utility class that encapsulates the handling of hook messages and whether
3
5
  # they affect lines the user has modified or not.
@@ -6,10 +8,12 @@ module Overcommit
6
8
  # output tuple from an array of {Overcommit::Hook::Message}s, respecting the
7
9
  # configuration settings for the given hook.
8
10
  class MessageProcessor
9
- ERRORS_MODIFIED_HEADER = 'Errors on modified lines:'
10
- WARNINGS_MODIFIED_HEADER = 'Warnings on modified lines:'
11
- ERRORS_UNMODIFIED_HEADER = "Errors on lines you didn't modify:"
12
- WARNINGS_UNMODIFIED_HEADER = "Warnings on lines you didn't modify:"
11
+ ERRORS_MODIFIED_HEADER = 'Errors on modified lines:'.freeze
12
+ WARNINGS_MODIFIED_HEADER = 'Warnings on modified lines:'.freeze
13
+ ERRORS_UNMODIFIED_HEADER = "Errors on lines you didn't modify:".freeze
14
+ WARNINGS_UNMODIFIED_HEADER = "Warnings on lines you didn't modify:".freeze
15
+ ERRORS_GENERIC_HEADER = 'Errors:'.freeze
16
+ WARNINGS_GENERIC_HEADER = 'Warnings:'.freeze
13
17
 
14
18
  # @param hook [Overcommit::Hook::Base]
15
19
  # @param unmodified_lines_setting [String] how to treat messages on
@@ -40,10 +44,19 @@ module Overcommit
40
44
  def handle_modified_lines(messages, status)
41
45
  messages = remove_ignored_messages(messages)
42
46
 
43
- messages_on_modified_lines, messages_on_unmodified_lines =
44
- messages.partition { |message| message_on_modified_line?(message) }
47
+ messages_with_line, generic_messages = messages.partition(&:line)
45
48
 
49
+ # Always print generic messages first
46
50
  output = print_messages(
51
+ generic_messages,
52
+ ERRORS_GENERIC_HEADER,
53
+ WARNINGS_GENERIC_HEADER
54
+ )
55
+
56
+ messages_on_modified_lines, messages_on_unmodified_lines =
57
+ messages_with_line.partition { |message| message_on_modified_line?(message) }
58
+
59
+ output += print_messages(
47
60
  messages_on_modified_lines,
48
61
  ERRORS_MODIFIED_HEADER,
49
62
  WARNINGS_MODIFIED_HEADER
@@ -54,7 +67,7 @@ module Overcommit
54
67
  WARNINGS_UNMODIFIED_HEADER
55
68
  )
56
69
 
57
- [transform_status(status, messages_on_modified_lines), output]
70
+ [transform_status(status, generic_messages + messages_on_modified_lines), output]
58
71
  end
59
72
 
60
73
  def transform_status(status, messages_on_modified_lines)
data/lib/overcommit/os.rb CHANGED
@@ -27,10 +27,10 @@ module Overcommit
27
27
  private
28
28
 
29
29
  def host_os
30
- @os ||= ::RbConfig::CONFIG['host_os']
30
+ @os ||= ::RbConfig::CONFIG['host_os'].freeze
31
31
  end
32
32
  end
33
33
 
34
- SEPARATOR = self.windows? ? '\\' : File::SEPARATOR
34
+ SEPARATOR = (windows? ? '\\' : File::SEPARATOR).freeze
35
35
  end
36
36
  end
@@ -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? && status != :pass
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
@@ -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)
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Defines the gem version.
2
4
  module Overcommit
3
- VERSION = '0.30.0'
5
+ VERSION = '0.32.0.rc1'.freeze
4
6
  end
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
@@ -30,7 +30,15 @@ require 'yaml'
30
30
  # rubocop:disable Style/RescueModifier
31
31
  if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
32
32
  ENV['BUNDLE_GEMFILE'] = gemfile
33
- require 'bundler/setup'
33
+ require 'bundler'
34
+
35
+ begin
36
+ Bundler.setup
37
+ rescue Bundler::BundlerError => ex
38
+ puts "Problem loading '#{gemfile}': #{ex.message}"
39
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound)
40
+ exit 78 # EX_CONFIG
41
+ end
34
42
  end
35
43
  # rubocop:enable Style/RescueModifier
36
44
 
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.30.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: 2015-12-24 00:00:00.000000000 Z
12
+ date: 2016-01-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: childprocess
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: 0.5.6
20
+ version: 0.5.8
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: 0.5.6
27
+ version: 0.5.8
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: iniparse
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '1.4'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '10.4'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '10.4'
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: rspec
44
58
  requirement: !ruby/object:Gem::Requirement
@@ -155,6 +169,7 @@ files:
155
169
  - lib/overcommit/hook/pre_commit/jsl.rb
156
170
  - lib/overcommit/hook/pre_commit/json_syntax.rb
157
171
  - lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb
172
+ - lib/overcommit/hook/pre_commit/mdl.rb
158
173
  - lib/overcommit/hook/pre_commit/merge_conflicts.rb
159
174
  - lib/overcommit/hook/pre_commit/nginx_test.rb
160
175
  - lib/overcommit/hook/pre_commit/pep257.rb
@@ -248,9 +263,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
248
263
  version: 1.9.3
249
264
  required_rubygems_version: !ruby/object:Gem::Requirement
250
265
  requirements:
251
- - - ">="
266
+ - - ">"
252
267
  - !ruby/object:Gem::Version
253
- version: '0'
268
+ version: 1.3.1
254
269
  requirements: []
255
270
  rubyforge_project:
256
271
  rubygems_version: 2.4.5.1