overcommit 0.54.0 → 0.58.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.
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: []