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.
Files changed (226) hide show
  1. checksums.yaml +7 -0
  2. data/bin/overcommit +50 -0
  3. data/config/default.yml +1356 -0
  4. data/config/starter.yml +33 -0
  5. data/lib/overcommit.rb +26 -0
  6. data/lib/overcommit/cli.rb +223 -0
  7. data/lib/overcommit/command_splitter.rb +146 -0
  8. data/lib/overcommit/configuration.rb +350 -0
  9. data/lib/overcommit/configuration_loader.rb +96 -0
  10. data/lib/overcommit/configuration_validator.rb +186 -0
  11. data/lib/overcommit/constants.rb +12 -0
  12. data/lib/overcommit/exceptions.rb +52 -0
  13. data/lib/overcommit/git_config.rb +22 -0
  14. data/lib/overcommit/git_repo.rb +286 -0
  15. data/lib/overcommit/git_version.rb +17 -0
  16. data/lib/overcommit/hook/base.rb +294 -0
  17. data/lib/overcommit/hook/commit_msg/base.rb +14 -0
  18. data/lib/overcommit/hook/commit_msg/capitalized_subject.rb +25 -0
  19. data/lib/overcommit/hook/commit_msg/empty_message.rb +12 -0
  20. data/lib/overcommit/hook/commit_msg/gerrit_change_id.rb +22 -0
  21. data/lib/overcommit/hook/commit_msg/hard_tabs.rb +17 -0
  22. data/lib/overcommit/hook/commit_msg/message_format.rb +31 -0
  23. data/lib/overcommit/hook/commit_msg/russian_novel.rb +16 -0
  24. data/lib/overcommit/hook/commit_msg/single_line_subject.rb +16 -0
  25. data/lib/overcommit/hook/commit_msg/spell_check.rb +45 -0
  26. data/lib/overcommit/hook/commit_msg/text_width.rb +56 -0
  27. data/lib/overcommit/hook/commit_msg/trailing_period.rb +16 -0
  28. data/lib/overcommit/hook/post_checkout/base.rb +22 -0
  29. data/lib/overcommit/hook/post_checkout/bower_install.rb +13 -0
  30. data/lib/overcommit/hook/post_checkout/bundle_install.rb +13 -0
  31. data/lib/overcommit/hook/post_checkout/composer_install.rb +13 -0
  32. data/lib/overcommit/hook/post_checkout/index_tags.rb +12 -0
  33. data/lib/overcommit/hook/post_checkout/npm_install.rb +13 -0
  34. data/lib/overcommit/hook/post_checkout/submodule_status.rb +12 -0
  35. data/lib/overcommit/hook/post_checkout/yarn_install.rb +13 -0
  36. data/lib/overcommit/hook/post_commit/base.rb +12 -0
  37. data/lib/overcommit/hook/post_commit/bower_install.rb +13 -0
  38. data/lib/overcommit/hook/post_commit/bundle_install.rb +13 -0
  39. data/lib/overcommit/hook/post_commit/commitplease.rb +16 -0
  40. data/lib/overcommit/hook/post_commit/composer_install.rb +13 -0
  41. data/lib/overcommit/hook/post_commit/git_guilt.rb +43 -0
  42. data/lib/overcommit/hook/post_commit/index_tags.rb +12 -0
  43. data/lib/overcommit/hook/post_commit/npm_install.rb +13 -0
  44. data/lib/overcommit/hook/post_commit/submodule_status.rb +12 -0
  45. data/lib/overcommit/hook/post_commit/yarn_install.rb +13 -0
  46. data/lib/overcommit/hook/post_merge/base.rb +12 -0
  47. data/lib/overcommit/hook/post_merge/bower_install.rb +13 -0
  48. data/lib/overcommit/hook/post_merge/bundle_install.rb +13 -0
  49. data/lib/overcommit/hook/post_merge/composer_install.rb +13 -0
  50. data/lib/overcommit/hook/post_merge/index_tags.rb +12 -0
  51. data/lib/overcommit/hook/post_merge/npm_install.rb +13 -0
  52. data/lib/overcommit/hook/post_merge/submodule_status.rb +12 -0
  53. data/lib/overcommit/hook/post_merge/yarn_install.rb +13 -0
  54. data/lib/overcommit/hook/post_rewrite/base.rb +12 -0
  55. data/lib/overcommit/hook/post_rewrite/bower_install.rb +13 -0
  56. data/lib/overcommit/hook/post_rewrite/bundle_install.rb +13 -0
  57. data/lib/overcommit/hook/post_rewrite/composer_install.rb +13 -0
  58. data/lib/overcommit/hook/post_rewrite/index_tags.rb +19 -0
  59. data/lib/overcommit/hook/post_rewrite/npm_install.rb +13 -0
  60. data/lib/overcommit/hook/post_rewrite/submodule_status.rb +12 -0
  61. data/lib/overcommit/hook/post_rewrite/yarn_install.rb +13 -0
  62. data/lib/overcommit/hook/pre_commit/author_email.rb +26 -0
  63. data/lib/overcommit/hook/pre_commit/author_name.rb +25 -0
  64. data/lib/overcommit/hook/pre_commit/base.rb +19 -0
  65. data/lib/overcommit/hook/pre_commit/berksfile_check.rb +24 -0
  66. data/lib/overcommit/hook/pre_commit/broken_symlinks.rb +17 -0
  67. data/lib/overcommit/hook/pre_commit/bundle_audit.rb +24 -0
  68. data/lib/overcommit/hook/pre_commit/bundle_check.rb +32 -0
  69. data/lib/overcommit/hook/pre_commit/bundle_outdated.rb +25 -0
  70. data/lib/overcommit/hook/pre_commit/case_conflicts.rb +27 -0
  71. data/lib/overcommit/hook/pre_commit/chamber_compare.rb +43 -0
  72. data/lib/overcommit/hook/pre_commit/chamber_security.rb +15 -0
  73. data/lib/overcommit/hook/pre_commit/chamber_verification.rb +36 -0
  74. data/lib/overcommit/hook/pre_commit/code_spell_check.rb +36 -0
  75. data/lib/overcommit/hook/pre_commit/coffee_lint.rb +35 -0
  76. data/lib/overcommit/hook/pre_commit/cook_style.rb +35 -0
  77. data/lib/overcommit/hook/pre_commit/credo.rb +27 -0
  78. data/lib/overcommit/hook/pre_commit/css_lint.rb +26 -0
  79. data/lib/overcommit/hook/pre_commit/dogma.rb +33 -0
  80. data/lib/overcommit/hook/pre_commit/es_lint.rb +38 -0
  81. data/lib/overcommit/hook/pre_commit/execute_permissions.rb +76 -0
  82. data/lib/overcommit/hook/pre_commit/fasterer.rb +25 -0
  83. data/lib/overcommit/hook/pre_commit/file_size.rb +47 -0
  84. data/lib/overcommit/hook/pre_commit/fix_me.rb +17 -0
  85. data/lib/overcommit/hook/pre_commit/flay.rb +38 -0
  86. data/lib/overcommit/hook/pre_commit/foodcritic.rb +149 -0
  87. data/lib/overcommit/hook/pre_commit/forbidden_branches.rb +26 -0
  88. data/lib/overcommit/hook/pre_commit/ginkgo_focus.rb +23 -0
  89. data/lib/overcommit/hook/pre_commit/go_fmt.rb +17 -0
  90. data/lib/overcommit/hook/pre_commit/go_lint.rb +29 -0
  91. data/lib/overcommit/hook/pre_commit/go_vet.rb +24 -0
  92. data/lib/overcommit/hook/pre_commit/golangci_lint.rb +21 -0
  93. data/lib/overcommit/hook/pre_commit/hadolint.rb +27 -0
  94. data/lib/overcommit/hook/pre_commit/haml_lint.rb +23 -0
  95. data/lib/overcommit/hook/pre_commit/hard_tabs.rb +15 -0
  96. data/lib/overcommit/hook/pre_commit/hlint.rb +34 -0
  97. data/lib/overcommit/hook/pre_commit/html_hint.rb +23 -0
  98. data/lib/overcommit/hook/pre_commit/html_tidy.rb +30 -0
  99. data/lib/overcommit/hook/pre_commit/image_optim.rb +28 -0
  100. data/lib/overcommit/hook/pre_commit/java_checkstyle.rb +27 -0
  101. data/lib/overcommit/hook/pre_commit/js_hint.rb +23 -0
  102. data/lib/overcommit/hook/pre_commit/js_lint.rb +22 -0
  103. data/lib/overcommit/hook/pre_commit/jscs.rb +27 -0
  104. data/lib/overcommit/hook/pre_commit/jsl.rb +28 -0
  105. data/lib/overcommit/hook/pre_commit/json_syntax.rb +21 -0
  106. data/lib/overcommit/hook/pre_commit/kt_lint.rb +19 -0
  107. data/lib/overcommit/hook/pre_commit/license_finder.rb +14 -0
  108. data/lib/overcommit/hook/pre_commit/license_header.rb +48 -0
  109. data/lib/overcommit/hook/pre_commit/line_endings.rb +77 -0
  110. data/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb +16 -0
  111. data/lib/overcommit/hook/pre_commit/mdl.rb +29 -0
  112. data/lib/overcommit/hook/pre_commit/merge_conflicts.rb +16 -0
  113. data/lib/overcommit/hook/pre_commit/nginx_test.rb +26 -0
  114. data/lib/overcommit/hook/pre_commit/pep257.rb +23 -0
  115. data/lib/overcommit/hook/pre_commit/pep8.rb +23 -0
  116. data/lib/overcommit/hook/pre_commit/php_cs.rb +43 -0
  117. data/lib/overcommit/hook/pre_commit/php_cs_fixer.rb +57 -0
  118. data/lib/overcommit/hook/pre_commit/php_lint.rb +44 -0
  119. data/lib/overcommit/hook/pre_commit/php_stan.rb +30 -0
  120. data/lib/overcommit/hook/pre_commit/pronto.rb +12 -0
  121. data/lib/overcommit/hook/pre_commit/puppet_lint.rb +26 -0
  122. data/lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb +29 -0
  123. data/lib/overcommit/hook/pre_commit/pycodestyle.rb +23 -0
  124. data/lib/overcommit/hook/pre_commit/pydocstyle.rb +23 -0
  125. data/lib/overcommit/hook/pre_commit/pyflakes.rb +32 -0
  126. data/lib/overcommit/hook/pre_commit/pylint.rb +32 -0
  127. data/lib/overcommit/hook/pre_commit/python_flake8.rb +32 -0
  128. data/lib/overcommit/hook/pre_commit/rails_best_practices.rb +34 -0
  129. data/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb +58 -0
  130. data/lib/overcommit/hook/pre_commit/rake_target.rb +12 -0
  131. data/lib/overcommit/hook/pre_commit/reek.rb +26 -0
  132. data/lib/overcommit/hook/pre_commit/rst_lint.rb +27 -0
  133. data/lib/overcommit/hook/pre_commit/rubo_cop.rb +35 -0
  134. data/lib/overcommit/hook/pre_commit/ruby_lint.rb +23 -0
  135. data/lib/overcommit/hook/pre_commit/ruby_syntax.rb +27 -0
  136. data/lib/overcommit/hook/pre_commit/scalariform.rb +22 -0
  137. data/lib/overcommit/hook/pre_commit/scalastyle.rb +31 -0
  138. data/lib/overcommit/hook/pre_commit/scss_lint.rb +43 -0
  139. data/lib/overcommit/hook/pre_commit/semi_standard.rb +23 -0
  140. data/lib/overcommit/hook/pre_commit/shell_check.rb +23 -0
  141. data/lib/overcommit/hook/pre_commit/slim_lint.rb +23 -0
  142. data/lib/overcommit/hook/pre_commit/sqlint.rb +26 -0
  143. data/lib/overcommit/hook/pre_commit/standard.rb +23 -0
  144. data/lib/overcommit/hook/pre_commit/stylelint.rb +23 -0
  145. data/lib/overcommit/hook/pre_commit/swift_lint.rb +19 -0
  146. data/lib/overcommit/hook/pre_commit/terraform_format.rb +19 -0
  147. data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +15 -0
  148. data/lib/overcommit/hook/pre_commit/travis_lint.rb +15 -0
  149. data/lib/overcommit/hook/pre_commit/ts_lint.rb +28 -0
  150. data/lib/overcommit/hook/pre_commit/vint.rb +22 -0
  151. data/lib/overcommit/hook/pre_commit/w3c_css.rb +67 -0
  152. data/lib/overcommit/hook/pre_commit/w3c_html.rb +64 -0
  153. data/lib/overcommit/hook/pre_commit/xml_lint.rb +24 -0
  154. data/lib/overcommit/hook/pre_commit/xml_syntax.rb +21 -0
  155. data/lib/overcommit/hook/pre_commit/yaml_lint.rb +18 -0
  156. data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +20 -0
  157. data/lib/overcommit/hook/pre_commit/yard_coverage.rb +90 -0
  158. data/lib/overcommit/hook/pre_commit/yarn_check.rb +37 -0
  159. data/lib/overcommit/hook/pre_push/base.rb +33 -0
  160. data/lib/overcommit/hook/pre_push/brakeman.rb +15 -0
  161. data/lib/overcommit/hook/pre_push/cargo_test.rb +12 -0
  162. data/lib/overcommit/hook/pre_push/go_test.rb +14 -0
  163. data/lib/overcommit/hook/pre_push/golangci_lint.rb +16 -0
  164. data/lib/overcommit/hook/pre_push/minitest.rb +20 -0
  165. data/lib/overcommit/hook/pre_push/php_unit.rb +16 -0
  166. data/lib/overcommit/hook/pre_push/pronto.rb +12 -0
  167. data/lib/overcommit/hook/pre_push/protected_branches.rb +74 -0
  168. data/lib/overcommit/hook/pre_push/pytest.rb +16 -0
  169. data/lib/overcommit/hook/pre_push/python_nose.rb +16 -0
  170. data/lib/overcommit/hook/pre_push/r_spec.rb +16 -0
  171. data/lib/overcommit/hook/pre_push/rake_target.rb +12 -0
  172. data/lib/overcommit/hook/pre_push/test_unit.rb +16 -0
  173. data/lib/overcommit/hook/pre_rebase/base.rb +14 -0
  174. data/lib/overcommit/hook/pre_rebase/merged_commits.rb +31 -0
  175. data/lib/overcommit/hook/prepare_commit_msg/base.rb +25 -0
  176. data/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb +57 -0
  177. data/lib/overcommit/hook/shared/bower_install.rb +15 -0
  178. data/lib/overcommit/hook/shared/bundle_install.rb +15 -0
  179. data/lib/overcommit/hook/shared/composer_install.rb +15 -0
  180. data/lib/overcommit/hook/shared/index_tags.rb +14 -0
  181. data/lib/overcommit/hook/shared/npm_install.rb +15 -0
  182. data/lib/overcommit/hook/shared/pronto.rb +21 -0
  183. data/lib/overcommit/hook/shared/rake_target.rb +26 -0
  184. data/lib/overcommit/hook/shared/submodule_status.rb +32 -0
  185. data/lib/overcommit/hook/shared/yarn_install.rb +15 -0
  186. data/lib/overcommit/hook_context.rb +19 -0
  187. data/lib/overcommit/hook_context/base.rb +139 -0
  188. data/lib/overcommit/hook_context/commit_msg.rb +48 -0
  189. data/lib/overcommit/hook_context/post_checkout.rb +36 -0
  190. data/lib/overcommit/hook_context/post_commit.rb +33 -0
  191. data/lib/overcommit/hook_context/post_merge.rb +37 -0
  192. data/lib/overcommit/hook_context/post_rewrite.rb +49 -0
  193. data/lib/overcommit/hook_context/pre_commit.rb +212 -0
  194. data/lib/overcommit/hook_context/pre_push.rb +89 -0
  195. data/lib/overcommit/hook_context/pre_rebase.rb +38 -0
  196. data/lib/overcommit/hook_context/prepare_commit_msg.rb +34 -0
  197. data/lib/overcommit/hook_context/run_all.rb +48 -0
  198. data/lib/overcommit/hook_loader/base.rb +48 -0
  199. data/lib/overcommit/hook_loader/built_in_hook_loader.rb +14 -0
  200. data/lib/overcommit/hook_loader/plugin_hook_loader.rb +102 -0
  201. data/lib/overcommit/hook_runner.rb +219 -0
  202. data/lib/overcommit/hook_signer.rb +123 -0
  203. data/lib/overcommit/installer.rb +193 -0
  204. data/lib/overcommit/interrupt_handler.rb +91 -0
  205. data/lib/overcommit/logger.rb +92 -0
  206. data/lib/overcommit/message_processor.rb +148 -0
  207. data/lib/overcommit/os.rb +38 -0
  208. data/lib/overcommit/printer.rb +145 -0
  209. data/lib/overcommit/subprocess.rb +98 -0
  210. data/lib/overcommit/utils.rb +309 -0
  211. data/lib/overcommit/utils/file_utils.rb +71 -0
  212. data/lib/overcommit/utils/messages_utils.rb +77 -0
  213. data/lib/overcommit/version.rb +6 -0
  214. data/libexec/gerrit-change-id +174 -0
  215. data/libexec/index-tags +17 -0
  216. data/template-dir/hooks/commit-msg +116 -0
  217. data/template-dir/hooks/overcommit-hook +116 -0
  218. data/template-dir/hooks/post-checkout +116 -0
  219. data/template-dir/hooks/post-commit +116 -0
  220. data/template-dir/hooks/post-merge +116 -0
  221. data/template-dir/hooks/post-rewrite +116 -0
  222. data/template-dir/hooks/pre-commit +116 -0
  223. data/template-dir/hooks/pre-push +116 -0
  224. data/template-dir/hooks/pre-rebase +116 -0
  225. data/template-dir/hooks/prepare-commit-msg +116 -0
  226. 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