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,44 @@
1
+ module Overcommit::HookContext
2
+ # Contains helpers related to contextual information used by pre-push hooks.
3
+ class PrePush < Base
4
+ attr_accessor :args
5
+
6
+ def remote_name
7
+ @args[0]
8
+ end
9
+
10
+ def remote_url
11
+ @args[1]
12
+ end
13
+
14
+ def pushed_refs
15
+ input_lines.map do |line|
16
+ PushedRef.new(*line.split(' '))
17
+ end
18
+ end
19
+
20
+ PushedRef = Struct.new(:local_ref, :local_sha1, :remote_ref, :remote_sha1) do
21
+ def forced?
22
+ !(created? || deleted? || overwritten_commits.empty?)
23
+ end
24
+
25
+ def created?
26
+ remote_sha1 == '0' * 40
27
+ end
28
+
29
+ def deleted?
30
+ local_sha1 == '0' * 40
31
+ end
32
+
33
+ def to_s
34
+ "#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}"
35
+ end
36
+
37
+ private
38
+
39
+ def overwritten_commits
40
+ `git rev-list #{remote_sha1} ^#{local_sha1}`.split("\n")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ module Overcommit::HookContext
2
+ # Contains helpers related to contextual information used by pre-rebase
3
+ # hooks.
4
+ class PreRebase < Base
5
+ # Returns the name of the branch we are rebasing onto.
6
+ def upstream_branch
7
+ @args[0]
8
+ end
9
+
10
+ # Returns the name of the branch being rebased. Empty if rebasing a
11
+ # detached HEAD.
12
+ def rebased_branch
13
+ @rebased_branch ||=
14
+ @args[1] || `git symbolic-ref --short --quiet HEAD`.chomp
15
+ end
16
+
17
+ # Returns whether we are rebasing a detached HEAD rather than a branch
18
+ def detached_head?
19
+ rebased_branch.empty?
20
+ end
21
+
22
+ # Returns whether this rebase is a fast-forward
23
+ def fast_forward?
24
+ rebased_commits.empty?
25
+ end
26
+
27
+ # Returns the SHA1-sums of the series of commits to be rebased
28
+ # in reverse topological order.
29
+ def rebased_commits
30
+ rebased_ref = detached_head? ? 'HEAD' : rebased_branch
31
+ @rebased_commits ||=
32
+ `git rev-list --topo-order --reverse #{upstream_branch}..#{rebased_ref}`.
33
+ split("\n")
34
+ end
35
+ end
36
+ end
@@ -22,11 +22,24 @@ module Overcommit
22
22
  # these calls to allow some sort of "are you sure?" double-interrupt
23
23
  # functionality, but until that's deemed necessary let's keep it simple.
24
24
  InterruptHandler.isolate_from_interrupts do
25
- @context.setup_environment
25
+ # Load hooks before setting up the environment so that the repository
26
+ # has not been touched yet. This way any load errors at this point don't
27
+ # result in Overcommit leaving the repository in a bad state.
26
28
  load_hooks
27
- result = run_hooks
28
- @context.cleanup_environment
29
- result
29
+
30
+ # Setup the environment without automatically calling
31
+ # `cleanup_environment` on an error. This is because it's possible that
32
+ # the `setup_environment` code did not fully complete, so there's no
33
+ # guarantee that `cleanup_environment` will be able to accomplish
34
+ # anything of value. The safest thing to do is therefore nothing in the
35
+ # unlikely case of failure.
36
+ @context.setup_environment
37
+
38
+ begin
39
+ run_hooks
40
+ ensure
41
+ @context.cleanup_environment
42
+ end
30
43
  end
31
44
  end
32
45
 
@@ -40,11 +53,13 @@ module Overcommit
40
53
 
41
54
  interrupted = false
42
55
  run_failed = false
56
+ run_warned = false
43
57
 
44
58
  @hooks.each do |hook|
45
59
  hook_status = run_hook(hook)
46
60
 
47
- run_failed = true if [:bad, :fail].include?(hook_status)
61
+ run_failed = true if hook_status == :fail
62
+ run_warned = true if hook_status == :warn
48
63
 
49
64
  if hook_status == :interrupt
50
65
  # Stop running any more hooks and assume a bad result
@@ -53,7 +68,7 @@ module Overcommit
53
68
  end
54
69
  end
55
70
 
56
- print_results(run_failed, interrupted)
71
+ print_results(run_failed, run_warned, interrupted)
57
72
 
58
73
  !(run_failed || interrupted)
59
74
  else
@@ -62,11 +77,16 @@ module Overcommit
62
77
  end
63
78
  end
64
79
 
65
- def print_results(failed, interrupted)
80
+ # @param failed [Boolean]
81
+ # @param warned [Boolean]
82
+ # @param interrupted [Boolean]
83
+ def print_results(failed, warned, interrupted)
66
84
  if interrupted
67
85
  @printer.run_interrupted
68
86
  elsif failed
69
87
  @printer.run_failed
88
+ elsif warned
89
+ @printer.run_warned
70
90
  else
71
91
  @printer.run_succeeded
72
92
  end
@@ -3,7 +3,7 @@ require 'fileutils'
3
3
  module Overcommit
4
4
  # Manages the installation of Overcommit hooks in a git repository.
5
5
  class Installer # rubocop:disable ClassLength
6
- TEMPLATE_DIRECTORY = File.join(OVERCOMMIT_HOME, 'template-dir')
6
+ TEMPLATE_DIRECTORY = File.join(Overcommit::HOME, 'template-dir')
7
7
  MASTER_HOOK = File.join(TEMPLATE_DIRECTORY, 'hooks', 'overcommit-hook')
8
8
 
9
9
  def initialize(logger)
@@ -30,7 +30,8 @@ module Overcommit
30
30
  def install
31
31
  log.log "Installing hooks into #{@target}"
32
32
 
33
- ensure_hooks_directory
33
+ ensure_directory(hooks_path)
34
+ preserve_old_hooks
34
35
  install_master_hook
35
36
  install_hook_symlinks
36
37
  install_starter_config
@@ -41,8 +42,9 @@ module Overcommit
41
42
  def uninstall
42
43
  log.log "Removing hooks from #{@target}"
43
44
 
44
- uninstall_master_hook
45
45
  uninstall_hook_symlinks
46
+ uninstall_master_hook
47
+ restore_old_hooks
46
48
 
47
49
  log.success "Successfully removed hooks from #{@target}"
48
50
  end
@@ -50,6 +52,7 @@ module Overcommit
50
52
  # @return [true,false] whether the hooks were updated
51
53
  def update
52
54
  unless FileUtils.compare_file(MASTER_HOOK, master_hook_install_path)
55
+ preserve_old_hooks
53
56
  install_master_hook
54
57
  install_hook_symlinks
55
58
 
@@ -63,12 +66,16 @@ module Overcommit
63
66
  File.join(Overcommit::Utils.git_dir(absolute_target), 'hooks')
64
67
  end
65
68
 
69
+ def old_hooks_path
70
+ File.join(hooks_path, 'old-hooks')
71
+ end
72
+
66
73
  def master_hook_install_path
67
74
  File.join(hooks_path, 'overcommit-hook')
68
75
  end
69
76
 
70
- def ensure_hooks_directory
71
- FileUtils.mkdir_p(hooks_path)
77
+ def ensure_directory(path)
78
+ FileUtils.mkdir_p(path)
72
79
  end
73
80
 
74
81
  def validate_target
@@ -118,6 +125,38 @@ module Overcommit
118
125
  overcommit_hook?(file)
119
126
  end
120
127
 
128
+ def preserve_old_hooks
129
+ return unless File.directory?(hooks_path)
130
+
131
+ ensure_directory(old_hooks_path)
132
+ Overcommit::Utils.supported_hook_types.each do |hook_type|
133
+ hook_file = File.join(hooks_path, hook_type)
134
+ unless can_replace_file?(hook_file)
135
+ log.warning "Hook '#{File.expand_path(hook_type)}' already exists and " \
136
+ "was not installed by Overcommit. Moving to '#{old_hooks_path}'"
137
+ FileUtils.mv(hook_file, old_hooks_path)
138
+ end
139
+ end
140
+ # Remove old-hooks directory if empty
141
+ FileUtils.rmdir(old_hooks_path)
142
+ end
143
+
144
+ def restore_old_hooks
145
+ return unless File.directory?(old_hooks_path)
146
+
147
+ log.log "Restoring old hooks from #{old_hooks_path}"
148
+
149
+ Dir.chdir(old_hooks_path) do
150
+ Overcommit::Utils.supported_hook_types.each do |hook_type|
151
+ FileUtils.mv(hook_type, hooks_path) if File.exist?(hook_type)
152
+ end
153
+ end
154
+ # Remove old-hooks directory if empty
155
+ FileUtils.rmdir(old_hooks_path)
156
+
157
+ log.success "Successfully restored old hooks from #{old_hooks_path}"
158
+ end
159
+
121
160
  def uninstall_hook_symlinks
122
161
  return unless File.directory?(hooks_path)
123
162
 
@@ -129,10 +168,10 @@ module Overcommit
129
168
  end
130
169
 
131
170
  def install_starter_config
132
- repo_config_file = File.join(@target, OVERCOMMIT_CONFIG_FILE_NAME)
171
+ repo_config_file = File.join(@target, Overcommit::CONFIG_FILE_NAME)
133
172
 
134
173
  return if File.exist?(repo_config_file)
135
- FileUtils.cp(File.join(OVERCOMMIT_HOME, 'config', 'starter.yml'), repo_config_file)
174
+ FileUtils.cp(File.join(Overcommit::HOME, 'config', 'starter.yml'), repo_config_file)
136
175
  end
137
176
 
138
177
  def overcommit_hook?(file)
@@ -126,6 +126,9 @@ module Overcommit
126
126
  end
127
127
 
128
128
  def message_on_modified_line?(message)
129
+ # Message without line number assumed to apply to entire file
130
+ return true unless message.line
131
+
129
132
  @hook.modified_lines_in_file(message.file).include?(message.line)
130
133
  end
131
134
  end
@@ -39,7 +39,7 @@ module Overcommit
39
39
  def end_hook(hook, status, output)
40
40
  # Want to print the header for quiet hooks only if the result wasn't good
41
41
  # so that the user knows what failed
42
- print_header(hook) if hook.quiet? && ![:good, :pass].include?(status)
42
+ print_header(hook) if hook.quiet? && status != :pass
43
43
 
44
44
  print_result(hook, status, output)
45
45
  end
@@ -58,6 +58,13 @@ module Overcommit
58
58
  log.newline
59
59
  end
60
60
 
61
+ # Executed when no hooks failed by the end of the run, but some warned.
62
+ def run_warned
63
+ log.newline
64
+ log.warning "⚠ All #{hook_script_name} hooks passed, but with warnings"
65
+ log.newline
66
+ end
67
+
61
68
  # Executed when no hooks failed by the end of the run.
62
69
  def run_succeeded
63
70
  log.newline
@@ -78,20 +85,9 @@ module Overcommit
78
85
  case status
79
86
  when :pass
80
87
  log.success 'OK' unless hook.quiet?
81
- when :good
82
- log.success 'OK'
83
- log.bold_error 'Hook returned a status of `:good`. This is deprecated ' \
84
- 'in favor of `:pass` and will be removed in a future ' \
85
- 'version of Overcommit'
86
88
  when :warn
87
89
  log.warning 'WARNING'
88
90
  print_report(output, :bold_warning)
89
- when :bad
90
- log.error 'FAILED'
91
- log.bold_error 'Hook returned a status of `:bad`. This is deprecated ' \
92
- 'in favor of `:fail` and will be removed in a future ' \
93
- 'version of Overcommit'
94
- print_report(output, :bold_error)
95
91
  when :fail
96
92
  log.error 'FAILED'
97
93
  print_report(output, :bold_error)
@@ -29,6 +29,17 @@ module Overcommit
29
29
  Result.new(process.exit_code, out.read, err.read)
30
30
  end
31
31
 
32
+ # Spawns a new process in the background using the given array of
33
+ # arguments (the first element is the command).
34
+ def spawn_detached(args)
35
+ process = ChildProcess.build(*args)
36
+ process.detach = true
37
+
38
+ assign_output_streams(process)
39
+
40
+ process.start
41
+ end
42
+
32
43
  private
33
44
 
34
45
  # @param process [ChildProcess]
@@ -4,9 +4,27 @@ require 'overcommit/subprocess'
4
4
  module Overcommit
5
5
  # Utility functions for general use.
6
6
  module Utils
7
+ # Helper class for doing quick constraint validations on version numbers.
8
+ #
9
+ # This allows us to execute code based on the git version.
10
+ class Version < Gem::Version
11
+ # Overload comparison operators so we can conveniently compare this
12
+ # version directly to a string in code.
13
+ %w[< <= > >= == !=].each do |operator|
14
+ define_method operator do |version|
15
+ case version
16
+ when String
17
+ super(Gem::Version.new(version))
18
+ else
19
+ super(version)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
7
25
  class << self
8
26
  def script_path(script)
9
- File.join(OVERCOMMIT_HOME, 'libexec', script)
27
+ File.join(Overcommit::HOME, 'libexec', script)
10
28
  end
11
29
 
12
30
  # Returns an absolute path to the root of the repository.
@@ -71,9 +89,9 @@ module Overcommit
71
89
 
72
90
  # Returns a list of supported hook types (pre-commit, commit-msg, etc.)
73
91
  def supported_hook_types
74
- Dir[File.join(OVERCOMMIT_HOME, 'lib', 'overcommit', 'hook', '*')].
92
+ Dir[File.join(HOOK_DIRECTORY, '*')].
75
93
  select { |file| File.directory?(file) }.
76
- map { |file| File.basename(file, '.rb').gsub('_', '-') }
94
+ map { |file| File.basename(file).gsub('_', '-') }
77
95
  end
78
96
 
79
97
  # Returns a list of supported hook classes (PreCommit, CommitMsg, etc.)
@@ -101,6 +119,11 @@ module Overcommit
101
119
  false
102
120
  end
103
121
 
122
+ # Return the parent command that triggered this hook run
123
+ def parent_command
124
+ `ps -ocommand= -p #{Process.ppid}`.chomp
125
+ end
126
+
104
127
  # Execute a command in a subprocess, capturing exit status and output from
105
128
  # both standard and error streams.
106
129
  #
@@ -124,15 +147,14 @@ module Overcommit
124
147
  # which we do not need to know the result.
125
148
  #
126
149
  # @param args [Array<String>]
127
- # @return [Thread] thread watching the resulting child process
150
+ # @return [ChildProcess] detached process spawned in the background
128
151
  def execute_in_background(args)
129
152
  if args.include?('|')
130
153
  raise Overcommit::Exceptions::InvalidCommandArgs,
131
154
  'Cannot pipe commands with the `execute_in_background` helper'
132
155
  end
133
156
 
134
- # Dissociate process from parent's input/output streams
135
- Process.detach(Process.spawn({}, *args, [:in, :out, :err] => '/dev/null'))
157
+ Subprocess.spawn_detached(args)
136
158
  end
137
159
 
138
160
  # Calls a block of code with a modified set of environment variables,
@@ -1,4 +1,4 @@
1
1
  # Defines the gem version.
2
2
  module Overcommit
3
- VERSION = '0.23.0'
3
+ VERSION = '0.24.0'
4
4
  end
@@ -43,9 +43,9 @@ begin
43
43
  exec($0, *ARGV) # Execute the updated hook with all original arguments
44
44
  end
45
45
 
46
- config = Overcommit::ConfigurationLoader.load_repo_config
46
+ config = Overcommit::ConfigurationLoader.new(logger).load_repo_config
47
47
 
48
- context = Overcommit::HookContext.create(hook_type, config, ARGV)
48
+ context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN)
49
49
  config.apply_environment!(context, ENV)
50
50
 
51
51
  printer = Overcommit::Printer.new(logger, context)
@@ -43,9 +43,9 @@ begin
43
43
  exec($0, *ARGV) # Execute the updated hook with all original arguments
44
44
  end
45
45
 
46
- config = Overcommit::ConfigurationLoader.load_repo_config
46
+ config = Overcommit::ConfigurationLoader.new(logger).load_repo_config
47
47
 
48
- context = Overcommit::HookContext.create(hook_type, config, ARGV)
48
+ context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN)
49
49
  config.apply_environment!(context, ENV)
50
50
 
51
51
  printer = Overcommit::Printer.new(logger, context)
@@ -43,9 +43,9 @@ begin
43
43
  exec($0, *ARGV) # Execute the updated hook with all original arguments
44
44
  end
45
45
 
46
- config = Overcommit::ConfigurationLoader.load_repo_config
46
+ config = Overcommit::ConfigurationLoader.new(logger).load_repo_config
47
47
 
48
- context = Overcommit::HookContext.create(hook_type, config, ARGV)
48
+ context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN)
49
49
  config.apply_environment!(context, ENV)
50
50
 
51
51
  printer = Overcommit::Printer.new(logger, context)