overcommit 0.75.0 → 0.76.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.
- checksums.yaml +4 -4
- data/config/default.yml +56 -0
- data/lib/overcommit/cli.rb +7 -1
- data/lib/overcommit/configuration.rb +27 -34
- data/lib/overcommit/configuration_loader.rb +2 -2
- data/lib/overcommit/configuration_validator.rb +17 -3
- data/lib/overcommit/exceptions.rb +3 -0
- data/lib/overcommit/gem_plugin.rb +165 -0
- data/lib/overcommit/git_config.rb +18 -0
- data/lib/overcommit/git_repo.rb +13 -2
- data/lib/overcommit/hook/base.rb +1 -1
- data/lib/overcommit/hook/commit_msg/spell_check.rb +5 -8
- data/lib/overcommit/hook/pre_commit/bundle_audit.rb +6 -1
- data/lib/overcommit/hook/pre_commit/line_endings.rb +1 -2
- data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +1 -1
- data/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb +52 -3
- data/lib/overcommit/hook_context/base.rb +4 -1
- data/lib/overcommit/hook_context/commit_msg.rb +10 -4
- data/lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb +25 -3
- data/lib/overcommit/hook_loader/base.rb +43 -11
- data/lib/overcommit/hook_loader/built_in_hook_loader.rb +2 -1
- data/lib/overcommit/hook_loader/plugin_hook_loader.rb +61 -18
- data/lib/overcommit/hook_runner.rb +1 -1
- data/lib/overcommit/hook_signer.rb +9 -29
- data/lib/overcommit/signature.rb +71 -0
- data/lib/overcommit/subprocess.rb +2 -1
- data/lib/overcommit/utils/messages_utils.rb +14 -0
- data/lib/overcommit/utils.rb +64 -6
- data/lib/overcommit/version.rb +1 -1
- data/lib/overcommit.rb +2 -0
- data/libexec/overcommit-parent-command.js +19 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b471934e19dc5c5c31b3ec1b5ed3dccebb4b54990cd3612890623310151fc189
|
|
4
|
+
data.tar.gz: 054c6937dc7c9dde353d2be3f6f03e4ad38bee29f2c348b4299a1ed2e004f36e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b32d40b8a2cb773a9bfb7007f67b50c93de5669c5d27df4ea2bfe1e45bd8a771313e3ec8ce79dbadd0762b4ab7b04ef90cd77dfebe5fb019bd33c5417b0bab2
|
|
7
|
+
data.tar.gz: cd1fbd57591dabb7b8ccbf4862e3c0ee3cbb2c2a9c6f0f9e6e2584066c6fbf4b5b77eaaa40ca0bc3638c2c805244991d56704ce9acb2b4a7aed3f0b682718389
|
data/config/default.yml
CHANGED
|
@@ -43,6 +43,10 @@ gemfile: false
|
|
|
43
43
|
# to the root of the repository.
|
|
44
44
|
plugin_directory: '.git-hooks'
|
|
45
45
|
|
|
46
|
+
# Explicit namespaces for gem-provided hooks. No gems are loaded by default.
|
|
47
|
+
# Example: { Company: overcommit-company }
|
|
48
|
+
gem_plugins: {}
|
|
49
|
+
|
|
46
50
|
# Whether to hide hook output by default. This results in completely silent hook
|
|
47
51
|
# runs except in the case of warning or failure.
|
|
48
52
|
quiet: false
|
|
@@ -165,6 +169,7 @@ PreCommit:
|
|
|
165
169
|
description: 'Check for vulnerable versions of gems'
|
|
166
170
|
required_executable: 'bundle-audit'
|
|
167
171
|
install_command: 'gem install bundler-audit'
|
|
172
|
+
update: false # Update ruby-advisory-db before checking for vulnerabilities
|
|
168
173
|
|
|
169
174
|
BundleCheck:
|
|
170
175
|
enabled: false
|
|
@@ -949,6 +954,57 @@ PreCommit:
|
|
|
949
954
|
install_command: 'gem install travis'
|
|
950
955
|
include: '.travis.yml'
|
|
951
956
|
|
|
957
|
+
Vale:
|
|
958
|
+
enabled: false
|
|
959
|
+
description: 'Check spelling and style with Vale'
|
|
960
|
+
required_executable: 'vale'
|
|
961
|
+
install_command: 'brew install vale'
|
|
962
|
+
message_pattern: !ruby/regexp /^(?<file>(?:\w:)?[^:]+):(?<line>[0-9]+):/
|
|
963
|
+
flags: ['--output=line']
|
|
964
|
+
include:
|
|
965
|
+
# Text, markup, and source formats supported by Vale. Some formats require
|
|
966
|
+
# additional software/configuration; use 'exclude' to disable them.
|
|
967
|
+
- '**/*.adoc'
|
|
968
|
+
- '**/*.bsh'
|
|
969
|
+
- '**/*.c'
|
|
970
|
+
- '**/*.cc'
|
|
971
|
+
- '**/*.cpp'
|
|
972
|
+
- '**/*.cs'
|
|
973
|
+
- '**/*.css'
|
|
974
|
+
- '**/*.csx'
|
|
975
|
+
- '**/*.cxx'
|
|
976
|
+
- '**/*.dita'
|
|
977
|
+
- '**/*.java'
|
|
978
|
+
- '**/*.js'
|
|
979
|
+
- '**/*.go'
|
|
980
|
+
- '**/*.h'
|
|
981
|
+
- '**/*.hpp'
|
|
982
|
+
- '**/*.hs'
|
|
983
|
+
- '**/*.html'
|
|
984
|
+
- '**/*.less'
|
|
985
|
+
- '**/*.lua'
|
|
986
|
+
- '**/*.md'
|
|
987
|
+
- '**/*.php'
|
|
988
|
+
- '**/*.pl'
|
|
989
|
+
- '**/*.pm'
|
|
990
|
+
- '**/*.pod'
|
|
991
|
+
- '**/*.py'
|
|
992
|
+
- '**/*.py3'
|
|
993
|
+
- '**/*.pyi'
|
|
994
|
+
- '**/*.pyw'
|
|
995
|
+
- '**/*.R'
|
|
996
|
+
- '**/*.r'
|
|
997
|
+
- '**/*.rb'
|
|
998
|
+
- '**/*.rpy'
|
|
999
|
+
- '**/*.rst'
|
|
1000
|
+
- '**/*.sass'
|
|
1001
|
+
- '**/*.sbt'
|
|
1002
|
+
- '**/*.scala'
|
|
1003
|
+
- '**/*.swift'
|
|
1004
|
+
- '**/*.txt'
|
|
1005
|
+
- '**/*.xhtml'
|
|
1006
|
+
- '**/*.xml'
|
|
1007
|
+
|
|
952
1008
|
Vint:
|
|
953
1009
|
enabled: false
|
|
954
1010
|
description: 'Analyze with Vint'
|
data/lib/overcommit/cli.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Overcommit
|
|
|
17
17
|
Overcommit::Utils.log = logger
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def run
|
|
20
|
+
def run # rubocop:disable Metrics/MethodLength
|
|
21
21
|
parse_arguments
|
|
22
22
|
|
|
23
23
|
case @options[:action]
|
|
@@ -35,6 +35,12 @@ module Overcommit
|
|
|
35
35
|
rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e
|
|
36
36
|
puts e
|
|
37
37
|
exit 78 # EX_CONFIG
|
|
38
|
+
rescue Overcommit::Exceptions::InvalidHookSignature
|
|
39
|
+
exit 78 # EX_CONFIG
|
|
40
|
+
rescue Overcommit::Exceptions::ConfigurationError,
|
|
41
|
+
Overcommit::Exceptions::HookLoadError => e
|
|
42
|
+
puts e
|
|
43
|
+
exit 78 # EX_CONFIG
|
|
38
44
|
rescue Overcommit::Exceptions::HookContextLoadError => e
|
|
39
45
|
puts e
|
|
40
46
|
exit 64 # EX_USAGE
|
|
@@ -127,12 +127,19 @@ module Overcommit
|
|
|
127
127
|
select { |hook_name| hook_enabled?(hook_context, hook_name) }
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
+
# Gem hooks are explicitly namespaced; discovery never executes gem code.
|
|
131
|
+
def enabled_gem_hooks(hook_context)
|
|
132
|
+
@hash[hook_context.hook_class_name].keys.select do |hook_name|
|
|
133
|
+
hook_name.include?('::') && hook_enabled?(hook_context, hook_name)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
130
137
|
# Returns a non-modifiable configuration for a hook.
|
|
131
138
|
def for_hook(hook, hook_type = nil)
|
|
132
139
|
unless hook_type
|
|
133
140
|
components = hook.class.name.split('::')
|
|
134
|
-
hook = components.
|
|
135
|
-
hook_type = components[
|
|
141
|
+
hook = components[3..].join('::')
|
|
142
|
+
hook_type = components[2]
|
|
136
143
|
end
|
|
137
144
|
|
|
138
145
|
# Merge hook configuration with special 'ALL' config
|
|
@@ -155,8 +162,9 @@ module Overcommit
|
|
|
155
162
|
# Applies additional configuration settings based on the provided
|
|
156
163
|
# environment variables.
|
|
157
164
|
def apply_environment!(hook_context, env)
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
separator = /(?<!:):(?!:)|[, ]/
|
|
166
|
+
skipped_hooks = "#{env['SKIP']} #{env['SKIP_CHECKS']} #{env['SKIP_HOOKS']}".split(separator)
|
|
167
|
+
only_hooks = env.fetch('ONLY') { '' }.split(separator)
|
|
160
168
|
hook_type = hook_context.hook_class_name
|
|
161
169
|
|
|
162
170
|
if only_hooks.any? || skipped_hooks.include?('all') || skipped_hooks.include?('ALL')
|
|
@@ -179,6 +187,10 @@ module Overcommit
|
|
|
179
187
|
end
|
|
180
188
|
|
|
181
189
|
def plugin_hook?(hook_context_or_type, hook_name)
|
|
190
|
+
if hook_name.include?('::')
|
|
191
|
+
return (@hash['gem_plugins'] || {}).key?(hook_name.split('::').first)
|
|
192
|
+
end
|
|
193
|
+
|
|
182
194
|
hook_type_name =
|
|
183
195
|
if hook_context_or_type.is_a?(String)
|
|
184
196
|
Overcommit::Utils.snake_case(hook_context_or_type)
|
|
@@ -190,12 +202,11 @@ module Overcommit
|
|
|
190
202
|
File.exist?(File.join(plugin_directory, hook_type_name, "#{hook_name}.rb"))
|
|
191
203
|
end
|
|
192
204
|
|
|
193
|
-
# Return whether
|
|
194
|
-
# was last calculated.
|
|
205
|
+
# Return whether this configuration has been explicitly trusted before.
|
|
195
206
|
#
|
|
196
207
|
# @return [true,false]
|
|
197
|
-
def
|
|
198
|
-
signature
|
|
208
|
+
def signature_verified?
|
|
209
|
+
signature_store.include?(signature)
|
|
199
210
|
end
|
|
200
211
|
|
|
201
212
|
# Return whether a previous signature has been recorded for this
|
|
@@ -203,7 +214,7 @@ module Overcommit
|
|
|
203
214
|
#
|
|
204
215
|
# @return [true,false]
|
|
205
216
|
def previous_signature?
|
|
206
|
-
|
|
217
|
+
signature_store.recorded?
|
|
207
218
|
end
|
|
208
219
|
|
|
209
220
|
# Returns whether this configuration should verify itself by checking the
|
|
@@ -233,13 +244,10 @@ module Overcommit
|
|
|
233
244
|
|
|
234
245
|
# Update the currently stored signature for this hook.
|
|
235
246
|
def update_signature!
|
|
247
|
+
signature_store.add(signature)
|
|
236
248
|
scope = Overcommit::GitConfig.signature_scope
|
|
237
|
-
result = Overcommit::Utils.execute(
|
|
238
|
-
%W[git config #{scope}] + [signature_config_key, signature]
|
|
239
|
-
)
|
|
240
|
-
|
|
241
249
|
verify_signature_value = @hash['verify_signatures'] ? 1 : 0
|
|
242
|
-
result
|
|
250
|
+
result = Overcommit::Utils.execute(
|
|
243
251
|
%W[git config #{scope} #{verify_signature_config_key} #{verify_signature_value}]
|
|
244
252
|
)
|
|
245
253
|
|
|
@@ -261,11 +269,14 @@ module Overcommit
|
|
|
261
269
|
# Ad hoc hooks are neither built-in nor have a plugin file written but
|
|
262
270
|
# still have a `command` specified to be run
|
|
263
271
|
!built_in_hook?(hook_context, hook_name) &&
|
|
272
|
+
!hook_name.include?('::') &&
|
|
264
273
|
!plugin_hook?(hook_context, hook_name) &&
|
|
265
274
|
(ad_hoc_conf['command'] || ad_hoc_conf['required_executable'])
|
|
266
275
|
end
|
|
267
276
|
|
|
268
277
|
def built_in_hook?(hook_context, hook_name)
|
|
278
|
+
return false if hook_name.include?('::')
|
|
279
|
+
|
|
269
280
|
hook_name = Overcommit::Utils.snake_case(hook_name)
|
|
270
281
|
|
|
271
282
|
File.exist?(File.join(Overcommit::HOME, 'lib', 'overcommit', 'hook',
|
|
@@ -320,26 +331,8 @@ module Overcommit
|
|
|
320
331
|
Digest::SHA256.hexdigest(@hash.to_json)
|
|
321
332
|
end
|
|
322
333
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
# This is intended to be compared against the current signature of this
|
|
326
|
-
# configuration object.
|
|
327
|
-
#
|
|
328
|
-
# @return [String]
|
|
329
|
-
def stored_signature
|
|
330
|
-
scope = Overcommit::GitConfig.signature_scope
|
|
331
|
-
result = Overcommit::Utils.execute(
|
|
332
|
-
%W[git config #{scope} --get] + [signature_config_key]
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
if result.status == 1 # Key doesn't exist
|
|
336
|
-
return ''
|
|
337
|
-
elsif result.status != 0
|
|
338
|
-
raise Overcommit::Exceptions::GitConfigError,
|
|
339
|
-
"Unable to read from local repo git config: #{result.stderr}"
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
result.stdout.chomp
|
|
334
|
+
def signature_store
|
|
335
|
+
@signature_store ||= Overcommit::Signature.new(signature_config_key)
|
|
343
336
|
end
|
|
344
337
|
|
|
345
338
|
def signature_config_key
|
|
@@ -28,7 +28,7 @@ module Overcommit
|
|
|
28
28
|
# default. Explicitly enable aliases if the option is available.
|
|
29
29
|
yaml =
|
|
30
30
|
begin
|
|
31
|
-
YAML.load_file(file, aliases: true)
|
|
31
|
+
YAML.load_file(file, permitted_classes: [Regexp, Symbol], aliases: true)
|
|
32
32
|
rescue ArgumentError
|
|
33
33
|
YAML.load_file(file)
|
|
34
34
|
end
|
|
@@ -95,7 +95,7 @@ module Overcommit
|
|
|
95
95
|
"No previously recorded signature for configuration file.\n" \
|
|
96
96
|
'Run `overcommit --sign` if you trust the hooks in this repository.'
|
|
97
97
|
|
|
98
|
-
elsif config.
|
|
98
|
+
elsif !config.signature_verified?
|
|
99
99
|
raise Overcommit::Exceptions::ConfigurationSignatureChanged,
|
|
100
100
|
"Signature of configuration file has changed!\n" \
|
|
101
101
|
"Run `overcommit --sign` once you've verified the configuration changes."
|
|
@@ -18,6 +18,7 @@ module Overcommit
|
|
|
18
18
|
|
|
19
19
|
hash = convert_nils_to_empty_hashes(hash)
|
|
20
20
|
ensure_hook_type_sections_exist(hash)
|
|
21
|
+
check_gem_plugins(hash)
|
|
21
22
|
check_hook_name_format(hash)
|
|
22
23
|
check_hook_env(hash)
|
|
23
24
|
check_for_missing_enabled_option(hash) unless @options[:default]
|
|
@@ -29,6 +30,19 @@ module Overcommit
|
|
|
29
30
|
|
|
30
31
|
private
|
|
31
32
|
|
|
33
|
+
# Validate declarations without resolving, activating, or requiring gems.
|
|
34
|
+
def check_gem_plugins(hash)
|
|
35
|
+
plugins = hash.fetch('gem_plugins', {})
|
|
36
|
+
valid = plugins.is_a?(Hash) && plugins.all? do |namespace, gem_name|
|
|
37
|
+
namespace.is_a?(String) && namespace.match?(/\A[A-Z][A-Za-z0-9]*\z/) &&
|
|
38
|
+
gem_name.is_a?(String) && gem_name.match?(/\A[a-zA-Z0-9][a-zA-Z0-9_.-]*\z/)
|
|
39
|
+
end
|
|
40
|
+
return if valid
|
|
41
|
+
|
|
42
|
+
raise Overcommit::Exceptions::ConfigurationError,
|
|
43
|
+
'`gem_plugins` must map CamelCase namespaces to gem names'
|
|
44
|
+
end
|
|
45
|
+
|
|
32
46
|
# Ensures that keys for all supported hook types exist (PreCommit,
|
|
33
47
|
# CommitMsg, etc.)
|
|
34
48
|
def ensure_hook_type_sections_exist(hash)
|
|
@@ -102,10 +116,10 @@ module Overcommit
|
|
|
102
116
|
hash.fetch(hook_type) { {} }.each_key do |hook_name|
|
|
103
117
|
next if hook_name == 'ALL'
|
|
104
118
|
|
|
105
|
-
unless hook_name.match?(/\A[A-Za-z0-9]
|
|
119
|
+
unless hook_name.match?(/\A(?:[A-Z][A-Za-z0-9]*::[A-Z][A-Za-z0-9]*|[A-Za-z0-9]+)\z/)
|
|
106
120
|
errors << "#{hook_type}::#{hook_name} has an invalid name " \
|
|
107
|
-
"#{hook_name}.
|
|
108
|
-
'
|
|
121
|
+
"#{hook_name}. Use an alphanumeric name or Namespace::HookName " \
|
|
122
|
+
'(no underscores or dashes, etc.)'
|
|
109
123
|
end
|
|
110
124
|
end
|
|
111
125
|
end
|
|
@@ -13,6 +13,9 @@ module Overcommit::Exceptions
|
|
|
13
13
|
# Raised when trying to read/write to/from the local repo git config fails.
|
|
14
14
|
class GitConfigError < Error; end
|
|
15
15
|
|
|
16
|
+
# Raised when local signature history cannot be read or written.
|
|
17
|
+
class SignatureError < Error; end
|
|
18
|
+
|
|
16
19
|
# Raised when there was a problem reading submodule information for a repo.
|
|
17
20
|
class GitSubmoduleError < Error; end
|
|
18
21
|
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'find'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module Overcommit
|
|
8
|
+
# A gem source and its approval, resolved without requiring its entrypoint.
|
|
9
|
+
class GemPlugin # rubocop:disable Metrics/ClassLength
|
|
10
|
+
attr_reader :namespace, :hook_names
|
|
11
|
+
|
|
12
|
+
def initialize(namespace, config, context)
|
|
13
|
+
@namespace = namespace
|
|
14
|
+
@config = config
|
|
15
|
+
@context = context
|
|
16
|
+
@gem_name = (config['gem_plugins'] || {})[namespace]
|
|
17
|
+
unless @gem_name
|
|
18
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
19
|
+
"Declare the gem for #{namespace} in `gem_plugins`"
|
|
20
|
+
end
|
|
21
|
+
@hook_names = config.enabled_gem_hooks(context).
|
|
22
|
+
select { |name| name.start_with?("#{namespace}::") }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def hook_name
|
|
26
|
+
"#{namespace} (gem #{@gem_name})"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def hook_path
|
|
30
|
+
specification.full_gem_path
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def signature_verified?
|
|
34
|
+
signature_store.include?(signature)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update_signature!
|
|
38
|
+
signature_store.add(signature)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Resolve every hook before requiring any code, so a missing hook fails closed.
|
|
42
|
+
def hook_paths
|
|
43
|
+
hook_names.map do |name|
|
|
44
|
+
relative = File.join('overcommit', 'hooks', @context.hook_type_name,
|
|
45
|
+
"#{Overcommit::Utils.snake_case(name.split('::').last)}.rb")
|
|
46
|
+
paths = specification.require_paths.map do |root|
|
|
47
|
+
File.expand_path(File.join(root, relative), hook_path)
|
|
48
|
+
end
|
|
49
|
+
path = paths.find { |candidate| File.file?(candidate) }
|
|
50
|
+
path = File.realpath(path) if path
|
|
51
|
+
unless path&.start_with?(File.realpath(hook_path) + File::SEPARATOR)
|
|
52
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
53
|
+
"Gem #{@gem_name} does not provide #{name} at #{relative}"
|
|
54
|
+
end
|
|
55
|
+
path
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def activate
|
|
60
|
+
specifications.reverse_each(&:activate)
|
|
61
|
+
rescue Gem::LoadError => e
|
|
62
|
+
raise Overcommit::Exceptions::HookLoadError, "Unable to activate #{@gem_name}: #{e.message}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def specification
|
|
68
|
+
@specification ||= find_specification(@gem_name)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def find_specification(name, requirement = Gem::Requirement.default)
|
|
72
|
+
spec = Gem.loaded_specs[name] || Gem::Specification.find_by_name(name, requirement)
|
|
73
|
+
unless requirement.satisfied_by?(spec.version)
|
|
74
|
+
raise Gem::LoadError, "#{name} #{spec.version} does not satisfy #{requirement}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
spec
|
|
78
|
+
rescue Gem::LoadError => e
|
|
79
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
80
|
+
"Unable to resolve gem plugin #{@gem_name}: #{e.message}. " \
|
|
81
|
+
'Install the gem and its dependencies; include them in your Gemfile when using Bundler.'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def specifications
|
|
85
|
+
@specifications ||= collect_specifications(specification, {}).values.sort_by(&:name)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def collect_specifications(spec, found)
|
|
89
|
+
if found.key?(spec.name)
|
|
90
|
+
unless found[spec.name].version == spec.version
|
|
91
|
+
raise Overcommit::Exceptions::HookLoadError, "Conflicting versions of #{spec.name}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
return found
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
found[spec.name] = spec
|
|
98
|
+
spec.runtime_dependencies.each do |dependency|
|
|
99
|
+
# Overcommit itself is already running and trusted, like built-in hooks.
|
|
100
|
+
next if dependency.name == 'overcommit'
|
|
101
|
+
|
|
102
|
+
collect_specifications(find_specification(dependency.name, dependency.requirement), found)
|
|
103
|
+
end
|
|
104
|
+
found
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def signature
|
|
108
|
+
digest = Digest::SHA256.new
|
|
109
|
+
settings = hook_names.sort.map do |name|
|
|
110
|
+
hook_config = @config.for_hook(name, @context.hook_class_name).
|
|
111
|
+
reject { |key, _| HookSigner::IGNORED_CONFIG_KEYS.include?(key) }
|
|
112
|
+
[name, hook_config]
|
|
113
|
+
end
|
|
114
|
+
digest << JSON.generate([namespace, @gem_name, settings])
|
|
115
|
+
specifications.each do |spec|
|
|
116
|
+
digest << JSON.generate([spec.full_name, spec.require_paths,
|
|
117
|
+
spec.runtime_dependencies.map(&:to_s).sort])
|
|
118
|
+
gem_roots(spec).each_with_index do |root, index|
|
|
119
|
+
digest << JSON.generate(index)
|
|
120
|
+
sign_directory(digest, root)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
digest.hexdigest
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def gem_roots(spec)
|
|
127
|
+
roots = [spec.full_gem_path] + spec.full_require_paths
|
|
128
|
+
roots = roots.select { |root| File.directory?(root) }.
|
|
129
|
+
map { |root| File.realpath(root) }.uniq
|
|
130
|
+
if roots.empty?
|
|
131
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
132
|
+
"Unable to find contents of gem #{spec.full_name} for approval"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
roots.reject do |root|
|
|
136
|
+
roots.any? { |parent| root.start_with?(parent + File::SEPARATOR) }
|
|
137
|
+
end.sort
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Cover helpers, data, added/deleted files, and native extensions, rather
|
|
141
|
+
# than just the selected hook file or a gem's potentially incomplete manifest.
|
|
142
|
+
def sign_directory(digest, root)
|
|
143
|
+
Find.find(root, ignore_error: false) do |path|
|
|
144
|
+
if File.basename(path) == '.git'
|
|
145
|
+
Find.prune
|
|
146
|
+
elsif File.symlink?(path)
|
|
147
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
148
|
+
"Gem plugin approvals cannot cover symbolic links: #{path}"
|
|
149
|
+
elsif File.file?(path)
|
|
150
|
+
relative = path.delete_prefix(root + File::SEPARATOR)
|
|
151
|
+
digest << JSON.generate([relative, Digest::SHA256.file(path).hexdigest])
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
rescue SystemCallError => e
|
|
155
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
156
|
+
"Unable to read gem plugin #{@gem_name}: #{e.message}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def signature_store
|
|
160
|
+
@signature_store ||= Overcommit::Signature.new(
|
|
161
|
+
"overcommit.#{@context.hook_class_name}.#{namespace}.gemsignature"
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -13,6 +13,24 @@ module Overcommit
|
|
|
13
13
|
char
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# Git does not pass cleanup options to commit-msg hooks. Use the invoking
|
|
17
|
+
# command's override, then configuration, and Git's documented editor marker.
|
|
18
|
+
def commit_cleanup_mode
|
|
19
|
+
command = Overcommit::Utils.parent_command.to_s.scrub
|
|
20
|
+
modes = command.scan(
|
|
21
|
+
/(?:\A|\s)["']?--cleanup
|
|
22
|
+
(?:=["']?|["']?\s+["']?)
|
|
23
|
+
(strip|whitespace|verbatim|scissors|default)["']?(?=\s|\z)/x
|
|
24
|
+
)
|
|
25
|
+
mode = modes.last&.first || `git config --get commit.cleanup`.chomp
|
|
26
|
+
Overcommit::Utils.log&.debug("Commit cleanup mode #{mode.inspect} from #{command.inspect}")
|
|
27
|
+
if mode.empty? || mode == 'default'
|
|
28
|
+
ENV['GIT_EDITOR'] == ':' ? 'whitespace' : 'strip'
|
|
29
|
+
else
|
|
30
|
+
mode
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
16
34
|
def hooks_path
|
|
17
35
|
path = `git config --get core.hooksPath`.chomp
|
|
18
36
|
return File.join(Overcommit::Utils.git_dir, 'hooks') if path.empty?
|
data/lib/overcommit/git_repo.rb
CHANGED
|
@@ -136,9 +136,20 @@ module Overcommit
|
|
|
136
136
|
#
|
|
137
137
|
# @return [Array<String>] list of absolute file paths
|
|
138
138
|
def all_files
|
|
139
|
-
deleted_files =
|
|
139
|
+
deleted_files, tracked_files = [%w[git ls-files -z --deleted],
|
|
140
|
+
%w[git ls-files -z]].map do |command|
|
|
141
|
+
result = Overcommit::Utils.execute(command)
|
|
142
|
+
unless result.success?
|
|
143
|
+
raise Overcommit::Exceptions::Error,
|
|
144
|
+
"Error listing files. EXIT STATUS: #{result.status}.\n" \
|
|
145
|
+
"STDOUT: #{result.stdout}.\n" \
|
|
146
|
+
"STDERR: #{result.stderr}."
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
result.stdout.split("\0")
|
|
150
|
+
end
|
|
140
151
|
|
|
141
|
-
(
|
|
152
|
+
(tracked_files - deleted_files).
|
|
142
153
|
map { |relative_file| File.expand_path(relative_file) }.
|
|
143
154
|
reject { |file| File.directory?(file) } # Exclude submodule directories
|
|
144
155
|
end
|
data/lib/overcommit/hook/base.rb
CHANGED
|
@@ -12,7 +12,11 @@ module Overcommit::Hook::CommitMsg
|
|
|
12
12
|
MISSPELLING_REGEX = /^[&#]\s(?<word>\w+)(?:.+?:\s(?<suggestions>.*))?/.freeze
|
|
13
13
|
|
|
14
14
|
def run
|
|
15
|
-
result =
|
|
15
|
+
result = ::Tempfile.create('commit-msg') do |file|
|
|
16
|
+
file.write(commit_message)
|
|
17
|
+
file.close
|
|
18
|
+
execute(command + [file.path])
|
|
19
|
+
end
|
|
16
20
|
return [:fail, "Error running spellcheck: #{result.stderr.chomp}"] unless result.success?
|
|
17
21
|
|
|
18
22
|
misspellings = parse_misspellings(result.stdout)
|
|
@@ -29,13 +33,6 @@ module Overcommit::Hook::CommitMsg
|
|
|
29
33
|
|
|
30
34
|
private
|
|
31
35
|
|
|
32
|
-
def uncommented_commit_msg_file
|
|
33
|
-
::Tempfile.open('commit-msg') do |file|
|
|
34
|
-
file.write(commit_message)
|
|
35
|
-
file.path
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
36
|
def parse_misspellings(output)
|
|
40
37
|
output.scan(MISSPELLING_REGEX).map do |word, suggestions|
|
|
41
38
|
Misspelling.new(word, suggestions)
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Overcommit::Hook::PreCommit
|
|
4
4
|
# Checks for vulnerable versions of gems in Gemfile.lock.
|
|
5
5
|
#
|
|
6
|
+
# Set `update: true` to refresh ruby-advisory-db before checking. Defaults to false.
|
|
7
|
+
#
|
|
6
8
|
# @see https://github.com/rubysec/bundler-audit
|
|
7
9
|
class BundleAudit < Base
|
|
8
10
|
LOCK_FILE = 'Gemfile.lock'
|
|
@@ -13,7 +15,10 @@ module Overcommit::Hook::PreCommit
|
|
|
13
15
|
stdout.split("\n")
|
|
14
16
|
return :pass if ignored_files.include?(LOCK_FILE)
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
audit_command = command
|
|
19
|
+
audit_command << '--update' if config['update']
|
|
20
|
+
|
|
21
|
+
result = execute(audit_command)
|
|
17
22
|
if result.success?
|
|
18
23
|
:pass
|
|
19
24
|
else
|
|
@@ -9,8 +9,7 @@ module Overcommit::Hook::PreCommit
|
|
|
9
9
|
messages = []
|
|
10
10
|
|
|
11
11
|
offending_files.map do |file_name|
|
|
12
|
-
|
|
13
|
-
begin
|
|
12
|
+
File.open(file_name) do |file|
|
|
14
13
|
messages += check_file(file, file_name)
|
|
15
14
|
rescue ArgumentError => e
|
|
16
15
|
# File is likely a binary file which this check should ignore, but
|