overcommit 0.23.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
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,