overcommit 0.5.0 → 0.6.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.
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