jawshooah-overcommit 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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