overcommit 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/bin/overcommit +3 -4
  3. data/config/default.yml +139 -0
  4. data/lib/overcommit.rb +7 -5
  5. data/lib/overcommit/cli.rb +59 -64
  6. data/lib/overcommit/configuration.rb +108 -34
  7. data/lib/overcommit/configuration_loader.rb +47 -0
  8. data/lib/overcommit/constants.rb +7 -0
  9. data/lib/overcommit/exceptions.rb +16 -0
  10. data/lib/overcommit/hook/base.rb +91 -0
  11. data/lib/overcommit/hook/commit_msg/base.rb +11 -0
  12. data/lib/overcommit/hook/commit_msg/gerrit_change_id.rb +18 -0
  13. data/lib/overcommit/{plugins → hook}/commit_msg/hard_tabs.rb +5 -6
  14. data/lib/overcommit/hook/commit_msg/russian_novel.rb +14 -0
  15. data/lib/overcommit/hook/commit_msg/single_line_subject.rb +12 -0
  16. data/lib/overcommit/hook/commit_msg/text_width.rb +20 -0
  17. data/lib/overcommit/hook/commit_msg/trailing_period.rb +12 -0
  18. data/lib/overcommit/hook/post_checkout/base.rb +11 -0
  19. data/lib/overcommit/hook/post_checkout/bundle_check.rb +29 -0
  20. data/lib/overcommit/hook/post_checkout/index_tags.rb +24 -0
  21. data/lib/overcommit/hook/pre_commit/author_email.rb +17 -0
  22. data/lib/overcommit/hook/pre_commit/author_name.rb +17 -0
  23. data/lib/overcommit/hook/pre_commit/base.rb +10 -0
  24. data/lib/overcommit/hook/pre_commit/bundle_check.rb +30 -0
  25. data/lib/overcommit/hook/pre_commit/coffee_lint.rb +14 -0
  26. data/lib/overcommit/hook/pre_commit/css_lint.rb +16 -0
  27. data/lib/overcommit/hook/pre_commit/haml_lint.rb +26 -0
  28. data/lib/overcommit/hook/pre_commit/hard_tabs.rb +16 -0
  29. data/lib/overcommit/hook/pre_commit/image_optim.rb +41 -0
  30. data/lib/overcommit/hook/pre_commit/js_hint.rb +15 -0
  31. data/lib/overcommit/hook/pre_commit/jscs.rb +31 -0
  32. data/lib/overcommit/hook/pre_commit/python_flake8.rb +14 -0
  33. data/lib/overcommit/hook/pre_commit/rubocop.rb +26 -0
  34. data/lib/overcommit/hook/pre_commit/scss_lint.rb +26 -0
  35. data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +15 -0
  36. data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +22 -0
  37. data/lib/overcommit/hook_context.rb +16 -0
  38. data/lib/overcommit/hook_context/base.rb +68 -0
  39. data/lib/overcommit/hook_context/commit_msg.rb +32 -0
  40. data/lib/overcommit/hook_context/post_checkout.rb +24 -0
  41. data/lib/overcommit/hook_context/pre_commit.rb +96 -0
  42. data/lib/overcommit/hook_runner.rb +150 -0
  43. data/lib/overcommit/installer.rb +61 -68
  44. data/lib/overcommit/logger.rb +16 -13
  45. data/lib/overcommit/utils.rb +63 -38
  46. data/lib/overcommit/version.rb +1 -1
  47. data/{bin/scripts → libexec}/gerrit-change-id +0 -0
  48. data/{bin/scripts → libexec}/index-tags +1 -3
  49. data/template-dir/hooks/commit-msg +83 -0
  50. data/template-dir/hooks/overcommit-hook +83 -0
  51. data/template-dir/hooks/post-checkout +83 -0
  52. data/template-dir/hooks/pre-commit +83 -0
  53. metadata +76 -57
  54. data/bin/hooks/commit-msg +0 -8
  55. data/bin/hooks/post-checkout +0 -9
  56. data/bin/hooks/post-merge +0 -23
  57. data/bin/hooks/pre-commit +0 -8
  58. data/bin/hooks/prepare-commit-msg +0 -159
  59. data/bin/run-hook +0 -8
  60. data/bin/scripts/check-gemfile +0 -9
  61. data/bin/scripts/csslint-rhino.js +0 -9080
  62. data/bin/scripts/jshint.js +0 -5921
  63. data/bin/scripts/jshint_runner.js +0 -42
  64. data/lib/overcommit/errors.rb +0 -3
  65. data/lib/overcommit/git_hook.rb +0 -89
  66. data/lib/overcommit/hook_specific_check.rb +0 -110
  67. data/lib/overcommit/hooks/commit_msg.rb +0 -7
  68. data/lib/overcommit/hooks/pre_commit.rb +0 -9
  69. data/lib/overcommit/plugins/commit_msg/change_id.rb +0 -15
  70. data/lib/overcommit/plugins/commit_msg/release_note.rb +0 -25
  71. data/lib/overcommit/plugins/commit_msg/russian_novel.rb +0 -16
  72. data/lib/overcommit/plugins/commit_msg/single_line_subject.rb +0 -13
  73. data/lib/overcommit/plugins/commit_msg/text_width.rb +0 -20
  74. data/lib/overcommit/plugins/commit_msg/trailing_period.rb +0 -13
  75. data/lib/overcommit/plugins/pre_commit/author_name.rb +0 -16
  76. data/lib/overcommit/plugins/pre_commit/causes_email.rb +0 -15
  77. data/lib/overcommit/plugins/pre_commit/coffee_lint.rb +0 -16
  78. data/lib/overcommit/plugins/pre_commit/css_linter.rb +0 -17
  79. data/lib/overcommit/plugins/pre_commit/haml_style.rb +0 -34
  80. data/lib/overcommit/plugins/pre_commit/haml_syntax.rb +0 -24
  81. data/lib/overcommit/plugins/pre_commit/image_optimization.rb +0 -50
  82. data/lib/overcommit/plugins/pre_commit/js_console_log.rb +0 -16
  83. data/lib/overcommit/plugins/pre_commit/js_syntax.rb +0 -30
  84. data/lib/overcommit/plugins/pre_commit/python_flake8.rb +0 -15
  85. data/lib/overcommit/plugins/pre_commit/ruby_style.rb +0 -67
  86. data/lib/overcommit/plugins/pre_commit/ruby_syntax.rb +0 -19
  87. data/lib/overcommit/plugins/pre_commit/scss_lint.rb +0 -66
  88. data/lib/overcommit/plugins/pre_commit/test_history.rb +0 -58
  89. data/lib/overcommit/plugins/pre_commit/whitespace.rb +0 -21
  90. data/lib/overcommit/plugins/pre_commit/yaml_syntax.rb +0 -22
  91. data/lib/overcommit/reporter.rb +0 -90
  92. 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,7 @@
1
+ # Global application constants.
2
+ module Overcommit
3
+ OVERCOMMIT_HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
4
+
5
+ REPO_URL = 'https://github.com/causes/overcommit'
6
+ BUG_REPORT_URL = "#{REPO_URL}/issues"
7
+ 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::GitHook
2
- class HardTabs < HookSpecificCheck
3
- include HookRegistry
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.join.index /\t/
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,10 @@
1
+ require 'forwardable'
2
+
3
+ module Overcommit::Hook::PreCommit
4
+ # Functionality common to all pre-commit hooks.
5
+ class Base < Overcommit::Hook::Base
6
+ extend Forwardable
7
+
8
+ def_delegators :@context, :modified_lines
9
+ end
10
+ 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