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
data/config/starter.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # Use this file to configure the Overcommit hooks you wish to use. This will
2
2
  # extend the default configuration defined in:
3
- # https://github.com/causes/overcommit/blob/master/config/default.yml
3
+ # https://github.com/brigade/overcommit/blob/master/config/default.yml
4
4
  #
5
5
  # At the topmost level of this YAML file is a key representing type of hook
6
6
  # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
@@ -8,10 +8,10 @@
8
8
  # `include`), whether to only display output if it fails (via `quiet`), etc.
9
9
  #
10
10
  # For a complete list of hooks, see:
11
- # https://github.com/causes/overcommit/tree/master/lib/overcommit/hook
11
+ # https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook
12
12
  #
13
13
  # For a complete list of options that you can use to customize hooks, see:
14
- # https://github.com/causes/overcommit#configuration
14
+ # https://github.com/brigade/overcommit#configuration
15
15
  #
16
16
  # Uncomment the following lines to make the configuration take effect.
17
17
 
data/lib/overcommit.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'overcommit/constants'
2
2
  require 'overcommit/exceptions'
3
+ require 'overcommit/utils'
4
+ require 'overcommit/git_version'
3
5
  require 'overcommit/configuration_validator'
4
6
  require 'overcommit/configuration'
5
7
  require 'overcommit/configuration_loader'
@@ -16,5 +18,4 @@ require 'overcommit/printer'
16
18
  require 'overcommit/hook_runner'
17
19
  require 'overcommit/installer'
18
20
  require 'overcommit/logger'
19
- require 'overcommit/utils'
20
21
  require 'overcommit/version'
@@ -4,8 +4,9 @@ module Overcommit
4
4
  # Responsible for parsing command-line options and executing appropriate
5
5
  # application logic based on those options.
6
6
  class CLI # rubocop:disable ClassLength
7
- def initialize(arguments, logger)
7
+ def initialize(arguments, input, logger)
8
8
  @arguments = arguments
9
+ @input = input
9
10
  @log = logger
10
11
  @options = {}
11
12
  end
@@ -127,7 +128,7 @@ module Overcommit
127
128
  end
128
129
 
129
130
  def print_template_directory_path
130
- puts File.join(OVERCOMMIT_HOME, 'template-dir')
131
+ puts File.join(Overcommit::HOME, 'template-dir')
131
132
  halt
132
133
  end
133
134
 
@@ -145,8 +146,6 @@ module Overcommit
145
146
  # Prints the hooks available in the current repo and whether they're
146
147
  # enabled/disabled.
147
148
  def print_installed_hooks
148
- config = Overcommit::ConfigurationLoader.load_repo_config
149
-
150
149
  config.all_hook_configs.each do |hook_type, hook_configs|
151
150
  print_hooks_for_hook_type(config, hook_configs, hook_type)
152
151
  end
@@ -174,10 +173,10 @@ module Overcommit
174
173
  end
175
174
 
176
175
  def sign_plugins
177
- config = Overcommit::ConfigurationLoader.load_repo_config
178
176
  context = Overcommit::HookContext.create(@options[:hook_to_sign],
179
177
  config,
180
- @arguments)
178
+ @arguments,
179
+ @input)
181
180
  Overcommit::HookLoader::PluginHookLoader.new(config,
182
181
  context,
183
182
  log).update_signatures
@@ -185,8 +184,7 @@ module Overcommit
185
184
  end
186
185
 
187
186
  def run_all
188
- config = Overcommit::ConfigurationLoader.load_repo_config
189
- context = Overcommit::HookContext.create('run-all', config, @arguments)
187
+ context = Overcommit::HookContext.create('run-all', config, @arguments, @input)
190
188
  config.apply_environment!(context, ENV)
191
189
 
192
190
  printer = Overcommit::Printer.new(log, context)
@@ -201,5 +199,10 @@ module Overcommit
201
199
  def halt(status = 0)
202
200
  exit status
203
201
  end
202
+
203
+ # Returns the configuration for this repository.
204
+ def config
205
+ @config ||= Overcommit::ConfigurationLoader.new(log).load_repo_config
206
+ end
204
207
  end
205
208
  end
@@ -2,8 +2,15 @@ module Overcommit
2
2
  # Stores configuration for Overcommit and the hooks it runs.
3
3
  class Configuration # rubocop:disable ClassLength
4
4
  # Creates a configuration from the given hash.
5
- def initialize(hash)
6
- @hash = ConfigurationValidator.new.validate(hash)
5
+ #
6
+ # @param hash [Hash] loaded YAML config file as a hash
7
+ # @param options [Hash]
8
+ # @option default [Boolean] whether this is the default built-in configuration
9
+ # @option logger [Overcommit::Logger]
10
+ def initialize(hash, options = {})
11
+ @options = options.dup
12
+ @options[:logger] ||= Overcommit::Logger.silent
13
+ @hash = Overcommit::ConfigurationValidator.new.validate(hash, options)
7
14
  end
8
15
 
9
16
  def ==(other)
@@ -145,7 +152,7 @@ module Overcommit
145
152
  def built_in_hook?(hook_context, hook_name)
146
153
  hook_name = Overcommit::Utils.snake_case(hook_name)
147
154
 
148
- File.exist?(File.join(OVERCOMMIT_HOME, 'lib', 'overcommit', 'hook',
155
+ File.exist?(File.join(Overcommit::HOME, 'lib', 'overcommit', 'hook',
149
156
  hook_context.hook_type_name, "#{hook_name}.rb"))
150
157
  end
151
158
 
@@ -164,10 +171,17 @@ module Overcommit
164
171
  all_enabled = @hash[hook_type]['ALL']['enabled']
165
172
  return all_enabled unless all_enabled.nil?
166
173
 
167
- true
174
+ false
168
175
  end
169
176
 
170
177
  def smart_merge(parent, child)
178
+ # Treat the ALL hook specially so that it overrides any configuration
179
+ # specified by the default configuration.
180
+ child_all = child['ALL']
181
+ unless child_all.nil?
182
+ parent = Hash[parent.collect { |k, v| [k, smart_merge(v, child_all)] }]
183
+ end
184
+
171
185
  parent.merge(child) do |_key, old, new|
172
186
  case old
173
187
  when Hash
@@ -3,38 +3,24 @@ require 'yaml'
3
3
  module Overcommit
4
4
  # Manages configuration file loading.
5
5
  class ConfigurationLoader
6
- DEFAULT_CONFIG_PATH = File.join(OVERCOMMIT_HOME, 'config', 'default.yml')
6
+ DEFAULT_CONFIG_PATH = File.join(Overcommit::HOME, 'config', 'default.yml')
7
7
 
8
8
  class << self
9
- def load_repo_config
10
- overcommit_yml = File.join(Overcommit::Utils.repo_root,
11
- OVERCOMMIT_CONFIG_FILE_NAME)
12
-
13
- if File.exist?(overcommit_yml)
14
- load_file(overcommit_yml)
15
- else
16
- default_configuration
17
- end
18
- end
19
-
9
+ # Loads and returns the default configuration.
10
+ #
11
+ # @return [Overcommit::Configuration]
20
12
  def default_configuration
21
- @default_config ||= load_from_file(DEFAULT_CONFIG_PATH)
22
- end
23
-
24
- private
25
-
26
- # Loads a configuration, ensuring it extends the default configuration.
27
- def load_file(file)
28
- config = load_from_file(file)
29
-
30
- default_configuration.merge(config)
31
- rescue => error
32
- raise Overcommit::Exceptions::ConfigurationError,
33
- "Unable to load configuration from '#{file}': #{error}",
34
- error.backtrace
13
+ @default_config ||= load_from_file(DEFAULT_CONFIG_PATH, default: true)
35
14
  end
36
15
 
37
- def load_from_file(file)
16
+ # Loads configuration from file.
17
+ #
18
+ # @param file [String] path to file
19
+ # @param options [Hash]
20
+ # @option default [Boolean] whether this is the default built-in configuration
21
+ # @option logger [Overcommit::Logger]
22
+ # @return [Overcommit::Configuration]
23
+ def load_from_file(file, options = {})
38
24
  hash =
39
25
  if yaml = YAML.load_file(file)
40
26
  yaml.to_hash
@@ -42,8 +28,39 @@ module Overcommit
42
28
  {}
43
29
  end
44
30
 
45
- Overcommit::Configuration.new(hash)
31
+ Overcommit::Configuration.new(hash, options)
46
32
  end
47
33
  end
34
+
35
+ # Create a configuration loader which writes warnings/errors to the given
36
+ # {Overcommit::Logger} instance.
37
+ def initialize(logger)
38
+ @log = logger
39
+ end
40
+
41
+ # Loads and returns the configuration for the repository we're running in.
42
+ #
43
+ # @return [Overcommit::Configuration]
44
+ def load_repo_config
45
+ overcommit_yml = File.join(Overcommit::Utils.repo_root,
46
+ Overcommit::CONFIG_FILE_NAME)
47
+
48
+ if File.exist?(overcommit_yml)
49
+ load_file(overcommit_yml)
50
+ else
51
+ self.class.default_configuration
52
+ end
53
+ end
54
+
55
+ # Loads a configuration, ensuring it extends the default configuration.
56
+ def load_file(file)
57
+ config = self.class.load_from_file(file, default: false, logger: @log)
58
+
59
+ self.class.default_configuration.merge(config)
60
+ rescue => error
61
+ raise Overcommit::Exceptions::ConfigurationError,
62
+ "Unable to load configuration from '#{file}': #{error}",
63
+ error.backtrace
64
+ end
48
65
  end
49
66
  end
@@ -2,9 +2,19 @@ module Overcommit
2
2
  # Validates and normalizes a configuration.
3
3
  class ConfigurationValidator
4
4
  # Validates hash for any invalid options, normalizing where possible.
5
- def validate(hash)
5
+ #
6
+ # @param hash [Hash] hash representation of YAML config
7
+ # @param options[Hash]
8
+ # @option default [Boolean] whether hash represents the default built-in config
9
+ # @option logger [Overcommit::Logger] logger to output warnings to
10
+ # @return [Hash] validated hash (potentially modified)
11
+ def validate(hash, options)
12
+ @options = options.dup
13
+ @log = options[:logger]
14
+
6
15
  hash = convert_nils_to_empty_hashes(hash)
7
16
  ensure_hook_type_sections_exist(hash)
17
+ check_for_missing_enabled_option(hash) unless @options[:default]
8
18
 
9
19
  hash
10
20
  end
@@ -36,5 +46,27 @@ module Overcommit
36
46
  end
37
47
  end
38
48
  end
49
+
50
+ # Prints a warning if there are any hooks listed in the configuration
51
+ # without `enabled` explicitly set.
52
+ def check_for_missing_enabled_option(hash)
53
+ return unless @log
54
+
55
+ any_warnings = false
56
+
57
+ Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
58
+ hash.fetch(hook_type, {}).each do |hook_name, hook_config|
59
+ next if hook_name == 'ALL'
60
+
61
+ if hook_config['enabled'].nil?
62
+ @log.warning "#{hook_type}::#{hook_name} hook does not explicitly " \
63
+ 'set `enabled` option in .overcommit.yml'
64
+ any_warnings = true
65
+ end
66
+ end
67
+ end
68
+
69
+ @log.newline if any_warnings
70
+ end
39
71
  end
40
72
  end
@@ -1,8 +1,10 @@
1
1
  # Global application constants.
2
2
  module Overcommit
3
- OVERCOMMIT_HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
4
- OVERCOMMIT_CONFIG_FILE_NAME = '.overcommit.yml'
3
+ HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
4
+ CONFIG_FILE_NAME = '.overcommit.yml'
5
5
 
6
- REPO_URL = 'https://github.com/causes/overcommit'
6
+ HOOK_DIRECTORY = File.join(HOME, 'lib', 'overcommit', 'hook')
7
+
8
+ REPO_URL = 'https://github.com/brigade/overcommit'
7
9
  BUG_REPORT_URL = "#{REPO_URL}/issues"
8
10
  end
@@ -5,6 +5,9 @@ module Overcommit::Exceptions
5
5
  # Raised when trying to read/write to/from the local repo git config fails.
6
6
  class GitConfigError < StandardError; end
7
7
 
8
+ # Raised when there was a problem reading submodule information for a repo.
9
+ class GitSubmoduleError < StandardError; end
10
+
8
11
  # Raised when a {HookContext} is unable to setup the environment before a run.
9
12
  class HookSetupFailed < StandardError; end
10
13
 
@@ -1,3 +1,5 @@
1
+ require 'iniparse'
2
+
1
3
  module Overcommit
2
4
  # Provide a set of utilities for certain interactions with `git`.
3
5
  module GitRepo
@@ -11,6 +13,50 @@ module Overcommit
11
13
  \s@@.*$
12
14
  /x
13
15
 
16
+ # Regular expression used to extract information from lines of
17
+ # `git submodule status` output
18
+ SUBMODULE_STATUS_REGEX = /
19
+ ^\s*(?<prefix>[-+U]?)(?<sha1>\w+)
20
+ \s(?<path>[^\s]+?)
21
+ (?:\s\((?<describe>.+)\))?$
22
+ /x
23
+
24
+ # Struct encapsulating submodule information extracted from the
25
+ # output of `git submodule status`
26
+ SubmoduleStatus = Struct.new(:prefix, :sha1, :path, :describe) do
27
+ # Returns whether the submodule has not been initialized
28
+ def uninitialized?
29
+ prefix == '-'
30
+ end
31
+
32
+ # Returns whether the submodule is out of date with the current
33
+ # index, i.e. its checked-out commit differs from that stored in
34
+ # the index of the parent repo
35
+ def outdated?
36
+ prefix == '+'
37
+ end
38
+
39
+ # Returns whether the submodule reference has a merge conflict
40
+ def merge_conflict?
41
+ prefix == 'U'
42
+ end
43
+ end
44
+
45
+ # Returns a list of SubmoduleStatus objects, one for each submodule in the
46
+ # parent repository.
47
+ #
48
+ # @option options [Boolean] recursive check submodules recursively
49
+ # @return [Array<SubmoduleStatus>]
50
+ def submodule_statuses(options = {})
51
+ flags = '--recursive' if options[:recursive]
52
+
53
+ `git submodule status #{flags}`.
54
+ scan(SUBMODULE_STATUS_REGEX).
55
+ map do |prefix, sha1, path, describe|
56
+ SubmoduleStatus.new(prefix, sha1, path, describe)
57
+ end
58
+ end
59
+
14
60
  # Extract the set of modified lines from a given file.
15
61
  #
16
62
  # @param file_path [String]
@@ -54,6 +100,19 @@ module Overcommit
54
100
  map { |relative_file| File.expand_path(relative_file) }
55
101
  end
56
102
 
103
+ # Returns the names of files in the given paths that are tracked by git.
104
+ #
105
+ # @param paths [Array<String>] list of paths to check
106
+ # @option options [String] ref ('HEAD') Git ref to check
107
+ # @return [Array<String>] list of absolute file paths
108
+ def list_files(paths = [], options = {})
109
+ ref = options[:ref] || 'HEAD'
110
+ `git ls-tree --name-only #{ref} #{paths.join(' ')}`.
111
+ split(/\n/).
112
+ map { |relative_file| File.expand_path(relative_file) }.
113
+ reject { |file| File.directory?(file) } # Exclude submodule directories
114
+ end
115
+
57
116
  # Returns the names of all files that are tracked by git.
58
117
  #
59
118
  # @return [Array<String>] list of absolute file paths
@@ -132,5 +191,62 @@ module Overcommit
132
191
  @cherry_head = nil
133
192
  end
134
193
  end
194
+
195
+ # Contains information about a registered submodule.
196
+ Submodule = Struct.new(:path, :url)
197
+
198
+ # Returns the submodules that have been staged for removal.
199
+ #
200
+ # `git` has an unexpected behavior where removing a submodule without
201
+ # committing (i.e. such that the submodule directory is removed and the
202
+ # changes to the index are staged) and then doing a hard reset results in
203
+ # the index being wiped but the empty directory of the once existent
204
+ # submodule being restored (but with no content).
205
+ #
206
+ # This prevents restoration of the stash of the submodule index changes,
207
+ # which breaks pre-commit hook restorations of the working index.
208
+ #
209
+ # Thus we expose this helper so the restoration code can manually delete the
210
+ # directory.
211
+ #
212
+ # @raise [Overcommit::Exceptions::GitSubmoduleError] when
213
+ def staged_submodule_removals
214
+ # There were no submodules before, so none could have been removed
215
+ return [] if `git ls-files .gitmodules`.empty?
216
+
217
+ previous = submodules(ref: 'HEAD')
218
+ current = submodules
219
+
220
+ previous - current
221
+ end
222
+
223
+ # Returns the current set of registered submodules.
224
+ #
225
+ # @param options [Hash]
226
+ # @return [Array<Overcommit::GitRepo::Submodule>]
227
+ def submodules(options = {})
228
+ ref = options[:ref]
229
+
230
+ modules = []
231
+ IniParse.parse(`git show #{ref}:.gitmodules`).each do |section|
232
+ # git < 1.8.5 does not update the .gitmodules file with submodule
233
+ # changes, so when we are looking at the current state of the work tree,
234
+ # we need to check if the submodule actually exists via another method,
235
+ # since the .gitmodules file we parsed does not represent reality.
236
+ if ref.nil? && GIT_VERSION < '1.8.5'
237
+ result = Overcommit::Utils.execute(%W[
238
+ git submodule status #{section['path']}
239
+ ])
240
+ next unless result.success?
241
+ end
242
+
243
+ modules << Submodule.new(section['path'], section['url'])
244
+ end
245
+
246
+ modules
247
+ rescue IniParse::IniParseError => ex
248
+ raise Overcommit::Exceptions::GitSubmoduleError,
249
+ "Unable to read submodule information from #{ref}:.gitmodules file: #{ex.message}"
250
+ end
135
251
  end
136
252
  end