overcommit 0.52.1 → 0.56.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: d754f1b3cae9fb7a74e38645a47a52e84337692b53ac32be7079296ef6f0c4ef
4
- data.tar.gz: 1065cb959d9285aca1541fc5ed6523e7c237dfc5f0fb6acbd129c1edc4c772c4
3
+ metadata.gz: d6ecbb7e4b390f8d17c9058da38464b73f7cec50a87e494430e9c15a9c40ecff
4
+ data.tar.gz: f90974df2fe0b918ff8711a228608ad1b312a730abd9ad7c45b02ee512daa496
5
5
  SHA512:
6
- metadata.gz: dee7031b48c46a3b42f27701dd922d63468340498f3745d11a1c5bbe2a573e408a2d1fe266b96313d319dc3e43eb8401d4624cf4e94d835df850b25efea859dd
7
- data.tar.gz: 69389da703ab37e63f35d7267b70a6a1a8fb3c7afd80cdadbe6f2ff32fe27428c45276b34b2286dd01930e0a9c78f5d2bc44f707c5980f501460897a79aebcf3
6
+ metadata.gz: a8b55f73d42ca1791bbd6a01281a0adbbe1f57385721d9ec4905e98dd9217480ae5b10ff8e9083048953974d40c97d104f126ca9b514fb05359ba6c402b4bf7b
7
+ data.tar.gz: b9263d9c65fbbeaa4cf7a38d5b7c2bb4af6d6ec7abf5e3ee22209a455a3b7529718b6eb9adbc14161539861420242093c7b4c3564e248ce84bc663cfb354899f
@@ -338,7 +338,7 @@ PreCommit:
338
338
  keywords: ['FContext','FDescribe','FIt','FMeasure','FSpecify','FWhen']
339
339
 
340
340
  GoFmt:
341
- enabled: true
341
+ enabled: false
342
342
  description: 'Fix with go fmt'
343
343
  required_executable: 'go'
344
344
  command: ['go', 'fmt']
@@ -885,7 +885,7 @@ PreCommit:
885
885
  enabled: false
886
886
  description: 'Analyze with YAMLlint'
887
887
  required_executable: 'yamllint'
888
- flags: ['--format=parsable']
888
+ flags: ['--format=parsable', '--strict']
889
889
  install_command: 'pip install yamllint'
890
890
  include:
891
891
  - '**/*.yaml'
@@ -1237,9 +1237,14 @@ PrepareCommitMsg:
1237
1237
  ReplaceBranch:
1238
1238
  enabled: false
1239
1239
  description: 'Prepends the commit message with text based on the branch name'
1240
- branch_pattern: '\A.*\w+[-_](\d+).*\z'
1240
+ branch_pattern: '\A(\d+)-(\w+).*\z'
1241
1241
  replacement_text: '[#\1]'
1242
- skipped_commit_types: ['message', 'template', 'merge', 'squash']
1242
+ skipped_commit_types:
1243
+ - 'message' # if message is given via `-m`, `-F`
1244
+ - 'template' # if `-t` is given or `commit.template` is set
1245
+ - 'commit' # if `-c`, `-C`, or `--amend` is given
1246
+ - 'merge' # if merging
1247
+ - 'squash' # if squashing
1243
1248
  on_fail: warn
1244
1249
 
1245
1250
  # Hooks that run during `git push`, after remote refs have been updated but
@@ -1297,6 +1302,13 @@ PrePush:
1297
1302
  flags: ['--bootstrap', 'vendor/autoload.php', 'tests']
1298
1303
  install_command: 'composer require --dev phpunit/phpunit'
1299
1304
 
1305
+ Pronto:
1306
+ enabled: false
1307
+ description: 'Analyzing with pronto'
1308
+ required_executable: 'pronto'
1309
+ install_command: 'gem install pronto'
1310
+ flags: ['run', '--exit-code']
1311
+
1300
1312
  ProtectedBranches:
1301
1313
  enabled: false
1302
1314
  description: 'Check for illegal pushes to protected branches'
@@ -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
@@ -84,7 +84,8 @@ module Overcommit::Hook
84
84
  end
85
85
 
86
86
  def skip?
87
- @config['skip']
87
+ @config['skip'] ||
88
+ (@config['skip_if'] ? execute(@config['skip_if']).success? : false)
88
89
  end
89
90
 
90
91
  def run?
@@ -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
@@ -12,21 +12,14 @@ module Overcommit::Hook::PreCommit
12
12
  def run
13
13
  messages = []
14
14
 
15
- applicable_files.each do |file|
16
- result = execute(command, args: [file])
17
- if result.status
18
- rows = result.stdout.split("\n")
19
-
20
- # Discard the csv header
21
- rows.shift
22
-
23
- # Push each of the errors in the particular file into the array
24
- rows.map do |row|
25
- messages << row
26
- end
27
- end
15
+ result = execute(command, args: applicable_files)
16
+ if result.status
17
+ messages = result.stdout.split("\n")
18
+ # Discard the csv header
19
+ messages.shift
28
20
  end
29
21
 
22
+ return :fail if messages.empty? && !result.success?
30
23
  return :pass if messages.empty?
31
24
 
32
25
  parse_messages(messages)
@@ -1,23 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'overcommit/hook/shared/pronto'
4
+
3
5
  module Overcommit::Hook::PreCommit
4
6
  # Runs `pronto`
5
7
  #
6
8
  # @see https://github.com/mmozuras/pronto
7
9
  class Pronto < Base
8
- MESSAGE_TYPE_CATEGORIZER = lambda do |type|
9
- type.include?('E') ? :error : :warning
10
- end
11
-
12
- def run
13
- result = execute(command)
14
- return :pass if result.success?
15
-
16
- extract_messages(
17
- result.stdout.split("\n"),
18
- /^(?<file>(?:\w:)?[^:]+):(?<line>\d+) (?<type>[^ ]+)/,
19
- MESSAGE_TYPE_CATEGORIZER,
20
- )
21
- end
10
+ include Overcommit::Hook::Shared::Pronto
22
11
  end
23
12
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
+ require 'overcommit/utils/messages_utils'
4
5
 
5
6
  module Overcommit::Hook::PrePush
6
7
  # Functionality common to all pre-push hooks.
@@ -17,6 +18,10 @@ module Overcommit::Hook::PrePush
17
18
 
18
19
  private
19
20
 
21
+ def extract_messages(*args)
22
+ Overcommit::Utils::MessagesUtils.extract_messages(*args)
23
+ end
24
+
20
25
  def exclude_remotes
21
26
  @config['exclude_remotes'] || []
22
27
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'overcommit/hook/shared/pronto'
4
+
5
+ module Overcommit::Hook::PrePush
6
+ # Runs `pronto`
7
+ #
8
+ # @see https://github.com/mmozuras/pronto
9
+ class Pronto < Base
10
+ include Overcommit::Hook::Shared::Pronto
11
+ end
12
+ 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}\n#{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
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Overcommit::Hook::Shared
4
+ # Shared code used by all Pronto hooks. Runs pronto linter.
5
+ module Pronto
6
+ MESSAGE_TYPE_CATEGORIZER = lambda do |type|
7
+ type.include?('E') ? :error : :warning
8
+ end
9
+
10
+ def run
11
+ result = execute(command)
12
+ return :pass if result.success?
13
+
14
+ extract_messages(
15
+ result.stdout.split("\n"),
16
+ /^(?<file>(?:\w:)?[^:]+):(?<line>\d+) (?<type>[^ ]+)/,
17
+ MESSAGE_TYPE_CATEGORIZER,
18
+ )
19
+ end
20
+ end
21
+ 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 --quiet])
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
@@ -51,6 +51,7 @@ module Overcommit
51
51
  def run_interrupted
52
52
  log.newline
53
53
  log.warning '⚠ Hook run interrupted by user'
54
+ log.warning '⚠ If files appear modified/missing, check your stash to recover them'
54
55
  log.newline
55
56
  end
56
57
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  module Overcommit
5
- VERSION = '0.52.1'
5
+ VERSION = '0.56.0'
6
6
  end
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.52.1
4
+ version: 0.56.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane da Silva
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-04 00:00:00.000000000 Z
11
+ date: 2020-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: childprocess
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 0.6.3
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '4'
22
+ version: '5'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 0.6.3
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '4'
32
+ version: '5'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: iniparse
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -216,6 +216,7 @@ files:
216
216
  - lib/overcommit/hook/pre_push/golangci_lint.rb
217
217
  - lib/overcommit/hook/pre_push/minitest.rb
218
218
  - lib/overcommit/hook/pre_push/php_unit.rb
219
+ - lib/overcommit/hook/pre_push/pronto.rb
219
220
  - lib/overcommit/hook/pre_push/protected_branches.rb
220
221
  - lib/overcommit/hook/pre_push/pytest.rb
221
222
  - lib/overcommit/hook/pre_push/python_nose.rb
@@ -231,12 +232,15 @@ files:
231
232
  - lib/overcommit/hook/shared/composer_install.rb
232
233
  - lib/overcommit/hook/shared/index_tags.rb
233
234
  - lib/overcommit/hook/shared/npm_install.rb
235
+ - lib/overcommit/hook/shared/pronto.rb
234
236
  - lib/overcommit/hook/shared/rake_target.rb
235
237
  - lib/overcommit/hook/shared/submodule_status.rb
236
238
  - lib/overcommit/hook/shared/yarn_install.rb
237
239
  - lib/overcommit/hook_context.rb
238
240
  - lib/overcommit/hook_context/base.rb
239
241
  - lib/overcommit/hook_context/commit_msg.rb
242
+ - lib/overcommit/hook_context/helpers/file_modifications.rb
243
+ - lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb
240
244
  - lib/overcommit/hook_context/post_checkout.rb
241
245
  - lib/overcommit/hook_context/post_commit.rb
242
246
  - lib/overcommit/hook_context/post_merge.rb