overcommit 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/overcommit +3 -4
- data/config/default.yml +139 -0
- data/lib/overcommit.rb +7 -5
- data/lib/overcommit/cli.rb +59 -64
- data/lib/overcommit/configuration.rb +108 -34
- data/lib/overcommit/configuration_loader.rb +47 -0
- data/lib/overcommit/constants.rb +7 -0
- data/lib/overcommit/exceptions.rb +16 -0
- data/lib/overcommit/hook/base.rb +91 -0
- data/lib/overcommit/hook/commit_msg/base.rb +11 -0
- data/lib/overcommit/hook/commit_msg/gerrit_change_id.rb +18 -0
- data/lib/overcommit/{plugins → hook}/commit_msg/hard_tabs.rb +5 -6
- data/lib/overcommit/hook/commit_msg/russian_novel.rb +14 -0
- data/lib/overcommit/hook/commit_msg/single_line_subject.rb +12 -0
- data/lib/overcommit/hook/commit_msg/text_width.rb +20 -0
- data/lib/overcommit/hook/commit_msg/trailing_period.rb +12 -0
- data/lib/overcommit/hook/post_checkout/base.rb +11 -0
- data/lib/overcommit/hook/post_checkout/bundle_check.rb +29 -0
- data/lib/overcommit/hook/post_checkout/index_tags.rb +24 -0
- data/lib/overcommit/hook/pre_commit/author_email.rb +17 -0
- data/lib/overcommit/hook/pre_commit/author_name.rb +17 -0
- data/lib/overcommit/hook/pre_commit/base.rb +10 -0
- data/lib/overcommit/hook/pre_commit/bundle_check.rb +30 -0
- data/lib/overcommit/hook/pre_commit/coffee_lint.rb +14 -0
- data/lib/overcommit/hook/pre_commit/css_lint.rb +16 -0
- data/lib/overcommit/hook/pre_commit/haml_lint.rb +26 -0
- data/lib/overcommit/hook/pre_commit/hard_tabs.rb +16 -0
- data/lib/overcommit/hook/pre_commit/image_optim.rb +41 -0
- data/lib/overcommit/hook/pre_commit/js_hint.rb +15 -0
- data/lib/overcommit/hook/pre_commit/jscs.rb +31 -0
- data/lib/overcommit/hook/pre_commit/python_flake8.rb +14 -0
- data/lib/overcommit/hook/pre_commit/rubocop.rb +26 -0
- data/lib/overcommit/hook/pre_commit/scss_lint.rb +26 -0
- data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +15 -0
- data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +22 -0
- data/lib/overcommit/hook_context.rb +16 -0
- data/lib/overcommit/hook_context/base.rb +68 -0
- data/lib/overcommit/hook_context/commit_msg.rb +32 -0
- data/lib/overcommit/hook_context/post_checkout.rb +24 -0
- data/lib/overcommit/hook_context/pre_commit.rb +96 -0
- data/lib/overcommit/hook_runner.rb +150 -0
- data/lib/overcommit/installer.rb +61 -68
- data/lib/overcommit/logger.rb +16 -13
- data/lib/overcommit/utils.rb +63 -38
- data/lib/overcommit/version.rb +1 -1
- data/{bin/scripts → libexec}/gerrit-change-id +0 -0
- data/{bin/scripts → libexec}/index-tags +1 -3
- data/template-dir/hooks/commit-msg +83 -0
- data/template-dir/hooks/overcommit-hook +83 -0
- data/template-dir/hooks/post-checkout +83 -0
- data/template-dir/hooks/pre-commit +83 -0
- metadata +76 -57
- data/bin/hooks/commit-msg +0 -8
- data/bin/hooks/post-checkout +0 -9
- data/bin/hooks/post-merge +0 -23
- data/bin/hooks/pre-commit +0 -8
- data/bin/hooks/prepare-commit-msg +0 -159
- data/bin/run-hook +0 -8
- data/bin/scripts/check-gemfile +0 -9
- data/bin/scripts/csslint-rhino.js +0 -9080
- data/bin/scripts/jshint.js +0 -5921
- data/bin/scripts/jshint_runner.js +0 -42
- data/lib/overcommit/errors.rb +0 -3
- data/lib/overcommit/git_hook.rb +0 -89
- data/lib/overcommit/hook_specific_check.rb +0 -110
- data/lib/overcommit/hooks/commit_msg.rb +0 -7
- data/lib/overcommit/hooks/pre_commit.rb +0 -9
- data/lib/overcommit/plugins/commit_msg/change_id.rb +0 -15
- data/lib/overcommit/plugins/commit_msg/release_note.rb +0 -25
- data/lib/overcommit/plugins/commit_msg/russian_novel.rb +0 -16
- data/lib/overcommit/plugins/commit_msg/single_line_subject.rb +0 -13
- data/lib/overcommit/plugins/commit_msg/text_width.rb +0 -20
- data/lib/overcommit/plugins/commit_msg/trailing_period.rb +0 -13
- data/lib/overcommit/plugins/pre_commit/author_name.rb +0 -16
- data/lib/overcommit/plugins/pre_commit/causes_email.rb +0 -15
- data/lib/overcommit/plugins/pre_commit/coffee_lint.rb +0 -16
- data/lib/overcommit/plugins/pre_commit/css_linter.rb +0 -17
- data/lib/overcommit/plugins/pre_commit/haml_style.rb +0 -34
- data/lib/overcommit/plugins/pre_commit/haml_syntax.rb +0 -24
- data/lib/overcommit/plugins/pre_commit/image_optimization.rb +0 -50
- data/lib/overcommit/plugins/pre_commit/js_console_log.rb +0 -16
- data/lib/overcommit/plugins/pre_commit/js_syntax.rb +0 -30
- data/lib/overcommit/plugins/pre_commit/python_flake8.rb +0 -15
- data/lib/overcommit/plugins/pre_commit/ruby_style.rb +0 -67
- data/lib/overcommit/plugins/pre_commit/ruby_syntax.rb +0 -19
- data/lib/overcommit/plugins/pre_commit/scss_lint.rb +0 -66
- data/lib/overcommit/plugins/pre_commit/test_history.rb +0 -58
- data/lib/overcommit/plugins/pre_commit/whitespace.rb +0 -21
- data/lib/overcommit/plugins/pre_commit/yaml_syntax.rb +0 -22
- data/lib/overcommit/reporter.rb +0 -90
- data/lib/overcommit/staged_file.rb +0 -86
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Overcommit
|
4
|
+
# Manages configuration file loading.
|
5
|
+
class ConfigurationLoader
|
6
|
+
DEFAULT_CONFIG_PATH = File.join(OVERCOMMIT_HOME, 'config', 'default.yml')
|
7
|
+
FILE_NAME = '.overcommit.yml'
|
8
|
+
|
9
|
+
def self.load_repo_config
|
10
|
+
overcommit_yml = File.join(Overcommit::Utils.repo_root, FILE_NAME)
|
11
|
+
|
12
|
+
if File.exists?(overcommit_yml)
|
13
|
+
load_file(overcommit_yml)
|
14
|
+
else
|
15
|
+
default_configuration
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.default_configuration
|
20
|
+
@default_config ||= load_from_file(DEFAULT_CONFIG_PATH)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Loads a configuration, ensuring it extends the default configuration.
|
26
|
+
def self.load_file(file)
|
27
|
+
config = load_from_file(file)
|
28
|
+
|
29
|
+
default_configuration.merge(config)
|
30
|
+
rescue => error
|
31
|
+
raise Overcommit::Exceptions::ConfigurationError,
|
32
|
+
"Unable to load configuration from '#{file}': #{error}",
|
33
|
+
error.backtrace
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.load_from_file(file)
|
37
|
+
hash =
|
38
|
+
if yaml = YAML.load_file(file)
|
39
|
+
yaml.to_hash
|
40
|
+
else
|
41
|
+
{}
|
42
|
+
end
|
43
|
+
|
44
|
+
Overcommit::Configuration.new(hash)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Overcommit::Exceptions
|
2
|
+
# Raised when a {Configuration} could not be loaded from a file.
|
3
|
+
class ConfigurationError < StandardError; end
|
4
|
+
|
5
|
+
# Raised when a hook could not be loaded by a {HookRunner}.
|
6
|
+
class HookLoadError < StandardError; end
|
7
|
+
|
8
|
+
# Raised when a {HookRunner} could not be loaded.
|
9
|
+
class HookContextLoadError < StandardError; end
|
10
|
+
|
11
|
+
# Raised when an installation target is not a valid git repository.
|
12
|
+
class InvalidGitRepo < StandardError; end
|
13
|
+
|
14
|
+
# Raised when an installation target already contains non-Overcommit hooks.
|
15
|
+
class PreExistingHooks < StandardError; end
|
16
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Overcommit::Hook
|
4
|
+
# Functionality common to all hooks.
|
5
|
+
class Base
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@context, :modified_files
|
9
|
+
|
10
|
+
def initialize(config, context)
|
11
|
+
@config = config.for_hook(self)
|
12
|
+
@context = context
|
13
|
+
end
|
14
|
+
|
15
|
+
# Runs the hook.
|
16
|
+
def run
|
17
|
+
raise NotImplementedError, 'Hook must define `run`'
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
self.class.name.split('::').last
|
22
|
+
end
|
23
|
+
|
24
|
+
def description
|
25
|
+
@config['description'] || "Running #{name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def required?
|
29
|
+
@config['required']
|
30
|
+
end
|
31
|
+
|
32
|
+
def quiet?
|
33
|
+
@config['quiet']
|
34
|
+
end
|
35
|
+
|
36
|
+
def enabled?
|
37
|
+
@config['enabled'] != false
|
38
|
+
end
|
39
|
+
|
40
|
+
def skip?
|
41
|
+
@config['skip']
|
42
|
+
end
|
43
|
+
|
44
|
+
def run?
|
45
|
+
enabled? &&
|
46
|
+
(!skip? || required?) &&
|
47
|
+
!(requires_modified_files? && applicable_files.empty?)
|
48
|
+
end
|
49
|
+
|
50
|
+
def in_path?(cmd)
|
51
|
+
Overcommit::Utils.in_path?(cmd)
|
52
|
+
end
|
53
|
+
|
54
|
+
def command(cmd)
|
55
|
+
Overcommit::Utils.command(cmd)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Gets a list of staged files that apply to this hook based on its
|
59
|
+
# configured `include` and `exclude` lists.
|
60
|
+
def applicable_files
|
61
|
+
@applicable_files ||= modified_files.select { |file| applicable_file?(file) }
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def requires_modified_files?
|
67
|
+
@config['requires_files']
|
68
|
+
end
|
69
|
+
|
70
|
+
def applicable_file?(file)
|
71
|
+
includes = Array(@config['include']).map { |glob| convert_glob_to_absolute(glob) }
|
72
|
+
included = includes.empty? ||
|
73
|
+
includes.any? { |glob| File.fnmatch(glob, file) }
|
74
|
+
|
75
|
+
excludes = Array(@config['exclude']).map { |glob| convert_glob_to_absolute(glob) }
|
76
|
+
excluded = excludes.any? { |glob| File.fnmatch(glob, file) }
|
77
|
+
|
78
|
+
included && !excluded
|
79
|
+
end
|
80
|
+
|
81
|
+
def convert_glob_to_absolute(glob)
|
82
|
+
repo_root = Overcommit::Utils.repo_root
|
83
|
+
|
84
|
+
if glob.start_with?('**')
|
85
|
+
repo_root + glob # Want ** to match items in the repo root as well
|
86
|
+
else
|
87
|
+
File.join(repo_root, glob)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Overcommit::Hook::CommitMsg
|
4
|
+
# Functionality common to all commit-msg hooks.
|
5
|
+
class Base < Overcommit::Hook::Base
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@context, :commit_message, :update_commit_message,
|
9
|
+
:commit_message_lines, :commit_message_file
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Overcommit::Hook::CommitMsg
|
2
|
+
# Ensures a Gerrit Change-Id line is included in the commit message.
|
3
|
+
#
|
4
|
+
# It may seem odd to do this here instead of in a prepare-commit-msg hook, but
|
5
|
+
# the reality is that if you want to _ensure_ the Change-Id is included then
|
6
|
+
# you need to do it in a commit-msg hook. This is because the user could still
|
7
|
+
# edit the message after a prepare-commit-msg hook was run.
|
8
|
+
class GerritChangeId < Base
|
9
|
+
def run
|
10
|
+
result = command("#{SCRIPT_LOCATION} #{commit_message_file}")
|
11
|
+
return (result.success? ? :good : :bad), result.stdout
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
SCRIPT_LOCATION = Overcommit::Utils.script_path('gerrit-change-id')
|
17
|
+
end
|
18
|
+
end
|
@@ -1,10 +1,9 @@
|
|
1
|
-
module Overcommit::
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def run_check
|
1
|
+
module Overcommit::Hook::CommitMsg
|
2
|
+
# Checks for hard tabs in commit messages.
|
3
|
+
class HardTabs < Base
|
4
|
+
def run
|
6
5
|
# Catches hard tabs entered by the user (not auto-generated)
|
7
|
-
if commit_message.
|
6
|
+
if commit_message.index(/\t/)
|
8
7
|
return :warn, "Don't use hard tabs in commit messages"
|
9
8
|
end
|
10
9
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Overcommit::Hook::CommitMsg
|
2
|
+
# Checks for long commit messages (not good or bad--just fun to point out)
|
3
|
+
class RussianNovel < Base
|
4
|
+
RUSSIAN_NOVEL_LENGTH = 30
|
5
|
+
|
6
|
+
def run
|
7
|
+
if commit_message_lines.length >= RUSSIAN_NOVEL_LENGTH
|
8
|
+
return :warn, 'You seem to have authored a Russian novel; congratulations!'
|
9
|
+
end
|
10
|
+
|
11
|
+
:good
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Overcommit::Hook::CommitMsg
|
2
|
+
# Ensures commit message subject lines are followed by a blank line.
|
3
|
+
class SingleLineSubject < Base
|
4
|
+
def run
|
5
|
+
unless commit_message_lines[1].to_s.strip.empty?
|
6
|
+
return :warn, 'Subject should be one line and followed by a blank line'
|
7
|
+
end
|
8
|
+
|
9
|
+
:good
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Overcommit::Hook::CommitMsg
|
2
|
+
# Ensures the number of columns the subject and commit message lines occupy is
|
3
|
+
# under the preferred limits.
|
4
|
+
class TextWidth < Base
|
5
|
+
def run
|
6
|
+
if commit_message_lines.first.size > 60
|
7
|
+
return :warn, 'Please keep the subject < ~60 characters'
|
8
|
+
end
|
9
|
+
|
10
|
+
commit_message_lines.each do |line|
|
11
|
+
chomped = line.chomp
|
12
|
+
if chomped.size > 72
|
13
|
+
return :warn, "> 72 characters, please hard wrap: '#{chomped}'"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
:good
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Overcommit::Hook::CommitMsg
|
2
|
+
# Ensures commit message subject lines do not have a trailing period
|
3
|
+
class TrailingPeriod < Base
|
4
|
+
def run
|
5
|
+
if commit_message_lines.first.rstrip.end_with?('.')
|
6
|
+
return :warn, 'Please omit trailing period from commit message subject'
|
7
|
+
end
|
8
|
+
|
9
|
+
:good
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Overcommit::Hook::PostCheckout
|
4
|
+
# Functionality common to all post-checkout hooks.
|
5
|
+
class Base < Overcommit::Hook::Base
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@context,
|
9
|
+
:previous_head, :new_head, :branch_checkout?, :file_checkout?
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Overcommit::Hook::PostCheckout
|
2
|
+
# If Gemfile dependencies were modified since HEAD was changed, check if
|
3
|
+
# currently installed gems satisfy the dependencies.
|
4
|
+
class BundleCheck < Base
|
5
|
+
def run
|
6
|
+
unless in_path?('bundle')
|
7
|
+
return :warn, 'bundler not installed -- run `gem install bundler`'
|
8
|
+
end
|
9
|
+
|
10
|
+
return :bad if dependencies_changed? && !dependencies_satisfied?
|
11
|
+
|
12
|
+
:good
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def dependencies_changed?
|
18
|
+
result = command("git diff --exit-code #{new_head} #{previous_head} --name-only")
|
19
|
+
|
20
|
+
result.stdout.split("\n").any? do |file|
|
21
|
+
Array(@config['include']).any? { |glob| File.fnmatch(glob, file) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def dependencies_satisfied?
|
26
|
+
command('bundle check').success?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Overcommit::Hook::PostCheckout
|
2
|
+
# Scans source code each time HEAD changes to generate an up-to-date index of
|
3
|
+
# all function/variable definitions, etc.
|
4
|
+
class IndexTags < Base
|
5
|
+
def run
|
6
|
+
unless in_path?('ctags')
|
7
|
+
return :good # Silently ignore
|
8
|
+
end
|
9
|
+
|
10
|
+
index_tags_in_background
|
11
|
+
|
12
|
+
:good
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
SCRIPT_LOCATION = Overcommit::Utils.script_path('index-tags')
|
18
|
+
|
19
|
+
def index_tags_in_background
|
20
|
+
# TODO: come up with Ruby 1.8-friendly way to do this
|
21
|
+
Process.detach(Process.spawn(SCRIPT_LOCATION))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Overcommit::Hook::PreCommit
|
2
|
+
# Checks the format of an author's email address.
|
3
|
+
class AuthorEmail < Base
|
4
|
+
def run
|
5
|
+
result = command('git config --get user.email')
|
6
|
+
email = result.stdout.chomp
|
7
|
+
|
8
|
+
unless email =~ /#{@config['pattern']}/
|
9
|
+
return :bad, "Author has an invalid email address: '#{email}'\n" <<
|
10
|
+
'Set your email with ' <<
|
11
|
+
'`git config --global user.email your_email@example.com`'
|
12
|
+
end
|
13
|
+
|
14
|
+
:good
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Overcommit::Hook::PreCommit
|
2
|
+
# Ensures that a commit author has a name with at least first and last names.
|
3
|
+
class AuthorName < Base
|
4
|
+
def run
|
5
|
+
result = command('git config --get user.name')
|
6
|
+
name = result.stdout.chomp
|
7
|
+
|
8
|
+
unless name.split(' ').count >= 2
|
9
|
+
return :bad, 'Author must have at least first and last name, but ' <<
|
10
|
+
"was: '#{name}'.\nSet your name with " <<
|
11
|
+
"`git config --global user.name 'Your Name'`"
|
12
|
+
end
|
13
|
+
|
14
|
+
:good
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Overcommit::Hook::PreCommit
|
2
|
+
# Check if local Gemfile.lock matches Gemfile when either changes, unless
|
3
|
+
# Gemfile.lock is ignored by git.
|
4
|
+
class BundleCheck < Base
|
5
|
+
def run
|
6
|
+
unless in_path?('bundle')
|
7
|
+
return :warn, 'bundler not installed -- run `gem install bundler`'
|
8
|
+
end
|
9
|
+
|
10
|
+
# Ignore if Gemfile.lock is not tracked by git
|
11
|
+
return :good if command("git check-ignore #{LOCK_FILE}").success?
|
12
|
+
|
13
|
+
result = command('bundle check')
|
14
|
+
unless result.success?
|
15
|
+
return :bad, result.stdout
|
16
|
+
end
|
17
|
+
|
18
|
+
result = command("git diff --quiet -- #{LOCK_FILE}")
|
19
|
+
unless result.success?
|
20
|
+
return :bad, "#{LOCK_FILE} is not up-to-date -- run `bundle check`"
|
21
|
+
end
|
22
|
+
|
23
|
+
:good
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
LOCK_FILE = 'Gemfile.lock'
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Overcommit::Hook::PreCommit
|
2
|
+
# Runs `coffeelint` against any modified CoffeeScript files.
|
3
|
+
class CoffeeLint < Base
|
4
|
+
def run
|
5
|
+
unless in_path?('coffeelint')
|
6
|
+
return :warn, 'Run `npm install -g coffeelint`'
|
7
|
+
end
|
8
|
+
|
9
|
+
result = command("coffeelint --quiet #{applicable_files.join(' ')}")
|
10
|
+
return :good if result.success?
|
11
|
+
return :bad, result.stdout
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|