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