Skip to content

Commit 3e2563c

Browse files
sdsShane da Silva
authored and
Shane da Silva
committed
Add support for commit-msg hooks
Change-Id: Ib159889f57131a40e0868c75f33429534a6db123 Reviewed-on: http://gerrit.causes.com/35560 Reviewed-by: Shane da Silva <shane@causes.com> Tested-by: Shane da Silva <shane@causes.com>
1 parent 69e87d3 commit 3e2563c

24 files changed

+1149
-1105
lines changed

TODO

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
Handle case where hooks are installed but overcommit isn't
1+
# Handle case where hooks are installed but overcommit isn't
22
Add user-friendly installation instructions (e.g. run `overcommit` in git
33
repo and get asked to install if it is not installed)
4-
Symlink all scripts to single Overcommit file
4+
#Symlink all scripts to single Overcommit file
55
Ask to remove sample files
66
Parallelize hook checks
77
Stash non-indexed changes before running checks so real filename can be used
@@ -15,6 +15,9 @@ Allow hooks to specify required executable (`executable` option, coupled with
1515
Allow hooks to specify whether they only apply to lines the user modified
1616
Split out Whitespace check into individual checks
1717
Allow SKIP arguments to be case insensitive
18+
Move ReleaseNote check into Causes repo
19+
Enable hook by simply including it in repo-specific .overcommit.yml (no need
20+
to specify enabled: true)
1821

1922

2023
Allow features of hooks to be customized (stealth, required, etc.)

bin/run-hook

-8
This file was deleted.

config/default.yml

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pre_commit:
2121
AuthorName:
2222
requires_files: false
2323
required: true
24-
silent: true
24+
quiet: true
2525

2626
CoffeeLint:
2727
description: 'Analyzing with coffeelint'
@@ -75,8 +75,15 @@ prepare_commit_msg:
7575
# project.
7676
commit_msg:
7777
ALL:
78+
requires_files: false
7879
quiet: false
7980

81+
GerritChangeId:
82+
enabled: false
83+
description: 'Ensuring Gerrit Change-Id is present'
84+
required: true
85+
quiet: true
86+
8087
HardTabs:
8188
description: 'Checking for hard tabs'
8289

lib/overcommit/configuration.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ def apply_environment!(hook_type, env)
4747
skipped_hooks = "#{env['SKIP']} #{env['SKIP_CHECKS']}".split(/[:, ]/)
4848

4949
if skipped_hooks.include?('all') || skipped_hooks.include?('ALL')
50-
@hash[hook_type]['ALL']['enabled'] = false
50+
@hash[hook_type]['ALL']['skip'] = true
5151
else
5252
skipped_hooks.each do |hook_name|
5353
@hash[hook_type][hook_name] ||= {}
54-
@hash[hook_type][hook_name]['enabled'] = false
54+
@hash[hook_type][hook_name]['skip'] = true
5555
end
5656
end
5757
end

lib/overcommit/hook/base.rb

+12-11
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
module Overcommit::Hook
44
# Functionality common to all hooks.
55
class Base
6-
def initialize(config)
6+
def initialize(config, hook_runner)
77
@config = config.hook_config(self)
8+
@hook_runner = hook_runner
89
end
910

1011
# Runs the hook.
@@ -24,8 +25,8 @@ def required?
2425
@config['required']
2526
end
2627

27-
def silent?
28-
@config['silent']
28+
def quiet?
29+
@config['quiet']
2930
end
3031

3132
def enabled?
@@ -37,8 +38,13 @@ def requires_modified_files?
3738
end
3839

3940
def skip?
40-
!(enabled? || required?) ||
41-
(requires_modified_files? && applicable_files.empty?)
41+
@config['skip']
42+
end
43+
44+
def run?
45+
enabled? &&
46+
(!skip? || required?) &&
47+
!(requires_modified_files? && applicable_files.empty?)
4248
end
4349

4450
# Gets a list of staged files that apply to this hook based on its
@@ -49,13 +55,8 @@ def applicable_files
4955

5056
private
5157

52-
# Get a list of added, copied, or modified files that have been staged.
53-
# Renames and deletions are ignored, since there should be nothing to check.
5458
def staged_files
55-
@staged_files ||=
56-
`git diff --cached --name-only --diff-filter=ACM --ignore-submodules=all`.
57-
split("\n").
58-
map { |relative_file| File.expand_path(relative_file) }
59+
@hook_runner.staged_files
5960
end
6061

6162
# Returns whether the specified file is applicable to this hook based on the
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module Overcommit::Hook::CommitMsg
2+
# Functionality common to all commit-msg hooks.
3+
class Base < Overcommit::Hook::Base
4+
def commit_message
5+
@hook_runner.commit_message
6+
end
7+
end
8+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module Overcommit::Hook::CommitMsg
2+
# Ensures a Gerrit Change-Id line is included in the commit message.
3+
#
4+
# It may seem odd to do this here instead of in a prepare-commit-msg hook, but
5+
# the reality is that if you want to _ensure_ the Change-Id is included then
6+
# you need to do it in a commit-msg hook. This is because the user could still
7+
# edit the message after a prepare-commit-msg hook was run.
8+
class GerritChangeId < Base
9+
SCRIPT_LOCATION = Overcommit::Utils.script_path('gerrit-change-id')
10+
11+
def run
12+
command "#{SCRIPT_LOCATION} #{commit_message_file}"
13+
:good
14+
end
15+
end
16+
end

lib/overcommit/plugins/commit_msg/hard_tabs.rb lib/overcommit/hook/commit_msg/hard_tabs.rb

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
module Overcommit::GitHook
2-
class HardTabs < HookSpecificCheck
3-
include HookRegistry
4-
5-
def run_check
1+
module Overcommit::Hook::CommitMsg
2+
# Checks for hard tabs in commit messages.
3+
class HardTabs < Base
4+
def run
65
# Catches hard tabs entered by the user (not auto-generated)
7-
if commit_message.join.index /\t/
6+
if commit_message.join.index(/\t/)
87
return :warn, "Don't use hard tabs in commit messages"
98
end
109

lib/overcommit/plugins/commit_msg/russian_novel.rb lib/overcommit/hook/commit_msg/russian_novel.rb

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
module Overcommit::GitHook
2-
class RussianNovel < HookSpecificCheck
3-
include HookRegistry
4-
5-
stealth!
6-
1+
module Overcommit::Hook::CommitMsg
2+
# Checks for long commit messages (not good or bad--just fun to point out)
3+
class RussianNovel < Base
74
RUSSIAN_NOVEL_LENGTH = 30
8-
def run_check
5+
6+
def run
97
if commit_message.length > RUSSIAN_NOVEL_LENGTH
108
return :warn, 'You seem to have authored a Russian novel; congratulations!'
119
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module Overcommit::Hook::CommitMsg
2+
# Ensures commit message subject lines are followed by a blank line.
3+
class SingleLineSubject < Base
4+
def run
5+
unless commit_message[1].to_s.strip.empty?
6+
return :warn, 'Subject should be one line and followed by a blank line'
7+
end
8+
9+
:good
10+
end
11+
end
12+
end

lib/overcommit/plugins/commit_msg/text_width.rb lib/overcommit/hook/commit_msg/text_width.rb

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
module Overcommit::GitHook
2-
class TextWidth < HookSpecificCheck
3-
include HookRegistry
4-
5-
def run_check
1+
module Overcommit::Hook::CommitMsg
2+
# Ensures the number of columns the subject and commit message lines occupy is
3+
# under the preferred limits.
4+
class TextWidth < Base
5+
def run
66
if commit_message.first.size > 60
77
return :warn, 'Please keep the subject < ~60 characters'
88
end

lib/overcommit/plugins/commit_msg/trailing_period.rb lib/overcommit/hook/commit_msg/trailing_period.rb

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
module Overcommit::GitHook
2-
class TrailingPeriod < HookSpecificCheck
3-
include HookRegistry
4-
5-
def run_check
1+
module Overcommit::Hook::CommitMsg
2+
# Ensures commit message subject lines do not have a trailing period
3+
class TrailingPeriod < Base
4+
def run
65
if commit_message[0].rstrip.end_with?('.')
76
return :warn, 'Please omit trailing period from commit message subject'
87
end

lib/overcommit/hook/pre_commit/whitespace.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ def run
77
# Catches hard tabs
88
result = command("grep -Inl \"\\t\" #{paths}")
99
unless result.stdout.empty?
10-
return :stop, "Don't use hard tabs:\n#{result.stdout}"
10+
return :bad, "Don't use hard tabs:\n#{result.stdout}"
1111
end
1212

1313
# Catches trailing whitespace, conflict markers etc
1414
result = command('git diff --check --cached')
15-
return :stop, result.stdout unless result.success?
15+
return :bad, result.stdout unless result.success?
1616

1717
:good
1818
end

lib/overcommit/hook_runner/base.rb

+21-9
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ def initialize(config)
99

1010
# Loads and runs the hooks registered for this {HookRunner}.
1111
def run(args, input, logger)
12-
#stash_unstaged_files
12+
@args = args
13+
@input = input
14+
@logger = logger
15+
16+
# stash_unstaged_files
1317
load_hooks
14-
run_hooks(args, input, logger)
18+
run_hooks
1519
ensure
16-
#restore_unstaged_files
20+
# restore_unstaged_files
1721
end
1822

1923
# Returns the type of hook this runner deals with (e.g. "CommitMsg",
@@ -26,14 +30,23 @@ def underscored_hook_type
2630
@underscored_hook_type ||= Overcommit::Utils.underscorize(hook_type)
2731
end
2832

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 staged_files
36+
@staged_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+
2942
private
3043

31-
def run_hooks(args, input, log)
32-
reporter = Overcommit::Reporter.new(self, @hooks, @config, log)
44+
def run_hooks
45+
reporter = Overcommit::Reporter.new(self, @hooks, @config, @logger)
3346

3447
reporter.start_hook_run
3548

36-
@hooks.reject { |hook| hook.skip? }.
49+
@hooks.select { |hook| hook.run? }.
3750
each do |hook|
3851
reporter.with_status(hook) do
3952
hook.run
@@ -72,8 +85,7 @@ def load_hook_plugins
7285
Dir[File.join(directory, '*.rb')].sort do |plugin|
7386
require plugin
7487

75-
hook_name = Overcommit::HookRunner.hook_type_to_class_name(File.basename(plugin, '.rb'))
76-
88+
hook_name = self.class.hook_type_to_class_name(File.basename(plugin, '.rb'))
7789
@hooks << create_hook(hook_name)
7890
end
7991
end
@@ -82,7 +94,7 @@ def load_hook_plugins
8294
# hook configuration.
8395
def create_hook(hook_name)
8496
Overcommit::Hook.const_get("#{hook_type}::#{hook_name}").
85-
new(@config)
97+
new(@config, self)
8698
rescue LoadError, NameError => error
8799
raise Overcommit::Exceptions::HookLoadError,
88100
"Unable to load hook '#{hook_name}': #{error}",
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module Overcommit::HookRunner
2+
# Manages loading and running 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 ||= raw_commit_message.
7+
reject { |line| line =~ /^#/ }.
8+
take_while { |line| !line.start_with?('diff --git') }
9+
end
10+
11+
def raw_commit_message
12+
@raw_commit_message ||= ::IO.readlines(commit_message_file)
13+
end
14+
15+
private
16+
17+
def commit_message_file
18+
unless @args[0] && ::File.exist?(@args[0])
19+
fail 'Not running in the context of a commit message'
20+
end
21+
22+
@args[0]
23+
end
24+
end
25+
end

lib/overcommit/plugins/commit_msg/change_id.rb

-15
This file was deleted.

lib/overcommit/plugins/commit_msg/single_line_subject.rb

-13
This file was deleted.

lib/overcommit/reporter.rb

+5-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def initialize(hook_runner, hooks, config, log)
88
@config = config
99
@name = hook_runner.underscored_hook_type.gsub('_', '-')
1010
@hooks = hooks
11-
@width = 60 - (@hooks.map { |s| s.name.length }.max || 0)
11+
@width = [@hooks.map { |s| s.description.length }.max + 3, 60].max
1212
@results = []
1313
end
1414

@@ -18,7 +18,7 @@ def start_hook_run
1818

1919
def with_status(hook, &block)
2020
title = " #{hook.description}"
21-
unless hook.silent?
21+
unless hook.quiet?
2222
log.partial title
2323
print '.' * (@width - title.length)
2424
end
@@ -37,6 +37,8 @@ def finish_hook_run
3737
else
3838
log.error "✗ One or more #{@name} checks failed"
3939
end
40+
41+
log.log # Newline
4042
end
4143

4244
def checks_passed?
@@ -46,7 +48,7 @@ def checks_passed?
4648
private
4749

4850
def print_hook_result(hook, title, status, output)
49-
if hook.silent?
51+
if hook.quiet?
5052
return if status == :good
5153
log.partial title
5254
end
File renamed without changes.

0 commit comments

Comments
 (0)