overcommit 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/default.yml +38 -0
- data/config/starter.yml +2 -1
- data/lib/overcommit/cli.rb +2 -0
- data/lib/overcommit/command_splitter.rb +144 -0
- data/lib/overcommit/configuration.rb +45 -10
- data/lib/overcommit/exceptions.rb +3 -0
- data/lib/overcommit/git_repo.rb +8 -0
- data/lib/overcommit/hook/base.rb +16 -2
- data/lib/overcommit/hook/post_rewrite/base.rb +1 -1
- data/lib/overcommit/hook/pre_commit/case_conflicts.rb +1 -1
- data/lib/overcommit/hook/pre_commit/hard_tabs.rb +1 -1
- data/lib/overcommit/hook/pre_commit/html_hint.rb +21 -0
- data/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb +1 -1
- data/lib/overcommit/hook/pre_commit/merge_conflicts.rb +1 -1
- data/lib/overcommit/hook/pre_commit/semi_standard.rb +2 -2
- data/lib/overcommit/hook/pre_commit/standard.rb +2 -2
- data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +1 -1
- data/lib/overcommit/hook_context/base.rb +43 -1
- data/lib/overcommit/hook_context/commit_msg.rb +2 -2
- data/lib/overcommit/hook_context/post_checkout.rb +8 -0
- data/lib/overcommit/hook_context/post_rewrite.rb +29 -0
- data/lib/overcommit/hook_context/pre_commit.rb +1 -14
- data/lib/overcommit/hook_loader/plugin_hook_loader.rb +42 -3
- data/lib/overcommit/hook_runner.rb +12 -0
- data/lib/overcommit/hook_signer.rb +41 -6
- data/lib/overcommit/logger.rb +5 -0
- data/lib/overcommit/printer.rb +1 -1
- data/lib/overcommit/subprocess.rb +22 -1
- data/lib/overcommit/utils.rb +58 -4
- data/lib/overcommit/version.rb +1 -1
- data/template-dir/hooks/commit-msg +24 -4
- data/template-dir/hooks/overcommit-hook +24 -4
- data/template-dir/hooks/post-checkout +24 -4
- data/template-dir/hooks/post-commit +24 -4
- data/template-dir/hooks/post-merge +24 -4
- data/template-dir/hooks/post-rewrite +24 -4
- data/template-dir/hooks/pre-commit +24 -4
- data/template-dir/hooks/pre-push +24 -4
- data/template-dir/hooks/pre-rebase +24 -4
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b15d613ceeafdf7cf3c762c611b309b596bf9fd9
|
4
|
+
data.tar.gz: 89d01be1ecf271fc7f8f6b6472fb6e056e8980c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a665c68e8e3066f1f4f2b62997474b20f910360a7aadf2b24d5c2caad64260a3c2f485ffbd9174b7681fbd24a743a07a97820a2c4fed38e5d54c00c95ceccd8
|
7
|
+
data.tar.gz: a570dcdcf2ac15e352f837ebb404819f197d74428881586f2c2eec1f6a4f4eebfbf5e4c80536cb38fe2a6ca941b5de129357ae0437c8b739a747738a4f2d1baf
|
data/config/default.yml
CHANGED
@@ -2,6 +2,37 @@
|
|
2
2
|
#
|
3
3
|
# This is an opinionated list of which hooks are valuable to run and what their
|
4
4
|
# out-of-the-box settings should be.
|
5
|
+
#-------------------------------------------------------------------------------
|
6
|
+
|
7
|
+
# Loads Bundler context from a Gemfile. If false, does nothing (default).
|
8
|
+
#
|
9
|
+
# Specifying a Gemfile for Bundler to load allows you to control which gems are
|
10
|
+
# available in the load path (i.e. loadable via `require`) within your hook
|
11
|
+
# runs. Note that having a Gemfile requires you to include `overcommit` itself
|
12
|
+
# in your Gemfile (otherwise Overcommit can't load itself!).
|
13
|
+
#
|
14
|
+
# This is useful if you want to:
|
15
|
+
#
|
16
|
+
# - Enforce a specific version of Overcommit to use for all hook runs
|
17
|
+
# (or to use a version from the master branch that has not been released yet)
|
18
|
+
# - Enforce a specific version or unreleased branch is used for a gem you want
|
19
|
+
# to use in your git hooks
|
20
|
+
#
|
21
|
+
# WARNING: This makes your hook runs slower, but you can work around this!
|
22
|
+
#
|
23
|
+
# Loading a Bundler context necessarily adds a startup delay to your hook runs
|
24
|
+
# as Bundler parses the Gemfile and checks that the dependencies are satisfied.
|
25
|
+
# Thus for projects with many gems this can introduce a noticeable delay.
|
26
|
+
#
|
27
|
+
# The recommended workaround is to create a separate Gemfile in the root of your
|
28
|
+
# repository (call it `.overcommit_gems.rb`), and include only the gems that
|
29
|
+
# your Overcommit hooks need in order to run. This significantly reduces the
|
30
|
+
# startup delay in your hook runs. Make sure to commit both
|
31
|
+
# `.overcommit_gems.rb` and the resulting `.overcommit_gems.rb.lock` file to
|
32
|
+
# your repository, and then set the `gemfile` option below to the name you gave
|
33
|
+
# the file.
|
34
|
+
# (Generate lock file by running `bundle install --gemfile=.overcommit_gems.rb`)
|
35
|
+
gemfile: false
|
5
36
|
|
6
37
|
# Where to store hook plugins specific to a repository. These are loaded in
|
7
38
|
# addition to the default hooks Overcommit comes with. The location is relative
|
@@ -195,6 +226,13 @@ PreCommit:
|
|
195
226
|
required_executable: 'grep'
|
196
227
|
flags: ['-IHn', "\t"]
|
197
228
|
|
229
|
+
HtmlHint:
|
230
|
+
enabled: false
|
231
|
+
description: 'Analyzing with HTMLHint'
|
232
|
+
required_executable: 'htmlhint'
|
233
|
+
install_command: 'npm install -g htmlhint'
|
234
|
+
include: '**/*.html'
|
235
|
+
|
198
236
|
HtmlTidy:
|
199
237
|
enabled: false
|
200
238
|
description: 'Analyzing HTML with tidy'
|
data/config/starter.yml
CHANGED
@@ -17,6 +17,7 @@
|
|
17
17
|
|
18
18
|
#PreCommit:
|
19
19
|
# RuboCop:
|
20
|
+
# enabled: true
|
20
21
|
# on_warn: fail # Treat all warnings as failures
|
21
22
|
#
|
22
23
|
# TrailingWhitespace:
|
@@ -25,7 +26,7 @@
|
|
25
26
|
#
|
26
27
|
#PostCheckout:
|
27
28
|
# ALL: # Special hook name that customizes all hooks of this type
|
28
|
-
#
|
29
|
+
# quiet: true # Change all post-checkout hooks to only display output on failure
|
29
30
|
#
|
30
31
|
# IndexTags:
|
31
32
|
# enabled: true # Generate a tags file with `ctags` each time HEAD changes
|
data/lib/overcommit/cli.rb
CHANGED
@@ -0,0 +1,144 @@
|
|
1
|
+
module Overcommit
|
2
|
+
# Distributes a list of arguments over multiple invocations of a command.
|
3
|
+
#
|
4
|
+
# This accomplishes the same functionality provided by `xargs` but in a
|
5
|
+
# cross-platform way that does not require any pre-existing tools.
|
6
|
+
#
|
7
|
+
# One of the tradeoffs with this approach is that we no longer deal with a
|
8
|
+
# single exit status from a command, but multiple (one for each invocation).
|
9
|
+
#
|
10
|
+
# This will return a struct similar to `Subprocess::Result` but with
|
11
|
+
# additional `statuses`, `stdouts`, and `stderrs` fields so hook authors can
|
12
|
+
# actually see the results of each invocation. If they don't care, the
|
13
|
+
# standard `status`, `stdout`, and `stderr` will still work but be a
|
14
|
+
# aggregation/concatenation of all statuses/outputs.
|
15
|
+
class CommandSplitter
|
16
|
+
# Encapsulates the result of a split argument run.
|
17
|
+
#
|
18
|
+
# @attr_reader statuses [Array<Integer>] status codes for invocations
|
19
|
+
# @attr_reader stdouts [Array<String>] standard outputs from invocations
|
20
|
+
# @attr_reader stderrs [Array<String>] standard error outputs from invocations
|
21
|
+
Result = Struct.new(:statuses, :stdouts, :stderrs) do
|
22
|
+
# Returns whether all invocations were successful.
|
23
|
+
#
|
24
|
+
# @return [true,false]
|
25
|
+
def success?
|
26
|
+
status == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns `0` if all invocations returned `0`; `1` otherwise.
|
30
|
+
#
|
31
|
+
# @return [true,false]
|
32
|
+
def status
|
33
|
+
statuses.all? { |code| code == 0 } ? 0 : 1
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns concatenated standard output streams of all invocations in the
|
37
|
+
# order they were executed.
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
def stdout
|
41
|
+
stdouts.join
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns concatenated standard error streams of all invocations in the
|
45
|
+
# order they were executed.
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
def stderr
|
49
|
+
stderrs.join
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def execute(initial_args, options)
|
55
|
+
options = options.dup
|
56
|
+
|
57
|
+
if (splittable_args = (options.delete(:args) { [] })).empty?
|
58
|
+
raise Overcommit::Exceptions::InvalidCommandArgs,
|
59
|
+
'Must specify list of arguments to split on'
|
60
|
+
end
|
61
|
+
|
62
|
+
# Execute each chunk of arguments in serial. We don't parallelize (yet)
|
63
|
+
# since in theory we want to support parallelization at the hook level
|
64
|
+
# and not within individual hooks.
|
65
|
+
results = extract_argument_lists(initial_args, splittable_args).map do |arg_list|
|
66
|
+
Overcommit::Subprocess.spawn(arg_list, options)
|
67
|
+
end
|
68
|
+
|
69
|
+
Result.new(results.map(&:status), results.map(&:stdout), results.map(&:stderr))
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Given a list of prefix arguments and suffix arguments that can be split,
|
75
|
+
# returns a list of argument lists that are executable on the current OS
|
76
|
+
# without exceeding command line limitations.
|
77
|
+
def extract_argument_lists(args, splittable_args)
|
78
|
+
# Total number of bytes needed to contain the prefix command
|
79
|
+
# (including byte separators between each argument)
|
80
|
+
prefix_bytes = (args.size - 1) + args.reduce(0) { |sum, arg| sum + arg.bytesize }
|
81
|
+
|
82
|
+
if prefix_bytes >= max_command_length
|
83
|
+
raise Overcommit::Exceptions::InvalidCommandArgs,
|
84
|
+
"Command `#{args.take(5).join(' ')} ...` is longer than the " \
|
85
|
+
'maximum number of bytes allowed by the operating system ' \
|
86
|
+
"(#{max_command_length})"
|
87
|
+
end
|
88
|
+
|
89
|
+
arg_lists = []
|
90
|
+
index = 0
|
91
|
+
while index <= splittable_args.length - 1
|
92
|
+
arg_list, index = arguments_under_limit(splittable_args,
|
93
|
+
index,
|
94
|
+
max_command_length - prefix_bytes)
|
95
|
+
arg_lists << args + arg_list
|
96
|
+
end
|
97
|
+
|
98
|
+
arg_lists
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Array<Array<String>, Integer>] tuple of arguments and new index
|
102
|
+
def arguments_under_limit(splittable_args, start_index, byte_limit)
|
103
|
+
index = start_index
|
104
|
+
total_bytes = 0
|
105
|
+
|
106
|
+
loop do
|
107
|
+
break if index > splittable_args.length - 1
|
108
|
+
total_bytes += splittable_args[index].bytesize
|
109
|
+
break if total_bytes > byte_limit # Not enough room
|
110
|
+
index += 1
|
111
|
+
end
|
112
|
+
|
113
|
+
if index == start_index
|
114
|
+
# No argument was consumed; perhaps a really long argument?
|
115
|
+
raise Overcommit::Exceptions::InvalidCommandArgs,
|
116
|
+
"Argument `#{splittable_args[index][0..5]}...` exceeds the " \
|
117
|
+
'maximum command length when appended to command prefix and ' \
|
118
|
+
"can't be split further"
|
119
|
+
end
|
120
|
+
|
121
|
+
[splittable_args[start_index...index], index]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns the maximum number of arguments allowed in a single command on
|
125
|
+
# this system.
|
126
|
+
#
|
127
|
+
# @return [Integer]
|
128
|
+
def max_command_length
|
129
|
+
@max_command_length ||=
|
130
|
+
if Gem.win_platform?
|
131
|
+
# Windows is limited to 2048 since that is a worst-case scenario.
|
132
|
+
# http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx
|
133
|
+
2048
|
134
|
+
else
|
135
|
+
# We fudge factor this by halving the buffer size since *nix systems
|
136
|
+
# usually have pretty large limits, and the actual limit changes
|
137
|
+
# depending on how much of your stack is environment variables.
|
138
|
+
# Definitely erring on the side of overly cautious.
|
139
|
+
`getconf ARG_MAX`.to_i / 2
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -18,10 +18,18 @@ module Overcommit
|
|
18
18
|
end
|
19
19
|
alias_method :eql?, :==
|
20
20
|
|
21
|
+
# Access the configuration as if it were a hash.
|
22
|
+
#
|
23
|
+
# @param key [String]
|
24
|
+
# @return [Array,Hash,Number,String]
|
25
|
+
def [](key)
|
26
|
+
@hash[key]
|
27
|
+
end
|
28
|
+
|
21
29
|
# Returns absolute path to the directory that external hook plugins should
|
22
30
|
# be loaded from.
|
23
31
|
def plugin_directory
|
24
|
-
File.join(Overcommit::Utils.repo_root, @hash['plugin_directory'] || '.
|
32
|
+
File.join(Overcommit::Utils.repo_root, @hash['plugin_directory'] || '.git-hooks')
|
25
33
|
end
|
26
34
|
|
27
35
|
def verify_plugin_signatures?
|
@@ -88,6 +96,14 @@ module Overcommit
|
|
88
96
|
select { |hook_name| hook_enabled?(hook_context, hook_name) }
|
89
97
|
end
|
90
98
|
|
99
|
+
# Returns the ad hoc hooks that have been enabled for a hook type.
|
100
|
+
def enabled_ad_hoc_hooks(hook_context)
|
101
|
+
@hash[hook_context.hook_class_name].keys.
|
102
|
+
select { |hook_name| hook_name != 'ALL' }.
|
103
|
+
select { |hook_name| ad_hoc_hook?(hook_context, hook_name) }.
|
104
|
+
select { |hook_name| hook_enabled?(hook_context, hook_name) }
|
105
|
+
end
|
106
|
+
|
91
107
|
# Returns a non-modifiable configuration for a hook.
|
92
108
|
def for_hook(hook, hook_type = nil)
|
93
109
|
unless hook_type
|
@@ -117,17 +133,25 @@ module Overcommit
|
|
117
133
|
# environment variables.
|
118
134
|
def apply_environment!(hook_context, env)
|
119
135
|
skipped_hooks = "#{env['SKIP']} #{env['SKIP_CHECKS']} #{env['SKIP_HOOKS']}".split(/[:, ]/)
|
136
|
+
only_hooks = env.fetch('ONLY', '').split(/[:, ]/)
|
120
137
|
hook_type = hook_context.hook_class_name
|
121
138
|
|
122
|
-
if skipped_hooks.include?('all') || skipped_hooks.include?('ALL')
|
139
|
+
if only_hooks.any? || skipped_hooks.include?('all') || skipped_hooks.include?('ALL')
|
123
140
|
@hash[hook_type]['ALL']['skip'] = true
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
141
|
+
end
|
142
|
+
|
143
|
+
only_hooks.select { |hook_name| hook_exists?(hook_context, hook_name) }.
|
144
|
+
map { |hook_name| Overcommit::Utils.camel_case(hook_name) }.
|
145
|
+
each do |hook_name|
|
146
|
+
@hash[hook_type][hook_name] ||= {}
|
147
|
+
@hash[hook_type][hook_name]['skip'] = false
|
148
|
+
end
|
149
|
+
|
150
|
+
skipped_hooks.select { |hook_name| hook_exists?(hook_context, hook_name) }.
|
151
|
+
map { |hook_name| Overcommit::Utils.camel_case(hook_name) }.
|
152
|
+
each do |hook_name|
|
153
|
+
@hash[hook_type][hook_name] ||= {}
|
154
|
+
@hash[hook_type][hook_name]['skip'] = true
|
131
155
|
end
|
132
156
|
end
|
133
157
|
|
@@ -149,6 +173,16 @@ module Overcommit
|
|
149
173
|
|
150
174
|
private
|
151
175
|
|
176
|
+
def ad_hoc_hook?(hook_context, hook_name)
|
177
|
+
ad_hoc_conf = @hash.fetch(hook_context.hook_class_name, {}).fetch(hook_name, {})
|
178
|
+
|
179
|
+
# Ad hoc hooks are neither built-in nor have a plugin file written but
|
180
|
+
# still have a `command` specified to be run
|
181
|
+
!built_in_hook?(hook_context, hook_name) &&
|
182
|
+
!plugin_hook?(hook_context, hook_name) &&
|
183
|
+
(ad_hoc_conf['command'] || ad_hoc_conf['required_executable'])
|
184
|
+
end
|
185
|
+
|
152
186
|
def built_in_hook?(hook_context, hook_name)
|
153
187
|
hook_name = Overcommit::Utils.snake_case(hook_name)
|
154
188
|
|
@@ -158,7 +192,8 @@ module Overcommit
|
|
158
192
|
|
159
193
|
def hook_exists?(hook_context, hook_name)
|
160
194
|
built_in_hook?(hook_context, hook_name) ||
|
161
|
-
plugin_hook?(hook_context, hook_name)
|
195
|
+
plugin_hook?(hook_context, hook_name) ||
|
196
|
+
ad_hoc_hook?(hook_context, hook_name)
|
162
197
|
end
|
163
198
|
|
164
199
|
def hook_enabled?(hook_context_or_type, hook_name)
|
@@ -30,6 +30,9 @@ module Overcommit::Exceptions
|
|
30
30
|
# Raised when an installation target is not a valid git repository.
|
31
31
|
class InvalidGitRepo < StandardError; end
|
32
32
|
|
33
|
+
# Raised when a hook was defined incorrectly.
|
34
|
+
class InvalidHookDefinition < StandardError; end
|
35
|
+
|
33
36
|
# Raised when one or more hook plugin signatures have changed.
|
34
37
|
class InvalidHookSignature < StandardError; end
|
35
38
|
|
data/lib/overcommit/git_repo.rb
CHANGED
@@ -113,6 +113,14 @@ module Overcommit
|
|
113
113
|
reject { |file| File.directory?(file) } # Exclude submodule directories
|
114
114
|
end
|
115
115
|
|
116
|
+
# Returns whether the specified file/path is tracked by this repository.
|
117
|
+
#
|
118
|
+
# @param path [String]
|
119
|
+
# @return [true,false]
|
120
|
+
def tracked?(path)
|
121
|
+
Overcommit::Utils.execute(%W[git ls-files #{path} --error-unmatch]).success?
|
122
|
+
end
|
123
|
+
|
116
124
|
# Returns the names of all files that are tracked by git.
|
117
125
|
#
|
118
126
|
# @return [Array<String>] list of absolute file paths
|
data/lib/overcommit/hook/base.rb
CHANGED
@@ -82,8 +82,22 @@ module Overcommit::Hook
|
|
82
82
|
Overcommit::Utils.in_path?(cmd)
|
83
83
|
end
|
84
84
|
|
85
|
-
|
86
|
-
|
85
|
+
# Execute a command in a separate process.
|
86
|
+
#
|
87
|
+
# If `splittable_args` is specified, ensures that those arguments are
|
88
|
+
# concatenated onto the end of the `cmd` arguments, but split up so that the
|
89
|
+
# operating system's maximum command length is not exceeded. This is useful
|
90
|
+
# for splitting up long file lists.
|
91
|
+
#
|
92
|
+
# @param cmd [Array<String>] command arguments
|
93
|
+
# @param options [Hash]
|
94
|
+
# @option options [Array<String>] :args arguments that can be split up over
|
95
|
+
# multiple invocations (usually a list of files)
|
96
|
+
# @option options [String] :input string to pass to process' standard input
|
97
|
+
# stream
|
98
|
+
# @return [#status,#stdout,#stderr] struct containing result of invocation
|
99
|
+
def execute(cmd, options = {})
|
100
|
+
Overcommit::Utils.execute(cmd, options)
|
87
101
|
end
|
88
102
|
|
89
103
|
def execute_in_background(cmd)
|
@@ -6,7 +6,7 @@ module Overcommit::Hook::PreCommit
|
|
6
6
|
repo_files = Set.new(applicable_files)
|
7
7
|
|
8
8
|
unless Overcommit::GitRepo.initial_commit?
|
9
|
-
paths = repo_files.map { |file| File.dirname(file) + File::SEPARATOR }
|
9
|
+
paths = repo_files.map { |file| File.dirname(file) + File::SEPARATOR }.uniq
|
10
10
|
repo_files += Overcommit::GitRepo.list_files(paths)
|
11
11
|
end
|
12
12
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Overcommit::Hook::PreCommit
|
2
|
+
# Runs `htmlhint` against any modified HTML files.
|
3
|
+
#
|
4
|
+
# @see http://htmlhint.com/
|
5
|
+
class HtmlHint < Base
|
6
|
+
def run
|
7
|
+
result = execute(command + applicable_files)
|
8
|
+
output = Overcommit::Utils.strip_color_codes(result.stdout.chomp)
|
9
|
+
|
10
|
+
message_groups = output.split("\n\n")[0..-2]
|
11
|
+
message_groups.map do |group|
|
12
|
+
lines = group.split("\n").map(&:strip)
|
13
|
+
file = lines[0][/(.+):/, 1]
|
14
|
+
extract_messages(
|
15
|
+
lines[1..-1].map { |msg| "#{file}: #{msg}" },
|
16
|
+
/^(?<file>[^:]+): line (?<line>\d+)/
|
17
|
+
)
|
18
|
+
end.flatten
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,7 +2,7 @@ module Overcommit::Hook::PreCommit
|
|
2
2
|
# Checks for local paths in files and issues a warning
|
3
3
|
class LocalPathsInGemfile < Base
|
4
4
|
def run
|
5
|
-
result = execute(command
|
5
|
+
result = execute(command, args: applicable_files)
|
6
6
|
|
7
7
|
unless result.stdout.empty?
|
8
8
|
return :warn, "Avoid pointing to local paths in Gemfiles:\n#{result.stdout}"
|