overcommit 0.52.1 → 0.56.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: 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