overcommit-jeygeethanmedia 0.53.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
|
5
|
+
module Overcommit
|
6
|
+
# Methods relating to the current operating system
|
7
|
+
module OS
|
8
|
+
class << self
|
9
|
+
def windows?
|
10
|
+
!(/mswin|msys|mingw|bccwin|wince|emc/ =~ host_os).nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def cygwin?
|
14
|
+
!(/cygwin/ =~ host_os).nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def mac?
|
18
|
+
!(/darwin|mac os/ =~ host_os).nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def unix?
|
22
|
+
!windows?
|
23
|
+
end
|
24
|
+
|
25
|
+
def linux?
|
26
|
+
unix? && !mac? && !cygwin?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def host_os
|
32
|
+
@host_os ||= ::RbConfig::CONFIG['host_os'].freeze
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
SEPARATOR = (windows? ? '\\' : File::SEPARATOR).freeze
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'monitor'
|
4
|
+
|
5
|
+
module Overcommit
|
6
|
+
# Provide a set of callbacks which can be executed as events occur during the
|
7
|
+
# course of {HookRunner#run}.
|
8
|
+
class Printer
|
9
|
+
attr_reader :log
|
10
|
+
|
11
|
+
def initialize(config, logger, context)
|
12
|
+
@config = config
|
13
|
+
@log = logger
|
14
|
+
@context = context
|
15
|
+
@lock = Monitor.new # Need to use monitor so we can have re-entrant locks
|
16
|
+
synchronize_all_methods
|
17
|
+
end
|
18
|
+
|
19
|
+
# Executed at the very beginning of running the collection of hooks.
|
20
|
+
def start_run
|
21
|
+
log.bold "Running #{hook_script_name} hooks" unless @config['quiet']
|
22
|
+
end
|
23
|
+
|
24
|
+
def nothing_to_run
|
25
|
+
log.debug "✓ No applicable #{hook_script_name} hooks to run"
|
26
|
+
end
|
27
|
+
|
28
|
+
def hook_skipped(hook)
|
29
|
+
log.warning "Skipping #{hook.name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def required_hook_not_skipped(hook)
|
33
|
+
log.warning "Cannot skip #{hook.name} since it is required"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Executed at the end of an individual hook run.
|
37
|
+
def end_hook(hook, status, output)
|
38
|
+
# Want to print the header for quiet hooks only if the result wasn't good
|
39
|
+
# so that the user knows what failed
|
40
|
+
print_header(hook) if (!hook.quiet? && !@config['quiet']) || status != :pass
|
41
|
+
|
42
|
+
print_result(hook, status, output)
|
43
|
+
end
|
44
|
+
|
45
|
+
def interrupt_triggered
|
46
|
+
log.newline
|
47
|
+
log.error 'Interrupt signal received. Stopping hooks...'
|
48
|
+
end
|
49
|
+
|
50
|
+
# Executed when a hook run was interrupted/cancelled by user.
|
51
|
+
def run_interrupted
|
52
|
+
log.newline
|
53
|
+
log.warning '⚠ Hook run interrupted by user'
|
54
|
+
log.warning '⚠ If files appear modified/missing, check your stash to recover them'
|
55
|
+
log.newline
|
56
|
+
end
|
57
|
+
|
58
|
+
# Executed when one or more hooks by the end of the run.
|
59
|
+
def run_failed
|
60
|
+
log.newline
|
61
|
+
log.error "✗ One or more #{hook_script_name} hooks failed"
|
62
|
+
log.newline
|
63
|
+
end
|
64
|
+
|
65
|
+
# Executed when no hooks failed by the end of the run, but some warned.
|
66
|
+
def run_warned
|
67
|
+
log.newline
|
68
|
+
log.warning "⚠ All #{hook_script_name} hooks passed, but with warnings"
|
69
|
+
log.newline
|
70
|
+
end
|
71
|
+
|
72
|
+
# Executed when no hooks failed by the end of the run.
|
73
|
+
def run_succeeded
|
74
|
+
unless @config['quiet']
|
75
|
+
log.newline
|
76
|
+
log.success "✓ All #{hook_script_name} hooks passed"
|
77
|
+
log.newline
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def hook_run_failed(message)
|
82
|
+
log.newline
|
83
|
+
log.log message
|
84
|
+
log.newline
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def print_header(hook)
|
90
|
+
hook_name = "[#{hook.name}] "
|
91
|
+
log.partial hook.description
|
92
|
+
log.partial '.' * [70 - hook.description.length - hook_name.length, 0].max
|
93
|
+
log.partial hook_name
|
94
|
+
end
|
95
|
+
|
96
|
+
def print_result(hook, status, output) # rubocop:disable Metrics/CyclomaticComplexity
|
97
|
+
case status
|
98
|
+
when :pass
|
99
|
+
log.success 'OK' unless @config['quiet'] || hook.quiet?
|
100
|
+
print_report(output)
|
101
|
+
when :warn
|
102
|
+
log.warning 'WARNING'
|
103
|
+
print_report(output, :bold_warning)
|
104
|
+
when :fail
|
105
|
+
log.error 'FAILED'
|
106
|
+
print_report(output, :bold_error)
|
107
|
+
when :interrupt
|
108
|
+
log.error 'INTERRUPTED'
|
109
|
+
print_report(output, :bold_error)
|
110
|
+
else
|
111
|
+
log.error '???'
|
112
|
+
print_report("Hook returned unknown status `#{status.inspect}` -- ignoring.",
|
113
|
+
:bold_error)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def print_report(output, format = :log)
|
118
|
+
log.send(format, output) unless output.nil? || output.empty?
|
119
|
+
end
|
120
|
+
|
121
|
+
def hook_script_name
|
122
|
+
@context.hook_script_name
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get all public methods that were defined on this class and wrap them with
|
126
|
+
# synchronization locks so we ensure the output isn't interleaved amongst
|
127
|
+
# the various threads.
|
128
|
+
def synchronize_all_methods
|
129
|
+
methods = self.class.instance_methods - self.class.superclass.instance_methods
|
130
|
+
|
131
|
+
methods.each do |method_name|
|
132
|
+
old_method = :"old_#{method_name}"
|
133
|
+
new_method = :"synchronized_#{method_name}"
|
134
|
+
|
135
|
+
self.class.__send__(:alias_method, old_method, method_name)
|
136
|
+
|
137
|
+
self.class.send(:define_method, new_method) do |*args|
|
138
|
+
@lock.synchronize { __send__(old_method, *args) }
|
139
|
+
end
|
140
|
+
|
141
|
+
self.class.__send__(:alias_method, method_name, new_method)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'childprocess'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module Overcommit
|
7
|
+
# Manages execution of a child process, collecting the exit status and
|
8
|
+
# standard out/error output.
|
9
|
+
class Subprocess
|
10
|
+
# Encapsulates the result of a process.
|
11
|
+
#
|
12
|
+
# @attr_reader status [Integer] exit status code returned by process
|
13
|
+
# @attr_reader stdout [String] standard output stream output
|
14
|
+
# @attr_reader stderr [String] standard error stream output
|
15
|
+
Result = Struct.new(:status, :stdout, :stderr) do
|
16
|
+
def success?
|
17
|
+
status == 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Spawns a new process using the given array of arguments (the first
|
23
|
+
# element is the command).
|
24
|
+
#
|
25
|
+
# @param args [Array<String>]
|
26
|
+
# @param options [Hash]
|
27
|
+
# @option options [String] input string to pass via standard input stream
|
28
|
+
# @return [Result]
|
29
|
+
def spawn(args, options = {})
|
30
|
+
args = win32_prepare_args(args) if OS.windows?
|
31
|
+
|
32
|
+
process = ChildProcess.build(*args)
|
33
|
+
|
34
|
+
out, err = assign_output_streams(process)
|
35
|
+
|
36
|
+
process.duplex = true if options[:input] # Make stdin available if needed
|
37
|
+
process.start
|
38
|
+
if options[:input]
|
39
|
+
begin
|
40
|
+
process.io.stdin.puts(options[:input])
|
41
|
+
rescue StandardError # rubocop:disable Lint/HandleExceptions
|
42
|
+
# Silently ignore if the standard input stream of the spawned
|
43
|
+
# process is closed before we get a chance to write to it. This
|
44
|
+
# happens on JRuby a lot.
|
45
|
+
ensure
|
46
|
+
process.io.stdin.close
|
47
|
+
end
|
48
|
+
end
|
49
|
+
process.wait
|
50
|
+
|
51
|
+
err.rewind
|
52
|
+
out.rewind
|
53
|
+
|
54
|
+
Result.new(process.exit_code, out.read, err.read)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Spawns a new process in the background using the given array of
|
58
|
+
# arguments (the first element is the command).
|
59
|
+
def spawn_detached(args)
|
60
|
+
args = win32_prepare_args(args) if OS.windows?
|
61
|
+
|
62
|
+
process = ChildProcess.build(*args)
|
63
|
+
process.detach = true
|
64
|
+
|
65
|
+
assign_output_streams(process)
|
66
|
+
|
67
|
+
process.start
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Necessary to run commands in the cmd.exe context.
|
73
|
+
# Args are joined to properly handle quotes and special characters.
|
74
|
+
def win32_prepare_args(args)
|
75
|
+
args = args.map do |arg|
|
76
|
+
# Quote args that contain whitespace
|
77
|
+
arg = "\"#{arg}\"" if arg =~ /\s/
|
78
|
+
|
79
|
+
# Escape cmd.exe metacharacters
|
80
|
+
arg.gsub(/[()%!^"<>&|]/, '^\0')
|
81
|
+
end
|
82
|
+
|
83
|
+
%w[cmd.exe /c] + [args.join(' ')]
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param process [ChildProcess]
|
87
|
+
# @return [Array<IO>]
|
88
|
+
def assign_output_streams(process)
|
89
|
+
%w[out err].map do |stream_name|
|
90
|
+
::Tempfile.new(stream_name).tap do |stream|
|
91
|
+
stream.sync = true
|
92
|
+
process.io.send("std#{stream_name}=", stream)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'overcommit/os'
|
5
|
+
require 'overcommit/subprocess'
|
6
|
+
require 'overcommit/command_splitter'
|
7
|
+
require 'tempfile'
|
8
|
+
|
9
|
+
module Overcommit
|
10
|
+
# Utility functions for general use.
|
11
|
+
module Utils
|
12
|
+
# Helper class for doing quick constraint validations on version numbers.
|
13
|
+
#
|
14
|
+
# This allows us to execute code based on the git version.
|
15
|
+
class Version < Gem::Version
|
16
|
+
# Overload comparison operators so we can conveniently compare this
|
17
|
+
# version directly to a string in code.
|
18
|
+
%w[< <= > >= == !=].each do |operator|
|
19
|
+
define_method operator do |version|
|
20
|
+
case version
|
21
|
+
when String
|
22
|
+
super(Gem::Version.new(version))
|
23
|
+
else
|
24
|
+
super(version)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# @return [Overcommit::Logger] logger with which to send debug output
|
32
|
+
attr_accessor :log
|
33
|
+
|
34
|
+
def script_path(script)
|
35
|
+
File.join(Overcommit::HOME, 'libexec', script)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns an absolute path to the root of the repository.
|
39
|
+
#
|
40
|
+
# We do this ourselves rather than call `git rev-parse --show-toplevel` to
|
41
|
+
# solve an issue where the .git directory might not actually be valid in
|
42
|
+
# tests.
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
def repo_root
|
46
|
+
@repo_root ||=
|
47
|
+
begin
|
48
|
+
result = execute(%w[git rev-parse --show-toplevel])
|
49
|
+
unless result.success?
|
50
|
+
raise Overcommit::Exceptions::InvalidGitRepo,
|
51
|
+
'Unable to determine location of GIT_DIR. ' \
|
52
|
+
'Not a recognizable Git repository!'
|
53
|
+
end
|
54
|
+
result.stdout.chomp("\n")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns an absolute path to the .git directory for a repo.
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
def git_dir
|
62
|
+
@git_dir ||=
|
63
|
+
begin
|
64
|
+
cmd = %w[git rev-parse]
|
65
|
+
cmd << (GIT_VERSION < '2.5' ? '--git-dir' : '--git-common-dir')
|
66
|
+
result = execute(cmd)
|
67
|
+
unless result.success?
|
68
|
+
raise Overcommit::Exceptions::InvalidGitRepo,
|
69
|
+
'Unable to determine location of GIT_DIR. ' \
|
70
|
+
'Not a recognizable Git repository!'
|
71
|
+
end
|
72
|
+
File.expand_path(result.stdout.chomp("\n"), Dir.pwd)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Remove ANSI escape sequences from a string.
|
77
|
+
#
|
78
|
+
# This is useful for stripping colorized output from external tools.
|
79
|
+
#
|
80
|
+
# @param text [String]
|
81
|
+
# @return [String]
|
82
|
+
def strip_color_codes(text)
|
83
|
+
text.gsub(/\e\[(\d+)(;\d+)*m/, '')
|
84
|
+
end
|
85
|
+
|
86
|
+
# Shamelessly stolen from:
|
87
|
+
# stackoverflow.com/questions/1509915/converting-camel-case-to-underscore-case-in-ruby
|
88
|
+
def snake_case(str)
|
89
|
+
str.gsub(/::/, '/').
|
90
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
91
|
+
gsub(/([a-z\d])([A-Z])/, '\1_\2').
|
92
|
+
tr('-', '_').
|
93
|
+
downcase
|
94
|
+
end
|
95
|
+
|
96
|
+
# Converts a string containing underscores/hyphens/spaces into CamelCase.
|
97
|
+
def camel_case(str)
|
98
|
+
str.split(/_|-| /).map { |part| part.sub(/^\w/, &:upcase) }.join
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns a list of supported hook types (pre-commit, commit-msg, etc.)
|
102
|
+
def supported_hook_types
|
103
|
+
Dir[File.join(HOOK_DIRECTORY, '*')].
|
104
|
+
select { |file| File.directory?(file) }.
|
105
|
+
reject { |file| File.basename(file) == 'shared' }.
|
106
|
+
map { |file| File.basename(file).tr('_', '-') }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns a list of supported hook classes (PreCommit, CommitMsg, etc.)
|
110
|
+
def supported_hook_type_classes
|
111
|
+
supported_hook_types.map do |file|
|
112
|
+
file.split('-').map(&:capitalize).join
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# @param cmd [String]
|
117
|
+
# @return [true,false] whether a command can be found given the current
|
118
|
+
# environment path.
|
119
|
+
def in_path?(cmd)
|
120
|
+
# ENV['PATH'] doesn't include the repo root, but that is a valid
|
121
|
+
# location for executables, so we want to add it to the list of places
|
122
|
+
# we are checking for the executable.
|
123
|
+
paths = [repo_root] + ENV['PATH'].split(File::PATH_SEPARATOR)
|
124
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
125
|
+
paths.each do |path|
|
126
|
+
exts.each do |ext|
|
127
|
+
cmd_with_ext = cmd.upcase.end_with?(ext.upcase) ? cmd : "#{cmd}#{ext}"
|
128
|
+
full_path = File.join(path, cmd_with_ext)
|
129
|
+
return true if File.executable?(full_path)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
false
|
133
|
+
end
|
134
|
+
|
135
|
+
# Return the parent command that triggered this hook run
|
136
|
+
#
|
137
|
+
# @return [String,nil] the command as a string, if a parent exists.
|
138
|
+
def parent_command
|
139
|
+
# When run in Docker containers, there may be no parent process.
|
140
|
+
return if Process.ppid.zero?
|
141
|
+
|
142
|
+
if OS.windows?
|
143
|
+
`wmic process where ProcessId=#{Process.ppid} get CommandLine /FORMAT:VALUE`.
|
144
|
+
strip.
|
145
|
+
slice(/(?<=CommandLine=).+/)
|
146
|
+
elsif OS.cygwin?
|
147
|
+
# Cygwin's `ps` command behaves differently than the traditional
|
148
|
+
# Linux version, but a comparable `procps` is provided to compensate.
|
149
|
+
`procps -ocommand= -p #{Process.ppid}`.chomp
|
150
|
+
else
|
151
|
+
`ps -ocommand= -p #{Process.ppid}`.chomp
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Execute a command in a subprocess, capturing exit status and output from
|
156
|
+
# both standard and error streams.
|
157
|
+
#
|
158
|
+
# This is intended to provide a centralized place to perform any checks or
|
159
|
+
# filtering of the command before executing it.
|
160
|
+
#
|
161
|
+
# The `args` option provides a convenient way of splitting up long
|
162
|
+
# argument lists which would otherwise exceed the maximum command line
|
163
|
+
# length of the OS. It will break up the list into chunks and run the
|
164
|
+
# command with the same prefix `initial_args`, finally combining the
|
165
|
+
# output together at the end.
|
166
|
+
#
|
167
|
+
# This requires that the external command you are running can have its
|
168
|
+
# work split up in this way and still produce the same resultant output
|
169
|
+
# when outputs of the individual commands are concatenated back together.
|
170
|
+
#
|
171
|
+
# @param initial_args [Array<String>]
|
172
|
+
# @param options [Hash]
|
173
|
+
# @option options [Array<String>] :args long list of arguments to split up
|
174
|
+
# @return [Overcommit::Subprocess::Result] status, stdout, and stderr
|
175
|
+
def execute(initial_args, options = {})
|
176
|
+
if initial_args.include?('|')
|
177
|
+
raise Overcommit::Exceptions::InvalidCommandArgs,
|
178
|
+
'Cannot pipe commands with the `execute` helper'
|
179
|
+
end
|
180
|
+
|
181
|
+
result =
|
182
|
+
if (splittable_args = options.fetch(:args) { [] }).any?
|
183
|
+
debug(initial_args.join(' ') + " ... (#{splittable_args.length} splittable args)")
|
184
|
+
Overcommit::CommandSplitter.execute(initial_args, options)
|
185
|
+
else
|
186
|
+
debug(initial_args.join(' '))
|
187
|
+
Overcommit::Subprocess.spawn(initial_args, options)
|
188
|
+
end
|
189
|
+
|
190
|
+
debug("EXIT STATUS: #{result.status}")
|
191
|
+
debug("STDOUT: #{result.stdout.inspect}")
|
192
|
+
debug("STDERR: #{result.stderr.inspect}")
|
193
|
+
|
194
|
+
result
|
195
|
+
end
|
196
|
+
|
197
|
+
# Execute a command in a subprocess, returning immediately.
|
198
|
+
#
|
199
|
+
# This provides a convenient way to execute long-running processes for
|
200
|
+
# which we do not need to know the result.
|
201
|
+
#
|
202
|
+
# @param args [Array<String>]
|
203
|
+
# @return [ChildProcess] detached process spawned in the background
|
204
|
+
def execute_in_background(args)
|
205
|
+
if args.include?('|')
|
206
|
+
raise Overcommit::Exceptions::InvalidCommandArgs,
|
207
|
+
'Cannot pipe commands with the `execute_in_background` helper'
|
208
|
+
end
|
209
|
+
|
210
|
+
debug("Spawning background task: #{args.join(' ')}")
|
211
|
+
Subprocess.spawn_detached(args)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Return the number of processors used by the OS for process scheduling.
|
215
|
+
#
|
216
|
+
# @see https://github.com/grosser/parallel/blob/v1.6.1/lib/parallel/processor_count.rb#L17-L51
|
217
|
+
def processor_count # rubocop:disable all
|
218
|
+
@processor_count ||=
|
219
|
+
begin
|
220
|
+
if Overcommit::OS.windows?
|
221
|
+
require 'win32ole'
|
222
|
+
result = WIN32OLE.connect('winmgmts://').ExecQuery(
|
223
|
+
'select NumberOfLogicalProcessors from Win32_Processor'
|
224
|
+
)
|
225
|
+
result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
|
226
|
+
elsif File.readable?('/proc/cpuinfo')
|
227
|
+
IO.read('/proc/cpuinfo').scan(/^processor/).size
|
228
|
+
elsif File.executable?('/usr/bin/hwprefs')
|
229
|
+
IO.popen('/usr/bin/hwprefs thread_count').read.to_i
|
230
|
+
elsif File.executable?('/usr/sbin/psrinfo')
|
231
|
+
IO.popen('/usr/sbin/psrinfo').read.scan(/^.*on-*line/).size
|
232
|
+
elsif File.executable?('/usr/sbin/ioscan')
|
233
|
+
IO.popen('/usr/sbin/ioscan -kC processor') do |out|
|
234
|
+
out.read.scan(/^.*processor/).size
|
235
|
+
end
|
236
|
+
elsif File.executable?('/usr/sbin/pmcycles')
|
237
|
+
IO.popen('/usr/sbin/pmcycles -m').read.count("\n")
|
238
|
+
elsif File.executable?('/usr/sbin/lsdev')
|
239
|
+
IO.popen('/usr/sbin/lsdev -Cc processor -S 1').read.count("\n")
|
240
|
+
elsif File.executable?('/usr/sbin/sysctl')
|
241
|
+
IO.popen('/usr/sbin/sysctl -n hw.ncpu').read.to_i
|
242
|
+
elsif File.executable?('/sbin/sysctl')
|
243
|
+
IO.popen('/sbin/sysctl -n hw.ncpu').read.to_i
|
244
|
+
else
|
245
|
+
# Unknown platform; assume 1 processor
|
246
|
+
1
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Calls a block of code with a modified set of environment variables,
|
252
|
+
# restoring them once the code has executed.
|
253
|
+
def with_environment(env)
|
254
|
+
old_env = {}
|
255
|
+
env.each do |var, value|
|
256
|
+
old_env[var] = ENV[var.to_s]
|
257
|
+
ENV[var.to_s] = value
|
258
|
+
end
|
259
|
+
|
260
|
+
yield
|
261
|
+
ensure
|
262
|
+
old_env.each { |var, value| ENV[var.to_s] = value }
|
263
|
+
end
|
264
|
+
|
265
|
+
# Returns whether a file is a broken symlink.
|
266
|
+
#
|
267
|
+
# @return [true,false]
|
268
|
+
def broken_symlink?(file)
|
269
|
+
# JRuby's implementation of File.exist? returns true for broken
|
270
|
+
# symlinks, so we need use File.size?
|
271
|
+
Overcommit::Utils::FileUtils.symlink?(file) && File.size?(file).nil?
|
272
|
+
end
|
273
|
+
|
274
|
+
# Convert a glob pattern to an absolute path glob pattern rooted from the
|
275
|
+
# repository root directory.
|
276
|
+
#
|
277
|
+
# @param glob [String]
|
278
|
+
# @return [String]
|
279
|
+
def convert_glob_to_absolute(glob)
|
280
|
+
File.join(repo_root, glob)
|
281
|
+
end
|
282
|
+
|
283
|
+
# Return whether a pattern matches the given path.
|
284
|
+
#
|
285
|
+
# @param pattern [String]
|
286
|
+
# @param path [String]
|
287
|
+
def matches_path?(pattern, path)
|
288
|
+
File.fnmatch?(
|
289
|
+
pattern, path,
|
290
|
+
File::FNM_PATHNAME | # Wildcard doesn't match separator
|
291
|
+
File::FNM_DOTMATCH # Wildcards match dotfiles
|
292
|
+
)
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
|
297
|
+
# Log debug output.
|
298
|
+
#
|
299
|
+
# This is necessary since some specs indirectly call utility functions but
|
300
|
+
# don't explicitly set the logger for the Utils class, so we do a quick
|
301
|
+
# check here to see if it's set before we attempt to log.
|
302
|
+
#
|
303
|
+
# @param args [Array<String>]
|
304
|
+
def debug(*args)
|
305
|
+
log&.debug(*args)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|