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,16 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `csslint` against any modified CSS files.
3
+ class CssLint < Base
4
+ def run
5
+ unless in_path?('csslint')
6
+ return :warn, 'csslint not installed -- run `npm install -g csslint`'
7
+ end
8
+
9
+ paths = applicable_files.join(' ')
10
+
11
+ result = command("csslint --quiet --format=compact #{paths} | grep 'Error - '")
12
+ output = result.stdout
13
+ return (output !~ /Error - (?!Unknown @ rule)/ ? :good : :bad), output
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `haml-lint` against any modified HAML files.
3
+ class HamlLint < Base
4
+ def run
5
+ unless in_path?('haml-lint')
6
+ return :warn, 'haml-lint not installed -- run `gem install haml-lint`'
7
+ end
8
+
9
+ result = command("haml-lint #{applicable_files.join(' ')}")
10
+ return :good if result.success?
11
+
12
+ # Keep lines from the output for files that we actually modified
13
+ error_lines, warning_lines = result.stdout.split("\n").partition do |output_line|
14
+ if match = output_line.match(/^([^:]+):(\d+)/)
15
+ file = match[1]
16
+ line = match[2]
17
+ end
18
+ modified_lines(file).include?(line.to_i)
19
+ end
20
+
21
+ return :bad, error_lines.join("\n") unless error_lines.empty?
22
+ return :warn, "Modified files have lints (on lines you didn't modify)\n" <<
23
+ warning_lines.join("\n")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Checks for hard tabs in files.
3
+ class HardTabs < Base
4
+ def run
5
+ paths = applicable_files.join(' ')
6
+
7
+ # Catches hard tabs
8
+ result = command("grep -IHn \"\t\" #{paths}")
9
+ unless result.stdout.empty?
10
+ return :bad, "Hard tabs detected:\n#{result.stdout}"
11
+ end
12
+
13
+ :good
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Checks for images that can be optimized with `image_optim`.
3
+ class ImageOptim < Base
4
+ def run
5
+ begin
6
+ require 'image_optim'
7
+ rescue LoadError
8
+ return :warn, 'image_optim not installed -- run `gem install image_optim`'
9
+ end
10
+
11
+ optimized_images =
12
+ begin
13
+ optimize_images(applicable_files)
14
+ rescue ::ImageOptim::BinNotFoundError => e
15
+ return :bad, "#{e.message}. The image_optim gem is dependendent on this binary."
16
+ end
17
+
18
+ if optimized_images.any?
19
+ return :bad,
20
+ "The following images were optimized:\n" <<
21
+ optimized_images.join("\n") <<
22
+ "\nPlease add them to your commit."
23
+ end
24
+
25
+ :good
26
+ end
27
+
28
+ private
29
+
30
+ def optimize_images(image_paths)
31
+ image_optim = ::ImageOptim.new(:pngout => false)
32
+
33
+ optimized_images =
34
+ image_optim.optimize_images!(image_paths) do |path, optimized|
35
+ path if optimized
36
+ end
37
+
38
+ optimized_images.compact
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `jshint` against any modified JavaScript files.
3
+ class JsHint < Base
4
+ def run
5
+ unless in_path?('jshint')
6
+ return :warn, 'jshint not installed -- run `npm install -g jshint`'
7
+ end
8
+
9
+ result = command("jshint #{applicable_files.join(' ')}")
10
+ output = result.stdout
11
+
12
+ return (output.empty? ? :good : :bad), output
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `jscs` (JavaScript Code Style Checker) against any modified JavaScript
3
+ # files.
4
+ class Jscs < Base
5
+ def run
6
+ unless in_path?('jscs')
7
+ return :warn, 'jscs not installed -- run `npm install -g jscs`'
8
+ end
9
+
10
+ result = command("jscs --reporter=inline #{applicable_files.join(' ')}")
11
+ return :good if result.success?
12
+
13
+ if /Config.*not found/i =~ result.stderr
14
+ return :warn, result.stderr.chomp
15
+ end
16
+
17
+ # Keep lines from the output for files that we actually modified
18
+ error_lines, warning_lines = result.stdout.split("\n").partition do |output_line|
19
+ if match = output_line.match(/^([^:]+):[^\d]+(\d+)/)
20
+ file = match[1]
21
+ line = match[2]
22
+ end
23
+ modified_lines(file).include?(line.to_i)
24
+ end
25
+
26
+ return :bad, error_lines.join("\n") unless error_lines.empty?
27
+ return :warn, "Modified files have lints (on lines you didn't modify)\n" <<
28
+ warning_lines.join("\n")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `flake8` against any modified Python files.
3
+ class PythonFlake8 < Base
4
+ def run
5
+ unless in_path?('flake8')
6
+ return :warn, 'flake8 not installed -- run `pip install flake8`'
7
+ end
8
+
9
+ result = command("flake8 #{applicable_files.join(' ')}")
10
+
11
+ return (result.success? ? :good : :bad), result.stdout
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `rubocop` against any modified Ruby files.
3
+ class Rubocop < Base
4
+ def run
5
+ unless in_path?('rubocop')
6
+ return :warn, 'Rubocop not installed -- run `gem install rubocop`'
7
+ end
8
+
9
+ result = command("rubocop --format=emacs #{applicable_files.join(' ')} 2>&1")
10
+ return :good if result.success?
11
+
12
+ # Keep lines from the output for files that we actually modified
13
+ error_lines, warning_lines = result.stdout.split("\n").partition do |output_line|
14
+ if match = output_line.match(/^([^:]+):(\d+)/)
15
+ file = match[1]
16
+ line = match[2]
17
+ end
18
+ modified_lines(file).include?(line.to_i)
19
+ end
20
+
21
+ return :bad, error_lines.join("\n") unless error_lines.empty?
22
+ return :warn, "Modified files have lints (on lines you didn't modify)\n" <<
23
+ warning_lines.join("\n")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Runs `scss-lint` against any modified SCSS files.
3
+ class ScssLint < Base
4
+ def run
5
+ unless in_path?('scss-lint')
6
+ return :warn, 'scss-lint not installed -- run `gem install scss-lint`'
7
+ end
8
+
9
+ result = command("scss-lint #{applicable_files.join(' ')}")
10
+ return :good if result.success?
11
+
12
+ # Keep lines from the output for files that we actually modified
13
+ error_lines, warning_lines = result.stdout.split("\n").partition do |output_line|
14
+ if match = output_line.match(/^([^:]+):(\d+)/)
15
+ file = match[1]
16
+ line = match[2]
17
+ end
18
+ modified_lines(file).include?(line.to_i)
19
+ end
20
+
21
+ return :bad, error_lines.join("\n") unless error_lines.empty?
22
+ return :warn, "Modified files have lints (on lines you didn't modify)\n" <<
23
+ warning_lines.join("\n")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module Overcommit::Hook::PreCommit
2
+ # Checks for trailing whitespace in files.
3
+ class TrailingWhitespace < Base
4
+ def run
5
+ paths = applicable_files.join(' ')
6
+
7
+ result = command("grep -IHn \"\\s$\" #{paths}")
8
+ unless result.stdout.empty?
9
+ return :bad, "Trailing whitespace detected:\n#{result.stdout}"
10
+ end
11
+
12
+ :good
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ require 'yaml'
2
+
3
+ module Overcommit::Hook::PreCommit
4
+ # Checks the syntax of any modified YAML files.
5
+ class YamlSyntax < Base
6
+ def run
7
+ clean = true
8
+ output = []
9
+
10
+ applicable_files.each do |file|
11
+ begin
12
+ YAML.load_file(file)
13
+ rescue ArgumentError => e
14
+ output << "#{e.message} parsing #{file}"
15
+ clean = false
16
+ end
17
+ end
18
+
19
+ return (clean ? :good : :bad), output
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # Utility module which manages the creation of {HookContext}s.
2
+ module Overcommit::HookContext
3
+ def self.create(hook_type, config, args, input)
4
+ underscored_hook_type = Overcommit::Utils.snake_case(hook_type)
5
+
6
+ require "overcommit/hook_context/#{underscored_hook_type}"
7
+
8
+ Overcommit::HookContext.const_get(hook_type).new(config, args, input)
9
+ rescue LoadError, NameError => error
10
+ # Could happen when a symlink was created for a hook type Overcommit does
11
+ # not yet support.
12
+ raise Overcommit::Exceptions::HookContextLoadError,
13
+ "Unable to load '#{hook_type}' hook context: '#{error}'",
14
+ error.backtrace
15
+ end
16
+ end
@@ -0,0 +1,68 @@
1
+ require 'set'
2
+
3
+ module Overcommit::HookContext
4
+ # Contains helpers related to the context with which a hook is being run.
5
+ #
6
+ # It acts as an adapter to the arguments passed to the hook, as well as
7
+ # context-specific information such as staged files, providing a single source
8
+ # of truth for this context.
9
+ #
10
+ # This is also important to house in a separate object so that any
11
+ # calculations can be memoized across all hooks in a single object, which
12
+ # helps with performance.
13
+ class Base
14
+ def initialize(config, args, input)
15
+ @config = config
16
+ @args = args
17
+ @input = input
18
+ end
19
+
20
+ # Returns the camel-cased type of this hook (e.g. PreCommit)
21
+ def hook_class_name
22
+ self.class.name.split('::').last
23
+ end
24
+
25
+ # Returns the snake-cased type of this hook (e.g. pre_commit)
26
+ def hook_type_name
27
+ Overcommit::Utils.snake_case(hook_class_name)
28
+ end
29
+
30
+ # Returns the actual name of the hook script being run (e.g. pre-commit).
31
+ def hook_script_name
32
+ hook_type_name.gsub('_', '-')
33
+ end
34
+
35
+ # Initializes anything related to the environment.
36
+ #
37
+ # This is called before the hooks are run by the [HookRunner]. Different
38
+ # hook types can perform different setup.
39
+ def setup_environment
40
+ # Implemented by subclass
41
+ end
42
+
43
+ # Resets the environment to an appropriate state.
44
+ #
45
+ # This is called after the hooks have been run by the [HookRunner].
46
+ # Different hook types can perform different cleanup operations, which are
47
+ # intended to "undo" the results of the call to {#setup_environment}.
48
+ def cleanup_environment
49
+ # Implemented by subclass
50
+ end
51
+
52
+ # Returns a list of files that have been modified.
53
+ #
54
+ # By default, this returns an empty list. Subclasses should implement if
55
+ # there is a concept of files changing for the type of hook being run.
56
+ def modified_files
57
+ []
58
+ end
59
+
60
+ # Returns a set of lines that have been modified for a file.
61
+ #
62
+ # By default, this returns an empty set. Subclasses should implement if
63
+ # there is a concept of files changing for the type of hook being run.
64
+ def modified_lines(file)
65
+ Set.new
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,32 @@
1
+ module Overcommit::HookContext
2
+ # Contains helpers related to contextual information used by commit-msg hooks.
3
+ class CommitMsg < Base
4
+ # User commit message stripped of comments and diff (from verbose output).
5
+ def commit_message
6
+ commit_message_lines.join
7
+ end
8
+
9
+ # Updates the commit message to the specified text.
10
+ def update_commit_message(message)
11
+ ::File.open(commit_message_file, 'w') do |file|
12
+ file.write(message)
13
+ end
14
+ end
15
+
16
+ def commit_message_lines
17
+ raw_commit_message_lines.
18
+ reject { |line| line =~ /^#/ }.
19
+ take_while { |line| !line.start_with?('diff --git') }
20
+ end
21
+
22
+ def commit_message_file
23
+ @args[0]
24
+ end
25
+
26
+ private
27
+
28
+ def raw_commit_message_lines
29
+ ::IO.readlines(commit_message_file)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module Overcommit::HookContext
2
+ class PostCheckout < Base
3
+ # Returns the ref of the HEAD that we transitioned from.
4
+ def previous_head
5
+ @args[0]
6
+ end
7
+
8
+ # Returns the ref of the new current HEAD.
9
+ def new_head
10
+ @args[1]
11
+ end
12
+
13
+ # Returns whether this checkout was the result of changing/updating a
14
+ # branch.
15
+ def branch_checkout?
16
+ @args[2].to_i == 1
17
+ end
18
+
19
+ # Returns whether this checkout was for a single file.
20
+ def file_checkout?
21
+ !branch_checkout?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,96 @@
1
+ require 'set'
2
+
3
+ module Overcommit::HookContext
4
+ # Contains helpers related to contextual information used by pre-commit hooks.
5
+ #
6
+ # This includes staged files, which lines of those files have been modified,
7
+ # etc.
8
+ class PreCommit < Base
9
+ # Stash unstaged contents of files so hooks don't see changes that aren't
10
+ # about to be committed.
11
+ def setup_environment
12
+ store_modified_times
13
+
14
+ if modified_files.any?
15
+ `git stash save --keep-index --quiet #{<<-MSG}`
16
+ "Overcommit: Stash of repo state before hook run at #{Time.now}"
17
+ MSG
18
+ end
19
+
20
+ # While running the hooks make it appear as if nothing changed
21
+ restore_modified_times
22
+ end
23
+
24
+ # Restore unstaged changes and reset file modification times so it appears
25
+ # as if nothing ever changed.
26
+ def cleanup_environment
27
+ `git reset --hard`
28
+ `git stash pop --index --quiet` if modified_files.any?
29
+
30
+ restore_modified_times
31
+ end
32
+
33
+ # Get a list of added, copied, or modified files that have been staged.
34
+ # Renames and deletions are ignored, since there should be nothing to check.
35
+ def modified_files
36
+ @modified_files ||=
37
+ `git diff --cached --name-only --diff-filter=ACM --ignore-submodules=all`.
38
+ split("\n").
39
+ map { |relative_file| File.expand_path(relative_file) }
40
+ end
41
+
42
+ # Returns the set of line numbers corresponding to the lines that were
43
+ # changed in a specified file.
44
+ def modified_lines(file)
45
+ @modified_lines ||= {}
46
+ @modified_lines[file] ||= extract_modified_lines(file)
47
+ end
48
+
49
+ private
50
+
51
+ DIFF_HUNK_REGEX = /
52
+ ^@@\s
53
+ [^\s]+\s # Ignore old file range
54
+ \+(\d+)(?:,(\d+))? # Extract range of hunk containing start line and number of lines
55
+ \s@@.*$
56
+ /x
57
+
58
+ def extract_modified_lines(staged_file)
59
+ lines = Set.new
60
+
61
+ `git diff --no-ext-diff --cached -U0 -- #{staged_file}`.
62
+ scan(DIFF_HUNK_REGEX) do |start_line, lines_added|
63
+
64
+ lines_added = (lines_added || 1).to_i # When blank, one line was added
65
+ cur_line = start_line.to_i
66
+
67
+ lines_added.times do
68
+ lines.add cur_line
69
+ cur_line += 1
70
+ end
71
+ end
72
+
73
+ lines
74
+ end
75
+
76
+ def store_modified_times
77
+ @modified_times = {}
78
+
79
+ modified_files.each do |file|
80
+ @modified_times[file] = File.mtime(file)
81
+ end
82
+ end
83
+
84
+ # Stores the modification times for all modified files to make it appear like
85
+ # they never changed.
86
+ #
87
+ # This prevents editors from complaining about files changing when we stash
88
+ # changes before running the hooks.
89
+ def restore_modified_times
90
+ modified_files.each do |file|
91
+ time = @modified_times[file]
92
+ File.utime(time, time, file)
93
+ end
94
+ end
95
+ end
96
+ end