jawshooah-overcommit 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/bin/overcommit +8 -0
  3. data/config/default.yml +275 -0
  4. data/config/starter.yml +31 -0
  5. data/lib/overcommit.rb +20 -0
  6. data/lib/overcommit/cli.rb +205 -0
  7. data/lib/overcommit/configuration.rb +183 -0
  8. data/lib/overcommit/configuration_loader.rb +49 -0
  9. data/lib/overcommit/configuration_validator.rb +40 -0
  10. data/lib/overcommit/constants.rb +8 -0
  11. data/lib/overcommit/exceptions.rb +35 -0
  12. data/lib/overcommit/git_repo.rb +147 -0
  13. data/lib/overcommit/hook/base.rb +174 -0
  14. data/lib/overcommit/hook/commit_msg/base.rb +11 -0
  15. data/lib/overcommit/hook/commit_msg/gerrit_change_id.rb +18 -0
  16. data/lib/overcommit/hook/commit_msg/hard_tabs.rb +13 -0
  17. data/lib/overcommit/hook/commit_msg/russian_novel.rb +14 -0
  18. data/lib/overcommit/hook/commit_msg/single_line_subject.rb +12 -0
  19. data/lib/overcommit/hook/commit_msg/text_width.rb +38 -0
  20. data/lib/overcommit/hook/commit_msg/trailing_period.rb +12 -0
  21. data/lib/overcommit/hook/post_checkout/base.rb +11 -0
  22. data/lib/overcommit/hook/post_checkout/index_tags.rb +26 -0
  23. data/lib/overcommit/hook/post_commit/base.rb +11 -0
  24. data/lib/overcommit/hook/post_commit/git_guilt.rb +9 -0
  25. data/lib/overcommit/hook/pre_commit/author_email.rb +18 -0
  26. data/lib/overcommit/hook/pre_commit/author_name.rb +17 -0
  27. data/lib/overcommit/hook/pre_commit/base.rb +70 -0
  28. data/lib/overcommit/hook/pre_commit/berksfile_check.rb +20 -0
  29. data/lib/overcommit/hook/pre_commit/brakeman.rb +12 -0
  30. data/lib/overcommit/hook/pre_commit/broken_symlinks.rb +15 -0
  31. data/lib/overcommit/hook/pre_commit/bundle_check.rb +25 -0
  32. data/lib/overcommit/hook/pre_commit/chamber_security.rb +11 -0
  33. data/lib/overcommit/hook/pre_commit/coffee_lint.rb +11 -0
  34. data/lib/overcommit/hook/pre_commit/css_lint.rb +11 -0
  35. data/lib/overcommit/hook/pre_commit/go_lint.rb +12 -0
  36. data/lib/overcommit/hook/pre_commit/haml_lint.rb +19 -0
  37. data/lib/overcommit/hook/pre_commit/hard_tabs.rb +14 -0
  38. data/lib/overcommit/hook/pre_commit/image_optim.rb +41 -0
  39. data/lib/overcommit/hook/pre_commit/js_hint.rb +13 -0
  40. data/lib/overcommit/hook/pre_commit/jscs.rb +22 -0
  41. data/lib/overcommit/hook/pre_commit/json_syntax.rb +22 -0
  42. data/lib/overcommit/hook/pre_commit/jsx_hint.rb +13 -0
  43. data/lib/overcommit/hook/pre_commit/jsxcs.rb +20 -0
  44. data/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb +14 -0
  45. data/lib/overcommit/hook/pre_commit/merge_conflicts.rb +14 -0
  46. data/lib/overcommit/hook/pre_commit/pry_binding.rb +14 -0
  47. data/lib/overcommit/hook/pre_commit/python_flake8.rb +11 -0
  48. data/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb +45 -0
  49. data/lib/overcommit/hook/pre_commit/reek.rb +22 -0
  50. data/lib/overcommit/hook/pre_commit/rubocop.rb +19 -0
  51. data/lib/overcommit/hook/pre_commit/scss_lint.rb +19 -0
  52. data/lib/overcommit/hook/pre_commit/shell_check.rb +19 -0
  53. data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +13 -0
  54. data/lib/overcommit/hook/pre_commit/travis_lint.rb +11 -0
  55. data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +22 -0
  56. data/lib/overcommit/hook_context.rb +17 -0
  57. data/lib/overcommit/hook_context/base.rb +69 -0
  58. data/lib/overcommit/hook_context/commit_msg.rb +32 -0
  59. data/lib/overcommit/hook_context/post_checkout.rb +26 -0
  60. data/lib/overcommit/hook_context/post_commit.rb +19 -0
  61. data/lib/overcommit/hook_context/pre_commit.rb +148 -0
  62. data/lib/overcommit/hook_context/run_all.rb +39 -0
  63. data/lib/overcommit/hook_loader/base.rb +36 -0
  64. data/lib/overcommit/hook_loader/built_in_hook_loader.rb +12 -0
  65. data/lib/overcommit/hook_loader/plugin_hook_loader.rb +61 -0
  66. data/lib/overcommit/hook_runner.rb +129 -0
  67. data/lib/overcommit/hook_signer.rb +79 -0
  68. data/lib/overcommit/installer.rb +148 -0
  69. data/lib/overcommit/interrupt_handler.rb +87 -0
  70. data/lib/overcommit/logger.rb +79 -0
  71. data/lib/overcommit/message_processor.rb +132 -0
  72. data/lib/overcommit/printer.rb +116 -0
  73. data/lib/overcommit/subprocess.rb +46 -0
  74. data/lib/overcommit/utils.rb +163 -0
  75. data/lib/overcommit/version.rb +4 -0
  76. data/libexec/gerrit-change-id +174 -0
  77. data/libexec/index-tags +17 -0
  78. data/template-dir/hooks/commit-msg +81 -0
  79. data/template-dir/hooks/overcommit-hook +81 -0
  80. data/template-dir/hooks/post-checkout +81 -0
  81. data/template-dir/hooks/pre-commit +81 -0
  82. metadata +184 -0
@@ -0,0 +1,36 @@
1
+ module Overcommit::HookLoader
2
+ # Responsible for loading hooks from a file.
3
+ class Base
4
+ # @param config [Overcommit::Configuration]
5
+ # @param context [Overcommit::HookContext]
6
+ # @param logger [Overcommit::Logger]
7
+ def initialize(config, context, logger)
8
+ @config = config
9
+ @context = context
10
+ @log = logger
11
+ end
12
+
13
+ # When implemented in subclasses, loads the hooks for which that subclass is
14
+ # responsible.
15
+ #
16
+ # @return [Array<Hook>]
17
+ def load_hooks
18
+ raise NotImplementedError
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :log
24
+
25
+ # Load and return a {Hook} from a CamelCase hook name.
26
+ def create_hook(hook_name)
27
+ Overcommit::Hook.const_get(@context.hook_class_name).
28
+ const_get(hook_name).
29
+ new(@config, @context)
30
+ rescue LoadError, NameError => error
31
+ raise Overcommit::Exceptions::HookLoadError,
32
+ "Unable to load hook '#{hook_name}': #{error}",
33
+ error.backtrace
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ module Overcommit::HookLoader
2
+ # Responsible for loading hooks that ship with Overcommit.
3
+ class BuiltInHookLoader < Base
4
+ def load_hooks
5
+ @config.enabled_builtin_hooks(@context).map do |hook_name|
6
+ underscored_hook_name = Overcommit::Utils.snake_case(hook_name)
7
+ require "overcommit/hook/#{@context.hook_type_name}/#{underscored_hook_name}"
8
+ create_hook(hook_name)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,61 @@
1
+ require 'digest'
2
+
3
+ module Overcommit::HookLoader
4
+ # Responsible for loading hooks that are specific to the repository Overcommit
5
+ # is running in.
6
+ class PluginHookLoader < Base
7
+ def load_hooks
8
+ check_for_modified_plugins if @config.verify_plugin_signatures?
9
+
10
+ plugin_paths.map do |plugin_path|
11
+ require plugin_path
12
+
13
+ hook_name = Overcommit::Utils.camel_case(File.basename(plugin_path, '.rb'))
14
+ create_hook(hook_name)
15
+ end
16
+ end
17
+
18
+ def update_signatures
19
+ log.success('No plugin signatures have changed') if modified_plugins.empty?
20
+
21
+ modified_plugins.each do |plugin|
22
+ plugin.update_signature!
23
+ log.warning "Updated signature of plugin #{plugin.hook_name}"
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def plugin_paths
30
+ directory = File.join(@config.plugin_directory, @context.hook_type_name)
31
+ Dir[File.join(directory, '*.rb')].sort
32
+ end
33
+
34
+ def modified_plugins
35
+ plugin_paths.
36
+ map { |path| Overcommit::HookSigner.new(path, @config, @context) }.
37
+ select(&:signature_changed?)
38
+ end
39
+
40
+ def check_for_modified_plugins
41
+ return if modified_plugins.empty?
42
+
43
+ log.bold_warning "The following #{@context.hook_script_name} plugins " \
44
+ 'have been added, changed, or had their configuration modified:'
45
+ log.newline
46
+
47
+ modified_plugins.each do |signer|
48
+ log.warning " * #{signer.hook_name} in #{signer.hook_path}"
49
+ end
50
+
51
+ log.newline
52
+ log.bold_warning 'You should verify the changes and then run:'
53
+ log.newline
54
+ log.warning "overcommit --sign #{@context.hook_script_name}"
55
+ log.newline
56
+ log.log "For more information, see #{Overcommit::REPO_URL}#security"
57
+
58
+ raise Overcommit::Exceptions::InvalidHookSignature
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,129 @@
1
+ module Overcommit
2
+ # Responsible for loading the hooks the repository has configured and running
3
+ # them, collecting and displaying the results.
4
+ class HookRunner
5
+ # @param config [Overcommit::Configuration]
6
+ # @param logger [Overcommit::Logger]
7
+ # @param context [Overcommit::HookContext]
8
+ # @param printer [Overcommit::Printer]
9
+ def initialize(config, logger, context, printer)
10
+ @config = config
11
+ @log = logger
12
+ @context = context
13
+ @printer = printer
14
+ @hooks = []
15
+ end
16
+
17
+ # Loads and runs the hooks registered for this {HookRunner}.
18
+ def run
19
+ # ASSUMPTION: we assume the setup and cleanup calls will never need to be
20
+ # interrupted, i.e. they will finish quickly. Should further evidence
21
+ # suggest this assumption does not hold, we will have to separately wrap
22
+ # these calls to allow some sort of "are you sure?" double-interrupt
23
+ # functionality, but until that's deemed necessary let's keep it simple.
24
+ InterruptHandler.isolate_from_interrupts do
25
+ @context.setup_environment
26
+ load_hooks
27
+ result = run_hooks
28
+ @context.cleanup_environment
29
+ result
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :log
36
+
37
+ def run_hooks
38
+ if @hooks.any?(&:enabled?)
39
+ @printer.start_run
40
+
41
+ interrupted = false
42
+ run_failed = false
43
+
44
+ @hooks.each do |hook|
45
+ hook_status = run_hook(hook)
46
+
47
+ run_failed = true if [:bad, :fail].include?(hook_status)
48
+
49
+ if hook_status == :interrupt
50
+ # Stop running any more hooks and assume a bad result
51
+ interrupted = true
52
+ break
53
+ end
54
+ end
55
+
56
+ print_results(run_failed, interrupted)
57
+
58
+ !(run_failed || interrupted)
59
+ else
60
+ @printer.nothing_to_run
61
+ true # Run was successful
62
+ end
63
+ end
64
+
65
+ def print_results(failed, interrupted)
66
+ if interrupted
67
+ @printer.run_interrupted
68
+ elsif failed
69
+ @printer.run_failed
70
+ else
71
+ @printer.run_succeeded
72
+ end
73
+ end
74
+
75
+ def run_hook(hook)
76
+ return if should_skip?(hook)
77
+
78
+ @printer.start_hook(hook)
79
+
80
+ status, output = nil, nil
81
+
82
+ begin
83
+ # Disable the interrupt handler during individual hook run so that
84
+ # Ctrl-C actually stops the current hook from being run, but doesn't
85
+ # halt the entire process.
86
+ InterruptHandler.disable_until_finished_or_interrupted do
87
+ status, output = hook.run_and_transform
88
+ end
89
+ rescue => ex
90
+ status = :fail
91
+ output = "Hook raised unexpected error\n#{ex.message}\n#{ex.backtrace.join("\n")}"
92
+ rescue Interrupt
93
+ # At this point, interrupt has been handled and protection is back in
94
+ # effect thanks to the InterruptHandler.
95
+ status = :interrupt
96
+ output = 'Hook was interrupted by Ctrl-C; restoring repo state...'
97
+ end
98
+
99
+ @printer.end_hook(hook, status, output)
100
+
101
+ status
102
+ end
103
+
104
+ def should_skip?(hook)
105
+ return true unless hook.enabled?
106
+
107
+ if hook.skip?
108
+ if hook.required?
109
+ @printer.required_hook_not_skipped(hook)
110
+ else
111
+ # Tell user if hook was skipped only if it actually would have run
112
+ @printer.hook_skipped(hook) if hook.run?
113
+ return true
114
+ end
115
+ end
116
+
117
+ !hook.run?
118
+ end
119
+
120
+ def load_hooks
121
+ require "overcommit/hook/#{@context.hook_type_name}/base"
122
+
123
+ @hooks += HookLoader::BuiltInHookLoader.new(@config, @context, @log).load_hooks
124
+
125
+ # Load plugin hooks after so they can subclass existing hooks
126
+ @hooks += HookLoader::PluginHookLoader.new(@config, @context, @log).load_hooks
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,79 @@
1
+ module Overcommit
2
+ # Calculates, stores, and retrieves stored signatures of hook plugins.
3
+ class HookSigner
4
+ attr_reader :hook_path, :hook_name
5
+
6
+ # We don't want to include the skip setting as it is set by Overcommit
7
+ # itself
8
+ IGNORED_CONFIG_KEYS = %w[skip]
9
+
10
+ # @param hook_path [String] path to the actual hook definition
11
+ # @param config [Overcommit::Configuration]
12
+ # @param context [Overcommit::HookContext]
13
+ def initialize(hook_path, config, context)
14
+ @hook_path = hook_path
15
+ @config = config
16
+ @context = context
17
+
18
+ @hook_name = Overcommit::Utils.camel_case(File.basename(@hook_path, '.rb'))
19
+ end
20
+
21
+ # Return whether the signature for this hook has changed since it was last
22
+ # calculated.
23
+ #
24
+ # @return [true,false]
25
+ def signature_changed?
26
+ signature != stored_signature
27
+ end
28
+
29
+ # Update the current stored signature for this hook.
30
+ def update_signature!
31
+ result = Overcommit::Utils.execute(
32
+ %w[git config --local] + [signature_config_key, signature]
33
+ )
34
+
35
+ unless result.success?
36
+ raise Overcommit::Exceptions::GitConfigError,
37
+ "Unable to write to local repo git config: #{result.stderr}"
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # Calculates a hash of a hook using a combination of its configuration and
44
+ # file contents.
45
+ #
46
+ # This way, if either the plugin code changes or its configuration changes,
47
+ # the hash will change and we can alert the user to this change.
48
+ def signature
49
+ hook_config = @config.for_hook(@hook_name, @context.hook_class_name).
50
+ dup.
51
+ tap { |config| IGNORED_CONFIG_KEYS.each { |k| config.delete(k) } }
52
+
53
+ Digest::SHA256.hexdigest(hook_contents + hook_config.to_s)
54
+ end
55
+
56
+ def hook_contents
57
+ File.open(@hook_path, 'r').read
58
+ end
59
+
60
+ def stored_signature
61
+ result = Overcommit::Utils.execute(
62
+ %w[git config --local --get] + [signature_config_key]
63
+ )
64
+
65
+ if result.status == 1 # Key doesn't exist
66
+ return ''
67
+ elsif result.status != 0
68
+ raise Overcommit::Exceptions::GitConfigError,
69
+ "Unable to read from local repo git config: #{result.stderr}"
70
+ end
71
+
72
+ result.stdout.chomp
73
+ end
74
+
75
+ def signature_config_key
76
+ "overcommit.#{@context.hook_class_name}.#{@hook_name}.signature"
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,148 @@
1
+ require 'fileutils'
2
+
3
+ module Overcommit
4
+ # Manages the installation of Overcommit hooks in a git repository.
5
+ class Installer # rubocop:disable ClassLength
6
+ TEMPLATE_DIRECTORY = File.join(OVERCOMMIT_HOME, 'template-dir')
7
+ MASTER_HOOK = File.join(TEMPLATE_DIRECTORY, 'hooks', 'overcommit-hook')
8
+
9
+ def initialize(logger)
10
+ @log = logger
11
+ end
12
+
13
+ def run(target, options)
14
+ @target = target
15
+ @options = options
16
+ validate_target
17
+
18
+ case @options[:action]
19
+ when :uninstall then uninstall
20
+ when :update then update
21
+ else
22
+ install
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :log
29
+
30
+ def install
31
+ log.log "Installing hooks into #{@target}"
32
+
33
+ ensure_hooks_directory
34
+ install_master_hook
35
+ install_hook_symlinks
36
+ install_starter_config
37
+
38
+ log.success "Successfully installed hooks into #{@target}"
39
+ end
40
+
41
+ def uninstall
42
+ log.log "Removing hooks from #{@target}"
43
+
44
+ uninstall_master_hook
45
+ uninstall_hook_symlinks
46
+
47
+ log.success "Successfully removed hooks from #{@target}"
48
+ end
49
+
50
+ # @return [true,false] whether the hooks were updated
51
+ def update
52
+ unless FileUtils.compare_file(MASTER_HOOK, master_hook_install_path)
53
+ install_master_hook
54
+ install_hook_symlinks
55
+
56
+ log.success "Hooks updated to Overcommit version #{Overcommit::VERSION}"
57
+ true
58
+ end
59
+ end
60
+
61
+ def hooks_path
62
+ absolute_target = File.expand_path(@target)
63
+ File.join(Overcommit::Utils.git_dir(absolute_target), 'hooks')
64
+ end
65
+
66
+ def master_hook_install_path
67
+ File.join(hooks_path, 'overcommit-hook')
68
+ end
69
+
70
+ def ensure_hooks_directory
71
+ FileUtils.mkdir_p(hooks_path)
72
+ end
73
+
74
+ def validate_target
75
+ absolute_target = File.expand_path(@target)
76
+
77
+ unless File.directory?(absolute_target)
78
+ raise Overcommit::Exceptions::InvalidGitRepo, 'is not a directory'
79
+ end
80
+
81
+ git_dir_check = Dir.chdir(absolute_target) do
82
+ Overcommit::Utils.execute(%w[git rev-parse --git-dir])
83
+ end
84
+
85
+ unless git_dir_check.success?
86
+ raise Overcommit::Exceptions::InvalidGitRepo, 'does not appear to be a git repository'
87
+ end
88
+ end
89
+
90
+ def install_master_hook
91
+ FileUtils.mkdir_p(hooks_path)
92
+ FileUtils.cp(MASTER_HOOK, master_hook_install_path)
93
+ end
94
+
95
+ def uninstall_master_hook
96
+ FileUtils.rm_rf(master_hook_install_path)
97
+ end
98
+
99
+ def install_hook_symlinks
100
+ # Link each hook type (pre-commit, commit-msg, etc.) to the master hook.
101
+ # We change directories so that the relative symlink paths work regardless
102
+ # of where the repository is located.
103
+ Dir.chdir(hooks_path) do
104
+ Overcommit::Utils.supported_hook_types.each do |hook_type|
105
+ unless can_replace_file?(hook_type)
106
+ raise Overcommit::Exceptions::PreExistingHooks,
107
+ "Hook '#{File.expand_path(hook_type)}' already exists and " \
108
+ 'was not installed by Overcommit'
109
+ end
110
+ FileUtils.ln_sf('overcommit-hook', hook_type)
111
+ end
112
+ end
113
+ end
114
+
115
+ def can_replace_file?(file)
116
+ @options[:force] ||
117
+ !File.exist?(file) ||
118
+ overcommit_hook?(file)
119
+ end
120
+
121
+ def uninstall_hook_symlinks
122
+ return unless File.directory?(hooks_path)
123
+
124
+ Dir.chdir(hooks_path) do
125
+ Overcommit::Utils.supported_hook_types.each do |hook_type|
126
+ FileUtils.rm_rf(hook_type) if overcommit_hook?(hook_type)
127
+ end
128
+ end
129
+ end
130
+
131
+ def install_starter_config
132
+ repo_config_file = File.join(@target, OVERCOMMIT_CONFIG_FILE_NAME)
133
+
134
+ return if File.exist?(repo_config_file)
135
+ FileUtils.cp(File.join(OVERCOMMIT_HOME, 'config', 'starter.yml'), repo_config_file)
136
+ end
137
+
138
+ def overcommit_hook?(file)
139
+ return true if File.read(file) =~ /OVERCOMMIT_DISABLE/
140
+ # TODO: Remove these checks once we hit version 1.0
141
+ File.symlink?(file) && File.readlink(file) == 'overcommit-hook'
142
+ rescue Errno::ENOENT
143
+ # Some Ruby implementations (e.g. JRuby) raise an error when the file
144
+ # doesn't exist. Standardize the behavior to return false.
145
+ false
146
+ end
147
+ end
148
+ end