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,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