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