Skip to content

Commit 599f797

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 599f797

File tree

9 files changed

+192
-0
lines changed

9 files changed

+192
-0
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ any Ruby code.
4040
* [PrePush](#prepush)
4141
* [PreRebase](#prerebase)
4242
* [Repo-Specific Hooks](#repo-specific-hooks)
43+
* [Gem-provided Hooks](#gem-provided-hooks)
4344
* [Adding Existing Git Hooks](#adding-existing-git-hooks)
4445
* [Security](#security)
4546
* [Contributing](#contributing)
@@ -645,6 +646,24 @@ PreCommit:
645646
include: '**/*_spec.rb'
646647
```
647648
649+
## Gem-provided Hooks
650+
651+
For hooks which should be available from multiple repositories, but which do not
652+
make sense to contribute back to Overcommit, you can package them as a Ruby Gem
653+
and `overcommit` can use them as if they were repo-specific hooks. This would be
654+
useful for an organization that has custom hooks they wish to apply to many
655+
repositories, but manage the hooks in a central way.
656+
657+
These are implemented similarly to Repo-Specific Hooks, but need to be placed
658+
in a Gem in the normal `overcommit` hook path. For example, to add `MyCustomHook`
659+
as a pre_commit hook you would put it here:
660+
`./lib/overcommit/hooks/pre_commit/my_custom_hook.rb`
661+
662+
You must ensure that the Gem is available in the environment where `overcommit` is
663+
being run.
664+
665+
See [Repo-Specific Hooks](#repo-specific-hooks) for details.
666+
648667
### Adding Existing Git Hooks
649668

650669
You might already have hook scripts written which you'd like to integrate with

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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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).with('overcommit/hook/pre_commit/my_custom_hook').and_return(true)
24+
allow(loader).to receive(:create_hook).with('MyCustomHook')
25+
expect(loader).to receive(:require).with('overcommit/hook/pre_commit/my_custom_hook')
26+
load_hooks
27+
end
28+
end
29+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
#require 'overcommit'
5+
#require 'overcommit/hook_loader/gem_hook_loader'
6+
7+
describe Overcommit::HookRunner do
8+
let(:hash) { {} }
9+
let(:config) { Overcommit::Configuration.new(hash) }
10+
let(:logger) { double('logger') }
11+
let(:context) { double('context') }
12+
let(:printer) { double('printer') }
13+
let(:runner) { described_class.new(config, logger, context, printer) }
14+
15+
describe '#load_hooks' do
16+
subject(:load_hooks) { runner.send(:load_hooks) }
17+
18+
before do
19+
context.stub(hook_class_name: 'PreCommit',
20+
hook_type_name: 'pre_commit')
21+
allow_any_instance_of(Overcommit::HookLoader::BuiltInHookLoader).to receive(:load_hooks).and_return([])
22+
allow_any_instance_of(Overcommit::HookLoader::PluginHookLoader).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).not_to receive(:load_hooks)
34+
load_hooks
35+
end
36+
end
37+
38+
context 'when gem_plugins is enabled' do
39+
let(:hash) do
40+
{
41+
'gem_plugins' => true
42+
}
43+
end
44+
let(:gemhookloader) { Overcommit::HookLoader::GemHookLoader.new(config, context,logger) }
45+
46+
it 'expects to load Gem hooks' do
47+
expect_any_instance_of(Overcommit::HookLoader::GemHookLoader).to receive(:load_hooks).and_call_original
48+
load_hooks
49+
end
50+
end
51+
end
52+
end

0 commit comments

Comments
 (0)