overcommit 0.54.0 → 0.58.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0efc67b235f1b64200fb3f1980dbbffdadafe330138488842faba82901de4536
4
- data.tar.gz: 75e7f62941308e828f498906bd5b8681b9a238336a616aa4c9c69465db4411d5
3
+ metadata.gz: b0d6feac2b1b10b983db054fa8931f302ff8eb8105c6d8788ad965ce986c990c
4
+ data.tar.gz: bf03cf18734e6139e5d7885ea1dcffb5d2aa0dcbad07ac8ec4d3cceff53d62e1
5
5
  SHA512:
6
- metadata.gz: 92d6182180b1ea2888f3e93124f011fa22ac6af31e610ed8537e1432a46b2ef731a525743ad39fa3981b772d324504665fd3c391bd157eda305a951962d8bbf9
7
- data.tar.gz: ddc6c719c3662f64f9e80d671acf6958e6a9aa75b873d8cef948b17f71d81ae33025cedaa4f1770a7bb39523c1a497bbfff8ebb1e59351e5168be41a31259e26
6
+ metadata.gz: 915b83736de391fe61565da7bd5caca5bf427cca3bf8f6f6aa4d6a639bf151e55d3fc61578b06eab6ffda215abe8e77e209b34d95a0ad4f6988b66fea4773a67
7
+ data.tar.gz: 445041c3c00b3d5cfa34c65aae868459f515c24ed4bc40017dd6bfdb91a3a6060c305a24eb03537a3ab0be401241f480fe12c42f3b70cf8fb6835f2d65f8dc6c
data/config/default.yml CHANGED
@@ -266,6 +266,14 @@ PreCommit:
266
266
  install_command: 'npm install -g csslint'
267
267
  include: '**/*.css'
268
268
 
269
+ DartAnalyzer:
270
+ enabled: false
271
+ description: 'Analyze with dartanalyzer'
272
+ required_executable: 'dartanalyzer'
273
+ flags: []
274
+ include:
275
+ - '**/*.dart'
276
+
269
277
  Dogma:
270
278
  enabled: false
271
279
  description: 'Analyze with dogma'
@@ -275,6 +283,13 @@ PreCommit:
275
283
  - '**/*.ex'
276
284
  - '**/*.exs'
277
285
 
286
+ ErbLint:
287
+ enabled: false
288
+ description: 'Analyze with ERB Lint'
289
+ required_executable: 'erblint'
290
+ install_command: 'bundle install erb_lint'
291
+ include: '**/*.html.erb'
292
+
278
293
  EsLint:
279
294
  enabled: false
280
295
  description: 'Analyze with ESLint'
@@ -338,7 +353,7 @@ PreCommit:
338
353
  keywords: ['FContext','FDescribe','FIt','FMeasure','FSpecify','FWhen']
339
354
 
340
355
  GoFmt:
341
- enabled: true
356
+ enabled: false
342
357
  description: 'Fix with go fmt'
343
358
  required_executable: 'go'
344
359
  command: ['go', 'fmt']
@@ -1237,9 +1252,14 @@ PrepareCommitMsg:
1237
1252
  ReplaceBranch:
1238
1253
  enabled: false
1239
1254
  description: 'Prepends the commit message with text based on the branch name'
1240
- branch_pattern: '\A.*\w+[-_](\d+).*\z'
1255
+ branch_pattern: '\A(\d+)-(\w+).*\z'
1241
1256
  replacement_text: '[#\1]'
1242
- skipped_commit_types: ['message', 'template', 'merge', 'squash']
1257
+ skipped_commit_types:
1258
+ - 'message' # if message is given via `-m`, `-F`
1259
+ - 'template' # if `-t` is given or `commit.template` is set
1260
+ - 'commit' # if `-c`, `-C`, or `--amend` is given
1261
+ - 'merge' # if merging
1262
+ - 'squash' # if squashing
1243
1263
  on_fail: warn
1244
1264
 
1245
1265
  # Hooks that run during `git push`, after remote refs have been updated but
@@ -1264,6 +1284,12 @@ PrePush:
1264
1284
  flags: ['test']
1265
1285
  include: 'src/**/*.rs'
1266
1286
 
1287
+ FlutterTest:
1288
+ enabled: false
1289
+ description: 'Run flutter test suite'
1290
+ required_executable: 'flutter'
1291
+ flags: ['test']
1292
+
1267
1293
  GitLfs:
1268
1294
  enabled: false
1269
1295
  description: 'Upload files tracked by Git LFS'
@@ -1310,6 +1336,12 @@ PrePush:
1310
1336
  destructive_only: true
1311
1337
  branches: ['master']
1312
1338
 
1339
+ PubTest:
1340
+ enabled: false
1341
+ description: 'Run pub test suite'
1342
+ required_executable: 'pub'
1343
+ flags: ['run', 'test']
1344
+
1313
1345
  Pytest:
1314
1346
  enabled: false
1315
1347
  description: 'Run pytest test suite'
@@ -24,13 +24,16 @@ module Overcommit
24
24
  # @option logger [Overcommit::Logger]
25
25
  # @return [Overcommit::Configuration]
26
26
  def load_from_file(file, options = {})
27
- hash =
28
- if yaml = YAML.load_file(file)
29
- yaml.to_hash
30
- else
31
- {}
27
+ # Psych 4 introduced breaking behavior that doesn't support aliases by
28
+ # default. Explicitly enable aliases if the option is available.
29
+ yaml =
30
+ begin
31
+ YAML.load_file(file, aliases: true)
32
+ rescue ArgumentError
33
+ YAML.load_file(file)
32
34
  end
33
35
 
36
+ hash = yaml ? yaml.to_hash : {}
34
37
  Overcommit::Configuration.new(hash, options)
35
38
  end
36
39
  end
@@ -1,52 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Overcommit::Exceptions
4
+ # Base error class.
5
+ class Error < StandardError; end
6
+
4
7
  # Raised when a {Configuration} could not be loaded from a file.
5
- class ConfigurationError < StandardError; end
8
+ class ConfigurationError < Error; end
6
9
 
7
10
  # Raised when the Overcommit configuration file signature has changed.
8
- class ConfigurationSignatureChanged < StandardError; end
11
+ class ConfigurationSignatureChanged < Error; end
9
12
 
10
13
  # Raised when trying to read/write to/from the local repo git config fails.
11
- class GitConfigError < StandardError; end
14
+ class GitConfigError < Error; end
12
15
 
13
16
  # Raised when there was a problem reading submodule information for a repo.
14
- class GitSubmoduleError < StandardError; end
17
+ class GitSubmoduleError < Error; end
15
18
 
16
19
  # Raised when there was a problem reading git revision information with `rev-list`.
17
- class GitRevListError < StandardError; end
20
+ class GitRevListError < Error; end
18
21
 
19
22
  # Raised when a {HookContext} is unable to setup the environment before a run.
20
- class HookSetupFailed < StandardError; end
23
+ class HookSetupFailed < Error; end
21
24
 
22
25
  # Raised when a {HookContext} is unable to clean the environment after a run.
23
- class HookCleanupFailed < StandardError; end
26
+ class HookCleanupFailed < Error; end
24
27
 
25
28
  # Raised when a hook run was cancelled by the user.
26
- class HookCancelled < StandardError; end
29
+ class HookCancelled < Error; end
27
30
 
28
31
  # Raised when a hook could not be loaded by a {HookRunner}.
29
- class HookLoadError < StandardError; end
32
+ class HookLoadError < Error; end
30
33
 
31
34
  # Raised when a {HookRunner} could not be loaded.
32
- class HookContextLoadError < StandardError; end
35
+ class HookContextLoadError < Error; end
33
36
 
34
37
  # Raised when a pipe character is used in the `execute` helper, as this was
35
38
  # likely used in error.
36
- class InvalidCommandArgs < StandardError; end
39
+ class InvalidCommandArgs < Error; end
37
40
 
38
41
  # Raised when an installation target is not a valid git repository.
39
- class InvalidGitRepo < StandardError; end
42
+ class InvalidGitRepo < Error; end
40
43
 
41
44
  # Raised when a hook was defined incorrectly.
42
- class InvalidHookDefinition < StandardError; end
45
+ class InvalidHookDefinition < Error; end
43
46
 
44
47
  # Raised when one or more hook plugin signatures have changed.
45
- class InvalidHookSignature < StandardError; end
48
+ class InvalidHookSignature < Error; end
46
49
 
47
50
  # Raised when there is a problem processing output into {Hook::Messages}s.
48
- class MessageProcessingError < StandardError; end
51
+ class MessageProcessingError < Error; end
49
52
 
50
53
  # Raised when an installation target already contains non-Overcommit hooks.
51
- class PreExistingHooks < StandardError; end
54
+ class PreExistingHooks < Error; end
52
55
  end
@@ -109,15 +109,16 @@ module Overcommit
109
109
  # @return [Array<String>] list of absolute file paths
110
110
  def list_files(paths = [], options = {})
111
111
  ref = options[:ref] || 'HEAD'
112
- path_list =
113
- if OS.windows?
114
- paths = paths.map { |path| path.gsub('"', '""') }
115
- paths.empty? ? '' : "\"#{paths.join('" "')}\""
116
- else
117
- paths.shelljoin
118
- end
119
- `git ls-tree --name-only #{ref} #{path_list}`.
120
- split(/\n/).
112
+
113
+ result = Overcommit::Utils.execute(%W[git ls-tree --name-only #{ref}], args: paths)
114
+ unless result.success?
115
+ raise Overcommit::Exceptions::Error,
116
+ "Error listing files. EXIT STATUS(es): #{result.statuses}.\n" \
117
+ "STDOUT(s): #{result.stdouts}.\n" \
118
+ "STDERR(s): #{result.stderrs}."
119
+ end
120
+
121
+ result.stdout.split(/\n/).
121
122
  map { |relative_file| File.expand_path(relative_file) }.
122
123
  reject { |file| File.directory?(file) } # Exclude submodule directories
123
124
  end
@@ -9,6 +9,6 @@ module Overcommit::Hook::CommitMsg
9
9
 
10
10
  def_delegators :@context, :empty_message?, :commit_message,
11
11
  :update_commit_message, :commit_message_lines,
12
- :commit_message_file
12
+ :commit_message_file, :modified_lines_in_file
13
13
  end
14
14
  end
@@ -12,9 +12,9 @@ module Overcommit::Hook::PreCommit
12
12
  result.stdout.chomp
13
13
  end
14
14
 
15
- unless name.split(' ').count >= 2
15
+ if name.empty?
16
16
  return :fail,
17
- "Author must have at least first and last name, but was: #{name}.\n" \
17
+ "Author name must be non-0 in length.\n" \
18
18
  'Set your name with `git config --global user.name "Your Name"` ' \
19
19
  'or via the GIT_AUTHOR_NAME environment variable'
20
20
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Overcommit::Hook::PreCommit
4
+ # Runs `dartanalyzer` against modified Dart files.
5
+ # @see https://dart.dev/tools/dartanalyzer
6
+ class DartAnalyzer < Base
7
+ MESSAGE_REGEX = /(?<type>.*)•\ (?<message>[^•]+)•\ (?<file>[^:]+):(?<line>\d+):(\d+)\.*/
8
+
9
+ def run
10
+ result = execute(command, args: applicable_files)
11
+ return :pass if result.success?
12
+
13
+ extract_messages(
14
+ result.stdout.split("\n").grep(MESSAGE_REGEX),
15
+ MESSAGE_REGEX,
16
+ lambda do |type|
17
+ type.include?('error') ? :error : :warning
18
+ end
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Overcommit::Hook::PreCommit
4
+ # Runs `erblint` against any modified ERB files.
5
+ #
6
+ # @see https://github.com/Shopify/erb-lint
7
+ class ErbLint < Base
8
+ MESSAGE_REGEX = /(?<message>.+)\nIn file: (?<file>.+):(?<line>\d+)/
9
+
10
+ def run
11
+ result = execute(command, args: applicable_files)
12
+ return :pass if result.success?
13
+
14
+ extract_messages(
15
+ result.stdout.split("\n\n")[1..-1],
16
+ MESSAGE_REGEX
17
+ )
18
+ end
19
+ end
20
+ end
@@ -19,20 +19,17 @@ module Overcommit::Hook::PreCommit
19
19
  # @see http://eslint.org/
20
20
  class EsLint < Base
21
21
  def run
22
+ eslint_regex = /^(?<file>[^\s](?:\w:)?[^:]+):[^\d]+(?<line>\d+).*?(?<type>Error|Warning)/
22
23
  result = execute(command, args: applicable_files)
23
24
  output = result.stdout.chomp
24
- messages = output.split("\n").grep(/Warning|Error/)
25
+ messages = output.split("\n").grep(eslint_regex)
25
26
 
26
27
  return [:fail, result.stderr] if messages.empty? && !result.success?
27
28
  return :pass if result.success? && output.empty?
28
29
 
29
30
  # example message:
30
31
  # path/to/file.js: line 1, col 0, Error - Error message (ruleName)
31
- extract_messages(
32
- messages,
33
- /^(?<file>(?:\w:)?[^:]+):[^\d]+(?<line>\d+).*?(?<type>Error|Warning)/,
34
- lambda { |type| type.downcase.to_sym }
35
- )
32
+ extract_messages(messages, eslint_regex, lambda { |type| type.downcase.to_sym })
36
33
  end
37
34
  end
38
35
  end
@@ -5,13 +5,34 @@ module Overcommit::Hook::PreCommit
5
5
  #
6
6
  # @see https://github.com/adrienverge/yamllint
7
7
  class YamlLint < Base
8
+ MESSAGE_REGEX = /
9
+ ^(?<file>.+)
10
+ :(?<line>\d+)
11
+ :(?<col>\d+)
12
+ :\s\[(?<type>\w+)\]
13
+ \s(?<msg>.+)$
14
+ /x
15
+
8
16
  def run
9
17
  result = execute(command, args: applicable_files)
18
+ parse_messages(result.stdout)
19
+ end
20
+
21
+ private
22
+
23
+ def parse_messages(output)
24
+ repo_root = Overcommit::Utils.repo_root
25
+
26
+ output.scan(MESSAGE_REGEX).map do |file, line, col, type, msg|
27
+ line = line.to_i
28
+ type = type.to_sym
29
+ # Obtain the path relative to the root of the repository
30
+ # for nicer output:
31
+ relpath = file.dup
32
+ relpath.slice!("#{repo_root}/")
10
33
 
11
- if result.success?
12
- :pass
13
- else
14
- return [:warn, result.stdout]
34
+ text = "#{relpath}:#{line}:#{col}:#{type} #{msg}"
35
+ Overcommit::Hook::Message.new(type, file, line, text)
15
36
  end
16
37
  end
17
38
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Overcommit::Hook::PrePush
4
+ # Runs Flutter test suite (`flutter test`) before push
5
+ #
6
+ # @see https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html
7
+ class FlutterTest < Base
8
+ def run
9
+ result = execute(command)
10
+ return :pass if result.success?
11
+
12
+ output = result.stdout + result.stderr
13
+ [:fail, output]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Overcommit::Hook::PrePush
4
+ # Runs Dart test suite (`pub run test`) before push
5
+ #
6
+ # @see https://pub.dev/packages/test#running-tests
7
+ class PubTest < Base
8
+ def run
9
+ result = execute(command)
10
+ return :pass if result.success?
11
+
12
+ output = result.stdout + result.stderr
13
+ [:fail, output]
14
+ end
15
+ end
16
+ end
@@ -2,11 +2,37 @@
2
2
 
3
3
  module Overcommit::Hook::PrepareCommitMsg
4
4
  # Prepends the commit message with a message based on the branch name.
5
+ #
6
+ # === What to prepend
7
+ #
5
8
  # It's possible to reference parts of the branch name through the captures in
6
9
  # the `branch_pattern` regex.
10
+ #
11
+ # For instance, if your current branch is `123-topic` then this config
12
+ #
13
+ # branch_pattern: '(\d+)-(\w+)'
14
+ # replacement_text: '[#\1]'
15
+ #
16
+ # would make this hook prepend commit messages with `[#123]`.
17
+ #
18
+ # Similarly, a replacement text of `[\1][\2]` would result in `[123][topic]`.
19
+ #
20
+ # == When to run this hook
21
+ #
22
+ # You can configure this to run only for specific types of commits by setting
23
+ # the `skipped_commit_types`. The allowed types are
24
+ #
25
+ # - 'message' - if message is given via `-m`, `-F`
26
+ # - 'template' - if `-t` is given or `commit.template` is set
27
+ # - 'commit' - if `-c`, `-C`, or `--amend` is given
28
+ # - 'merge' - if merging
29
+ # - 'squash' - if squashing
30
+ #
7
31
  class ReplaceBranch < Base
32
+ DEFAULT_BRANCH_PATTERN = /\A(\d+)-(\w+).*\z/
33
+
8
34
  def run
9
- return :pass if skipped_commit_types.include? commit_message_source
35
+ return :pass if skip?
10
36
 
11
37
  Overcommit::Utils.log.debug(
12
38
  "Checking if '#{Overcommit::GitRepo.current_branch}' matches #{branch_pattern}"
@@ -17,21 +43,25 @@ module Overcommit::Hook::PrepareCommitMsg
17
43
  Overcommit::Utils.log.debug("Writing #{commit_message_filename} with #{new_template}")
18
44
 
19
45
  modify_commit_message do |old_contents|
20
- "#{new_template} #{old_contents}"
46
+ "#{new_template}#{old_contents}"
21
47
  end
22
48
 
23
49
  :pass
24
50
  end
25
51
 
26
52
  def new_template
27
- @new_template ||= Overcommit::GitRepo.current_branch.gsub(branch_pattern, replacement_text)
53
+ @new_template ||=
54
+ begin
55
+ curr_branch = Overcommit::GitRepo.current_branch
56
+ curr_branch.gsub(branch_pattern, replacement_text).strip
57
+ end
28
58
  end
29
59
 
30
60
  def branch_pattern
31
61
  @branch_pattern ||=
32
62
  begin
33
63
  pattern = config['branch_pattern']
34
- Regexp.new((pattern || '').empty? ? '\A.*\w+[-_](\d+).*\z' : pattern)
64
+ Regexp.new((pattern || '').empty? ? DEFAULT_BRANCH_PATTERN : pattern)
35
65
  end
36
66
  end
37
67
 
@@ -53,5 +83,9 @@ module Overcommit::Hook::PrepareCommitMsg
53
83
  def skipped_commit_types
54
84
  @skipped_commit_types ||= config['skipped_commit_types'].map(&:to_sym)
55
85
  end
86
+
87
+ def skip?
88
+ skipped_commit_types.include?(commit_message_source)
89
+ end
56
90
  end
57
91
  end
@@ -1,8 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'pre_commit'
4
+ require_relative 'helpers/stash_unstaged_changes'
5
+ require_relative 'helpers/file_modifications'
6
+
3
7
  module Overcommit::HookContext
4
8
  # Contains helpers related to contextual information used by commit-msg hooks.
5
9
  class CommitMsg < Base
10
+ include Overcommit::HookContext::Helpers::StashUnstagedChanges
11
+ include Overcommit::HookContext::Helpers::FileModifications
12
+
6
13
  def empty_message?
7
14
  commit_message.strip.empty?
8
15
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Overcommit::HookContext
4
+ module Helpers
5
+ # This module contains methods for determining what files were changed and on what unique line
6
+ # numbers did the change occur.
7
+ module FileModifications
8
+ # Returns whether this hook run was triggered by `git commit --amend`
9
+ def amendment?
10
+ return @amendment unless @amendment.nil?
11
+
12
+ cmd = Overcommit::Utils.parent_command
13
+ return unless cmd
14
+ amend_pattern = 'commit(\s.*)?\s--amend(\s|$)'
15
+
16
+ # Since the ps command can return invalid byte sequences for commands
17
+ # containing unicode characters, we replace the offending characters,
18
+ # since the pattern we're looking for will consist of ASCII characters
19
+ unless cmd.valid_encoding?
20
+ cmd = Overcommit::Utils.
21
+ parent_command.
22
+ encode('UTF-16be', invalid: :replace, replace: '?').
23
+ encode('UTF-8')
24
+ end
25
+
26
+ return @amendment if
27
+ # True if the command is a commit with the --amend flag
28
+ @amendment = !(/\s#{amend_pattern}/ =~ cmd).nil?
29
+
30
+ # Check for git aliases that call `commit --amend`
31
+ `git config --get-regexp "^alias\\." "#{amend_pattern}"`.
32
+ scan(/alias\.([-\w]+)/). # Extract the alias
33
+ each do |match|
34
+ return @amendment if
35
+ # True if the command uses a git alias for `commit --amend`
36
+ @amendment = !(/git(\.exe)?\s+#{match[0]}/ =~ cmd).nil?
37
+ end
38
+
39
+ @amendment
40
+ end
41
+
42
+ # Get a list of added, copied, or modified files that have been staged.
43
+ # Renames and deletions are ignored, since there should be nothing to check.
44
+ def modified_files
45
+ unless @modified_files
46
+ currently_staged = Overcommit::GitRepo.modified_files(staged: true)
47
+ @modified_files = currently_staged
48
+
49
+ # Include files modified in last commit if amending
50
+ if amendment?
51
+ subcmd = 'show --format=%n'
52
+ previously_modified = Overcommit::GitRepo.modified_files(subcmd: subcmd)
53
+ @modified_files |= filter_modified_files(previously_modified)
54
+ end
55
+ end
56
+ @modified_files
57
+ end
58
+
59
+ # Returns the set of line numbers corresponding to the lines that were
60
+ # changed in a specified file.
61
+ def modified_lines_in_file(file)
62
+ @modified_lines ||= {}
63
+ unless @modified_lines[file]
64
+ @modified_lines[file] =
65
+ Overcommit::GitRepo.extract_modified_lines(file, staged: true)
66
+
67
+ # Include lines modified in last commit if amending
68
+ if amendment?
69
+ subcmd = 'show --format=%n'
70
+ @modified_lines[file] +=
71
+ Overcommit::GitRepo.extract_modified_lines(file, subcmd: subcmd)
72
+ end
73
+ end
74
+ @modified_lines[file]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Overcommit::HookContext
4
+ module Helpers
5
+ # This module contains behavior for stashing unstaged changes before hooks are ran and restoring
6
+ # them afterwards
7
+ module StashUnstagedChanges
8
+ # Stash unstaged contents of files so hooks don't see changes that aren't
9
+ # about to be committed.
10
+ def setup_environment
11
+ store_modified_times
12
+ Overcommit::GitRepo.store_merge_state
13
+ Overcommit::GitRepo.store_cherry_pick_state
14
+
15
+ # Don't attempt to stash changes if all changes are staged, as this
16
+ # prevents us from modifying files at all, which plays better with
17
+ # editors/tools which watch for file changes.
18
+ if !initial_commit? && unstaged_changes?
19
+ stash_changes
20
+
21
+ # While running hooks make it appear as if nothing changed
22
+ restore_modified_times
23
+ end
24
+ end
25
+
26
+ # Returns whether the current git branch is empty (has no commits).
27
+ def initial_commit?
28
+ return @initial_commit unless @initial_commit.nil?
29
+ @initial_commit = Overcommit::GitRepo.initial_commit?
30
+ end
31
+
32
+ # Restore unstaged changes and reset file modification times so it appears
33
+ # as if nothing ever changed.
34
+ #
35
+ # We want to restore the modification times for each of the files after
36
+ # every step to ensure as little time as possible has passed while the
37
+ # modification time on the file was newer. This helps us play more nicely
38
+ # with file watchers.
39
+ def cleanup_environment
40
+ if @changes_stashed
41
+ clear_working_tree
42
+ restore_working_tree
43
+ restore_modified_times
44
+ end
45
+
46
+ Overcommit::GitRepo.restore_merge_state
47
+ Overcommit::GitRepo.restore_cherry_pick_state
48
+ end
49
+
50
+ private
51
+
52
+ # Stores the modification times for all modified files to make it appear like
53
+ # they never changed.
54
+ #
55
+ # This prevents (some) editors from complaining about files changing when we
56
+ # stash changes before running the hooks.
57
+ def store_modified_times
58
+ @modified_times = {}
59
+
60
+ staged_files = modified_files
61
+ unstaged_files = Overcommit::GitRepo.modified_files(staged: false)
62
+
63
+ (staged_files + unstaged_files).each do |file|
64
+ next if Overcommit::Utils.broken_symlink?(file)
65
+ next unless File.exist?(file) # Ignore renamed files (old file no longer exists)
66
+ @modified_times[file] = File.mtime(file)
67
+ end
68
+ end
69
+
70
+ # Returns whether there are any changes to tracked files which have not yet
71
+ # been staged.
72
+ def unstaged_changes?
73
+ result = Overcommit::Utils.execute(%w[git --no-pager diff --quiet])
74
+ !result.success?
75
+ end
76
+
77
+ def stash_changes
78
+ @stash_attempted = true
79
+
80
+ stash_message = "Overcommit: Stash of repo state before hook run at #{Time.now}"
81
+ result = Overcommit::Utils.with_environment('GIT_LITERAL_PATHSPECS' => '0') do
82
+ Overcommit::Utils.execute(
83
+ %w[git -c commit.gpgsign=false stash save --keep-index --quiet] + [stash_message]
84
+ )
85
+ end
86
+
87
+ unless result.success?
88
+ # Failure to stash in this case is likely due to a configuration
89
+ # issue (e.g. author/email not set or GPG signing key incorrect)
90
+ raise Overcommit::Exceptions::HookSetupFailed,
91
+ "Unable to setup environment for #{hook_script_name} hook run:" \
92
+ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
93
+ end
94
+
95
+ @changes_stashed = `git stash list -1`.include?(stash_message)
96
+ end
97
+
98
+ # Restores the file modification times for all modified files to make it
99
+ # appear like they never changed.
100
+ def restore_modified_times
101
+ @modified_times.each do |file, time|
102
+ next if Overcommit::Utils.broken_symlink?(file)
103
+ next unless File.exist?(file)
104
+ File.utime(time, time, file)
105
+ end
106
+ end
107
+
108
+ # Clears the working tree so that the stash can be applied.
109
+ def clear_working_tree
110
+ removed_submodules = Overcommit::GitRepo.staged_submodule_removals
111
+
112
+ result = Overcommit::Utils.execute(%w[git reset --hard])
113
+ unless result.success?
114
+ raise Overcommit::Exceptions::HookCleanupFailed,
115
+ "Unable to cleanup working tree after #{hook_script_name} hooks run:" \
116
+ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
117
+ end
118
+
119
+ # Hard-resetting a staged submodule removal results in the index being
120
+ # reset but the submodule being restored as an empty directory. This empty
121
+ # directory prevents us from stashing on a subsequent run if a hook fails.
122
+ #
123
+ # Work around this by removing these empty submodule directories as there
124
+ # doesn't appear any reason to keep them around.
125
+ removed_submodules.each do |submodule|
126
+ FileUtils.rmdir(submodule.path)
127
+ end
128
+ end
129
+
130
+ # Applies the stash to the working tree to restore the user's state.
131
+ def restore_working_tree
132
+ result = Overcommit::Utils.execute(%w[git stash pop --index])
133
+ unless result.success?
134
+ raise Overcommit::Exceptions::HookCleanupFailed,
135
+ "Unable to restore working tree after #{hook_script_name} hooks run:" \
136
+ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'fileutils'
4
4
  require 'set'
5
+ require_relative 'helpers/stash_unstaged_changes'
6
+ require_relative 'helpers/file_modifications'
5
7
 
6
8
  module Overcommit::HookContext
7
9
  # Contains helpers related to contextual information used by pre-commit hooks.
@@ -9,204 +11,8 @@ module Overcommit::HookContext
9
11
  # This includes staged files, which lines of those files have been modified,
10
12
  # etc. It is also responsible for saving/restoring the state of the repo so
11
13
  # hooks only inspect staged changes.
12
- class PreCommit < Base # rubocop:disable ClassLength
13
- # Returns whether this hook run was triggered by `git commit --amend`
14
- def amendment?
15
- return @amendment unless @amendment.nil?
16
-
17
- cmd = Overcommit::Utils.parent_command
18
- return unless cmd
19
- amend_pattern = 'commit(\s.*)?\s--amend(\s|$)'
20
-
21
- # Since the ps command can return invalid byte sequences for commands
22
- # containing unicode characters, we replace the offending characters,
23
- # since the pattern we're looking for will consist of ASCII characters
24
- unless cmd.valid_encoding?
25
- cmd = Overcommit::Utils.parent_command.encode('UTF-16be', invalid: :replace, replace: '?').
26
- encode('UTF-8')
27
- end
28
-
29
- return @amendment if
30
- # True if the command is a commit with the --amend flag
31
- @amendment = !(/\s#{amend_pattern}/ =~ cmd).nil?
32
-
33
- # Check for git aliases that call `commit --amend`
34
- `git config --get-regexp "^alias\\." "#{amend_pattern}"`.
35
- scan(/alias\.([-\w]+)/). # Extract the alias
36
- each do |match|
37
- return @amendment if
38
- # True if the command uses a git alias for `commit --amend`
39
- @amendment = !(/git(\.exe)?\s+#{match[0]}/ =~ cmd).nil?
40
- end
41
-
42
- @amendment
43
- end
44
-
45
- # Stash unstaged contents of files so hooks don't see changes that aren't
46
- # about to be committed.
47
- def setup_environment
48
- store_modified_times
49
- Overcommit::GitRepo.store_merge_state
50
- Overcommit::GitRepo.store_cherry_pick_state
51
-
52
- # Don't attempt to stash changes if all changes are staged, as this
53
- # prevents us from modifying files at all, which plays better with
54
- # editors/tools which watch for file changes.
55
- if !initial_commit? && unstaged_changes?
56
- stash_changes
57
-
58
- # While running hooks make it appear as if nothing changed
59
- restore_modified_times
60
- end
61
- end
62
-
63
- # Restore unstaged changes and reset file modification times so it appears
64
- # as if nothing ever changed.
65
- #
66
- # We want to restore the modification times for each of the files after
67
- # every step to ensure as little time as possible has passed while the
68
- # modification time on the file was newer. This helps us play more nicely
69
- # with file watchers.
70
- def cleanup_environment
71
- if @changes_stashed
72
- clear_working_tree
73
- restore_working_tree
74
- restore_modified_times
75
- end
76
-
77
- Overcommit::GitRepo.restore_merge_state
78
- Overcommit::GitRepo.restore_cherry_pick_state
79
- end
80
-
81
- # Get a list of added, copied, or modified files that have been staged.
82
- # Renames and deletions are ignored, since there should be nothing to check.
83
- def modified_files
84
- unless @modified_files
85
- currently_staged = Overcommit::GitRepo.modified_files(staged: true)
86
- @modified_files = currently_staged
87
-
88
- # Include files modified in last commit if amending
89
- if amendment?
90
- subcmd = 'show --format=%n'
91
- previously_modified = Overcommit::GitRepo.modified_files(subcmd: subcmd)
92
- @modified_files |= filter_modified_files(previously_modified)
93
- end
94
- end
95
- @modified_files
96
- end
97
-
98
- # Returns the set of line numbers corresponding to the lines that were
99
- # changed in a specified file.
100
- def modified_lines_in_file(file)
101
- @modified_lines ||= {}
102
- unless @modified_lines[file]
103
- @modified_lines[file] =
104
- Overcommit::GitRepo.extract_modified_lines(file, staged: true)
105
-
106
- # Include lines modified in last commit if amending
107
- if amendment?
108
- subcmd = 'show --format=%n'
109
- @modified_lines[file] +=
110
- Overcommit::GitRepo.extract_modified_lines(file, subcmd: subcmd)
111
- end
112
- end
113
- @modified_lines[file]
114
- end
115
-
116
- # Returns whether the current git branch is empty (has no commits).
117
- def initial_commit?
118
- return @initial_commit unless @initial_commit.nil?
119
- @initial_commit = Overcommit::GitRepo.initial_commit?
120
- end
121
-
122
- private
123
-
124
- def stash_changes
125
- @stash_attempted = true
126
-
127
- stash_message = "Overcommit: Stash of repo state before hook run at #{Time.now}"
128
- result = Overcommit::Utils.with_environment('GIT_LITERAL_PATHSPECS' => '0') do
129
- Overcommit::Utils.execute(
130
- %w[git -c commit.gpgsign=false stash save --keep-index --quiet] + [stash_message]
131
- )
132
- end
133
-
134
- unless result.success?
135
- # Failure to stash in this case is likely due to a configuration
136
- # issue (e.g. author/email not set or GPG signing key incorrect)
137
- raise Overcommit::Exceptions::HookSetupFailed,
138
- "Unable to setup environment for #{hook_script_name} hook run:" \
139
- "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
140
- end
141
-
142
- @changes_stashed = `git stash list -1`.include?(stash_message)
143
- end
144
-
145
- # Clears the working tree so that the stash can be applied.
146
- def clear_working_tree
147
- removed_submodules = Overcommit::GitRepo.staged_submodule_removals
148
-
149
- result = Overcommit::Utils.execute(%w[git reset --hard])
150
- unless result.success?
151
- raise Overcommit::Exceptions::HookCleanupFailed,
152
- "Unable to cleanup working tree after #{hook_script_name} hooks run:" \
153
- "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
154
- end
155
-
156
- # Hard-resetting a staged submodule removal results in the index being
157
- # reset but the submodule being restored as an empty directory. This empty
158
- # directory prevents us from stashing on a subsequent run if a hook fails.
159
- #
160
- # Work around this by removing these empty submodule directories as there
161
- # doesn't appear any reason to keep them around.
162
- removed_submodules.each do |submodule|
163
- FileUtils.rmdir(submodule.path)
164
- end
165
- end
166
-
167
- # Applies the stash to the working tree to restore the user's state.
168
- def restore_working_tree
169
- result = Overcommit::Utils.execute(%w[git stash pop --index --quiet])
170
- unless result.success?
171
- raise Overcommit::Exceptions::HookCleanupFailed,
172
- "Unable to restore working tree after #{hook_script_name} hooks run:" \
173
- "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
174
- end
175
- end
176
-
177
- # Returns whether there are any changes to tracked files which have not yet
178
- # been staged.
179
- def unstaged_changes?
180
- result = Overcommit::Utils.execute(%w[git --no-pager diff --quiet])
181
- !result.success?
182
- end
183
-
184
- # Stores the modification times for all modified files to make it appear like
185
- # they never changed.
186
- #
187
- # This prevents (some) editors from complaining about files changing when we
188
- # stash changes before running the hooks.
189
- def store_modified_times
190
- @modified_times = {}
191
-
192
- staged_files = modified_files
193
- unstaged_files = Overcommit::GitRepo.modified_files(staged: false)
194
-
195
- (staged_files + unstaged_files).each do |file|
196
- next if Overcommit::Utils.broken_symlink?(file)
197
- next unless File.exist?(file) # Ignore renamed files (old file no longer exists)
198
- @modified_times[file] = File.mtime(file)
199
- end
200
- end
201
-
202
- # Restores the file modification times for all modified files to make it
203
- # appear like they never changed.
204
- def restore_modified_times
205
- @modified_times.each do |file, time|
206
- next if Overcommit::Utils.broken_symlink?(file)
207
- next unless File.exist?(file)
208
- File.utime(time, time, file)
209
- end
210
- end
14
+ class PreCommit < Base
15
+ include Overcommit::HookContext::Helpers::StashUnstagedChanges
16
+ include Overcommit::HookContext::Helpers::FileModifications
211
17
  end
212
18
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  module Overcommit
5
- VERSION = '0.54.0'
5
+ VERSION = '0.58.0'
6
6
  end
data/libexec/index-tags CHANGED
@@ -6,10 +6,12 @@
6
6
 
7
7
  set -e
8
8
 
9
- trap "rm -f $GIT_DIR/tags.$$" EXIT
10
- err_file=$GIT_DIR/ctags.err
11
- if ctags --tag-relative -Rf$GIT_DIR/tags.$$ --exclude=.git "$@" 2>${err_file}; then
12
- mv $GIT_DIR/tags.$$ $GIT_DIR/tags
9
+ dir="`git rev-parse --git-dir`"
10
+
11
+ trap "rm -f $dir/tags.$$" EXIT
12
+ err_file=$dir/ctags.err
13
+ if ctags --tag-relative -Rf$dir/tags.$$ --exclude=.git "$@" 2>${err_file}; then
14
+ mv $dir/tags.$$ $dir/tags
13
15
  [ -e ${err_file} ] && rm -f ${err_file}
14
16
  else
15
17
  # Ignore STDERR unless `ctags` returned a non-zero exit code
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: overcommit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.54.0
4
+ version: 0.58.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane da Silva
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-25 00:00:00.000000000 Z
11
+ date: 2021-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: childprocess
@@ -44,6 +44,20 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '1.4'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rexml
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.2'
47
61
  description: Utility to install, configure, and extend Git hooks
48
62
  email:
49
63
  - shane@dasilva.io
@@ -129,7 +143,9 @@ files:
129
143
  - lib/overcommit/hook/pre_commit/cook_style.rb
130
144
  - lib/overcommit/hook/pre_commit/credo.rb
131
145
  - lib/overcommit/hook/pre_commit/css_lint.rb
146
+ - lib/overcommit/hook/pre_commit/dart_analyzer.rb
132
147
  - lib/overcommit/hook/pre_commit/dogma.rb
148
+ - lib/overcommit/hook/pre_commit/erb_lint.rb
133
149
  - lib/overcommit/hook/pre_commit/es_lint.rb
134
150
  - lib/overcommit/hook/pre_commit/execute_permissions.rb
135
151
  - lib/overcommit/hook/pre_commit/fasterer.rb
@@ -212,12 +228,14 @@ files:
212
228
  - lib/overcommit/hook/pre_push/base.rb
213
229
  - lib/overcommit/hook/pre_push/brakeman.rb
214
230
  - lib/overcommit/hook/pre_push/cargo_test.rb
231
+ - lib/overcommit/hook/pre_push/flutter_test.rb
215
232
  - lib/overcommit/hook/pre_push/go_test.rb
216
233
  - lib/overcommit/hook/pre_push/golangci_lint.rb
217
234
  - lib/overcommit/hook/pre_push/minitest.rb
218
235
  - lib/overcommit/hook/pre_push/php_unit.rb
219
236
  - lib/overcommit/hook/pre_push/pronto.rb
220
237
  - lib/overcommit/hook/pre_push/protected_branches.rb
238
+ - lib/overcommit/hook/pre_push/pub_test.rb
221
239
  - lib/overcommit/hook/pre_push/pytest.rb
222
240
  - lib/overcommit/hook/pre_push/python_nose.rb
223
241
  - lib/overcommit/hook/pre_push/r_spec.rb
@@ -239,6 +257,8 @@ files:
239
257
  - lib/overcommit/hook_context.rb
240
258
  - lib/overcommit/hook_context/base.rb
241
259
  - lib/overcommit/hook_context/commit_msg.rb
260
+ - lib/overcommit/hook_context/helpers/file_modifications.rb
261
+ - lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb
242
262
  - lib/overcommit/hook_context/post_checkout.rb
243
263
  - lib/overcommit/hook_context/post_commit.rb
244
264
  - lib/overcommit/hook_context/post_merge.rb
@@ -296,8 +316,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
296
316
  - !ruby/object:Gem::Version
297
317
  version: '0'
298
318
  requirements: []
299
- rubygems_version: 3.1.1
300
- signing_key:
319
+ rubygems_version: 3.1.4
320
+ signing_key:
301
321
  specification_version: 4
302
322
  summary: Git hook manager
303
323
  test_files: []