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,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)