overcommit 0.53.0 → 0.57.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: 7f21bd45048df18d59d884ff63d79c3d3304116125497e788e1127b53c449c52
4
- data.tar.gz: 5e7c92a6e7c241b4f3f0498fe42347fde0863317f4e7097adb89e6ebd44fb0b4
3
+ metadata.gz: b5c35b43073b67da5cd0f8218c5ffd914b0601f1aac6d5ac9263ec3945893259
4
+ data.tar.gz: 9c08bf2a7153993c48e3d15f30dee2c846edbb5f8500f89a41e027ec80ffb767
5
5
  SHA512:
6
- metadata.gz: 3c7182368533a88cbccabcf3dfdee1df57bbdc37b5a865a4f3bc37d30eb128d5af5ac655932141a68687a3fb56500dbe443e56f26b82fa43d10e12cc0ee8cf44
7
- data.tar.gz: 8ef215e68656618666eebe1a48369a924731e909674a27cc42172427ace29104ba67d0cdaafa72c22b8693deb9baaf81d41c6355653111d5775d6a81f5f1260b
6
+ metadata.gz: 6cdcab7f4b31feed71b3f584f1e025a930d9b767e4e9acf00562ed57fac38442381454f57758bd967a657c9f966842f44212b53e2b482fe50613723590383d02
7
+ data.tar.gz: 6d763e1d1eca286dd9c8861ee265bd8b3ed29e740d10d430e82c461ae64b313ef2057b11db654c0f694c21dcefcf187b8e7ea27bfbf1a16eba3d6f47776b4772
@@ -275,6 +275,13 @@ PreCommit:
275
275
  - '**/*.ex'
276
276
  - '**/*.exs'
277
277
 
278
+ ErbLint:
279
+ enabled: false
280
+ description: 'Analyze with ERB Lint'
281
+ required_executable: 'erblint'
282
+ install_command: 'bundle install erb_lint'
283
+ include: '**/*.html.erb'
284
+
278
285
  EsLint:
279
286
  enabled: false
280
287
  description: 'Analyze with ESLint'
@@ -338,7 +345,7 @@ PreCommit:
338
345
  keywords: ['FContext','FDescribe','FIt','FMeasure','FSpecify','FWhen']
339
346
 
340
347
  GoFmt:
341
- enabled: true
348
+ enabled: false
342
349
  description: 'Fix with go fmt'
343
350
  required_executable: 'go'
344
351
  command: ['go', 'fmt']
@@ -885,7 +892,7 @@ PreCommit:
885
892
  enabled: false
886
893
  description: 'Analyze with YAMLlint'
887
894
  required_executable: 'yamllint'
888
- flags: ['--format=parsable']
895
+ flags: ['--format=parsable', '--strict']
889
896
  install_command: 'pip install yamllint'
890
897
  include:
891
898
  - '**/*.yaml'
@@ -1237,9 +1244,14 @@ PrepareCommitMsg:
1237
1244
  ReplaceBranch:
1238
1245
  enabled: false
1239
1246
  description: 'Prepends the commit message with text based on the branch name'
1240
- branch_pattern: '\A.*\w+[-_](\d+).*\z'
1247
+ branch_pattern: '\A(\d+)-(\w+).*\z'
1241
1248
  replacement_text: '[#\1]'
1242
- skipped_commit_types: ['message', 'template', 'merge', 'squash']
1249
+ skipped_commit_types:
1250
+ - 'message' # if message is given via `-m`, `-F`
1251
+ - 'template' # if `-t` is given or `commit.template` is set
1252
+ - 'commit' # if `-c`, `-C`, or `--amend` is given
1253
+ - 'merge' # if merging
1254
+ - 'squash' # if squashing
1243
1255
  on_fail: warn
1244
1256
 
1245
1257
  # Hooks that run during `git push`, after remote refs have been updated but
@@ -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,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
@@ -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 --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
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  module Overcommit
5
- VERSION = '0.53.0'
5
+ VERSION = '0.57.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.53.0
4
+ version: 0.57.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-04-24 00:00:00.000000000 Z
11
+ date: 2020-10-03 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
@@ -130,6 +130,7 @@ files:
130
130
  - lib/overcommit/hook/pre_commit/credo.rb
131
131
  - lib/overcommit/hook/pre_commit/css_lint.rb
132
132
  - lib/overcommit/hook/pre_commit/dogma.rb
133
+ - lib/overcommit/hook/pre_commit/erb_lint.rb
133
134
  - lib/overcommit/hook/pre_commit/es_lint.rb
134
135
  - lib/overcommit/hook/pre_commit/execute_permissions.rb
135
136
  - lib/overcommit/hook/pre_commit/fasterer.rb
@@ -239,6 +240,8 @@ files:
239
240
  - lib/overcommit/hook_context.rb
240
241
  - lib/overcommit/hook_context/base.rb
241
242
  - lib/overcommit/hook_context/commit_msg.rb
243
+ - lib/overcommit/hook_context/helpers/file_modifications.rb
244
+ - lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb
242
245
  - lib/overcommit/hook_context/post_checkout.rb
243
246
  - lib/overcommit/hook_context/post_commit.rb
244
247
  - lib/overcommit/hook_context/post_merge.rb