jawshooah-overcommit 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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