Skip to content

Commit cbc96e4

Browse files
sdsShane da Silva
authored and
Shane da Silva
committed
Move to HookContexts
Change-Id: Ib9082dd93b5019c79e4117ec60f9fd11cd905786 Reviewed-on: http://gerrit.causes.com/35561 Reviewed-by: Shane da Silva <shane@causes.com> Tested-by: Shane da Silva <shane@causes.com>
1 parent 3e2563c commit cbc96e4

19 files changed

+330
-247
lines changed

config/default.yml

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
# to the root of the repository.
99
plugin_directory: '.githooks'
1010

11+
# Hooks that run after HEAD changes or a file is explicitly checked out. Useful
12+
# for updating source tags (e.g. via ctags) or warning about new migrations,
13+
# etc.
14+
post_checkout:
15+
1116
# Hooks that are run after `git commit` is executed, before the commit message
1217
# editor is displayed. These hooks are ideal for syntax checkers, linters, and
1318
# other checks that you want to run before you allow a commit object to be

lib/overcommit.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
require 'overcommit/configuration'
44
require 'overcommit/configuration_loader'
55
require 'overcommit/hook/base'
6+
require 'overcommit/hook_context/base'
7+
require 'overcommit/hook_context'
68
require 'overcommit/hook_runner'
7-
require 'overcommit/hook_runner/base'
89
require 'overcommit/installer'
910
require 'overcommit/logger'
1011
require 'overcommit/reporter'

lib/overcommit/configuration.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ def apply_environment!(hook_type, env)
4949
if skipped_hooks.include?('all') || skipped_hooks.include?('ALL')
5050
@hash[hook_type]['ALL']['skip'] = true
5151
else
52-
skipped_hooks.each do |hook_name|
52+
skipped_hooks.select { |hook_name| hook_exists?(hook_type, hook_name) }.
53+
each do |hook_name|
5354
@hash[hook_type][hook_name] ||= {}
5455
@hash[hook_type][hook_name]['skip'] = true
5556
end
@@ -62,6 +63,11 @@ def apply_environment!(hook_type, env)
6263

6364
private
6465

66+
def hook_exists?(hook_type, hook_name)
67+
File.exist?(File.join(OVERCOMMIT_HOME, 'lib', 'overcommit', 'hook',
68+
hook_type, "#{hook_name}.rb"))
69+
end
70+
6571
# Validates the configuration for any invalid options, normalizing it where
6672
# possible.
6773
def validate

lib/overcommit/exceptions.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class ConfigurationError < StandardError; end
66
class HookLoadError < StandardError; end
77

88
# Raised when a {HookRunner} could not be loaded.
9-
class HookRunnerLoadError < StandardError; end
9+
class HookContextLoadError < StandardError; end
1010

1111
# Raised when a installation target is not a valid git repository.
1212
class InvalidGitRepo < StandardError; end

lib/overcommit/hook/base.rb

+15-77
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
require 'wopen3'
1+
require 'forwardable'
22

33
module Overcommit::Hook
44
# Functionality common to all hooks.
55
class Base
6-
def initialize(config, hook_runner)
6+
extend Forwardable
7+
8+
def_delegators :@context, :staged_files
9+
10+
def initialize(config, context)
711
@config = config.hook_config(self)
8-
@hook_runner = hook_runner
12+
@context = context
913
end
1014

1115
# Runs the hook.
@@ -47,6 +51,14 @@ def run?
4751
!(requires_modified_files? && applicable_files.empty?)
4852
end
4953

54+
def in_path?(cmd)
55+
Overcommit::Utils.in_path?(cmd)
56+
end
57+
58+
def command(cmd)
59+
Overcommit::Utils.command(cmd)
60+
end
61+
5062
# Gets a list of staged files that apply to this hook based on its
5163
# configured `include` and `exclude` lists.
5264
def applicable_files
@@ -55,12 +67,6 @@ def applicable_files
5567

5668
private
5769

58-
def staged_files
59-
@hook_runner.staged_files
60-
end
61-
62-
# Returns whether the specified file is applicable to this hook based on the
63-
# hook's `include` and `exclude` file glob patterns.
6470
def applicable_file?(file)
6571
includes = Array(@config['include']).map { |glob| convert_glob_to_absolute(glob) }
6672
included = includes.empty? ||
@@ -81,73 +87,5 @@ def convert_glob_to_absolute(glob)
8187
File.join(repo_root, glob)
8288
end
8389
end
84-
85-
# Returns the set of line numbers corresponding to the lines that were
86-
# changed in a specified file.
87-
def modified_lines(staged_file)
88-
@modified_lines ||= {}
89-
@modified_lines[staged_file] ||= extract_modified_lines(staged_file)
90-
end
91-
92-
DIFF_HUNK_REGEX = /
93-
^@@\s
94-
[^\s]+\s # Ignore old file range
95-
\+(\d+)(?:,(\d+))? # Extract range of hunk containing start line and number of lines
96-
\s@@.*$
97-
/x
98-
99-
def extract_modified_lines(staged_file)
100-
lines = Set.new
101-
102-
`git diff --no-ext-diff --cached -U0 -- #{staged_file}`.
103-
scan(DIFF_HUNK_REGEX) do |start_line, lines_added|
104-
105-
lines_added = (lines_added || 1).to_i # When blank, one line was added
106-
cur_line = start_line.to_i
107-
108-
lines_added.times do
109-
lines.add cur_line
110-
cur_line += 1
111-
end
112-
end
113-
114-
lines
115-
end
116-
117-
# Returns whether a command can be found given the current environment path.
118-
def in_path?(cmd)
119-
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
120-
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
121-
exts.each do |ext|
122-
exe = File.join(path, "#{cmd}#{ext}")
123-
return true if File.executable? exe
124-
end
125-
end
126-
false
127-
end
128-
129-
# Wrap external subshell calls. This is necessary in order to allow
130-
# Overcommit to call other Ruby executables without requiring that they be
131-
# specified in Overcommit's Gemfile--a nasty consequence of using
132-
# `bundle exec overcommit` while developing locally.
133-
def command(command)
134-
with_environment 'RUBYOPT' => nil do
135-
Wopen3.system(command)
136-
end
137-
end
138-
139-
# Calls a block of code with a modified set of environment variables,
140-
# restoring them once the code has executed.
141-
def with_environment(env, &block)
142-
old_env = {}
143-
env.each do |var, value|
144-
old_env[var] = ENV[var.to_s]
145-
ENV[var.to_s] = value
146-
end
147-
148-
yield
149-
ensure
150-
old_env.each { |var, value| ENV[var.to_s] = value }
151-
end
15290
end
15391
end
+5-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
require 'forwardable'
2+
13
module Overcommit::Hook::CommitMsg
24
# Functionality common to all commit-msg hooks.
35
class Base < Overcommit::Hook::Base
4-
def commit_message
5-
@hook_runner.commit_message
6-
end
6+
extend Forwardable
7+
8+
def_delegators :@context, :commit_message
79
end
810
end
+11
Original file line numberDiff line numberDiff line change
@@ -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_delegator :@context,
9+
*%i[previous_head new_head branch_checkout? file_checkout?]
10+
end
11+
end
+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
require 'forwardable'
2+
13
module Overcommit::Hook::PreCommit
24
# Functionality common to all pre-commit hooks.
35
class Base < Overcommit::Hook::Base
6+
extend Forwardable
7+
8+
def_delegators :@context, :modified_lines
49
end
510
end

lib/overcommit/hook_context.rb

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Utility module which manages the creation of {HookContext}s.
2+
module Overcommit::HookContext
3+
def self.create(hook_type, config, args, input)
4+
require "overcommit/hook_context/#{hook_type.gsub('-', '_')}"
5+
6+
Overcommit::HookContext.const_get(hook_type_to_class_name(hook_type))
7+
.new(config, args, input)
8+
rescue LoadError, NameError => error
9+
# Could happen when a symlink was created for a hook type Overcommit does
10+
# not yet support.
11+
raise Overcommit::Exceptions::HookContextLoadError,
12+
"Unable to load '#{hook_type}' hook context: '#{error}'",
13+
error.backtrace
14+
end
15+
16+
private
17+
18+
def self.hook_type_to_class_name(hook_type)
19+
hook_type.split('-').map { |s| s.capitalize }.join
20+
end
21+
end

lib/overcommit/hook_context/base.rb

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module Overcommit::HookContext
2+
# Contains helpers related to the context with which a hook is being run.
3+
#
4+
# It acts as an adapter to the arguments passed to the hook, as well as
5+
# context-specific information such as staged files, providing a single source
6+
# of truth for this context.
7+
#
8+
# This is also important to house in a separate object so that any
9+
# calculations can be memoized across all hooks in a single object, which
10+
# helps with performance.
11+
class Base
12+
def initialize(config, args, input)
13+
@config = config
14+
@args = args
15+
@input = input
16+
end
17+
18+
# Get a list of added, copied, or modified files that have been staged.
19+
# Renames and deletions are ignored, since there should be nothing to check.
20+
def staged_files
21+
@staged_files ||=
22+
`git diff --cached --name-only --diff-filter=ACM --ignore-submodules=all`.
23+
split("\n").
24+
map { |relative_file| File.expand_path(relative_file) }
25+
end
26+
end
27+
end

lib/overcommit/hook_runner/commit_msg.rb lib/overcommit/hook_context/commit_msg.rb

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
module Overcommit::HookRunner
2-
# Manages loading and running commit-msg hooks.
1+
module Overcommit::HookContext
32
class CommitMsg < Base
43
# User commit message stripped of comments and diff (from verbose output)
54
def commit_message
@@ -8,12 +7,12 @@ def commit_message
87
take_while { |line| !line.start_with?('diff --git') }
98
end
109

10+
private
11+
1112
def raw_commit_message
1213
@raw_commit_message ||= ::IO.readlines(commit_message_file)
1314
end
1415

15-
private
16-
1716
def commit_message_file
1817
unless @args[0] && ::File.exist?(@args[0])
1918
fail 'Not running in the context of a commit message'
Original file line numberDiff line numberDiff line change
@@ -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
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
module Overcommit::HookContext
2+
# Contains helpers related to contextual information used by pre-commit hooks.
3+
#
4+
# This includes staged files, which lines of those files have been modified,
5+
# etc.
6+
class PreCommit < Base
7+
# Returns the set of line numbers corresponding to the lines that were
8+
# changed in a specified file.
9+
def modified_lines(staged_file)
10+
@modified_lines ||= {}
11+
@modified_lines[staged_file] ||= extract_modified_lines(staged_file)
12+
end
13+
14+
private
15+
16+
DIFF_HUNK_REGEX = /
17+
^@@\s
18+
[^\s]+\s # Ignore old file range
19+
\+(\d+)(?:,(\d+))? # Extract range of hunk containing start line and number of lines
20+
\s@@.*$
21+
/x
22+
23+
def extract_modified_lines(staged_file)
24+
lines = Set.new
25+
26+
`git diff --no-ext-diff --cached -U0 -- #{staged_file}`.
27+
scan(DIFF_HUNK_REGEX) do |start_line, lines_added|
28+
29+
lines_added = (lines_added || 1).to_i # When blank, one line was added
30+
cur_line = start_line.to_i
31+
32+
lines_added.times do
33+
lines.add cur_line
34+
cur_line += 1
35+
end
36+
end
37+
38+
lines
39+
end
40+
end
41+
end

0 commit comments

Comments
 (0)