overcommit-jeygeethanmedia 0.53.1
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 +7 -0
- data/bin/overcommit +50 -0
- data/config/default.yml +1356 -0
- data/config/starter.yml +33 -0
- data/lib/overcommit.rb +26 -0
- data/lib/overcommit/cli.rb +223 -0
- data/lib/overcommit/command_splitter.rb +146 -0
- data/lib/overcommit/configuration.rb +350 -0
- data/lib/overcommit/configuration_loader.rb +96 -0
- data/lib/overcommit/configuration_validator.rb +186 -0
- data/lib/overcommit/constants.rb +12 -0
- data/lib/overcommit/exceptions.rb +52 -0
- data/lib/overcommit/git_config.rb +22 -0
- data/lib/overcommit/git_repo.rb +286 -0
- data/lib/overcommit/git_version.rb +17 -0
- data/lib/overcommit/hook/base.rb +294 -0
- data/lib/overcommit/hook/commit_msg/base.rb +14 -0
- data/lib/overcommit/hook/commit_msg/capitalized_subject.rb +25 -0
- data/lib/overcommit/hook/commit_msg/empty_message.rb +12 -0
- data/lib/overcommit/hook/commit_msg/gerrit_change_id.rb +22 -0
- data/lib/overcommit/hook/commit_msg/hard_tabs.rb +17 -0
- data/lib/overcommit/hook/commit_msg/message_format.rb +31 -0
- data/lib/overcommit/hook/commit_msg/russian_novel.rb +16 -0
- data/lib/overcommit/hook/commit_msg/single_line_subject.rb +16 -0
- data/lib/overcommit/hook/commit_msg/spell_check.rb +45 -0
- data/lib/overcommit/hook/commit_msg/text_width.rb +56 -0
- data/lib/overcommit/hook/commit_msg/trailing_period.rb +16 -0
- data/lib/overcommit/hook/post_checkout/base.rb +22 -0
- data/lib/overcommit/hook/post_checkout/bower_install.rb +13 -0
- data/lib/overcommit/hook/post_checkout/bundle_install.rb +13 -0
- data/lib/overcommit/hook/post_checkout/composer_install.rb +13 -0
- data/lib/overcommit/hook/post_checkout/index_tags.rb +12 -0
- data/lib/overcommit/hook/post_checkout/npm_install.rb +13 -0
- data/lib/overcommit/hook/post_checkout/submodule_status.rb +12 -0
- data/lib/overcommit/hook/post_checkout/yarn_install.rb +13 -0
- data/lib/overcommit/hook/post_commit/base.rb +12 -0
- data/lib/overcommit/hook/post_commit/bower_install.rb +13 -0
- data/lib/overcommit/hook/post_commit/bundle_install.rb +13 -0
- data/lib/overcommit/hook/post_commit/commitplease.rb +16 -0
- data/lib/overcommit/hook/post_commit/composer_install.rb +13 -0
- data/lib/overcommit/hook/post_commit/git_guilt.rb +43 -0
- data/lib/overcommit/hook/post_commit/index_tags.rb +12 -0
- data/lib/overcommit/hook/post_commit/npm_install.rb +13 -0
- data/lib/overcommit/hook/post_commit/submodule_status.rb +12 -0
- data/lib/overcommit/hook/post_commit/yarn_install.rb +13 -0
- data/lib/overcommit/hook/post_merge/base.rb +12 -0
- data/lib/overcommit/hook/post_merge/bower_install.rb +13 -0
- data/lib/overcommit/hook/post_merge/bundle_install.rb +13 -0
- data/lib/overcommit/hook/post_merge/composer_install.rb +13 -0
- data/lib/overcommit/hook/post_merge/index_tags.rb +12 -0
- data/lib/overcommit/hook/post_merge/npm_install.rb +13 -0
- data/lib/overcommit/hook/post_merge/submodule_status.rb +12 -0
- data/lib/overcommit/hook/post_merge/yarn_install.rb +13 -0
- data/lib/overcommit/hook/post_rewrite/base.rb +12 -0
- data/lib/overcommit/hook/post_rewrite/bower_install.rb +13 -0
- data/lib/overcommit/hook/post_rewrite/bundle_install.rb +13 -0
- data/lib/overcommit/hook/post_rewrite/composer_install.rb +13 -0
- data/lib/overcommit/hook/post_rewrite/index_tags.rb +19 -0
- data/lib/overcommit/hook/post_rewrite/npm_install.rb +13 -0
- data/lib/overcommit/hook/post_rewrite/submodule_status.rb +12 -0
- data/lib/overcommit/hook/post_rewrite/yarn_install.rb +13 -0
- data/lib/overcommit/hook/pre_commit/author_email.rb +26 -0
- data/lib/overcommit/hook/pre_commit/author_name.rb +25 -0
- data/lib/overcommit/hook/pre_commit/base.rb +19 -0
- data/lib/overcommit/hook/pre_commit/berksfile_check.rb +24 -0
- data/lib/overcommit/hook/pre_commit/broken_symlinks.rb +17 -0
- data/lib/overcommit/hook/pre_commit/bundle_audit.rb +24 -0
- data/lib/overcommit/hook/pre_commit/bundle_check.rb +32 -0
- data/lib/overcommit/hook/pre_commit/bundle_outdated.rb +25 -0
- data/lib/overcommit/hook/pre_commit/case_conflicts.rb +27 -0
- data/lib/overcommit/hook/pre_commit/chamber_compare.rb +43 -0
- data/lib/overcommit/hook/pre_commit/chamber_security.rb +15 -0
- data/lib/overcommit/hook/pre_commit/chamber_verification.rb +36 -0
- data/lib/overcommit/hook/pre_commit/code_spell_check.rb +36 -0
- data/lib/overcommit/hook/pre_commit/coffee_lint.rb +35 -0
- data/lib/overcommit/hook/pre_commit/cook_style.rb +35 -0
- data/lib/overcommit/hook/pre_commit/credo.rb +27 -0
- data/lib/overcommit/hook/pre_commit/css_lint.rb +26 -0
- data/lib/overcommit/hook/pre_commit/dogma.rb +33 -0
- data/lib/overcommit/hook/pre_commit/es_lint.rb +38 -0
- data/lib/overcommit/hook/pre_commit/execute_permissions.rb +76 -0
- data/lib/overcommit/hook/pre_commit/fasterer.rb +25 -0
- data/lib/overcommit/hook/pre_commit/file_size.rb +47 -0
- data/lib/overcommit/hook/pre_commit/fix_me.rb +17 -0
- data/lib/overcommit/hook/pre_commit/flay.rb +38 -0
- data/lib/overcommit/hook/pre_commit/foodcritic.rb +149 -0
- data/lib/overcommit/hook/pre_commit/forbidden_branches.rb +26 -0
- data/lib/overcommit/hook/pre_commit/ginkgo_focus.rb +23 -0
- data/lib/overcommit/hook/pre_commit/go_fmt.rb +17 -0
- data/lib/overcommit/hook/pre_commit/go_lint.rb +29 -0
- data/lib/overcommit/hook/pre_commit/go_vet.rb +24 -0
- data/lib/overcommit/hook/pre_commit/golangci_lint.rb +21 -0
- data/lib/overcommit/hook/pre_commit/hadolint.rb +27 -0
- data/lib/overcommit/hook/pre_commit/haml_lint.rb +23 -0
- data/lib/overcommit/hook/pre_commit/hard_tabs.rb +15 -0
- data/lib/overcommit/hook/pre_commit/hlint.rb +34 -0
- data/lib/overcommit/hook/pre_commit/html_hint.rb +23 -0
- data/lib/overcommit/hook/pre_commit/html_tidy.rb +30 -0
- data/lib/overcommit/hook/pre_commit/image_optim.rb +28 -0
- data/lib/overcommit/hook/pre_commit/java_checkstyle.rb +27 -0
- data/lib/overcommit/hook/pre_commit/js_hint.rb +23 -0
- data/lib/overcommit/hook/pre_commit/js_lint.rb +22 -0
- data/lib/overcommit/hook/pre_commit/jscs.rb +27 -0
- data/lib/overcommit/hook/pre_commit/jsl.rb +28 -0
- data/lib/overcommit/hook/pre_commit/json_syntax.rb +21 -0
- data/lib/overcommit/hook/pre_commit/kt_lint.rb +19 -0
- data/lib/overcommit/hook/pre_commit/license_finder.rb +14 -0
- data/lib/overcommit/hook/pre_commit/license_header.rb +48 -0
- data/lib/overcommit/hook/pre_commit/line_endings.rb +77 -0
- data/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb +16 -0
- data/lib/overcommit/hook/pre_commit/mdl.rb +29 -0
- data/lib/overcommit/hook/pre_commit/merge_conflicts.rb +16 -0
- data/lib/overcommit/hook/pre_commit/nginx_test.rb +26 -0
- data/lib/overcommit/hook/pre_commit/pep257.rb +23 -0
- data/lib/overcommit/hook/pre_commit/pep8.rb +23 -0
- data/lib/overcommit/hook/pre_commit/php_cs.rb +43 -0
- data/lib/overcommit/hook/pre_commit/php_cs_fixer.rb +57 -0
- data/lib/overcommit/hook/pre_commit/php_lint.rb +44 -0
- data/lib/overcommit/hook/pre_commit/php_stan.rb +30 -0
- data/lib/overcommit/hook/pre_commit/pronto.rb +12 -0
- data/lib/overcommit/hook/pre_commit/puppet_lint.rb +26 -0
- data/lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb +29 -0
- data/lib/overcommit/hook/pre_commit/pycodestyle.rb +23 -0
- data/lib/overcommit/hook/pre_commit/pydocstyle.rb +23 -0
- data/lib/overcommit/hook/pre_commit/pyflakes.rb +32 -0
- data/lib/overcommit/hook/pre_commit/pylint.rb +32 -0
- data/lib/overcommit/hook/pre_commit/python_flake8.rb +32 -0
- data/lib/overcommit/hook/pre_commit/rails_best_practices.rb +34 -0
- data/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb +58 -0
- data/lib/overcommit/hook/pre_commit/rake_target.rb +12 -0
- data/lib/overcommit/hook/pre_commit/reek.rb +26 -0
- data/lib/overcommit/hook/pre_commit/rst_lint.rb +27 -0
- data/lib/overcommit/hook/pre_commit/rubo_cop.rb +35 -0
- data/lib/overcommit/hook/pre_commit/ruby_lint.rb +23 -0
- data/lib/overcommit/hook/pre_commit/ruby_syntax.rb +27 -0
- data/lib/overcommit/hook/pre_commit/scalariform.rb +22 -0
- data/lib/overcommit/hook/pre_commit/scalastyle.rb +31 -0
- data/lib/overcommit/hook/pre_commit/scss_lint.rb +43 -0
- data/lib/overcommit/hook/pre_commit/semi_standard.rb +23 -0
- data/lib/overcommit/hook/pre_commit/shell_check.rb +23 -0
- data/lib/overcommit/hook/pre_commit/slim_lint.rb +23 -0
- data/lib/overcommit/hook/pre_commit/sqlint.rb +26 -0
- data/lib/overcommit/hook/pre_commit/standard.rb +23 -0
- data/lib/overcommit/hook/pre_commit/stylelint.rb +23 -0
- data/lib/overcommit/hook/pre_commit/swift_lint.rb +19 -0
- data/lib/overcommit/hook/pre_commit/terraform_format.rb +19 -0
- data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +15 -0
- data/lib/overcommit/hook/pre_commit/travis_lint.rb +15 -0
- data/lib/overcommit/hook/pre_commit/ts_lint.rb +28 -0
- data/lib/overcommit/hook/pre_commit/vint.rb +22 -0
- data/lib/overcommit/hook/pre_commit/w3c_css.rb +67 -0
- data/lib/overcommit/hook/pre_commit/w3c_html.rb +64 -0
- data/lib/overcommit/hook/pre_commit/xml_lint.rb +24 -0
- data/lib/overcommit/hook/pre_commit/xml_syntax.rb +21 -0
- data/lib/overcommit/hook/pre_commit/yaml_lint.rb +18 -0
- data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +20 -0
- data/lib/overcommit/hook/pre_commit/yard_coverage.rb +90 -0
- data/lib/overcommit/hook/pre_commit/yarn_check.rb +37 -0
- data/lib/overcommit/hook/pre_push/base.rb +33 -0
- data/lib/overcommit/hook/pre_push/brakeman.rb +15 -0
- data/lib/overcommit/hook/pre_push/cargo_test.rb +12 -0
- data/lib/overcommit/hook/pre_push/go_test.rb +14 -0
- data/lib/overcommit/hook/pre_push/golangci_lint.rb +16 -0
- data/lib/overcommit/hook/pre_push/minitest.rb +20 -0
- data/lib/overcommit/hook/pre_push/php_unit.rb +16 -0
- data/lib/overcommit/hook/pre_push/pronto.rb +12 -0
- data/lib/overcommit/hook/pre_push/protected_branches.rb +74 -0
- data/lib/overcommit/hook/pre_push/pytest.rb +16 -0
- data/lib/overcommit/hook/pre_push/python_nose.rb +16 -0
- data/lib/overcommit/hook/pre_push/r_spec.rb +16 -0
- data/lib/overcommit/hook/pre_push/rake_target.rb +12 -0
- data/lib/overcommit/hook/pre_push/test_unit.rb +16 -0
- data/lib/overcommit/hook/pre_rebase/base.rb +14 -0
- data/lib/overcommit/hook/pre_rebase/merged_commits.rb +31 -0
- data/lib/overcommit/hook/prepare_commit_msg/base.rb +25 -0
- data/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb +57 -0
- data/lib/overcommit/hook/shared/bower_install.rb +15 -0
- data/lib/overcommit/hook/shared/bundle_install.rb +15 -0
- data/lib/overcommit/hook/shared/composer_install.rb +15 -0
- data/lib/overcommit/hook/shared/index_tags.rb +14 -0
- data/lib/overcommit/hook/shared/npm_install.rb +15 -0
- data/lib/overcommit/hook/shared/pronto.rb +21 -0
- data/lib/overcommit/hook/shared/rake_target.rb +26 -0
- data/lib/overcommit/hook/shared/submodule_status.rb +32 -0
- data/lib/overcommit/hook/shared/yarn_install.rb +15 -0
- data/lib/overcommit/hook_context.rb +19 -0
- data/lib/overcommit/hook_context/base.rb +139 -0
- data/lib/overcommit/hook_context/commit_msg.rb +48 -0
- data/lib/overcommit/hook_context/post_checkout.rb +36 -0
- data/lib/overcommit/hook_context/post_commit.rb +33 -0
- data/lib/overcommit/hook_context/post_merge.rb +37 -0
- data/lib/overcommit/hook_context/post_rewrite.rb +49 -0
- data/lib/overcommit/hook_context/pre_commit.rb +212 -0
- data/lib/overcommit/hook_context/pre_push.rb +89 -0
- data/lib/overcommit/hook_context/pre_rebase.rb +38 -0
- data/lib/overcommit/hook_context/prepare_commit_msg.rb +34 -0
- data/lib/overcommit/hook_context/run_all.rb +48 -0
- data/lib/overcommit/hook_loader/base.rb +48 -0
- data/lib/overcommit/hook_loader/built_in_hook_loader.rb +14 -0
- data/lib/overcommit/hook_loader/plugin_hook_loader.rb +102 -0
- data/lib/overcommit/hook_runner.rb +219 -0
- data/lib/overcommit/hook_signer.rb +123 -0
- data/lib/overcommit/installer.rb +193 -0
- data/lib/overcommit/interrupt_handler.rb +91 -0
- data/lib/overcommit/logger.rb +92 -0
- data/lib/overcommit/message_processor.rb +148 -0
- data/lib/overcommit/os.rb +38 -0
- data/lib/overcommit/printer.rb +145 -0
- data/lib/overcommit/subprocess.rb +98 -0
- data/lib/overcommit/utils.rb +309 -0
- data/lib/overcommit/utils/file_utils.rb +71 -0
- data/lib/overcommit/utils/messages_utils.rb +77 -0
- data/lib/overcommit/version.rb +6 -0
- data/libexec/gerrit-change-id +174 -0
- data/libexec/index-tags +17 -0
- data/template-dir/hooks/commit-msg +116 -0
- data/template-dir/hooks/overcommit-hook +116 -0
- data/template-dir/hooks/post-checkout +116 -0
- data/template-dir/hooks/post-commit +116 -0
- data/template-dir/hooks/post-merge +116 -0
- data/template-dir/hooks/post-rewrite +116 -0
- data/template-dir/hooks/pre-commit +116 -0
- data/template-dir/hooks/pre-push +116 -0
- data/template-dir/hooks/pre-rebase +116 -0
- data/template-dir/hooks/prepare-commit-msg +116 -0
- metadata +303 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module Overcommit::HookContext
|
|
6
|
+
# Simulates a pre-commit context pretending that all files have been changed.
|
|
7
|
+
#
|
|
8
|
+
# This results in pre-commit hooks running against the entire repository,
|
|
9
|
+
# which is useful for automated CI scripts.
|
|
10
|
+
class RunAll < Base
|
|
11
|
+
def modified_files
|
|
12
|
+
@modified_files ||= all_files
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns all lines in the file since in this context the entire repo is
|
|
16
|
+
# being scrutinized.
|
|
17
|
+
#
|
|
18
|
+
# @param file [String]
|
|
19
|
+
# @return [Set]
|
|
20
|
+
def modified_lines_in_file(file)
|
|
21
|
+
@modified_lines_in_file ||= {}
|
|
22
|
+
@modified_lines_in_file[file] ||= Set.new(1..count_lines(file))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def hook_class_name
|
|
26
|
+
'PreCommit'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def hook_type_name
|
|
30
|
+
'pre_commit'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def hook_script_name
|
|
34
|
+
'pre-commit'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initial_commit?
|
|
38
|
+
return @initial_commit unless @initial_commit.nil?
|
|
39
|
+
@initial_commit = Overcommit::GitRepo.initial_commit?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def count_lines(file)
|
|
45
|
+
File.foreach(file).count
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overcommit::HookLoader
|
|
4
|
+
# Responsible for loading hooks from a file.
|
|
5
|
+
class Base
|
|
6
|
+
# @param config [Overcommit::Configuration]
|
|
7
|
+
# @param context [Overcommit::HookContext]
|
|
8
|
+
# @param logger [Overcommit::Logger]
|
|
9
|
+
def initialize(config, context, logger)
|
|
10
|
+
@config = config
|
|
11
|
+
@context = context
|
|
12
|
+
@log = logger
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# When implemented in subclasses, loads the hooks for which that subclass is
|
|
16
|
+
# responsible.
|
|
17
|
+
#
|
|
18
|
+
# @return [Array<Hook>]
|
|
19
|
+
def load_hooks
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :log
|
|
26
|
+
|
|
27
|
+
# Load and return a {Hook} from a CamelCase hook name.
|
|
28
|
+
def create_hook(hook_name)
|
|
29
|
+
hook_type_class = Overcommit::Hook.const_get(@context.hook_class_name)
|
|
30
|
+
hook_base_class = hook_type_class.const_get(:Base)
|
|
31
|
+
hook_class = hook_type_class.const_get(hook_name)
|
|
32
|
+
unless hook_class < hook_base_class
|
|
33
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
34
|
+
"Class #{hook_name} is not a subclass of #{hook_base_class}."
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
Overcommit::Hook.const_get(@context.hook_class_name).
|
|
39
|
+
const_get(hook_name).
|
|
40
|
+
new(@config, @context)
|
|
41
|
+
rescue LoadError, NameError => error
|
|
42
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
43
|
+
"Unable to load hook '#{hook_name}': #{error}",
|
|
44
|
+
error.backtrace
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overcommit::HookLoader
|
|
4
|
+
# Responsible for loading hooks that ship with Overcommit.
|
|
5
|
+
class BuiltInHookLoader < Base
|
|
6
|
+
def load_hooks
|
|
7
|
+
@config.enabled_builtin_hooks(@context).map do |hook_name|
|
|
8
|
+
underscored_hook_name = Overcommit::Utils.snake_case(hook_name)
|
|
9
|
+
require "overcommit/hook/#{@context.hook_type_name}/#{underscored_hook_name}"
|
|
10
|
+
create_hook(hook_name)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
|
|
5
|
+
module Overcommit::HookLoader
|
|
6
|
+
# Responsible for loading hooks that are specific to the repository Overcommit
|
|
7
|
+
# is running in.
|
|
8
|
+
class PluginHookLoader < Base
|
|
9
|
+
def load_hooks
|
|
10
|
+
check_for_modified_plugins if @config.verify_signatures?
|
|
11
|
+
|
|
12
|
+
hooks = plugin_paths.map do |plugin_path|
|
|
13
|
+
require plugin_path
|
|
14
|
+
|
|
15
|
+
hook_name = Overcommit::Utils.camel_case(File.basename(plugin_path, '.rb'))
|
|
16
|
+
create_hook(hook_name)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
hooks + ad_hoc_hook_names.map do |hook_name|
|
|
20
|
+
create_ad_hoc_hook(hook_name)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def update_signatures
|
|
25
|
+
log.success('No plugin signatures have changed') if modified_plugins.empty?
|
|
26
|
+
|
|
27
|
+
modified_plugins.each do |plugin|
|
|
28
|
+
plugin.update_signature!
|
|
29
|
+
log.warning "Updated signature of plugin #{plugin.hook_name}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def plugin_paths
|
|
36
|
+
directory = File.join(@config.plugin_directory, @context.hook_type_name)
|
|
37
|
+
Dir[File.join(directory, '*.rb')].sort
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def plugin_hook_names
|
|
41
|
+
plugin_paths.map do |path|
|
|
42
|
+
Overcommit::Utils.camel_case(File.basename(path, '.rb'))
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ad_hoc_hook_names
|
|
47
|
+
@config.enabled_ad_hoc_hooks(@context)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def modified_plugins
|
|
51
|
+
(plugin_hook_names + ad_hoc_hook_names).
|
|
52
|
+
map { |hook_name| Overcommit::HookSigner.new(hook_name, @config, @context) }.
|
|
53
|
+
select(&:signature_changed?)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def check_for_modified_plugins
|
|
57
|
+
return if modified_plugins.empty?
|
|
58
|
+
|
|
59
|
+
log.bold_warning "The following #{@context.hook_script_name} plugins " \
|
|
60
|
+
'have been added, changed, or had their configuration modified:'
|
|
61
|
+
log.newline
|
|
62
|
+
|
|
63
|
+
modified_plugins.each do |signer|
|
|
64
|
+
log.warning " * #{signer.hook_name} in #{signer.hook_path}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
log.newline
|
|
68
|
+
log.bold_warning 'You should verify the changes and then run:'
|
|
69
|
+
log.newline
|
|
70
|
+
log.warning "overcommit --sign #{@context.hook_script_name}"
|
|
71
|
+
log.newline
|
|
72
|
+
log.log "For more information, see #{Overcommit::REPO_URL}#security"
|
|
73
|
+
|
|
74
|
+
raise Overcommit::Exceptions::InvalidHookSignature
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def create_ad_hoc_hook(hook_name)
|
|
78
|
+
hook_module = Overcommit::Hook.const_get(@context.hook_class_name)
|
|
79
|
+
hook_base = hook_module.const_get('Base')
|
|
80
|
+
|
|
81
|
+
# Implement a simple class that executes the command and returns pass/fail
|
|
82
|
+
# based on the exit status
|
|
83
|
+
hook_class = Class.new(hook_base) do
|
|
84
|
+
def run
|
|
85
|
+
result = @context.execute_hook(command)
|
|
86
|
+
|
|
87
|
+
if result.success?
|
|
88
|
+
:pass
|
|
89
|
+
else
|
|
90
|
+
[:fail, result.stdout + result.stderr]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
hook_module.const_set(hook_name, hook_class).new(@config, @context)
|
|
96
|
+
rescue LoadError, NameError => error
|
|
97
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
98
|
+
"Unable to load hook '#{hook_name}': #{error}",
|
|
99
|
+
error.backtrace
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overcommit
|
|
4
|
+
# Responsible for loading the hooks the repository has configured and running
|
|
5
|
+
# them, collecting and displaying the results.
|
|
6
|
+
class HookRunner # rubocop:disable Metrics/ClassLength
|
|
7
|
+
# @param config [Overcommit::Configuration]
|
|
8
|
+
# @param logger [Overcommit::Logger]
|
|
9
|
+
# @param context [Overcommit::HookContext]
|
|
10
|
+
# @param printer [Overcommit::Printer]
|
|
11
|
+
def initialize(config, logger, context, printer)
|
|
12
|
+
@config = config
|
|
13
|
+
@log = logger
|
|
14
|
+
@context = context
|
|
15
|
+
@printer = printer
|
|
16
|
+
@hooks = []
|
|
17
|
+
|
|
18
|
+
@lock = Mutex.new
|
|
19
|
+
@resource = ConditionVariable.new
|
|
20
|
+
@slots_available = @config.concurrency
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Loads and runs the hooks registered for this {HookRunner}.
|
|
24
|
+
def run
|
|
25
|
+
# ASSUMPTION: we assume the setup and cleanup calls will never need to be
|
|
26
|
+
# interrupted, i.e. they will finish quickly. Should further evidence
|
|
27
|
+
# suggest this assumption does not hold, we will have to separately wrap
|
|
28
|
+
# these calls to allow some sort of "are you sure?" double-interrupt
|
|
29
|
+
# functionality, but until that's deemed necessary let's keep it simple.
|
|
30
|
+
InterruptHandler.isolate_from_interrupts do
|
|
31
|
+
# Load hooks before setting up the environment so that the repository
|
|
32
|
+
# has not been touched yet. This way any load errors at this point don't
|
|
33
|
+
# result in Overcommit leaving the repository in a bad state.
|
|
34
|
+
load_hooks
|
|
35
|
+
|
|
36
|
+
# Setup the environment without automatically calling
|
|
37
|
+
# `cleanup_environment` on an error. This is because it's possible that
|
|
38
|
+
# the `setup_environment` code did not fully complete, so there's no
|
|
39
|
+
# guarantee that `cleanup_environment` will be able to accomplish
|
|
40
|
+
# anything of value. The safest thing to do is therefore nothing in the
|
|
41
|
+
# unlikely case of failure.
|
|
42
|
+
@context.setup_environment
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
run_hooks
|
|
46
|
+
ensure
|
|
47
|
+
@context.cleanup_environment
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
attr_reader :log
|
|
55
|
+
|
|
56
|
+
def run_hooks # rubocop:disable Metrics/MethodLength
|
|
57
|
+
if @hooks.any?(&:enabled?)
|
|
58
|
+
@printer.start_run
|
|
59
|
+
|
|
60
|
+
# Sort so hooks requiring fewer processors get queued first. This
|
|
61
|
+
# ensures we make better use of our available processors
|
|
62
|
+
@hooks_left = @hooks.sort_by { |hook| processors_for_hook(hook) }
|
|
63
|
+
@threads = Array.new(@config.concurrency) { Thread.new(&method(:consume)) }
|
|
64
|
+
|
|
65
|
+
begin
|
|
66
|
+
InterruptHandler.disable_until_finished_or_interrupted do
|
|
67
|
+
@threads.each(&:join)
|
|
68
|
+
end
|
|
69
|
+
rescue Interrupt
|
|
70
|
+
@printer.interrupt_triggered
|
|
71
|
+
# We received an interrupt on the main thread, so alert the
|
|
72
|
+
# remaining workers that an exception occurred
|
|
73
|
+
@interrupted = true
|
|
74
|
+
@threads.each { |thread| thread.raise Interrupt }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
print_results
|
|
78
|
+
|
|
79
|
+
hook_failed = @failed || @interrupted
|
|
80
|
+
|
|
81
|
+
if hook_failed
|
|
82
|
+
message = @context.post_fail_message
|
|
83
|
+
@printer.hook_run_failed(message) unless message.nil?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
!hook_failed
|
|
87
|
+
else
|
|
88
|
+
@printer.nothing_to_run
|
|
89
|
+
true # Run was successful
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def consume
|
|
94
|
+
loop do
|
|
95
|
+
hook = @lock.synchronize { @hooks_left.pop }
|
|
96
|
+
break unless hook
|
|
97
|
+
run_hook(hook)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def wait_for_slot(hook)
|
|
102
|
+
@lock.synchronize do
|
|
103
|
+
slots_needed = processors_for_hook(hook)
|
|
104
|
+
|
|
105
|
+
loop do
|
|
106
|
+
if @slots_available >= slots_needed
|
|
107
|
+
@slots_available -= slots_needed
|
|
108
|
+
|
|
109
|
+
# Give another thread a chance since there are still slots available
|
|
110
|
+
@resource.signal if @slots_available > 0
|
|
111
|
+
break
|
|
112
|
+
elsif @slots_available > 0
|
|
113
|
+
# It's possible that another hook that requires fewer slots can be
|
|
114
|
+
# served, so give another a chance
|
|
115
|
+
@resource.signal
|
|
116
|
+
|
|
117
|
+
# Wait for a signal from another thread to try again
|
|
118
|
+
@resource.wait(@lock)
|
|
119
|
+
else
|
|
120
|
+
# Otherwise there are not slots left, so just wait for signal
|
|
121
|
+
@resource.wait(@lock)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def release_slot(hook)
|
|
128
|
+
@lock.synchronize do
|
|
129
|
+
slots_released = processors_for_hook(hook)
|
|
130
|
+
@slots_available += slots_released
|
|
131
|
+
|
|
132
|
+
# Signal every time in case there are threads that are already waiting for
|
|
133
|
+
# these slots to be released
|
|
134
|
+
@resource.signal
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def processors_for_hook(hook)
|
|
139
|
+
hook.parallelize? ? hook.processors : @config.concurrency
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def print_results
|
|
143
|
+
if @interrupted
|
|
144
|
+
@printer.run_interrupted
|
|
145
|
+
elsif @failed
|
|
146
|
+
@printer.run_failed
|
|
147
|
+
elsif @warned
|
|
148
|
+
@printer.run_warned
|
|
149
|
+
else
|
|
150
|
+
@printer.run_succeeded
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def run_hook(hook) # rubocop:disable Metrics/CyclomaticComplexity
|
|
155
|
+
status, output = nil, nil
|
|
156
|
+
|
|
157
|
+
begin
|
|
158
|
+
wait_for_slot(hook)
|
|
159
|
+
return if should_skip?(hook)
|
|
160
|
+
|
|
161
|
+
status, output = hook.run_and_transform
|
|
162
|
+
rescue Overcommit::Exceptions::MessageProcessingError => ex
|
|
163
|
+
status = :fail
|
|
164
|
+
output = ex.message
|
|
165
|
+
rescue StandardError => ex
|
|
166
|
+
status = :fail
|
|
167
|
+
output = "Hook raised unexpected error\n#{ex.message}\n#{ex.backtrace.join("\n")}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
@failed = true if status == :fail
|
|
171
|
+
@warned = true if status == :warn
|
|
172
|
+
|
|
173
|
+
@printer.end_hook(hook, status, output) unless @interrupted
|
|
174
|
+
|
|
175
|
+
status
|
|
176
|
+
rescue Interrupt
|
|
177
|
+
@interrupted = true
|
|
178
|
+
ensure
|
|
179
|
+
release_slot(hook)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def should_skip?(hook)
|
|
183
|
+
return true if @interrupted || !hook.enabled?
|
|
184
|
+
|
|
185
|
+
if hook.skip?
|
|
186
|
+
if hook.required?
|
|
187
|
+
@printer.required_hook_not_skipped(hook)
|
|
188
|
+
else
|
|
189
|
+
# Tell user if hook was skipped only if it actually would have run
|
|
190
|
+
@printer.hook_skipped(hook) if hook.run?
|
|
191
|
+
return true
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
!hook.run?
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def load_hooks
|
|
199
|
+
require "overcommit/hook/#{@context.hook_type_name}/base"
|
|
200
|
+
|
|
201
|
+
@hooks += HookLoader::BuiltInHookLoader.new(@config, @context, @log).load_hooks
|
|
202
|
+
|
|
203
|
+
# Load plugin hooks after so they can subclass existing hooks
|
|
204
|
+
@hooks += HookLoader::PluginHookLoader.new(@config, @context, @log).load_hooks
|
|
205
|
+
rescue LoadError => ex
|
|
206
|
+
# Include a more helpful message that will probably save some confusion
|
|
207
|
+
message = 'A load error occurred. ' +
|
|
208
|
+
if @config['gemfile']
|
|
209
|
+
"Did you forget to specify a gem in your `#{@config['gemfile']}`?"
|
|
210
|
+
else
|
|
211
|
+
'Did you forget to install a gem?'
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
raise Overcommit::Exceptions::HookLoadError,
|
|
215
|
+
"#{message}\n#{ex.message}",
|
|
216
|
+
ex.backtrace
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Overcommit
|
|
4
|
+
# Calculates, stores, and retrieves stored signatures of hook plugins.
|
|
5
|
+
class HookSigner
|
|
6
|
+
attr_reader :hook_name
|
|
7
|
+
|
|
8
|
+
# We don't want to include the skip setting as it is set by Overcommit
|
|
9
|
+
# itself
|
|
10
|
+
IGNORED_CONFIG_KEYS = %w[skip].freeze
|
|
11
|
+
|
|
12
|
+
# @param hook_name [String] name of the hook
|
|
13
|
+
# @param config [Overcommit::Configuration]
|
|
14
|
+
# @param context [Overcommit::HookContext]
|
|
15
|
+
def initialize(hook_name, config, context)
|
|
16
|
+
@hook_name = hook_name
|
|
17
|
+
@config = config
|
|
18
|
+
@context = context
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns the path of the file that should be incorporated into this hooks
|
|
22
|
+
# signature.
|
|
23
|
+
#
|
|
24
|
+
# @return [String]
|
|
25
|
+
def hook_path
|
|
26
|
+
@hook_path ||= begin
|
|
27
|
+
plugin_path = File.join(@config.plugin_directory,
|
|
28
|
+
@context.hook_type_name,
|
|
29
|
+
"#{Overcommit::Utils.snake_case(@hook_name)}.rb")
|
|
30
|
+
|
|
31
|
+
if File.exist?(plugin_path)
|
|
32
|
+
plugin_path
|
|
33
|
+
else
|
|
34
|
+
# Otherwise this is an ad hoc hook using an existing hook script
|
|
35
|
+
hook_config = @config.for_hook(@hook_name, @context.hook_class_name)
|
|
36
|
+
|
|
37
|
+
command = Array(hook_config['command'] || hook_config['required_executable'])
|
|
38
|
+
|
|
39
|
+
if @config.verify_signatures? &&
|
|
40
|
+
signable_file?(command.first) &&
|
|
41
|
+
!Overcommit::GitRepo.tracked?(command.first)
|
|
42
|
+
raise Overcommit::Exceptions::InvalidHookDefinition,
|
|
43
|
+
'Hook specified a `required_executable` or `command` that ' \
|
|
44
|
+
'is a path relative to the root of the repository, and so ' \
|
|
45
|
+
'must be tracked by Git in order to be signed'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
File.join(Overcommit::Utils.repo_root, command.first.to_s)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def signable_file?(file)
|
|
54
|
+
return unless file
|
|
55
|
+
sep = Overcommit::OS.windows? ? '\\' : File::SEPARATOR
|
|
56
|
+
file.start_with?(".#{sep}") ||
|
|
57
|
+
file.start_with?(Overcommit::Utils.repo_root)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Return whether the signature for this hook has changed since it was last
|
|
61
|
+
# calculated.
|
|
62
|
+
#
|
|
63
|
+
# @return [true,false]
|
|
64
|
+
def signature_changed?
|
|
65
|
+
signature != stored_signature
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Update the current stored signature for this hook.
|
|
69
|
+
def update_signature!
|
|
70
|
+
result = Overcommit::Utils.execute(
|
|
71
|
+
%w[git config --local] + [signature_config_key, signature]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
unless result.success?
|
|
75
|
+
raise Overcommit::Exceptions::GitConfigError,
|
|
76
|
+
"Unable to write to local repo git config: #{result.stderr}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# Calculates a hash of a hook using a combination of its configuration and
|
|
83
|
+
# file contents.
|
|
84
|
+
#
|
|
85
|
+
# This way, if either the plugin code changes or its configuration changes,
|
|
86
|
+
# the hash will change and we can alert the user to this change.
|
|
87
|
+
def signature
|
|
88
|
+
hook_config = @config.for_hook(@hook_name, @context.hook_class_name).
|
|
89
|
+
dup.
|
|
90
|
+
tap { |config| IGNORED_CONFIG_KEYS.each { |k| config.delete(k) } }
|
|
91
|
+
|
|
92
|
+
content_to_sign =
|
|
93
|
+
if signable_file?(hook_path) && Overcommit::GitRepo.tracked?(hook_path)
|
|
94
|
+
hook_contents
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
Digest::SHA256.hexdigest(content_to_sign.to_s + hook_config.to_s)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def hook_contents
|
|
101
|
+
File.read(hook_path)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def stored_signature
|
|
105
|
+
result = Overcommit::Utils.execute(
|
|
106
|
+
%w[git config --local --get] + [signature_config_key]
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if result.status == 1 # Key doesn't exist
|
|
110
|
+
return ''
|
|
111
|
+
elsif result.status != 0
|
|
112
|
+
raise Overcommit::Exceptions::GitConfigError,
|
|
113
|
+
"Unable to read from local repo git config: #{result.stderr}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
result.stdout.chomp
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def signature_config_key
|
|
120
|
+
"overcommit.#{@context.hook_class_name}.#{@hook_name}.signature"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|