overcommit 0.23.0 → 0.24.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/bin/overcommit +1 -1
  3. data/config/default.yml +154 -18
  4. data/config/starter.yml +3 -3
  5. data/lib/overcommit.rb +2 -1
  6. data/lib/overcommit/cli.rb +11 -8
  7. data/lib/overcommit/configuration.rb +18 -4
  8. data/lib/overcommit/configuration_loader.rb +45 -28
  9. data/lib/overcommit/configuration_validator.rb +33 -1
  10. data/lib/overcommit/constants.rb +5 -3
  11. data/lib/overcommit/exceptions.rb +3 -0
  12. data/lib/overcommit/git_repo.rb +116 -0
  13. data/lib/overcommit/git_version.rb +15 -0
  14. data/lib/overcommit/hook/base.rb +42 -5
  15. data/lib/overcommit/hook/commit_msg/capitalized_subject.rb +13 -0
  16. data/lib/overcommit/hook/commit_msg/spell_check.rb +41 -0
  17. data/lib/overcommit/hook/post_checkout/submodule_status.rb +30 -0
  18. data/lib/overcommit/hook/post_commit/submodule_status.rb +30 -0
  19. data/lib/overcommit/hook/post_merge/submodule_status.rb +30 -0
  20. data/lib/overcommit/hook/post_rewrite/submodule_status.rb +30 -0
  21. data/lib/overcommit/hook/pre_commit/base.rb +2 -2
  22. data/lib/overcommit/hook/pre_commit/bundle_check.rb +1 -1
  23. data/lib/overcommit/hook/pre_commit/case_conflicts.rb +20 -0
  24. data/lib/overcommit/hook/pre_commit/coffee_lint.rb +29 -2
  25. data/lib/overcommit/hook/pre_commit/css_lint.rb +1 -8
  26. data/lib/overcommit/hook/pre_commit/go_lint.rb +8 -2
  27. data/lib/overcommit/hook/pre_commit/go_vet.rb +20 -0
  28. data/lib/overcommit/hook/pre_commit/html_tidy.rb +1 -10
  29. data/lib/overcommit/hook/pre_commit/image_optim.rb +11 -28
  30. data/lib/overcommit/hook/pre_commit/js_lint.rb +18 -0
  31. data/lib/overcommit/hook/pre_commit/jsl.rb +24 -0
  32. data/lib/overcommit/hook/pre_commit/json_syntax.rb +4 -7
  33. data/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb +1 -1
  34. data/lib/overcommit/hook/pre_commit/ruby_lint.rb +19 -0
  35. data/lib/overcommit/hook/pre_commit/scss_lint.rb +8 -1
  36. data/lib/overcommit/hook/pre_commit/w3c_css.rb +4 -18
  37. data/lib/overcommit/hook/pre_commit/w3c_html.rb +4 -18
  38. data/lib/overcommit/hook/pre_commit/xml_syntax.rb +19 -0
  39. data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +4 -8
  40. data/lib/overcommit/hook/pre_push/base.rb +10 -0
  41. data/lib/overcommit/hook/pre_push/protected_branches.rb +27 -0
  42. data/lib/overcommit/hook/pre_push/r_spec.rb +12 -0
  43. data/lib/overcommit/hook/pre_rebase/base.rb +11 -0
  44. data/lib/overcommit/hook_context.rb +2 -2
  45. data/lib/overcommit/hook_context/base.rb +5 -7
  46. data/lib/overcommit/hook_context/pre_commit.rb +66 -16
  47. data/lib/overcommit/hook_context/pre_push.rb +44 -0
  48. data/lib/overcommit/hook_context/pre_rebase.rb +36 -0
  49. data/lib/overcommit/hook_runner.rb +27 -7
  50. data/lib/overcommit/installer.rb +46 -7
  51. data/lib/overcommit/message_processor.rb +3 -0
  52. data/lib/overcommit/printer.rb +8 -12
  53. data/lib/overcommit/subprocess.rb +11 -0
  54. data/lib/overcommit/utils.rb +28 -6
  55. data/lib/overcommit/version.rb +1 -1
  56. data/template-dir/hooks/commit-msg +2 -2
  57. data/template-dir/hooks/overcommit-hook +2 -2
  58. data/template-dir/hooks/post-checkout +2 -2
  59. data/template-dir/hooks/post-commit +2 -2
  60. data/template-dir/hooks/post-merge +2 -2
  61. data/template-dir/hooks/post-rewrite +2 -2
  62. data/template-dir/hooks/pre-commit +2 -2
  63. data/template-dir/hooks/pre-push +81 -0
  64. data/template-dir/hooks/pre-rebase +81 -0
  65. metadata +33 -13
  66. data/lib/overcommit/hook/pre_commit/pry_binding.rb +0 -14
@@ -0,0 +1,15 @@
1
+ # Returns the version of the available git binary.
2
+ #
3
+ # This is intended to be used to conveniently execute code based on a specific
4
+ # git version. Simply compare to a version string:
5
+ #
6
+ # @example
7
+ # if GIT_VERSION <= '1.8.5'
8
+ # ...
9
+ # end
10
+ module Overcommit
11
+ GIT_VERSION = begin
12
+ version = `git --version`.chomp[/[\d\.]+/, 0]
13
+ Overcommit::Utils::Version.new(version)
14
+ end
15
+ end
@@ -39,7 +39,7 @@ module Overcommit::Hook
39
39
  # implement `#run`, and we needed a wrapper step to transform the status
40
40
  # based on any custom configuration.
41
41
  def run_and_transform
42
- if output = check_for_executable
42
+ if output = check_for_requirements
43
43
  status = :fail
44
44
  else
45
45
  status, output = process_hook_return_value(run)
@@ -62,7 +62,7 @@ module Overcommit::Hook
62
62
  # @return [Array<Symbol,String>] tuple of status and output
63
63
  def process_hook_return_value(hook_return_value)
64
64
  if hook_return_value.is_a?(Array) &&
65
- hook_return_value.first.is_a?(Message)
65
+ (hook_return_value.first.is_a?(Message) || hook_return_value.empty?)
66
66
  # Process messages into a status and output
67
67
  Overcommit::MessageProcessor.new(
68
68
  self,
@@ -119,6 +119,10 @@ module Overcommit::Hook
119
119
  @config['required_executable']
120
120
  end
121
121
 
122
+ def required_libraries
123
+ Array(@config['required_library'] || @config['required_libraries'])
124
+ end
125
+
122
126
  # Return command to execute for this hook.
123
127
  #
124
128
  # This is intended to be configurable so hooks can prefix their commands
@@ -179,18 +183,51 @@ module Overcommit::Hook
179
183
  included && !excluded
180
184
  end
181
185
 
186
+ # Check for any required executables or libraries.
187
+ #
188
+ # Returns output if any requirements are not met.
189
+ def check_for_requirements
190
+ check_for_executable || check_for_libraries
191
+ end
192
+
182
193
  # If the hook defines a required executable, check if it's in the path and
183
194
  # display the install command if one exists.
184
195
  def check_for_executable
185
196
  return unless required_executable && !in_path?(required_executable)
186
197
 
187
198
  output = "'#{required_executable}' is not installed (or is not in your PATH)"
199
+ output << install_command_prompt
200
+
201
+ output
202
+ end
188
203
 
204
+ def install_command_prompt
189
205
  if install_command = @config['install_command']
190
- output += "\nInstall it by running: #{install_command}"
206
+ "\nInstall it by running: #{install_command}"
207
+ else
208
+ ''
191
209
  end
210
+ end
192
211
 
193
- output
212
+ # If the hook defines required library paths that it wants to load, attempt
213
+ # to load them.
214
+ def check_for_libraries
215
+ output = []
216
+
217
+ required_libraries.each do |library|
218
+ begin
219
+ require library
220
+ rescue LoadError
221
+ install_command = @config['install_command']
222
+ install_command = " -- install via #{install_command}" if install_command
223
+
224
+ output << "Unable to load '#{library}'#{install_command}"
225
+ end
226
+ end
227
+
228
+ return if output.empty?
229
+
230
+ output.join("\n")
194
231
  end
195
232
 
196
233
  # Transforms the hook's status based on custom configuration.
@@ -198,7 +235,7 @@ module Overcommit::Hook
198
235
  # This allows users to change failures into warnings, or vice versa.
199
236
  def transform_status(status)
200
237
  case status
201
- when :fail, :bad
238
+ when :fail
202
239
  @config.fetch('on_fail', :fail).to_sym
203
240
  when :warn
204
241
  @config.fetch('on_warn', :warn).to_sym
@@ -0,0 +1,13 @@
1
+ module Overcommit::Hook::CommitMsg
2
+ # Ensures commit message subject lines start with a capital letter.
3
+ class CapitalizedSubject < Base
4
+ def run
5
+ first_letter = commit_message_lines[0].to_s.match(/^[[:punct:]]*(.)/)[1]
6
+ unless first_letter.match(/[[:upper:]]/)
7
+ return :warn, 'Subject should start with a capital letter'
8
+ end
9
+
10
+ :pass
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ require 'tempfile'
2
+
3
+ module Overcommit::Hook::CommitMsg
4
+ # Checks the commit message for potential misspellings
5
+ class SpellCheck < Base
6
+ Misspelling = Struct.new(:word, :suggestions)
7
+
8
+ MISSPELLING_REGEX = /^[&#]\s(?<word>\w+)(?:.+?:\s(?<suggestions>.*))?/
9
+
10
+ def run
11
+ result = execute(command + [uncommented_commit_msg_file])
12
+ return [:fail, "Error running spellcheck: #{result.stderr.chomp}"] unless result.success?
13
+
14
+ misspellings = parse_misspellings(result.stdout)
15
+ return :pass if misspellings.empty?
16
+
17
+ messages = misspellings.map do |misspelled|
18
+ msg = "Potential misspelling: #{misspelled.word}."
19
+ msg += " Suggestions: #{misspelled.suggestions}" unless misspelled.suggestions.nil?
20
+ msg
21
+ end
22
+
23
+ [:warn, messages.join("\n")]
24
+ end
25
+
26
+ private
27
+
28
+ def uncommented_commit_msg_file
29
+ ::Tempfile.open('commit-msg') do |file|
30
+ file.write(commit_message)
31
+ file.path
32
+ end
33
+ end
34
+
35
+ def parse_misspellings(output)
36
+ output.scan(MISSPELLING_REGEX).map do |word, suggestions|
37
+ Misspelling.new(word, suggestions)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ module Overcommit::Hook::PostCheckout
2
+ # Checks the status of submodules in the current repository and
3
+ # notifies the user if any are uninitialized, out of date with
4
+ # the current index, or contain merge conflicts.
5
+ class SubmoduleStatus < Base
6
+ def run
7
+ messages = []
8
+ submodule_statuses.each do |submodule_status|
9
+ path = submodule_status.path
10
+ if submodule_status.uninitialized?
11
+ messages << "Submodule #{path} is uninitialized."
12
+ elsif submodule_status.outdated?
13
+ messages << "Submodule #{path} is out of date with the current index."
14
+ elsif submodule_status.merge_conflict?
15
+ messages << "Submodule #{path} has merge conflicts."
16
+ end
17
+ end
18
+
19
+ return :pass if messages.empty?
20
+
21
+ [:warn, messages.join("\n")]
22
+ end
23
+
24
+ private
25
+
26
+ def submodule_statuses
27
+ Overcommit::GitRepo.submodule_statuses(recursive: config['recursive'])
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ module Overcommit::Hook::PostCommit
2
+ # Checks the status of submodules in the current repository and
3
+ # notifies the user if any are uninitialized, out of date with
4
+ # the current index, or contain merge conflicts.
5
+ class SubmoduleStatus < Base
6
+ def run
7
+ messages = []
8
+ submodule_statuses.each do |submodule_status|
9
+ path = submodule_status.path
10
+ if submodule_status.uninitialized?
11
+ messages << "Submodule #{path} is uninitialized."
12
+ elsif submodule_status.outdated?
13
+ messages << "Submodule #{path} is out of date with the current index."
14
+ elsif submodule_status.merge_conflict?
15
+ messages << "Submodule #{path} has merge conflicts."
16
+ end
17
+ end
18
+
19
+ return :pass if messages.empty?
20
+
21
+ [:warn, messages.join("\n")]
22
+ end
23
+
24
+ private
25
+
26
+ def submodule_statuses
27
+ Overcommit::GitRepo.submodule_statuses(recursive: config['recursive'])
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ module Overcommit::Hook::PostMerge
2
+ # Checks the status of submodules in the current repository and
3
+ # notifies the user if any are uninitialized, out of date with
4
+ # the current index, or contain merge conflicts.
5
+ class SubmoduleStatus < Base
6
+ def run
7
+ messages = []
8
+ submodule_statuses.each do |submodule_status|
9
+ path = submodule_status.path
10
+ if submodule_status.uninitialized?
11
+ messages << "Submodule #{path} is uninitialized."
12
+ elsif submodule_status.outdated?
13
+ messages << "Submodule #{path} is out of date with the current index."
14
+ elsif submodule_status.merge_conflict?
15
+ messages << "Submodule #{path} has merge conflicts."
16
+ end
17
+ end
18
+
19
+ return :pass if messages.empty?
20
+
21
+ [:warn, messages.join("\n")]
22
+ end
23
+
24
+ private
25
+
26
+ def submodule_statuses
27
+ Overcommit::GitRepo.submodule_statuses(recursive: config['recursive'])
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ module Overcommit::Hook::PostRewrite
2
+ # Checks the status of submodules in the current repository and
3
+ # notifies the user if any are uninitialized, out of date with
4
+ # the current index, or contain merge conflicts.
5
+ class SubmoduleStatus < Base
6
+ def run
7
+ messages = []
8
+ submodule_statuses.each do |submodule_status|
9
+ path = submodule_status.path
10
+ if submodule_status.uninitialized?
11
+ messages << "Submodule #{path} is uninitialized."
12
+ elsif submodule_status.outdated?
13
+ messages << "Submodule #{path} is out of date with the current index."
14
+ elsif submodule_status.merge_conflict?
15
+ messages << "Submodule #{path} has merge conflicts."
16
+ end
17
+ end
18
+
19
+ return :pass if messages.empty?
20
+
21
+ [:warn, messages.join("\n")]
22
+ end
23
+
24
+ private
25
+
26
+ def submodule_statuses
27
+ Overcommit::GitRepo.submodule_statuses(recursive: config['recursive'])
28
+ end
29
+ end
30
+ end
@@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit
5
5
  class Base < Overcommit::Hook::Base
6
6
  extend Forwardable
7
7
 
8
- def_delegators :@context, :modified_lines_in_file
8
+ def_delegators :@context, :modified_lines_in_file, :amendment?
9
9
 
10
10
  private
11
11
 
@@ -32,7 +32,7 @@ module Overcommit::Hook::PreCommit
32
32
  end
33
33
 
34
34
  file = extract_file(match, message)
35
- line = extract_line(match, message)
35
+ line = extract_line(match, message) unless match[:line].nil?
36
36
  type = extract_type(match, message, type_categorizer)
37
37
 
38
38
  Overcommit::Hook::Message.new(type, file, line, message)
@@ -16,7 +16,7 @@ module Overcommit::Hook::PreCommit
16
16
 
17
17
  result = execute(%w[git diff --quiet --] + [LOCK_FILE])
18
18
  unless result.success?
19
- return :fail, "#{LOCK_FILE} is not up-to-date -- run `#{command.join(' ')} check`"
19
+ return :fail, "#{LOCK_FILE} is not up-to-date -- run `#{command.join(' ')}`"
20
20
  end
21
21
 
22
22
  :pass
@@ -0,0 +1,20 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Checks for files that would conflict in case-insensitive filesystems
3
+ # Adapted from https://github.com/pre-commit/pre-commit-hooks
4
+ class CaseConflicts < Base
5
+ def run
6
+ paths = Set.new(applicable_files.map { |file| File.dirname(file) + File::SEPARATOR })
7
+ repo_files = Set.new(Overcommit::GitRepo.list_files(paths.to_a) + applicable_files)
8
+ conflict_hash = repo_files.classify(&:downcase).
9
+ select { |_, files| files.size > 1 }
10
+ conflict_files = applicable_files.
11
+ select { |file| conflict_hash.include?(file.downcase) }
12
+
13
+ conflict_files.map do |file|
14
+ conflicts = conflict_hash[file.downcase].map { |f| File.basename(f) }
15
+ msg = "Conflict detected for case-insensitive file systems: #{conflicts.join(', ')}"
16
+ Overcommit::Hook::Message.new(:error, file, nil, msg)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -3,9 +3,36 @@ module Overcommit::Hook::PreCommit
3
3
  class CoffeeLint < Base
4
4
  def run
5
5
  result = execute(command + applicable_files)
6
- return :pass if result.success?
7
6
 
8
- [:fail, result.stdout]
7
+ begin
8
+ parse_json_messages(result.stdout)
9
+ rescue JSON::ParserError => e
10
+ [:fail, "Error parsing coffeelint output: #{e.message}"]
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def parse_json_messages(output)
17
+ JSON.parse(output).collect do |file, messages|
18
+ messages.collect { |msg| extract_message(file, msg) }
19
+ end.flatten
20
+ end
21
+
22
+ def extract_message(file, message_hash)
23
+ type = message_hash['level'].include?('w') ? :warning : :error
24
+ line = message_hash['lineNumber']
25
+ rule = message_hash['rule']
26
+ msg = message_hash['message']
27
+ text =
28
+ if rule == 'coffeescript_error'
29
+ # Syntax errors are output in different format.
30
+ # Splice in the file name and grab the first line.
31
+ msg.sub('[stdin]', file).split("\n")[0]
32
+ else
33
+ "#{file}:#{line}: #{msg} (#{rule})"
34
+ end
35
+ Overcommit::Hook::Message.new(type, file, line, text)
9
36
  end
10
37
  end
11
38
  end
@@ -13,17 +13,10 @@ module Overcommit::Hook::PreCommit
13
13
  return :pass if result.success? && output.empty?
14
14
 
15
15
  extract_messages(
16
- output.split("\n").collect(&method(:add_line_number)),
16
+ output.split("\n").reject(&:empty?),
17
17
  MESSAGE_REGEX,
18
18
  lambda { |type| type.downcase.to_sym }
19
19
  )
20
20
  end
21
-
22
- private
23
-
24
- # Hack to include messages that apply to the entire file
25
- def add_line_number(message)
26
- message.sub(/(?<!\d,\s)(Error|Warning)/, 'line 0, \1')
27
- end
28
21
  end
29
22
  end
@@ -3,10 +3,16 @@ module Overcommit::Hook::PreCommit
3
3
  class GoLint < Base
4
4
  def run
5
5
  result = execute(command + applicable_files)
6
+ output = result.stdout + result.stderr
6
7
  # Unfortunately the exit code is always 0
7
- return :pass if result.stdout.empty?
8
+ return :pass if output.empty?
8
9
 
9
- [:fail, result.stdout]
10
+ # example message:
11
+ # path/to/file.go:1:1: Error message
12
+ extract_messages(
13
+ output.split("\n"),
14
+ /^(?<file>[^:]+):(?<line>\d+)/
15
+ )
10
16
  end
11
17
  end
12
18
  end
@@ -0,0 +1,20 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `go vet` against any modified Golang files.
3
+ class GoVet < Base
4
+ def run
5
+ result = execute(command + applicable_files)
6
+ return :pass if result.success?
7
+
8
+ if result.stderr =~ /no such tool "vet"/
9
+ return :fail, "`go tool vet` is not installed#{install_command_prompt}"
10
+ end
11
+
12
+ # example message:
13
+ # path/to/file.go:7: Error message
14
+ extract_messages(
15
+ result.stderr.split("\n"),
16
+ /^(?<file>[^:]+):(?<line>\d+)/
17
+ )
18
+ end
19
+ end
20
+ end
@@ -11,19 +11,10 @@ module Overcommit::Hook::PreCommit
11
11
  def run
12
12
  # example message:
13
13
  # line 4 column 24 - Warning: <html> proprietary attribute "class"
14
- messages = collect_messages
15
-
16
- return :pass if messages.empty?
17
-
18
- messages
19
- end
20
-
21
- private
22
-
23
- def collect_messages
24
14
  applicable_files.collect do |file|
25
15
  result = execute(command + [file])
26
16
  output = result.stderr.chomp
17
+
27
18
  extract_messages(
28
19
  output.split("\n").collect { |msg| "#{file}: #{msg}" },
29
20
  MESSAGE_REGEX,