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 +4 -4
- data/config/default.yml +16 -4
- data/lib/overcommit/exceptions.rb +19 -16
- data/lib/overcommit/git_repo.rb +10 -9
- data/lib/overcommit/hook/commit_msg/base.rb +1 -1
- data/lib/overcommit/hook/pre_commit/author_name.rb +2 -2
- data/lib/overcommit/hook/pre_commit/erb_lint.rb +20 -0
- data/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb +38 -4
- data/lib/overcommit/hook_context/commit_msg.rb +7 -0
- data/lib/overcommit/hook_context/helpers/file_modifications.rb +78 -0
- data/lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb +141 -0
- data/lib/overcommit/hook_context/pre_commit.rb +5 -199
- data/lib/overcommit/version.rb +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5c35b43073b67da5cd0f8218c5ffd914b0601f1aac6d5ac9263ec3945893259
|
4
|
+
data.tar.gz: 9c08bf2a7153993c48e3d15f30dee2c846edbb5f8500f89a41e027ec80ffb767
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6cdcab7f4b31feed71b3f584f1e025a930d9b767e4e9acf00562ed57fac38442381454f57758bd967a657c9f966842f44212b53e2b482fe50613723590383d02
|
7
|
+
data.tar.gz: 6d763e1d1eca286dd9c8861ee265bd8b3ed29e740d10d430e82c461ae64b313ef2057b11db654c0f694c21dcefcf187b8e7ea27bfbf1a16eba3d6f47776b4772
|
data/config/default.yml
CHANGED
@@ -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:
|
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
|
1247
|
+
branch_pattern: '\A(\d+)-(\w+).*\z'
|
1241
1248
|
replacement_text: '[#\1]'
|
1242
|
-
skipped_commit_types:
|
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 <
|
8
|
+
class ConfigurationError < Error; end
|
6
9
|
|
7
10
|
# Raised when the Overcommit configuration file signature has changed.
|
8
|
-
class ConfigurationSignatureChanged <
|
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 <
|
14
|
+
class GitConfigError < Error; end
|
12
15
|
|
13
16
|
# Raised when there was a problem reading submodule information for a repo.
|
14
|
-
class GitSubmoduleError <
|
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 <
|
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 <
|
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 <
|
26
|
+
class HookCleanupFailed < Error; end
|
24
27
|
|
25
28
|
# Raised when a hook run was cancelled by the user.
|
26
|
-
class HookCancelled <
|
29
|
+
class HookCancelled < Error; end
|
27
30
|
|
28
31
|
# Raised when a hook could not be loaded by a {HookRunner}.
|
29
|
-
class HookLoadError <
|
32
|
+
class HookLoadError < Error; end
|
30
33
|
|
31
34
|
# Raised when a {HookRunner} could not be loaded.
|
32
|
-
class HookContextLoadError <
|
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 <
|
39
|
+
class InvalidCommandArgs < Error; end
|
37
40
|
|
38
41
|
# Raised when an installation target is not a valid git repository.
|
39
|
-
class InvalidGitRepo <
|
42
|
+
class InvalidGitRepo < Error; end
|
40
43
|
|
41
44
|
# Raised when a hook was defined incorrectly.
|
42
|
-
class InvalidHookDefinition <
|
45
|
+
class InvalidHookDefinition < Error; end
|
43
46
|
|
44
47
|
# Raised when one or more hook plugin signatures have changed.
|
45
|
-
class InvalidHookSignature <
|
48
|
+
class InvalidHookSignature < Error; end
|
46
49
|
|
47
50
|
# Raised when there is a problem processing output into {Hook::Messages}s.
|
48
|
-
class MessageProcessingError <
|
51
|
+
class MessageProcessingError < Error; end
|
49
52
|
|
50
53
|
# Raised when an installation target already contains non-Overcommit hooks.
|
51
|
-
class PreExistingHooks <
|
54
|
+
class PreExistingHooks < Error; end
|
52
55
|
end
|
data/lib/overcommit/git_repo.rb
CHANGED
@@ -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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
@@ -12,9 +12,9 @@ module Overcommit::Hook::PreCommit
|
|
12
12
|
result.stdout.chomp
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
if name.empty?
|
16
16
|
return :fail,
|
17
|
-
"Author must
|
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
|
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}
|
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 ||=
|
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? ?
|
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
|
13
|
-
|
14
|
-
|
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
|
data/lib/overcommit/version.rb
CHANGED
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.
|
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-
|
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: '
|
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: '
|
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
|