overcommit 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/bin/overcommit +3 -4
  3. data/config/default.yml +139 -0
  4. data/lib/overcommit.rb +7 -5
  5. data/lib/overcommit/cli.rb +59 -64
  6. data/lib/overcommit/configuration.rb +108 -34
  7. data/lib/overcommit/configuration_loader.rb +47 -0
  8. data/lib/overcommit/constants.rb +7 -0
  9. data/lib/overcommit/exceptions.rb +16 -0
  10. data/lib/overcommit/hook/base.rb +91 -0
  11. data/lib/overcommit/hook/commit_msg/base.rb +11 -0
  12. data/lib/overcommit/hook/commit_msg/gerrit_change_id.rb +18 -0
  13. data/lib/overcommit/{plugins → hook}/commit_msg/hard_tabs.rb +5 -6
  14. data/lib/overcommit/hook/commit_msg/russian_novel.rb +14 -0
  15. data/lib/overcommit/hook/commit_msg/single_line_subject.rb +12 -0
  16. data/lib/overcommit/hook/commit_msg/text_width.rb +20 -0
  17. data/lib/overcommit/hook/commit_msg/trailing_period.rb +12 -0
  18. data/lib/overcommit/hook/post_checkout/base.rb +11 -0
  19. data/lib/overcommit/hook/post_checkout/bundle_check.rb +29 -0
  20. data/lib/overcommit/hook/post_checkout/index_tags.rb +24 -0
  21. data/lib/overcommit/hook/pre_commit/author_email.rb +17 -0
  22. data/lib/overcommit/hook/pre_commit/author_name.rb +17 -0
  23. data/lib/overcommit/hook/pre_commit/base.rb +10 -0
  24. data/lib/overcommit/hook/pre_commit/bundle_check.rb +30 -0
  25. data/lib/overcommit/hook/pre_commit/coffee_lint.rb +14 -0
  26. data/lib/overcommit/hook/pre_commit/css_lint.rb +16 -0
  27. data/lib/overcommit/hook/pre_commit/haml_lint.rb +26 -0
  28. data/lib/overcommit/hook/pre_commit/hard_tabs.rb +16 -0
  29. data/lib/overcommit/hook/pre_commit/image_optim.rb +41 -0
  30. data/lib/overcommit/hook/pre_commit/js_hint.rb +15 -0
  31. data/lib/overcommit/hook/pre_commit/jscs.rb +31 -0
  32. data/lib/overcommit/hook/pre_commit/python_flake8.rb +14 -0
  33. data/lib/overcommit/hook/pre_commit/rubocop.rb +26 -0
  34. data/lib/overcommit/hook/pre_commit/scss_lint.rb +26 -0
  35. data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +15 -0
  36. data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +22 -0
  37. data/lib/overcommit/hook_context.rb +16 -0
  38. data/lib/overcommit/hook_context/base.rb +68 -0
  39. data/lib/overcommit/hook_context/commit_msg.rb +32 -0
  40. data/lib/overcommit/hook_context/post_checkout.rb +24 -0
  41. data/lib/overcommit/hook_context/pre_commit.rb +96 -0
  42. data/lib/overcommit/hook_runner.rb +150 -0
  43. data/lib/overcommit/installer.rb +61 -68
  44. data/lib/overcommit/logger.rb +16 -13
  45. data/lib/overcommit/utils.rb +63 -38
  46. data/lib/overcommit/version.rb +1 -1
  47. data/{bin/scripts → libexec}/gerrit-change-id +0 -0
  48. data/{bin/scripts → libexec}/index-tags +1 -3
  49. data/template-dir/hooks/commit-msg +83 -0
  50. data/template-dir/hooks/overcommit-hook +83 -0
  51. data/template-dir/hooks/post-checkout +83 -0
  52. data/template-dir/hooks/pre-commit +83 -0
  53. metadata +76 -57
  54. data/bin/hooks/commit-msg +0 -8
  55. data/bin/hooks/post-checkout +0 -9
  56. data/bin/hooks/post-merge +0 -23
  57. data/bin/hooks/pre-commit +0 -8
  58. data/bin/hooks/prepare-commit-msg +0 -159
  59. data/bin/run-hook +0 -8
  60. data/bin/scripts/check-gemfile +0 -9
  61. data/bin/scripts/csslint-rhino.js +0 -9080
  62. data/bin/scripts/jshint.js +0 -5921
  63. data/bin/scripts/jshint_runner.js +0 -42
  64. data/lib/overcommit/errors.rb +0 -3
  65. data/lib/overcommit/git_hook.rb +0 -89
  66. data/lib/overcommit/hook_specific_check.rb +0 -110
  67. data/lib/overcommit/hooks/commit_msg.rb +0 -7
  68. data/lib/overcommit/hooks/pre_commit.rb +0 -9
  69. data/lib/overcommit/plugins/commit_msg/change_id.rb +0 -15
  70. data/lib/overcommit/plugins/commit_msg/release_note.rb +0 -25
  71. data/lib/overcommit/plugins/commit_msg/russian_novel.rb +0 -16
  72. data/lib/overcommit/plugins/commit_msg/single_line_subject.rb +0 -13
  73. data/lib/overcommit/plugins/commit_msg/text_width.rb +0 -20
  74. data/lib/overcommit/plugins/commit_msg/trailing_period.rb +0 -13
  75. data/lib/overcommit/plugins/pre_commit/author_name.rb +0 -16
  76. data/lib/overcommit/plugins/pre_commit/causes_email.rb +0 -15
  77. data/lib/overcommit/plugins/pre_commit/coffee_lint.rb +0 -16
  78. data/lib/overcommit/plugins/pre_commit/css_linter.rb +0 -17
  79. data/lib/overcommit/plugins/pre_commit/haml_style.rb +0 -34
  80. data/lib/overcommit/plugins/pre_commit/haml_syntax.rb +0 -24
  81. data/lib/overcommit/plugins/pre_commit/image_optimization.rb +0 -50
  82. data/lib/overcommit/plugins/pre_commit/js_console_log.rb +0 -16
  83. data/lib/overcommit/plugins/pre_commit/js_syntax.rb +0 -30
  84. data/lib/overcommit/plugins/pre_commit/python_flake8.rb +0 -15
  85. data/lib/overcommit/plugins/pre_commit/ruby_style.rb +0 -67
  86. data/lib/overcommit/plugins/pre_commit/ruby_syntax.rb +0 -19
  87. data/lib/overcommit/plugins/pre_commit/scss_lint.rb +0 -66
  88. data/lib/overcommit/plugins/pre_commit/test_history.rb +0 -58
  89. data/lib/overcommit/plugins/pre_commit/whitespace.rb +0 -21
  90. data/lib/overcommit/plugins/pre_commit/yaml_syntax.rb +0 -22
  91. data/lib/overcommit/reporter.rb +0 -90
  92. data/lib/overcommit/staged_file.rb +0 -86
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fd49b47c55670147b58f3b1f9d24c563af35b589
4
- data.tar.gz: 986b58c2716ebd051826a84f420e1c568ba98e7e
3
+ metadata.gz: a23b361b3bac1357e190f81ea1912710b9bd62bf
4
+ data.tar.gz: d0d707d8f82ed50aff865fd170b769baf8d78869
5
5
  SHA512:
6
- metadata.gz: 555c4a7777514b88d459eff53b03e5be698499273c95d24b0fa39b779f5ab0d2d59334ec497eaca1061852e06ca14b4d69af0fdd8119dbdc40509c069d29a3b5
7
- data.tar.gz: d4e0f5ce2ca1c0a5af8f430c65e7f485c3936837e09659f460e489d8f11cfcf30731ac4fd331984816ecb501cffeaff0e87624911c8cabb93e16be8d21580f5b
6
+ metadata.gz: a7a1110c6239f00662de316e677dab141db03ae87f9c13eca2c2a0dc2b78ed5b3bb41cbbf0d4c11a79436423b0a3cda2215de6a4d926c7b3a48e0b4ab586e1ca
7
+ data.tar.gz: 408ac82b4911e94f48670c674870eb79acfd93109f2edd1ca19993c49f6a40f193161a8cd1f26ec7b4121ee66e0391f3ce12715a332152bca128636ec218476d
data/bin/overcommit CHANGED
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $: << File.expand_path('../../lib', __FILE__)
4
-
5
3
  require 'overcommit'
6
4
  require 'overcommit/cli'
7
5
 
8
- Overcommit::CLI.new(ARGV).tap do |cli|
9
- cli.parse_arguments
6
+ logger = Overcommit::Logger.new(STDOUT)
7
+
8
+ Overcommit::CLI.new(ARGV, logger).tap do |cli|
10
9
  cli.run
11
10
  end
@@ -0,0 +1,139 @@
1
+ # Default configuration that all Overcommit configurations inherit from.
2
+ #
3
+ # This is an opinionated list of which hooks are valuable to run and what their
4
+ # out-of-the-box settings should be.
5
+
6
+ # Where to store hook plugins specific to a repository. These are loaded in
7
+ # addition to the default hooks Overcommit comes with. The location is relative
8
+ # to the root of the repository.
9
+ plugin_directory: '.git-hooks'
10
+
11
+ # Hooks that run after HEAD changes or a file is explicitly checked out. Useful
12
+ # for updating source tags (e.g. via ctags) or warning about new migrations,
13
+ # etc.
14
+ PostCheckout:
15
+ ALL:
16
+ required: false
17
+ quiet: false
18
+ BundleCheck:
19
+ description: 'Checking Gemfile dependencies'
20
+ include:
21
+ - 'Gemfile'
22
+ - 'Gemfile.lock'
23
+ - '*.gemspec'
24
+ IndexTags:
25
+ description: 'Indexing source code tags'
26
+
27
+ # Hooks that are run after `git commit` is executed, before the commit message
28
+ # editor is displayed. These hooks are ideal for syntax checkers, linters, and
29
+ # other checks that you want to run before you allow a commit object to be
30
+ # created.
31
+ PreCommit:
32
+ ALL:
33
+ requires_files: true
34
+ required: false
35
+ quiet: false
36
+
37
+ AuthorEmail:
38
+ description: 'Checking author email'
39
+ requires_files: false
40
+ required: true
41
+ quiet: true
42
+ pattern: '^[^@]+@.*$'
43
+
44
+ AuthorName:
45
+ description: 'Checking for author name'
46
+ requires_files: false
47
+ required: true
48
+ quiet: true
49
+
50
+ BundleCheck:
51
+ description: 'Checking Gemfile dependencies'
52
+ include:
53
+ - 'Gemfile'
54
+ - 'Gemfile.lock'
55
+ - '*.gemspec'
56
+
57
+ CoffeeLint:
58
+ description: 'Analyzing with coffeelint'
59
+ include: '**/*.coffee'
60
+
61
+ CssLint:
62
+ description: 'Analyzing with csslint'
63
+ include: '**/*.css'
64
+
65
+ HamlLint:
66
+ description: 'Analyzing with haml-lint'
67
+ include: '**/*.haml'
68
+
69
+ HardTabs:
70
+ description: 'Checking for hard tabs'
71
+
72
+ ImageOptim:
73
+ description: 'Checking for optimizable images'
74
+ include:
75
+ - '**/*.gif'
76
+ - '**/*.jpg'
77
+ - '**/*.jpeg'
78
+ - '**/*.png'
79
+
80
+ Jscs:
81
+ description: 'Analyzing with JSCS'
82
+ include: '**/*.js'
83
+
84
+ JsHint:
85
+ description: 'Analyzing with JSHint'
86
+ include: '**/*.js'
87
+
88
+ PythonFlake8:
89
+ description: 'Analyzing with flake8'
90
+ include: '**/*.py'
91
+
92
+ Rubocop:
93
+ description: 'Analyzing with Rubocop'
94
+ include:
95
+ - '**/*.gemspec'
96
+ - '**/*.rake'
97
+ - '**/*.rb'
98
+ - '**/Gemfile'
99
+ - '**/Rakefile'
100
+
101
+ ScssLint:
102
+ description: 'Analyzing with scss-lint'
103
+ include: '**/*.scss'
104
+
105
+ TrailingWhitespace:
106
+ description: 'Checking for trailing whitespace'
107
+
108
+ YamlSyntax:
109
+ description: 'Checking YAML syntax'
110
+ include: '**/*.yml'
111
+
112
+ # Hooks that are run against every commit message after a user has written it.
113
+ # These hooks are useful for enforcing policies on commit messages written for a
114
+ # project.
115
+ CommitMsg:
116
+ ALL:
117
+ requires_files: false
118
+ quiet: false
119
+
120
+ GerritChangeId:
121
+ enabled: false
122
+ description: 'Ensuring Gerrit Change-Id is present'
123
+ required: true
124
+
125
+ HardTabs:
126
+ description: 'Checking for hard tabs'
127
+
128
+ RussianNovel:
129
+ description: 'Checking length of commit message'
130
+ quiet: true
131
+
132
+ SingleLineSubject:
133
+ description: 'Checking subject line'
134
+
135
+ TextWidth:
136
+ description: 'Checking text width'
137
+
138
+ TrailingPeriod:
139
+ description: 'Checking for trailing periods in subject'
data/lib/overcommit.rb CHANGED
@@ -1,10 +1,12 @@
1
+ require 'overcommit/constants'
2
+ require 'overcommit/exceptions'
1
3
  require 'overcommit/configuration'
2
- require 'overcommit/errors'
3
- require 'overcommit/git_hook'
4
- require 'overcommit/hook_specific_check'
4
+ require 'overcommit/configuration_loader'
5
+ require 'overcommit/hook/base'
6
+ require 'overcommit/hook_context/base'
7
+ require 'overcommit/hook_context'
8
+ require 'overcommit/hook_runner'
5
9
  require 'overcommit/installer'
6
10
  require 'overcommit/logger'
7
- require 'overcommit/reporter'
8
- require 'overcommit/staged_file'
9
11
  require 'overcommit/utils'
10
12
  require 'overcommit/version'
@@ -1,75 +1,61 @@
1
1
  require 'optparse'
2
2
 
3
3
  module Overcommit
4
+ # Responsible for parsing command-line options and executing appropriate
5
+ # application logic based on those options.
4
6
  class CLI
5
- attr_reader :options
6
-
7
- def initialize(arguments = [])
7
+ def initialize(arguments, logger)
8
8
  @arguments = arguments
9
+ @log = logger
9
10
  @options = {}
10
11
  end
11
12
 
13
+ def run
14
+ parse_arguments
15
+
16
+ case @options[:action]
17
+ when :install, :uninstall
18
+ install_or_uninstall
19
+ when :template_dir
20
+ print_template_directory_path
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :log
27
+
12
28
  def parse_arguments
13
29
  @parser = OptionParser.new do |opts|
14
- opts.banner = "Usage: #{opts.program_name} [options] target"
30
+ opts.banner = "Usage: #{opts.program_name} [options] [target-repo]"
15
31
 
16
32
  opts.on_tail('-h', '--help', 'Show this message') do
17
33
  print_help opts.help
18
34
  end
19
35
 
20
36
  opts.on_tail('-v', '--version', 'Show version') do
21
- log.log VERSION
22
- exit 0
37
+ print_version(opts.program_name)
23
38
  end
24
39
 
25
- opts.on('-l', '--list-templates', 'List built-in templates') do
26
- Overcommit.config.templates.each_pair do |name, configuration|
27
- log.bold name
28
- log.log YAML.dump(configuration), ''
29
- end
30
- exit 0
40
+ opts.on('-u', '--uninstall', 'Remove Overcommit hooks from a repository') do
41
+ @options[:action] = :uninstall
31
42
  end
32
43
 
33
- opts.on('-a', '--all', 'Include all git hooks') do
34
- @options[:template] = 'all'
44
+ opts.on('-i', '--install', 'Install Overcommit hooks in a repository') do
45
+ @options[:action] = :install
35
46
  end
36
47
 
37
- opts.on('-t', '--template template',
38
- 'Specify a template of hooks') do |template|
39
- @options[:template] = template
40
- end
41
-
42
- opts.on('--uninstall', 'Remove overcommit from target') do
43
- @options[:uninstall] = true
44
- end
45
-
46
- opts.on('-e', '--exclude hook_name,...', Array,
47
- 'Exclude hooks from installation') do |excludes|
48
- # Transform from:
49
- #
50
- # pre_commit/test_history,commit_msg/change_id
51
- #
52
- # Into:
53
- #
54
- # {
55
- # 'commit_msg' => ['change_id'],
56
- # 'pre_commit' => ['test_history']
57
- # }
58
- @options[:excludes] = excludes.inject({}) do |memo, exclude|
59
- parts = exclude.split(%r{[:/.]})
60
- next memo unless parts.size == 2
61
-
62
- memo[parts.first] ||= []
63
- memo[parts.first] << parts.last
64
-
65
- memo
66
- end
48
+ opts.on('-t', '--template-dir', 'Print location of template directory') do
49
+ @options[:action] = :template_dir
67
50
  end
68
51
  end
69
52
 
70
53
  begin
71
54
  @parser.parse!(@arguments)
72
55
 
56
+ # Default action is to install
57
+ @options[:action] ||= :install
58
+
73
59
  # Unconsumed arguments are our targets
74
60
  @options[:targets] = @arguments
75
61
  rescue OptionParser::InvalidOption => ex
@@ -77,38 +63,47 @@ module Overcommit
77
63
  end
78
64
  end
79
65
 
80
- def run
81
- if @options[:targets].nil? || @options[:targets].empty?
82
- log.warning 'You must supply at least one directory'
83
- log.log @parser.help
84
- exit 2
66
+ def install_or_uninstall
67
+ if Array(@options[:targets]).empty?
68
+ @options[:targets] = [Overcommit::Utils.repo_root].compact
69
+ end
70
+
71
+ if @options[:targets].empty?
72
+ log.warning 'You are not in a git repository.'
73
+ log.log 'You must either specify the path to a repository or ' <<
74
+ 'change your current directory to a repository.'
75
+ halt 64 # EX_USAGE
85
76
  end
86
77
 
87
78
  @options[:targets].each do |target|
88
79
  begin
89
- Installer.new(@options, target).run
90
- rescue NotAGitRepoError => e
91
- log.warning "Skipping #{target}: #{e}"
80
+ Installer.new(log).run(target, @options)
81
+ rescue Overcommit::Exceptions::InvalidGitRepo => error
82
+ log.warning "Invalid repo #{target}: #{error}"
83
+ halt 69 # EX_UNAVAILABLE
92
84
  end
93
85
  end
86
+ end
94
87
 
95
- log.success "#{@options[:uninstall] ? 'Removal' : 'Installation'} complete"
96
-
97
- rescue ArgumentError => ex
98
- error "Installation failed: #{ex}"
99
- exit 3
88
+ def print_template_directory_path
89
+ puts File.join(OVERCOMMIT_HOME, 'template-dir')
90
+ halt
100
91
  end
101
92
 
102
- private
93
+ def print_help(message, error = nil)
94
+ log.error "#{error}\n" if error
95
+ log.log message
96
+ halt(error ? 64 : 0) # 64 = EX_USAGE
97
+ end
103
98
 
104
- def log
105
- Logger.instance
99
+ def print_version(program_name)
100
+ log.log "#{program_name} #{Overcommit::VERSION}"
101
+ halt
106
102
  end
107
103
 
108
- def print_help(message, ex = nil)
109
- log.error ex.to_s + "\n" if ex
110
- log.log message
111
- exit 0
104
+ # Used for ease of stubbing in tests
105
+ def halt(status = 0)
106
+ exit status
112
107
  end
113
108
  end
114
109
  end
@@ -1,54 +1,128 @@
1
- require 'singleton'
2
- require 'yaml'
3
-
4
1
  module Overcommit
2
+ # Stores configuration for Overcommit and the hooks it runs.
5
3
  class Configuration
6
- include Singleton
4
+ # Creates a configuration from the given hash.
5
+ def initialize(hash)
6
+ @hash = hash
7
+ validate
8
+ end
9
+
10
+ def ==(other)
11
+ super || @hash == other.hash
12
+ end
13
+ alias_method :eql?, :==
7
14
 
8
- attr_reader :templates
15
+ # Returns absolute path to the directory that external hook plugins should
16
+ # be loaded from.
17
+ def plugin_directory
18
+ File.join(Overcommit::Utils.repo_root, @hash['plugin_directory'] || '.githooks')
19
+ end
9
20
 
10
- def initialize
11
- @templates = YAML.load_file(Utils.absolute_path('config/templates.yml'))
21
+ # Returns the built-in hooks that have been enabled for a hook type.
22
+ def enabled_builtin_hooks(hook_type)
23
+ @hash[hook_type].keys.
24
+ select { |hook_name| hook_name != 'ALL' }.
25
+ select { |hook_name| hook_enabled?(hook_type, hook_name) }
12
26
  end
13
27
 
14
- # Read the repo-specific 'overcommit.yml' file to determine what behavior
15
- # the user wants.
16
- def repo_settings
17
- config_file = Utils.repo_path('.git/hooks/overcommit.yml')
28
+ # Returns a non-modifiable configuration for a hook.
29
+ def for_hook(hook, hook_type = nil)
30
+ unless hook_type
31
+ components = hook.class.name.split('::')
32
+ hook = components.last
33
+ hook_type = components[-2]
34
+ end
18
35
 
19
- File.exist?(config_file) ? YAML.load_file(config_file) : {}
36
+ # Merge hook configuration with special 'ALL' config
37
+ smart_merge(@hash[hook_type]['ALL'], @hash[hook_type][hook] || {}).freeze
20
38
  end
21
39
 
22
- # Given the current configuration, return a set of paths which should be
23
- # loaded as plugins (`require`d)
24
- def desired_plugins
25
- excludes = repo_settings['excludes'] || {}
40
+ # Merges the given configuration with this one, returning a new
41
+ # {Configuration}. The provided configuration will either add to or replace
42
+ # any options defined in this configuration.
43
+ def merge(config)
44
+ self.class.new(smart_merge(@hash, config.hash))
45
+ end
26
46
 
27
- plugin_directories.map do |dir|
28
- Dir[File.join(dir, Utils.hook_name, '*.rb')].map do |plugin|
29
- basename = File.basename(plugin, '.rb')
30
- if !(excludes[Utils.hook_name] || []).include?(basename)
31
- plugin
32
- end
33
- end.compact
34
- end.flatten
47
+ # Applies additional configuration settings based on the provided
48
+ # environment variables.
49
+ def apply_environment!(hook_type, env)
50
+ skipped_hooks = "#{env['SKIP']} #{env['SKIP_CHECKS']} #{env['SKIP_HOOKS']}".split(/[:, ]/)
51
+
52
+ if skipped_hooks.include?('all') || skipped_hooks.include?('ALL')
53
+ @hash[hook_type]['ALL']['skip'] = true
54
+ else
55
+ skipped_hooks.select { |hook_name| hook_exists?(hook_type, hook_name) }.
56
+ map { |hook_name| Overcommit::Utils.camel_case(hook_name) }.
57
+ each do |hook_name|
58
+ @hash[hook_type][hook_name] ||= {}
59
+ @hash[hook_type][hook_name]['skip'] = true
60
+ end
61
+ end
35
62
  end
36
63
 
64
+ protected
65
+
66
+ attr_reader :hash
67
+
37
68
  private
38
69
 
39
- def plugin_directories
40
- # Start with the base plugins provided by the gem
41
- plugin_dirs = [File.expand_path('../plugins', __FILE__)]
42
- repo_specific = Utils.repo_path('.githooks')
70
+ def hook_exists?(hook_type, hook_name)
71
+ hook_name = Overcommit::Utils.snake_case(hook_name)
72
+ underscored_hook_type = Overcommit::Utils.snake_case(hook_type)
73
+
74
+ File.exist?(File.join(OVERCOMMIT_HOME, 'lib', 'overcommit', 'hook',
75
+ underscored_hook_type, "#{hook_name}.rb"))
76
+ end
77
+
78
+ def hook_enabled?(hook_type, hook_name)
79
+ individual_enabled = @hash[hook_type][hook_name]['enabled']
80
+ return individual_enabled unless individual_enabled.nil?
43
81
 
44
- # Add on any repo-specific checks
45
- plugin_dirs << repo_specific if File.directory?(repo_specific)
82
+ all_enabled = @hash[hook_type]['ALL']['enabled']
83
+ return all_enabled unless all_enabled.nil?
46
84
 
47
- plugin_dirs
85
+ true
48
86
  end
49
- end
50
87
 
51
- def self.config
52
- Configuration.instance
88
+ # Validates the configuration for any invalid options, normalizing it where
89
+ # possible.
90
+ def validate
91
+ @hash = convert_nils_to_empty_hashes(@hash)
92
+ ensure_hook_type_sections_exist(@hash)
93
+ end
94
+
95
+ def smart_merge(parent, child)
96
+ parent.merge(child) do |key, old, new|
97
+ case old
98
+ when Array
99
+ old + Array(new)
100
+ when Hash
101
+ smart_merge(old, new)
102
+ else
103
+ new
104
+ end
105
+ end
106
+ end
107
+
108
+ def ensure_hook_type_sections_exist(hash)
109
+ Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
110
+ hash[hook_type] ||= {}
111
+ hash[hook_type]['ALL'] ||= {}
112
+ end
113
+ end
114
+
115
+ def convert_nils_to_empty_hashes(hash)
116
+ hash.inject({}) do |h, (key, value)|
117
+ h[key] =
118
+ case value
119
+ when nil then {}
120
+ when Hash then convert_nils_to_empty_hashes(value)
121
+ else
122
+ value
123
+ end
124
+ h
125
+ end
126
+ end
53
127
  end
54
128
  end