jawshooah-overcommit 0.22.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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/bin/overcommit +8 -0
  3. data/config/default.yml +275 -0
  4. data/config/starter.yml +31 -0
  5. data/lib/overcommit.rb +20 -0
  6. data/lib/overcommit/cli.rb +205 -0
  7. data/lib/overcommit/configuration.rb +183 -0
  8. data/lib/overcommit/configuration_loader.rb +49 -0
  9. data/lib/overcommit/configuration_validator.rb +40 -0
  10. data/lib/overcommit/constants.rb +8 -0
  11. data/lib/overcommit/exceptions.rb +35 -0
  12. data/lib/overcommit/git_repo.rb +147 -0
  13. data/lib/overcommit/hook/base.rb +174 -0
  14. data/lib/overcommit/hook/commit_msg/base.rb +11 -0
  15. data/lib/overcommit/hook/commit_msg/gerrit_change_id.rb +18 -0
  16. data/lib/overcommit/hook/commit_msg/hard_tabs.rb +13 -0
  17. data/lib/overcommit/hook/commit_msg/russian_novel.rb +14 -0
  18. data/lib/overcommit/hook/commit_msg/single_line_subject.rb +12 -0
  19. data/lib/overcommit/hook/commit_msg/text_width.rb +38 -0
  20. data/lib/overcommit/hook/commit_msg/trailing_period.rb +12 -0
  21. data/lib/overcommit/hook/post_checkout/base.rb +11 -0
  22. data/lib/overcommit/hook/post_checkout/index_tags.rb +26 -0
  23. data/lib/overcommit/hook/post_commit/base.rb +11 -0
  24. data/lib/overcommit/hook/post_commit/git_guilt.rb +9 -0
  25. data/lib/overcommit/hook/pre_commit/author_email.rb +18 -0
  26. data/lib/overcommit/hook/pre_commit/author_name.rb +17 -0
  27. data/lib/overcommit/hook/pre_commit/base.rb +70 -0
  28. data/lib/overcommit/hook/pre_commit/berksfile_check.rb +20 -0
  29. data/lib/overcommit/hook/pre_commit/brakeman.rb +12 -0
  30. data/lib/overcommit/hook/pre_commit/broken_symlinks.rb +15 -0
  31. data/lib/overcommit/hook/pre_commit/bundle_check.rb +25 -0
  32. data/lib/overcommit/hook/pre_commit/chamber_security.rb +11 -0
  33. data/lib/overcommit/hook/pre_commit/coffee_lint.rb +11 -0
  34. data/lib/overcommit/hook/pre_commit/css_lint.rb +11 -0
  35. data/lib/overcommit/hook/pre_commit/go_lint.rb +12 -0
  36. data/lib/overcommit/hook/pre_commit/haml_lint.rb +19 -0
  37. data/lib/overcommit/hook/pre_commit/hard_tabs.rb +14 -0
  38. data/lib/overcommit/hook/pre_commit/image_optim.rb +41 -0
  39. data/lib/overcommit/hook/pre_commit/js_hint.rb +13 -0
  40. data/lib/overcommit/hook/pre_commit/jscs.rb +22 -0
  41. data/lib/overcommit/hook/pre_commit/json_syntax.rb +22 -0
  42. data/lib/overcommit/hook/pre_commit/jsx_hint.rb +13 -0
  43. data/lib/overcommit/hook/pre_commit/jsxcs.rb +20 -0
  44. data/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb +14 -0
  45. data/lib/overcommit/hook/pre_commit/merge_conflicts.rb +14 -0
  46. data/lib/overcommit/hook/pre_commit/pry_binding.rb +14 -0
  47. data/lib/overcommit/hook/pre_commit/python_flake8.rb +11 -0
  48. data/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb +45 -0
  49. data/lib/overcommit/hook/pre_commit/reek.rb +22 -0
  50. data/lib/overcommit/hook/pre_commit/rubocop.rb +19 -0
  51. data/lib/overcommit/hook/pre_commit/scss_lint.rb +19 -0
  52. data/lib/overcommit/hook/pre_commit/shell_check.rb +19 -0
  53. data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +13 -0
  54. data/lib/overcommit/hook/pre_commit/travis_lint.rb +11 -0
  55. data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +22 -0
  56. data/lib/overcommit/hook_context.rb +17 -0
  57. data/lib/overcommit/hook_context/base.rb +69 -0
  58. data/lib/overcommit/hook_context/commit_msg.rb +32 -0
  59. data/lib/overcommit/hook_context/post_checkout.rb +26 -0
  60. data/lib/overcommit/hook_context/post_commit.rb +19 -0
  61. data/lib/overcommit/hook_context/pre_commit.rb +148 -0
  62. data/lib/overcommit/hook_context/run_all.rb +39 -0
  63. data/lib/overcommit/hook_loader/base.rb +36 -0
  64. data/lib/overcommit/hook_loader/built_in_hook_loader.rb +12 -0
  65. data/lib/overcommit/hook_loader/plugin_hook_loader.rb +61 -0
  66. data/lib/overcommit/hook_runner.rb +129 -0
  67. data/lib/overcommit/hook_signer.rb +79 -0
  68. data/lib/overcommit/installer.rb +148 -0
  69. data/lib/overcommit/interrupt_handler.rb +87 -0
  70. data/lib/overcommit/logger.rb +79 -0
  71. data/lib/overcommit/message_processor.rb +132 -0
  72. data/lib/overcommit/printer.rb +116 -0
  73. data/lib/overcommit/subprocess.rb +46 -0
  74. data/lib/overcommit/utils.rb +163 -0
  75. data/lib/overcommit/version.rb +4 -0
  76. data/libexec/gerrit-change-id +174 -0
  77. data/libexec/index-tags +17 -0
  78. data/template-dir/hooks/commit-msg +81 -0
  79. data/template-dir/hooks/overcommit-hook +81 -0
  80. data/template-dir/hooks/post-checkout +81 -0
  81. data/template-dir/hooks/pre-commit +81 -0
  82. metadata +184 -0
@@ -0,0 +1,183 @@
1
+ module Overcommit
2
+ # Stores configuration for Overcommit and the hooks it runs.
3
+ class Configuration # rubocop:disable ClassLength
4
+ # Creates a configuration from the given hash.
5
+ def initialize(hash)
6
+ @hash = ConfigurationValidator.new.validate(hash)
7
+ end
8
+
9
+ def ==(other)
10
+ super || @hash == other.hash
11
+ end
12
+ alias_method :eql?, :==
13
+
14
+ # Returns absolute path to the directory that external hook plugins should
15
+ # be loaded from.
16
+ def plugin_directory
17
+ File.join(Overcommit::Utils.repo_root, @hash['plugin_directory'] || '.githooks')
18
+ end
19
+
20
+ def verify_plugin_signatures?
21
+ @hash['verify_plugin_signatures'] != false
22
+ end
23
+
24
+ # Returns configuration for all hooks in each hook type.
25
+ #
26
+ # @return [Hash]
27
+ def all_hook_configs
28
+ smart_merge(all_builtin_hook_configs, all_plugin_hook_configs)
29
+ end
30
+
31
+ # Returns configuration for all built-in hooks in each hook type.
32
+ #
33
+ # @return [Hash]
34
+ def all_builtin_hook_configs
35
+ hook_configs = {}
36
+
37
+ Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
38
+ hook_names = @hash[hook_type].keys.reject { |name| name == 'ALL' }
39
+
40
+ hook_configs[hook_type] = Hash[
41
+ hook_names.map do |hook_name|
42
+ [hook_name, for_hook(hook_name, hook_type)]
43
+ end
44
+ ]
45
+ end
46
+
47
+ hook_configs
48
+ end
49
+
50
+ # Returns configuration for all plugin hooks in each hook type.
51
+ #
52
+ # @return [Hash]
53
+ def all_plugin_hook_configs
54
+ hook_configs = {}
55
+
56
+ Overcommit::Utils.supported_hook_types.each do |hook_type|
57
+ hook_type_class_name = Overcommit::Utils.camel_case(hook_type)
58
+
59
+ directory = File.join(plugin_directory, hook_type.gsub('-', '_'))
60
+ plugin_paths = Dir[File.join(directory, '*.rb')].sort
61
+
62
+ hook_names = plugin_paths.map do |path|
63
+ Overcommit::Utils.camel_case(File.basename(path, '.rb'))
64
+ end
65
+
66
+ hook_configs[hook_type_class_name] = Hash[
67
+ hook_names.map do |hook_name|
68
+ [hook_name, for_hook(hook_name, Overcommit::Utils.camel_case(hook_type))]
69
+ end
70
+ ]
71
+ end
72
+
73
+ hook_configs
74
+ end
75
+
76
+ # Returns the built-in hooks that have been enabled for a hook type.
77
+ def enabled_builtin_hooks(hook_context)
78
+ @hash[hook_context.hook_class_name].keys.
79
+ select { |hook_name| hook_name != 'ALL' }.
80
+ select { |hook_name| built_in_hook?(hook_context, hook_name) }.
81
+ select { |hook_name| hook_enabled?(hook_context, hook_name) }
82
+ end
83
+
84
+ # Returns a non-modifiable configuration for a hook.
85
+ def for_hook(hook, hook_type = nil)
86
+ unless hook_type
87
+ components = hook.class.name.split('::')
88
+ hook = components.last
89
+ hook_type = components[-2]
90
+ end
91
+
92
+ # Merge hook configuration with special 'ALL' config
93
+ hook_config = smart_merge(@hash[hook_type]['ALL'], @hash[hook_type][hook] || {})
94
+
95
+ # Need to specially handle `enabled` option since not setting it does not
96
+ # necessarily mean the hook is disabled
97
+ hook_config['enabled'] = hook_enabled?(hook_type, hook)
98
+
99
+ hook_config.freeze
100
+ end
101
+
102
+ # Merges the given configuration with this one, returning a new
103
+ # {Configuration}. The provided configuration will either add to or replace
104
+ # any options defined in this configuration.
105
+ def merge(config)
106
+ self.class.new(smart_merge(@hash, config.hash))
107
+ end
108
+
109
+ # Applies additional configuration settings based on the provided
110
+ # environment variables.
111
+ def apply_environment!(hook_context, env)
112
+ skipped_hooks = "#{env['SKIP']} #{env['SKIP_CHECKS']} #{env['SKIP_HOOKS']}".split(/[:, ]/)
113
+ hook_type = hook_context.hook_class_name
114
+
115
+ if skipped_hooks.include?('all') || skipped_hooks.include?('ALL')
116
+ @hash[hook_type]['ALL']['skip'] = true
117
+ else
118
+ skipped_hooks.select { |hook_name| hook_exists?(hook_context, hook_name) }.
119
+ map { |hook_name| Overcommit::Utils.camel_case(hook_name) }.
120
+ each do |hook_name|
121
+ @hash[hook_type][hook_name] ||= {}
122
+ @hash[hook_type][hook_name]['skip'] = true
123
+ end
124
+ end
125
+ end
126
+
127
+ def plugin_hook?(hook_context_or_type, hook_name)
128
+ hook_type_name =
129
+ if hook_context_or_type.is_a?(String)
130
+ Overcommit::Utils.snake_case(hook_context_or_type)
131
+ else
132
+ hook_context_or_type.hook_type_name
133
+ end
134
+ hook_name = Overcommit::Utils.snake_case(hook_name)
135
+
136
+ File.exist?(File.join(plugin_directory, hook_type_name, "#{hook_name}.rb"))
137
+ end
138
+
139
+ protected
140
+
141
+ attr_reader :hash
142
+
143
+ private
144
+
145
+ def built_in_hook?(hook_context, hook_name)
146
+ hook_name = Overcommit::Utils.snake_case(hook_name)
147
+
148
+ File.exist?(File.join(OVERCOMMIT_HOME, 'lib', 'overcommit', 'hook',
149
+ hook_context.hook_type_name, "#{hook_name}.rb"))
150
+ end
151
+
152
+ def hook_exists?(hook_context, hook_name)
153
+ built_in_hook?(hook_context, hook_name) ||
154
+ plugin_hook?(hook_context, hook_name)
155
+ end
156
+
157
+ def hook_enabled?(hook_context_or_type, hook_name)
158
+ hook_type = hook_context_or_type.is_a?(String) ? hook_context_or_type :
159
+ hook_context_or_type.hook_class_name
160
+
161
+ individual_enabled = @hash[hook_type].fetch(hook_name, {})['enabled']
162
+ return individual_enabled unless individual_enabled.nil?
163
+
164
+ all_enabled = @hash[hook_type]['ALL']['enabled']
165
+ return all_enabled unless all_enabled.nil?
166
+
167
+ true
168
+ end
169
+
170
+ def smart_merge(parent, child)
171
+ parent.merge(child) do |_key, old, new|
172
+ case old
173
+ when Array
174
+ old + Array(new)
175
+ when Hash
176
+ smart_merge(old, new)
177
+ else
178
+ new
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,49 @@
1
+ require 'yaml'
2
+
3
+ module Overcommit
4
+ # Manages configuration file loading.
5
+ class ConfigurationLoader
6
+ DEFAULT_CONFIG_PATH = File.join(OVERCOMMIT_HOME, 'config', 'default.yml')
7
+
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
+
20
+ 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
35
+ end
36
+
37
+ def load_from_file(file)
38
+ hash =
39
+ if yaml = YAML.load_file(file)
40
+ yaml.to_hash
41
+ else
42
+ {}
43
+ end
44
+
45
+ Overcommit::Configuration.new(hash)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ module Overcommit
2
+ # Validates and normalizes a configuration.
3
+ class ConfigurationValidator
4
+ # Validates hash for any invalid options, normalizing where possible.
5
+ def validate(hash)
6
+ hash = convert_nils_to_empty_hashes(hash)
7
+ ensure_hook_type_sections_exist(hash)
8
+
9
+ hash
10
+ end
11
+
12
+ private
13
+
14
+ # Ensures that keys for all supported hook types exist (PreCommit,
15
+ # CommitMsg, etc.)
16
+ def ensure_hook_type_sections_exist(hash)
17
+ Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
18
+ hash[hook_type] ||= {}
19
+ hash[hook_type]['ALL'] ||= {}
20
+ end
21
+ end
22
+
23
+ # Normalizes `nil` values to empty hashes.
24
+ #
25
+ # This is useful for when we want to merge two configuration hashes
26
+ # together, since it's easier to merge two hashes than to have to check if
27
+ # one of the values is nil.
28
+ def convert_nils_to_empty_hashes(hash)
29
+ hash.each_with_object({}) do |(key, value), h|
30
+ h[key] =
31
+ case value
32
+ when nil then {}
33
+ when Hash then convert_nils_to_empty_hashes(value)
34
+ else
35
+ value
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ # Global application constants.
2
+ module Overcommit
3
+ OVERCOMMIT_HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
4
+ OVERCOMMIT_CONFIG_FILE_NAME = '.overcommit.yml'
5
+
6
+ REPO_URL = 'https://github.com/causes/overcommit'
7
+ BUG_REPORT_URL = "#{REPO_URL}/issues"
8
+ end
@@ -0,0 +1,35 @@
1
+ module Overcommit::Exceptions
2
+ # Raised when a {Configuration} could not be loaded from a file.
3
+ class ConfigurationError < StandardError; end
4
+
5
+ # Raised when trying to read/write to/from the local repo git config fails.
6
+ class GitConfigError < StandardError; end
7
+
8
+ # Raised when a {HookContext} is unable to setup the environment before a run.
9
+ class HookSetupFailed < StandardError; end
10
+
11
+ # Raised when a {HookContext} is unable to clean the environment after a run.
12
+ class HookCleanupFailed < StandardError; end
13
+
14
+ # Raised when a hook run was cancelled by the user.
15
+ class HookCancelled < StandardError; end
16
+
17
+ # Raised when a hook could not be loaded by a {HookRunner}.
18
+ class HookLoadError < StandardError; end
19
+
20
+ # Raised when a {HookRunner} could not be loaded.
21
+ class HookContextLoadError < StandardError; end
22
+
23
+ # Raised when a pipe character is used in the `execute` helper, as this was
24
+ # likely used in error.
25
+ class InvalidCommandArgs < StandardError; end
26
+
27
+ # Raised when an installation target is not a valid git repository.
28
+ class InvalidGitRepo < StandardError; end
29
+
30
+ # Raised when one or more hook plugin signatures have changed.
31
+ class InvalidHookSignature < StandardError; end
32
+
33
+ # Raised when an installation target already contains non-Overcommit hooks.
34
+ class PreExistingHooks < StandardError; end
35
+ end
@@ -0,0 +1,147 @@
1
+ module Overcommit
2
+ # Provide a set of utilities for certain interactions with `git`.
3
+ module GitRepo
4
+ module_function
5
+
6
+ # Regular expression used to extract diff ranges from hunks of diff output.
7
+ DIFF_HUNK_REGEX = /
8
+ ^@@\s
9
+ [^\s]+\s # Ignore old file range
10
+ \+(\d+)(?:,(\d+))? # Extract range of hunk containing start line and number of lines
11
+ \s@@.*$
12
+ /x
13
+
14
+ # Extract the set of modified lines from a given file.
15
+ #
16
+ # @param file_path [String]
17
+ # @param options [Hash]
18
+ # @return [Set] line numbers that have been modified in file
19
+ def extract_modified_lines(file_path, options)
20
+ lines = Set.new
21
+
22
+ flags = '--cached' if options[:staged]
23
+ refs = 'HEAD~ HEAD' if options[:last_commit]
24
+
25
+ `git diff --no-ext-diff -U0 #{flags} #{refs} -- #{file_path}`.
26
+ scan(DIFF_HUNK_REGEX) do |start_line, lines_added|
27
+ lines_added = (lines_added || 1).to_i # When blank, one line was added
28
+ cur_line = start_line.to_i
29
+
30
+ lines_added.times do
31
+ lines.add cur_line
32
+ cur_line += 1
33
+ end
34
+ end
35
+
36
+ lines
37
+ end
38
+
39
+ # Extract the set of modified lines from a given file.
40
+ #
41
+ # @param file_path [String]
42
+ # @param options [Hash]
43
+ # @return [Set] line numbers that have been modified in file
44
+ def extract_modified_lines_last_commit(file_path)
45
+ extract_modified_lines(file_path, last_commit: true)
46
+ end
47
+
48
+ # Returns the names of all files that have been modified from compared to
49
+ # HEAD.
50
+ #
51
+ # @param options [Hash]
52
+ # @return [Array<String>] list of absolute file paths
53
+ def modified_files(options)
54
+ flags = '--cached' if options[:staged]
55
+ refs = 'HEAD~ HEAD' if options[:last_commit]
56
+
57
+ `git diff --name-only -z --diff-filter=ACM --ignore-submodules=all #{flags} #{refs}`.
58
+ split("\0").
59
+ map { |relative_file| File.expand_path(relative_file) }
60
+ end
61
+
62
+ # Returns the names of all files that were modified in the last commit.
63
+ #
64
+ # @return [Array<String>] lsit of absolute file paths
65
+ def modified_files_last_commit
66
+ modified_files(last_commit: true)
67
+ end
68
+
69
+ # Returns the names of all files that are tracked by git.
70
+ #
71
+ # @return [Array<String>] list of absolute file paths
72
+ def all_files
73
+ `git ls-files`.
74
+ split(/\n/).
75
+ map { |relative_file| File.expand_path(relative_file) }
76
+ end
77
+
78
+ # Returns whether the current git branch is empty (has no commits).
79
+ # @return [true,false]
80
+ def initial_commit?
81
+ !Overcommit::Utils.execute(%w[git rev-parse HEAD]).success?
82
+ end
83
+
84
+ # Store any relevant files that are present when repo is in the middle of a
85
+ # merge.
86
+ #
87
+ # Restored via [#restore_merge_state].
88
+ def store_merge_state
89
+ merge_head = `git rev-parse MERGE_HEAD 2> /dev/null`.chomp
90
+
91
+ # Store the merge state if we're in the middle of resolving a merge
92
+ # conflict. This is necessary since stashing removes the merge state.
93
+ if merge_head != 'MERGE_HEAD'
94
+ @merge_head = merge_head
95
+ end
96
+
97
+ merge_msg_file = File.expand_path('MERGE_MSG', Overcommit::Utils.git_dir)
98
+ @merge_msg = File.open(merge_msg_file).read if File.exist?(merge_msg_file)
99
+ end
100
+
101
+ # Store any relevant files that are present when repo is in the middle of a
102
+ # cherry-pick.
103
+ #
104
+ # Restored via [#restore_cherry_pick_state].
105
+ def store_cherry_pick_state
106
+ cherry_head = `git rev-parse CHERRY_PICK_HEAD 2> /dev/null`.chomp
107
+
108
+ # Store the merge state if we're in the middle of resolving a merge
109
+ # conflict. This is necessary since stashing removes the merge state.
110
+ if cherry_head != 'CHERRY_PICK_HEAD'
111
+ @cherry_head = cherry_head
112
+ end
113
+ end
114
+
115
+ # Restore any relevant files that were present when repo was in the middle
116
+ # of a merge.
117
+ def restore_merge_state
118
+ if @merge_head
119
+ FileUtils.touch(File.expand_path('MERGE_MODE', Overcommit::Utils.git_dir))
120
+
121
+ File.open(File.expand_path('MERGE_HEAD', Overcommit::Utils.git_dir), 'w') do |f|
122
+ f.write("#{@merge_head}\n")
123
+ end
124
+ @merge_head = nil
125
+ end
126
+
127
+ if @merge_msg
128
+ File.open(File.expand_path('MERGE_MSG', Overcommit::Utils.git_dir), 'w') do |f|
129
+ f.write("#{@merge_msg}\n")
130
+ end
131
+ @merge_msg = nil
132
+ end
133
+ end
134
+
135
+ # Restore any relevant files that were present when repo was in the middle
136
+ # of a cherry-pick.
137
+ def restore_cherry_pick_state
138
+ if @cherry_head
139
+ File.open(File.expand_path('CHERRY_PICK_HEAD',
140
+ Overcommit::Utils.git_dir), 'w') do |f|
141
+ f.write("#{@cherry_head}\n")
142
+ end
143
+ @cherry_head = nil
144
+ end
145
+ end
146
+ end
147
+ end