Skip to content

Commit 2d2d97a

Browse files
committed
Add Gem hook plugin support
Support loading Overcommit hooks supplied by Gems in the same fashion as hooks that can be provided in Repo-Specific hooks.
1 parent 192c84b commit 2d2d97a

File tree

9 files changed

+196
-0
lines changed

9 files changed

+196
-0
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ any Ruby code.
4141
* [PreRebase](#prerebase)
4242
* [Repo-Specific Hooks](#repo-specific-hooks)
4343
* [Adding Existing Git Hooks](#adding-existing-git-hooks)
44+
* [Gem-provided Hooks](#gem-provided-hooks)
4445
* [Security](#security)
4546
* [Contributing](#contributing)
4647
* [Community](#community)
@@ -671,6 +672,24 @@ of hook, see the [git-hooks documentation][GHD].
671672

672673
[GHD]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
673674

675+
## Gem-provided Hooks
676+
677+
For hooks which should be available from multiple repositories, but which do not
678+
make sense to contribute back to Overcommit, you can package them as a Ruby Gem
679+
and `overcommit` can use them as if they were repo-specific hooks. This would be
680+
useful for an organization that has custom hooks they wish to apply to many
681+
repositories, but manage the hooks in a central way.
682+
683+
These are implemented similarly to Repo-Specific Hooks, but need to be placed
684+
in a Gem in the normal `overcommit` hook path. For example, to add `MyCustomHook`
685+
as a pre_commit hook you would put it here:
686+
`./lib/overcommit/hooks/pre_commit/my_custom_hook.rb`
687+
688+
You must ensure that the Gem is available in the environment where `overcommit` is
689+
being run.
690+
691+
See [Repo-Specific Hooks](#repo-specific-hooks) for details.
692+
674693
## Security
675694

676695
While Overcommit can make managing Git hooks easier and more convenient,

config/default.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
# (Generate lock file by running `bundle install --gemfile=.overcommit_gems.rb`)
3535
gemfile: false
3636

37+
# If enabled, Overcommit will look for plugins provided anywhere in the
38+
# standard load path and load them as it would with built-in or repo-specific
39+
# hooks.
40+
gem_plugins: true
41+
3742
# Where to store hook plugins specific to a repository. These are loaded in
3843
# addition to the default hooks Overcommit comes with. The location is relative
3944
# to the root of the repository.

lib/overcommit.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
require 'overcommit/hook_signer'
1818
require 'overcommit/hook_loader/base'
1919
require 'overcommit/hook_loader/built_in_hook_loader'
20+
require 'overcommit/hook_loader/gem_hook_loader'
2021
require 'overcommit/hook_loader/plugin_hook_loader'
2122
require 'overcommit/interrupt_handler'
2223
require 'overcommit/printer'

lib/overcommit/configuration.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ def enabled_builtin_hooks(hook_context)
119119
select { |hook_name| hook_enabled?(hook_context, hook_name) }
120120
end
121121

122+
# Returns the gem-provided hooks that have been enabled for a hook type.
123+
def enabled_gem_hooks(hook_context)
124+
@hash[hook_context.hook_class_name].keys.
125+
reject { |hook_name| hook_name == 'ALL' }.
126+
select { |hook_name| gem_hook?(hook_context, hook_name) }.
127+
select { |hook_name| hook_enabled?(hook_context, hook_name) }
128+
end
129+
122130
# Returns the ad hoc hooks that have been enabled for a hook type.
123131
def enabled_ad_hoc_hooks(hook_context)
124132
@hash[hook_context.hook_class_name].keys.
@@ -259,6 +267,7 @@ def ad_hoc_hook?(hook_context, hook_name)
259267
# Ad hoc hooks are neither built-in nor have a plugin file written but
260268
# still have a `command` specified to be run
261269
!built_in_hook?(hook_context, hook_name) &&
270+
!gem_hook?(hook_context, hook_name) &&
262271
!plugin_hook?(hook_context, hook_name) &&
263272
(ad_hoc_conf['command'] || ad_hoc_conf['required_executable'])
264273
end
@@ -270,8 +279,18 @@ def built_in_hook?(hook_context, hook_name)
270279
hook_context.hook_type_name, "#{hook_name}.rb"))
271280
end
272281

282+
def gem_hook?(hook_context, hook_name)
283+
hook_name = Overcommit::Utils.snake_case(hook_name)
284+
285+
$LOAD_PATH.any? do |path|
286+
File.exist?(File.join(path, 'overcommit', 'hook',
287+
hook_context.hook_type_name, "#{hook_name}.rb"))
288+
end
289+
end
290+
273291
def hook_exists?(hook_context, hook_name)
274292
built_in_hook?(hook_context, hook_name) ||
293+
gem_hook?(hook_context, hook_name) ||
275294
plugin_hook?(hook_context, hook_name) ||
276295
ad_hoc_hook?(hook_context, hook_name)
277296
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
module Overcommit::HookLoader
4+
# Responsible for loading hooks that ship with Overcommit.
5+
class GemHookLoader < Base
6+
def load_hooks
7+
@config.enabled_gem_hooks(@context).map do |hook_name|
8+
underscored_hook_name = Overcommit::Utils.snake_case(hook_name)
9+
require "overcommit/hook/#{@context.hook_type_name}/#{underscored_hook_name}"
10+
create_hook(hook_name)
11+
end
12+
end
13+
end
14+
end

lib/overcommit/hook_runner.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ def load_hooks
200200

201201
@hooks += HookLoader::BuiltInHookLoader.new(@config, @context, @log).load_hooks
202202

203+
# Load Gem-based hooks next, if gem_plugins is enabled:
204+
if @config['gem_plugins']
205+
@hooks += HookLoader::GemHookLoader.new(@config, @context, @log).load_hooks
206+
end
207+
203208
# Load plugin hooks after so they can subclass existing hooks
204209
@hooks += HookLoader::PluginHookLoader.new(@config, @context, @log).load_hooks
205210
rescue LoadError => ex

spec/overcommit/configuration_spec.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,52 @@
294294
end
295295
end
296296
end
297+
298+
describe '#enabled_gem_hooks' do
299+
let(:hash) do
300+
{
301+
'PreCommit' => {
302+
'MyCustomHook' => {
303+
'enabled' => true
304+
},
305+
'MyOtherHook' => {
306+
'enabled' => false
307+
},
308+
}
309+
}
310+
end
311+
312+
let(:context) { double('context') }
313+
subject { config.enabled_gem_hooks(context) }
314+
315+
before do
316+
context.stub(hook_class_name: 'PreCommit',
317+
hook_type_name: 'pre_commit')
318+
end
319+
320+
it 'excludes hooks that are not found' do
321+
subject.should_not include 'MyCustomHook'
322+
subject.should_not include 'MyOtherHook'
323+
end
324+
325+
context 'when custom hooks are found' do
326+
before do
327+
$LOAD_PATH.unshift('/my/custom/path/lib')
328+
allow(File).to receive(:exist?).
329+
with('/my/custom/path/lib/overcommit/hook/pre_commit/my_custom_hook.rb').
330+
and_return(true)
331+
allow(File).to receive(:exist?).
332+
with('/my/custom/path/lib/overcommit/hook/pre_commit/my_other_hook.rb').
333+
and_return(true)
334+
end
335+
336+
it 'includes hooks that are enabled and found' do
337+
subject.should include 'MyCustomHook'
338+
end
339+
340+
it 'excludes hooks that are not enable but found' do
341+
subject.should_not include 'MyOtherHook'
342+
end
343+
end
344+
end
297345
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe Overcommit::HookLoader::GemHookLoader do
6+
let(:hash) { {} }
7+
let(:config) { Overcommit::Configuration.new(hash) }
8+
let(:logger) { double('logger') }
9+
let(:context) { double('context') }
10+
let(:loader) { described_class.new(config, context, logger) }
11+
12+
describe '#load_hooks' do
13+
subject(:load_hooks) { loader.send(:load_hooks) }
14+
15+
before do
16+
context.stub(hook_class_name: 'PreCommit',
17+
hook_type_name: 'pre_commit')
18+
end
19+
20+
it 'loads enabled gem hooks' do
21+
allow(config).to receive(:enabled_gem_hooks).with(context).and_return(['MyCustomHook'])
22+
23+
allow(loader).to receive(:require).
24+
with('overcommit/hook/pre_commit/my_custom_hook').
25+
and_return(true)
26+
allow(loader).to receive(:create_hook).with('MyCustomHook')
27+
expect(loader).to receive(:require).with('overcommit/hook/pre_commit/my_custom_hook')
28+
load_hooks
29+
end
30+
end
31+
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe Overcommit::HookRunner do
6+
let(:hash) { {} }
7+
let(:config) { Overcommit::Configuration.new(hash) }
8+
let(:logger) { double('logger') }
9+
let(:context) { double('context') }
10+
let(:printer) { double('printer') }
11+
let(:runner) { described_class.new(config, logger, context, printer) }
12+
13+
describe '#load_hooks' do
14+
subject(:load_hooks) { runner.send(:load_hooks) }
15+
16+
before do
17+
context.stub(hook_class_name: 'PreCommit',
18+
hook_type_name: 'pre_commit')
19+
allow_any_instance_of(Overcommit::HookLoader::BuiltInHookLoader).
20+
to receive(:load_hooks).and_return([])
21+
allow_any_instance_of(Overcommit::HookLoader::PluginHookLoader).
22+
to receive(:load_hooks).and_return([])
23+
end
24+
25+
context 'when gem_plugins is disabled' do
26+
let(:hash) do
27+
{
28+
'gem_plugins' => false
29+
}
30+
end
31+
32+
it 'expects not to load Gem hooks' do
33+
expect_any_instance_of(Overcommit::HookLoader::GemHookLoader).
34+
not_to receive(:load_hooks)
35+
load_hooks
36+
end
37+
end
38+
39+
context 'when gem_plugins is enabled' do
40+
let(:hash) do
41+
{
42+
'gem_plugins' => true
43+
}
44+
end
45+
let(:gemhookloader) { Overcommit::HookLoader::GemHookLoader.new(config, context, logger) }
46+
47+
it 'expects to load Gem hooks' do
48+
expect_any_instance_of(Overcommit::HookLoader::GemHookLoader).
49+
to receive(:load_hooks).and_call_original
50+
load_hooks
51+
end
52+
end
53+
end
54+
end

0 commit comments

Comments
 (0)