overcommit 0.30.0 → 0.32.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +123 -93
  3. data/lib/overcommit/configuration.rb +24 -2
  4. data/lib/overcommit/configuration_validator.rb +28 -1
  5. data/lib/overcommit/constants.rb +7 -5
  6. data/lib/overcommit/exceptions.rb +6 -0
  7. data/lib/overcommit/git_repo.rb +6 -0
  8. data/lib/overcommit/hook/base.rb +22 -4
  9. data/lib/overcommit/hook/commit_msg/capitalized_subject.rb +1 -1
  10. data/lib/overcommit/hook/commit_msg/message_format.rb +27 -0
  11. data/lib/overcommit/hook/pre_commit/base.rb +17 -8
  12. data/lib/overcommit/hook/pre_commit/berksfile_check.rb +3 -1
  13. data/lib/overcommit/hook/pre_commit/bundle_check.rb +3 -1
  14. data/lib/overcommit/hook/pre_commit/es_lint.rb +10 -0
  15. data/lib/overcommit/hook/pre_commit/execute_permissions.rb +10 -0
  16. data/lib/overcommit/hook/pre_commit/forbidden_branches.rb +24 -0
  17. data/lib/overcommit/hook/pre_commit/mdl.rb +23 -0
  18. data/lib/overcommit/hook/pre_commit/rubo_cop.rb +15 -4
  19. data/lib/overcommit/hook/pre_commit/scss_lint.rb +23 -10
  20. data/lib/overcommit/hook/pre_push/minitest.rb +4 -0
  21. data/lib/overcommit/hook/pre_push/protected_branches.rb +13 -6
  22. data/lib/overcommit/hook_context/base.rb +7 -0
  23. data/lib/overcommit/hook_context/pre_commit.rb +7 -0
  24. data/lib/overcommit/hook_context/pre_push.rb +12 -1
  25. data/lib/overcommit/hook_context/run_all.rb +1 -1
  26. data/lib/overcommit/hook_runner.rb +90 -41
  27. data/lib/overcommit/hook_signer.rb +1 -1
  28. data/lib/overcommit/message_processor.rb +20 -7
  29. data/lib/overcommit/os.rb +2 -2
  30. data/lib/overcommit/printer.rb +32 -8
  31. data/lib/overcommit/utils.rb +36 -0
  32. data/lib/overcommit/version.rb +3 -1
  33. data/template-dir/hooks/commit-msg +9 -1
  34. data/template-dir/hooks/overcommit-hook +9 -1
  35. data/template-dir/hooks/post-checkout +9 -1
  36. data/template-dir/hooks/post-commit +9 -1
  37. data/template-dir/hooks/post-merge +9 -1
  38. data/template-dir/hooks/post-rewrite +9 -1
  39. data/template-dir/hooks/pre-commit +9 -1
  40. data/template-dir/hooks/pre-push +9 -1
  41. data/template-dir/hooks/pre-rebase +9 -1
  42. metadata +21 -4
@@ -13,13 +13,15 @@ module Overcommit
13
13
  def initialize(hash, options = {})
14
14
  @options = options.dup
15
15
  @options[:logger] ||= Overcommit::Logger.silent
16
- @hash = Overcommit::ConfigurationValidator.new.validate(hash, options)
16
+ @hash = hash # Assign so validator can read original values
17
+ unless options[:validate] == false
18
+ @hash = Overcommit::ConfigurationValidator.new.validate(self, hash, options)
19
+ end
17
20
  end
18
21
 
19
22
  def ==(other)
20
23
  super || @hash == other.hash
21
24
  end
22
- alias_method :eql?, :==
23
25
 
24
26
  # Access the configuration as if it were a hash.
25
27
  #
@@ -35,6 +37,26 @@ module Overcommit
35
37
  File.join(Overcommit::Utils.repo_root, @hash['plugin_directory'] || '.git-hooks')
36
38
  end
37
39
 
40
+ def concurrency
41
+ @concurrency ||=
42
+ begin
43
+ cores = Overcommit::Utils.processor_count
44
+ content = @hash.fetch('concurrency', '%{processors}')
45
+ if content.is_a?(String)
46
+ concurrency_expr = content % { processors: cores }
47
+
48
+ a, op, b = concurrency_expr.scan(%r{(\d+)\s*([+\-*\/])\s*(\d+)})[0]
49
+ if a
50
+ a.to_i.send(op, b.to_i)
51
+ else
52
+ concurrency_expr.to_i
53
+ end
54
+ else
55
+ content.to_i
56
+ end
57
+ end
58
+ end
59
+
38
60
  # Returns configuration for all hooks in each hook type.
39
61
  #
40
62
  # @return [Hash]
@@ -3,12 +3,13 @@ module Overcommit
3
3
  class ConfigurationValidator
4
4
  # Validates hash for any invalid options, normalizing where possible.
5
5
  #
6
+ # @param config [Overcommit::Configuration]
6
7
  # @param hash [Hash] hash representation of YAML config
7
8
  # @param options[Hash]
8
9
  # @option default [Boolean] whether hash represents the default built-in config
9
10
  # @option logger [Overcommit::Logger] logger to output warnings to
10
11
  # @return [Hash] validated hash (potentially modified)
11
- def validate(hash, options)
12
+ def validate(config, hash, options)
12
13
  @options = options.dup
13
14
  @log = options[:logger]
14
15
 
@@ -16,6 +17,7 @@ module Overcommit
16
17
  ensure_hook_type_sections_exist(hash)
17
18
  check_hook_name_format(hash)
18
19
  check_for_missing_enabled_option(hash) unless @options[:default]
20
+ check_for_too_many_processors(config, hash)
19
21
  check_for_verify_plugin_signatures_option(hash)
20
22
 
21
23
  hash
@@ -96,6 +98,31 @@ module Overcommit
96
98
  @log.newline if any_warnings
97
99
  end
98
100
 
101
+ # Prints a warning if any hook has a number of processors larger than the
102
+ # global `concurrency` setting.
103
+ def check_for_too_many_processors(config, hash)
104
+ concurrency = config.concurrency
105
+
106
+ errors = []
107
+ Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
108
+ hash.fetch(hook_type, {}).each do |hook_name, hook_config|
109
+ processors = hook_config.fetch('processors', 1)
110
+ if processors > concurrency
111
+ errors << "#{hook_type}::#{hook_name} `processors` value " \
112
+ "(#{processors}) is larger than the global `concurrency` " \
113
+ "option (#{concurrency})"
114
+ end
115
+ end
116
+ end
117
+
118
+ if errors.any?
119
+ @log.error errors.join("\n") if @log
120
+ @log.newline if @log
121
+ raise Overcommit::Exceptions::ConfigurationError,
122
+ 'One or more hooks had invalid `processor` value configured'
123
+ end
124
+ end
125
+
99
126
  # Prints a warning if the `verify_plugin_signatures` option is used instead
100
127
  # of the new `verify_signatures` option.
101
128
  def check_for_verify_plugin_signatures_option(hash)
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Global application constants.
2
4
  module Overcommit
3
- HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
4
- CONFIG_FILE_NAME = '.overcommit.yml'
5
+ HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
6
+ CONFIG_FILE_NAME = '.overcommit.yml'.freeze
5
7
 
6
- HOOK_DIRECTORY = File.join(HOME, 'lib', 'overcommit', 'hook')
8
+ HOOK_DIRECTORY = File.join(HOME, 'lib', 'overcommit', 'hook').freeze
7
9
 
8
- REPO_URL = 'https://github.com/brigade/overcommit'
9
- BUG_REPORT_URL = "#{REPO_URL}/issues"
10
+ REPO_URL = 'https://github.com/brigade/overcommit'.freeze
11
+ BUG_REPORT_URL = "#{REPO_URL}/issues".freeze
10
12
  end
@@ -11,6 +11,9 @@ module Overcommit::Exceptions
11
11
  # Raised when there was a problem reading submodule information for a repo.
12
12
  class GitSubmoduleError < StandardError; end
13
13
 
14
+ # Raised when there was a problem reading git revision information with `rev-list`.
15
+ class GitRevListError < StandardError; end
16
+
14
17
  # Raised when a {HookContext} is unable to setup the environment before a run.
15
18
  class HookSetupFailed < StandardError; end
16
19
 
@@ -39,6 +42,9 @@ module Overcommit::Exceptions
39
42
  # Raised when one or more hook plugin signatures have changed.
40
43
  class InvalidHookSignature < StandardError; end
41
44
 
45
+ # Raised when there is a problem processing output into {Hook::Messages}s.
46
+ class MessageProcessingError < StandardError; end
47
+
42
48
  # Raised when an installation target already contains non-Overcommit hooks.
43
49
  class PreExistingHooks < StandardError; end
44
50
  end
@@ -266,5 +266,11 @@ module Overcommit
266
266
  split(/\s+/).
267
267
  reject { |s| s.empty? || s == '*' }
268
268
  end
269
+
270
+ # Returns the name of the currently checked out branch.
271
+ # @return [String]
272
+ def current_branch
273
+ `git symbolic-ref --short -q HEAD`.chomp
274
+ end
269
275
  end
270
276
  end
@@ -11,13 +11,13 @@ module Overcommit::Hook
11
11
  end
12
12
 
13
13
  # Possible types of messages.
14
- MESSAGE_TYPES = [:error, :warning]
14
+ MESSAGE_TYPES = [:error, :warning].freeze
15
15
 
16
16
  # Functionality common to all hooks.
17
17
  class Base # rubocop:disable Metrics/ClassLength
18
18
  extend Forwardable
19
19
 
20
- def_delegators :@context, :modified_files
20
+ def_delegators :@context, :all_files, :modified_files
21
21
  attr_reader :config
22
22
 
23
23
  # @param config [Overcommit::Configuration]
@@ -54,13 +54,21 @@ module Overcommit::Hook
54
54
  end
55
55
 
56
56
  def description
57
- @config['description'] || "Running #{name}"
57
+ @config['description'] || "Run #{name}"
58
58
  end
59
59
 
60
60
  def required?
61
61
  @config['required']
62
62
  end
63
63
 
64
+ def parallelize?
65
+ @config['parallelize'] != false
66
+ end
67
+
68
+ def processors
69
+ @config.fetch('processors', 1)
70
+ end
71
+
64
72
  def quiet?
65
73
  @config['quiet']
66
74
  end
@@ -147,11 +155,21 @@ module Overcommit::Hook
147
155
  # Gets a list of staged files that apply to this hook based on its
148
156
  # configured `include` and `exclude` lists.
149
157
  def applicable_files
150
- @applicable_files ||= modified_files.select { |file| applicable_file?(file) }.sort
158
+ @applicable_files ||= select_applicable(modified_files)
159
+ end
160
+
161
+ # Gets a list of all files that apply to this hook based on its
162
+ # configured `include` and `exclude` lists.
163
+ def included_files
164
+ @included_files ||= select_applicable(all_files)
151
165
  end
152
166
 
153
167
  private
154
168
 
169
+ def select_applicable(list)
170
+ list.select { |file| applicable_file?(file) }.sort
171
+ end
172
+
155
173
  def applicable_file?(file)
156
174
  includes = Array(@config['include']).flatten.map do |glob|
157
175
  Overcommit::Utils.convert_glob_to_absolute(glob)
@@ -5,7 +5,7 @@ module Overcommit::Hook::CommitMsg
5
5
  return :pass if empty_message?
6
6
 
7
7
  first_letter = commit_message_lines[0].to_s.match(/^[[:punct:]]*(.)/)[1]
8
- unless first_letter.match(/[[:upper:]]/)
8
+ unless first_letter =~ /[[:upper:]]/
9
9
  return :warn, 'Subject should start with a capital letter'
10
10
  end
11
11
 
@@ -0,0 +1,27 @@
1
+ module Overcommit::Hook::CommitMsg
2
+ # Ensures the commit message follows a specific format.
3
+ class MessageFormat < Base
4
+ def run
5
+ error_msg = validate_pattern(commit_message_lines.join("\n"))
6
+ return :fail, error_msg if error_msg
7
+
8
+ :pass
9
+ end
10
+
11
+ private
12
+
13
+ def validate_pattern(message)
14
+ pattern = config['pattern']
15
+ return if pattern.empty?
16
+
17
+ expected_pattern_message = config['expected_pattern_message']
18
+ sample_message = config['sample_message']
19
+
20
+ [
21
+ 'Commit message pattern mismatch.',
22
+ "Expected : #{expected_pattern_message}",
23
+ "Sample : #{sample_message}"
24
+ ].join("\n") unless message =~ /#{pattern}/
25
+ end
26
+ end
27
+ end
@@ -22,13 +22,16 @@ module Overcommit::Hook::PreCommit
22
22
  # @param type_categorizer [Proc] function executed against the `type`
23
23
  # capture group to convert it to a `:warning` or `:error` symbol. Assumes
24
24
  # `:error` if `nil`.
25
- # @raise [RuntimeError] line of output did not match regex
25
+ # @raise [Overcommit::Exceptions::MessageProcessingError] line of output did
26
+ # not match regex
26
27
  # @return [Array<Message>]
27
28
  def extract_messages(output_messages, regex, type_categorizer = nil)
28
- output_messages.map do |message|
29
+ output_messages.map.with_index do |message, index|
29
30
  unless match = message.match(regex)
30
- raise 'Unexpected output: unable to determine line number or type ' \
31
- "of error/warning for message '#{message}'"
31
+ raise Overcommit::Exceptions::MessageProcessingError,
32
+ 'Unexpected output: unable to determine line number or type ' \
33
+ "of error/warning for output:\n" \
34
+ "#{output_messages[index..-1].join("\n")}"
32
35
  end
33
36
 
34
37
  file = extract_file(match, message)
@@ -40,17 +43,22 @@ module Overcommit::Hook::PreCommit
40
43
  end
41
44
 
42
45
  def extract_file(match, message)
43
- if match[:file].nil? || match[:file].empty?
44
- raise "Unexpected output: no file found in '#{message}'"
46
+ return unless match.names.include?('file')
47
+
48
+ if match[:file].to_s.empty?
49
+ raise Overcommit::Exceptions::MessageProcessingError,
50
+ "Unexpected output: no file found in '#{message}'"
45
51
  end
46
52
 
47
53
  match[:file]
48
54
  end
49
55
 
50
56
  def extract_line(match, message)
57
+ return unless match.names.include?('line')
51
58
  Integer(match[:line])
52
59
  rescue ArgumentError, TypeError
53
- raise "Unexpected output: invalid line number found in '#{message}'"
60
+ raise Overcommit::Exceptions::MessageProcessingError,
61
+ "Unexpected output: invalid line number found in '#{message}'"
54
62
  end
55
63
 
56
64
  def extract_type(match, message, type_categorizer)
@@ -58,7 +66,8 @@ module Overcommit::Hook::PreCommit
58
66
  type_match = match.names.include?('type') ? match[:type] : nil
59
67
  type = type_categorizer.call(type_match)
60
68
  unless Overcommit::Hook::MESSAGE_TYPES.include?(type)
61
- raise "Invalid message type '#{type}' for '#{message}': must " \
69
+ raise Overcommit::Exceptions::MessageProcessingError,
70
+ "Invalid message type '#{type}' for '#{message}': must " \
62
71
  "be one of #{Overcommit::Hook::MESSAGE_TYPES.inspect}"
63
72
  end
64
73
  type
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Overcommit::Hook::PreCommit
2
4
  # Check if local Berksfile.lock matches Berksfile when either changes, unless
3
5
  # Berksfile.lock is ignored by git.
4
6
  #
5
7
  # @see http://berkshelf.com/
6
8
  class BerksfileCheck < Base
7
- LOCK_FILE = 'Berksfile.lock'
9
+ LOCK_FILE = 'Berksfile.lock'.freeze
8
10
 
9
11
  def run
10
12
  # Ignore if Berksfile.lock is not tracked by git
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Overcommit::Hook::PreCommit
2
4
  # Check if local Gemfile.lock matches Gemfile when either changes, unless
3
5
  # Gemfile.lock is ignored by git.
4
6
  #
5
7
  # @see http://bundler.io/
6
8
  class BundleCheck < Base
7
- LOCK_FILE = 'Gemfile.lock'
9
+ LOCK_FILE = 'Gemfile.lock'.freeze
8
10
 
9
11
  def run
10
12
  # Ignore if Gemfile.lock is not tracked by git
@@ -1,6 +1,16 @@
1
1
  module Overcommit::Hook::PreCommit
2
2
  # Runs `eslint` against any modified JavaScript files.
3
3
  #
4
+ # Protip: if you have an npm script set up to run eslint, you can configure
5
+ # this hook to run eslint via your npm script by using the `command` option in
6
+ # your .overcommit.yml file. This can be useful if you have some eslint
7
+ # configuration built into your npm script that you don't want to repeat
8
+ # somewhere else. Example:
9
+ #
10
+ # EsLint:
11
+ # enabled: true
12
+ # command: ['npm', 'run', 'lint']
13
+ #
4
14
  # @see http://eslint.org/
5
15
  class EsLint < Base
6
16
  def run
@@ -2,6 +2,16 @@ module Overcommit::Hook::PreCommit
2
2
  # Checks for files with execute permissions, which are usually not necessary
3
3
  # in source code files (and are typically caused by a misconfigured editor
4
4
  # assigning incorrect default permissions).
5
+ #
6
+ # Protip: if you have some files that you want to allow execute permissions
7
+ # on, you can disable this hook for those files by using the `exclude` option
8
+ # on your .overcommit.yml file. Example:
9
+ #
10
+ # ExecutePermissions:
11
+ # enabled: true
12
+ # exclude:
13
+ # - 'path/to/my/file/that/should/have/execute/permissions.sh'
14
+ # - 'directory/that/should/have/execute/permissions/**/*'
5
15
  class ExecutePermissions < Base
6
16
  def run
7
17
  file_modes = {}
@@ -0,0 +1,24 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Prevents commits to branches matching one of the configured patterns.
3
+ class ForbiddenBranches < Base
4
+ def run
5
+ return :pass unless forbidden_commit?
6
+
7
+ [:fail, "Committing to #{current_branch} is forbidden"]
8
+ end
9
+
10
+ private
11
+
12
+ def forbidden_commit?
13
+ forbidden_branch_patterns.any? { |p| File.fnmatch(p, current_branch) }
14
+ end
15
+
16
+ def forbidden_branch_patterns
17
+ @forbidden_branch_patterns ||= Array(config['branch_patterns'])
18
+ end
19
+
20
+ def current_branch
21
+ @current_branch ||= Overcommit::GitRepo.current_branch
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `mdl` against any modified Markdown files
3
+ #
4
+ # @see https://github.com/mivok/markdownlint
5
+ class Mdl < Base
6
+ MESSAGE_REGEX = /^(?<file>(?:\w:)?[^:]+):(?<line>\d+)/
7
+
8
+ def run
9
+ result = execute(command, args: applicable_files)
10
+ output = result.stdout.chomp
11
+
12
+ return :pass if result.success?
13
+ return [:fail, result.stderr] unless result.stderr.empty?
14
+
15
+ # example message:
16
+ # path/to/file.md:1: MD001 Error message
17
+ extract_messages(
18
+ output.split("\n"),
19
+ MESSAGE_REGEX
20
+ )
21
+ end
22
+ end
23
+ end
@@ -3,20 +3,31 @@ module Overcommit::Hook::PreCommit
3
3
  #
4
4
  # @see http://batsov.com/rubocop/
5
5
  class RuboCop < Base
6
- MESSAGE_TYPE_CATEGORIZER = lambda do |type|
6
+ GENERIC_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
7
+ type =~ /^warn/ ? :warning : :error
8
+ end
9
+
10
+ COP_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
7
11
  type.include?('W') ? :warning : :error
8
12
  end
9
13
 
10
14
  def run
11
15
  result = execute(command, args: applicable_files)
12
16
  return :pass if result.success?
13
- return [:fail, result.stderr] unless result.stderr.empty?
14
17
 
15
- extract_messages(
18
+ generic_messages = extract_messages(
19
+ result.stderr.split("\n"),
20
+ /^(?<type>[a-z]+)/i,
21
+ GENERIC_MESSAGE_TYPE_CATEGORIZER,
22
+ )
23
+
24
+ cop_messages = extract_messages(
16
25
  result.stdout.split("\n"),
17
26
  /^(?<file>(?:\w:)?[^:]+):(?<line>\d+):[^ ]+ (?<type>[^ ]+)/,
18
- MESSAGE_TYPE_CATEGORIZER,
27
+ COP_MESSAGE_TYPE_CATEGORIZER,
19
28
  )
29
+
30
+ generic_messages + cop_messages
20
31
  end
21
32
  end
22
33
  end
@@ -3,10 +3,6 @@ module Overcommit::Hook::PreCommit
3
3
  #
4
4
  # @see https://github.com/brigade/scss-lint
5
5
  class ScssLint < Base
6
- MESSAGE_TYPE_CATEGORIZER = lambda do |type|
7
- type.include?('W') ? :warning : :error
8
- end
9
-
10
6
  def run
11
7
  result = execute(command, args: applicable_files)
12
8
 
@@ -16,13 +12,30 @@ module Overcommit::Hook::PreCommit
16
12
  return :pass if [0, 81].include?(result.status)
17
13
 
18
14
  # Any status that isn't indicating lint warnings or errors indicates failure
19
- return :fail, result.stdout unless [1, 2].include?(result.status)
15
+ return :fail, (result.stdout + result.stderr) unless [1, 2].include?(result.status)
16
+
17
+ begin
18
+ collect_lint_messages(JSON.parse(result.stdout))
19
+ rescue JSON::ParserError => ex
20
+ return :fail, "Unable to parse JSON returned by SCSS-Lint: #{ex.message}\n" \
21
+ "STDOUT: #{result.stdout}\nSTDERR: #{result.stderr}"
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def collect_lint_messages(files_to_lints)
28
+ files_to_lints.flat_map do |path, lints|
29
+ lints.map do |lint|
30
+ severity = lint['severity'] == 'warning' ? :warning : :error
31
+
32
+ message = lint['reason']
33
+ message = "#{lint['linter']}: #{message}" if lint['linter']
34
+ message = "#{path}:#{lint['line']} #{message}"
20
35
 
21
- extract_messages(
22
- result.stdout.split("\n"),
23
- /^(?<file>(?:\w:)?[^:]+):(?<line>\d+)[^ ]* (?<type>[^ ]+)/,
24
- MESSAGE_TYPE_CATEGORIZER,
25
- )
36
+ Overcommit::Hook::Message.new(severity, path, lint['line'], message)
37
+ end
38
+ end
26
39
  end
27
40
  end
28
41
  end
@@ -10,5 +10,9 @@ module Overcommit::Hook::PrePush
10
10
  output = result.stdout + result.stderr
11
11
  [:fail, output]
12
12
  end
13
+
14
+ def command
15
+ super + included_files.map { |file| "-r#{file}" }
16
+ end
13
17
  end
14
18
  end
@@ -13,15 +13,22 @@ module Overcommit::Hook::PrePush
13
13
 
14
14
  private
15
15
 
16
- def branches
17
- @branches ||= config['branches']
18
- end
19
-
20
16
  def illegal_pushes
21
17
  @illegal_pushes ||= pushed_refs.select do |pushed_ref|
22
- (pushed_ref.deleted? || pushed_ref.forced?) &&
23
- branches.any? { |branch| pushed_ref.remote_ref == "refs/heads/#{branch}" }
18
+ protected?(pushed_ref.remote_ref) && pushed_ref.destructive?
24
19
  end
25
20
  end
21
+
22
+ def protected?(remote_ref)
23
+ ref_name = remote_ref[%r{refs/heads/(.*)}, 1]
24
+ protected_branch_patterns.any? do |pattern|
25
+ File.fnmatch(pattern, ref_name)
26
+ end
27
+ end
28
+
29
+ def protected_branch_patterns
30
+ @protected_branch_patterns ||= Array(config['branches']).
31
+ concat(Array(config['branch_patterns']))
32
+ end
26
33
  end
27
34
  end
@@ -79,6 +79,13 @@ module Overcommit::HookContext
79
79
  []
80
80
  end
81
81
 
82
+ # Returns the full list of files tracked by git
83
+ #
84
+ # @return [Array<String>]
85
+ def all_files
86
+ Overcommit::GitRepo.all_files
87
+ end
88
+
82
89
  # Returns the contents of the entire standard input stream that were passed
83
90
  # to the hook.
84
91
  #
@@ -15,6 +15,13 @@ module Overcommit::HookContext
15
15
  cmd = Overcommit::Utils.parent_command
16
16
  amend_pattern = 'commit(\s.*)?\s--amend(\s|$)'
17
17
 
18
+ # Since the ps command can return invalid byte sequences for commands
19
+ # containing unicode characters, we replace the offending characters,
20
+ # since the pattern we're looking for will consist of ASCII characters
21
+ unless cmd.valid_encoding?
22
+ cmd.encode!('UTF-16be', invalid: :replace, replace: '?').encode!('UTF-8')
23
+ end
24
+
18
25
  return @amendment if
19
26
  # True if the command is a commit with the --amend flag
20
27
  @amendment = !(/\s#{amend_pattern}/ =~ cmd).nil?
@@ -30,6 +30,10 @@ module Overcommit::HookContext
30
30
  local_sha1 == '0' * 40
31
31
  end
32
32
 
33
+ def destructive?
34
+ deleted? || forced?
35
+ end
36
+
33
37
  def to_s
34
38
  "#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}"
35
39
  end
@@ -37,7 +41,14 @@ module Overcommit::HookContext
37
41
  private
38
42
 
39
43
  def overwritten_commits
40
- `git rev-list #{remote_sha1} ^#{local_sha1}`.split("\n")
44
+ return @overwritten_commits if defined? @overwritten_commits
45
+ result = Overcommit::Subprocess.spawn(%W[git rev-list #{remote_sha1} ^#{local_sha1}])
46
+ if result.success?
47
+ result.stdout.split("\n")
48
+ else
49
+ raise Overcommit::Exceptions::GitRevListError,
50
+ "Unable to check if commits on the remote ref will be overwritten: #{result.stderr}"
51
+ end
41
52
  end
42
53
  end
43
54
  end
@@ -7,7 +7,7 @@ module Overcommit::HookContext
7
7
  # which is useful for automated CI scripts.
8
8
  class RunAll < Base
9
9
  def modified_files
10
- @modified_files ||= Overcommit::GitRepo.all_files
10
+ @modified_files ||= all_files
11
11
  end
12
12
 
13
13
  # Returns all lines in the file since in this context the entire repo is