rabbitt-githooks 1.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.rubocop.yml +73 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +339 -0
- data/README.md +4 -0
- data/Rakefile +19 -0
- data/bin/githooks +15 -0
- data/bin/githooks-runner +17 -0
- data/hooks/commit-messages.rb +29 -0
- data/hooks/formatting.rb +53 -0
- data/lib/githooks.rb +89 -0
- data/lib/githooks/action.rb +150 -0
- data/lib/githooks/cli.rb +93 -0
- data/lib/githooks/commands/config.rb +107 -0
- data/lib/githooks/core_ext.rb +23 -0
- data/lib/githooks/core_ext/array.rb +3 -0
- data/lib/githooks/core_ext/array/extract_options.rb +5 -0
- data/lib/githooks/core_ext/array/min_max.rb +37 -0
- data/lib/githooks/core_ext/array/select_with_index.rb +13 -0
- data/lib/githooks/core_ext/numbers.rb +1 -0
- data/lib/githooks/core_ext/numbers/infinity.rb +19 -0
- data/lib/githooks/core_ext/pathname.rb +27 -0
- data/lib/githooks/core_ext/process.rb +7 -0
- data/lib/githooks/core_ext/string.rb +3 -0
- data/lib/githooks/core_ext/string/git_option_path_split.rb +6 -0
- data/lib/githooks/core_ext/string/inflections.rb +67 -0
- data/lib/githooks/core_ext/string/strip_empty_lines.rb +9 -0
- data/lib/githooks/error.rb +10 -0
- data/lib/githooks/hook.rb +159 -0
- data/lib/githooks/repository.rb +152 -0
- data/lib/githooks/repository/config.rb +170 -0
- data/lib/githooks/repository/diff_index_entry.rb +80 -0
- data/lib/githooks/repository/file.rb +125 -0
- data/lib/githooks/repository/limiter.rb +55 -0
- data/lib/githooks/runner.rb +317 -0
- data/lib/githooks/section.rb +98 -0
- data/lib/githooks/system_utils.rb +109 -0
- data/lib/githooks/terminal_colors.rb +63 -0
- data/lib/githooks/version.rb +22 -0
- data/rabbitt-githooks.gemspec +49 -0
- data/thoughts.txt +56 -0
- metadata +175 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
module GitHooks
|
2
|
+
module CLI
|
3
|
+
class Config < Thor
|
4
|
+
VALID_CONFIG_OPTIONS = %w( path script pre-run-execute post-run-execute )
|
5
|
+
|
6
|
+
# class_option :verbose, type: :boolean, desc: 'verbose output', default: false
|
7
|
+
# class_option :debug, type: :boolean, desc: 'debug output', default: false
|
8
|
+
|
9
|
+
class_option :global, aliases: '-G', type: :boolean, desc: 'use global config', default: false
|
10
|
+
class_option :hooks, { # rubocop:disable BracesAroundHashParameters
|
11
|
+
type: :array,
|
12
|
+
desc: 'choose specific hooks to affect',
|
13
|
+
enum: %w( pre-commit commit-msg )
|
14
|
+
}
|
15
|
+
class_option :repo, { # rubocop:disable BracesAroundHashParameters
|
16
|
+
aliases: '-r',
|
17
|
+
type: :string,
|
18
|
+
desc: 'Repository path to look up configuration values for.'
|
19
|
+
}
|
20
|
+
|
21
|
+
desc :get, 'display the value for a configuration option'
|
22
|
+
def get(option_name) # rubocop:disable MethodLength
|
23
|
+
unless VALID_CONFIG_OPTIONS.include? option_name
|
24
|
+
puts "Invalid option '#{option_name}': expected one of #{VALID_CONFIG_OPTIONS.join(', ')}"
|
25
|
+
return 1
|
26
|
+
end
|
27
|
+
|
28
|
+
GitHooks.verbose = true if options['verbose']
|
29
|
+
GitHooks.debug = true if options['debug']
|
30
|
+
options['repo'] ||= GitHooks::Repository.root_path
|
31
|
+
|
32
|
+
repo_data = GitHooks::Repository::Config.new.get(
|
33
|
+
option_name,
|
34
|
+
repo_path: options['repo'], global: options['global']
|
35
|
+
)
|
36
|
+
if repo_data.nil?
|
37
|
+
puts "Repository [#{options['repo']}] option '#{option_name}' is currently not set."
|
38
|
+
return
|
39
|
+
end
|
40
|
+
[repo_data].flatten.each do |value|
|
41
|
+
puts "#{option_name}: #{value || 'not set'}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
desc :set, 'Sets the configuration value '
|
46
|
+
method_option :'overwrite-all', { # rubocop:disable BracesAroundHashParameters
|
47
|
+
aliases: '-O',
|
48
|
+
type: :boolean,
|
49
|
+
desc: 'overwrite all existing values.',
|
50
|
+
default: false
|
51
|
+
}
|
52
|
+
def set(option_name, option_value)
|
53
|
+
GitHooks.verbose = true if options['verbose']
|
54
|
+
GitHooks.debug = true if options['debug']
|
55
|
+
options['repo'] ||= GitHooks::Repository.root_path
|
56
|
+
|
57
|
+
GitHooks::Repository::Config.new.set(
|
58
|
+
option_name,
|
59
|
+
option_value,
|
60
|
+
repo_path: options['repo'],
|
61
|
+
global: options['global'],
|
62
|
+
overwrite: options['overwrite-all']
|
63
|
+
).status.success?
|
64
|
+
rescue ArgumentError => e
|
65
|
+
puts e.message
|
66
|
+
end
|
67
|
+
|
68
|
+
desc :unset, 'Unsets a configuration value'
|
69
|
+
def unset(option_name, option_value = nil)
|
70
|
+
GitHooks.verbose = true if options['verbose']
|
71
|
+
GitHooks.debug = true if options['debug']
|
72
|
+
options['repo'] ||= GitHooks::Repository.root_path
|
73
|
+
|
74
|
+
GitHooks::Repository::Config.new.unset(
|
75
|
+
option_name,
|
76
|
+
option_value,
|
77
|
+
repo_path: options['repo'],
|
78
|
+
global: options['global']
|
79
|
+
)
|
80
|
+
rescue ArgumentError => e
|
81
|
+
puts e.message
|
82
|
+
end
|
83
|
+
|
84
|
+
desc :list, 'Lists all githooks configuration values'
|
85
|
+
def list
|
86
|
+
puts options.inspect
|
87
|
+
|
88
|
+
GitHooks.verbose = true if options['verbose']
|
89
|
+
GitHooks.debug = true if options['debug']
|
90
|
+
|
91
|
+
options['repo'] ||= GitHooks::Repository.root_path
|
92
|
+
config = GitHooks::Repository::Config.new
|
93
|
+
|
94
|
+
githooks = config.list(global: options['global'], repo_path: options['repo'])['githooks']
|
95
|
+
githooks.each do |path, data|
|
96
|
+
puts "Repository #{path}:"
|
97
|
+
key_size, value_size = data.keys.collect(&:size).max, data.values.collect(&:size).max
|
98
|
+
data.each do |key, value|
|
99
|
+
[value].flatten.each do |v|
|
100
|
+
printf " %-#{key_size}s : %-#{value_size}s\n", key, v
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end if githooks
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2013 Carl P. Corliss
|
3
|
+
|
4
|
+
This program is free software; you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation; either version 2 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License along
|
15
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
=end
|
18
|
+
|
19
|
+
require_relative 'core_ext/array'
|
20
|
+
require_relative 'core_ext/numbers'
|
21
|
+
require_relative 'core_ext/string'
|
22
|
+
require_relative 'core_ext/pathname'
|
23
|
+
require_relative 'core_ext/process'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2013 Carl P. Corliss
|
3
|
+
|
4
|
+
This program is free software; you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation; either version 2 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License along
|
15
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
=end
|
18
|
+
|
19
|
+
require_relative '../numbers/infinity'
|
20
|
+
|
21
|
+
class Array
|
22
|
+
def min(&block)
|
23
|
+
collection = block_given? ? collect { |obj| yield obj } : self
|
24
|
+
collection.inject(Infinity) do |min, num|
|
25
|
+
min = num < min ? num : min
|
26
|
+
min
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def max(&block)
|
31
|
+
collection = block_given? ? collect { |obj| yield obj } : self
|
32
|
+
collection.inject(0) do |max, num|
|
33
|
+
max = num > max ? num : max
|
34
|
+
max
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Array
|
2
|
+
def select_with_index(regexp = nil, &block)
|
3
|
+
[].tap do |collection|
|
4
|
+
each_with_index do |node, index|
|
5
|
+
if regexp.is_a? Regexp
|
6
|
+
collection << [index, node] if node =~ regexp
|
7
|
+
elsif block_given?
|
8
|
+
collection << [index, node] if yield(node)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'numbers/infinity'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2013 Carl P. Corliss
|
3
|
+
|
4
|
+
This program is free software; you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation; either version 2 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License along
|
15
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
=end
|
18
|
+
|
19
|
+
Infinity = 1.0 / 0 unless defined? Infinity
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
if RUBY_ENGINE == 'jruby'
|
4
|
+
class Pathname
|
5
|
+
def realpath(basedir = nil)
|
6
|
+
unless (path = java_realpath(basedir)).exist?
|
7
|
+
fail Errno::ENOENT, path.to_s
|
8
|
+
end
|
9
|
+
path
|
10
|
+
end
|
11
|
+
|
12
|
+
def realdirpath(basedir = nil)
|
13
|
+
java_realpath(basedir)
|
14
|
+
end
|
15
|
+
|
16
|
+
def java_realpath(basedir = nil)
|
17
|
+
if basedir && !@path.start_with?('/')
|
18
|
+
path = self.class.new(basedir).realpath + @path
|
19
|
+
else
|
20
|
+
path = @path.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
self.class.new java.io.File.new(path.to_s).canonical_path
|
24
|
+
end
|
25
|
+
private :java_realpath
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
=begin
|
2
|
+
Mostly borrowed from Rails' ActiveSupport::Inflections
|
3
|
+
=end
|
4
|
+
|
5
|
+
class String
|
6
|
+
def constantize
|
7
|
+
names = split('::')
|
8
|
+
names.shift if names.empty? || names.first.empty?
|
9
|
+
|
10
|
+
names.inject(Object) do |obj, name|
|
11
|
+
obj.const_defined?(name) ? obj.const_get(name) : obj.const_missing(name)
|
12
|
+
end
|
13
|
+
rescue NameError => e
|
14
|
+
raise unless e.message =~ /uninitialized constant/
|
15
|
+
end
|
16
|
+
|
17
|
+
def camelize
|
18
|
+
dup.camelize!
|
19
|
+
end
|
20
|
+
|
21
|
+
def camelize!
|
22
|
+
tap do
|
23
|
+
gsub!('-', '_')
|
24
|
+
sub!(/^[a-z\d]*/, &:capitalize)
|
25
|
+
gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
|
26
|
+
gsub!('/', '::')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def underscore
|
31
|
+
dup.underscore!
|
32
|
+
end
|
33
|
+
|
34
|
+
def underscore!
|
35
|
+
tap do
|
36
|
+
gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
37
|
+
gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
38
|
+
tr!('-', '_')
|
39
|
+
downcase!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def titleize
|
44
|
+
dup.titleize!
|
45
|
+
end
|
46
|
+
alias_method :titlize, :titleize
|
47
|
+
|
48
|
+
def titleize!
|
49
|
+
tap do
|
50
|
+
replace(
|
51
|
+
split(/\b/).collect(&:capitalize).join
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
alias_method :titlize!, :titleize!
|
56
|
+
|
57
|
+
def dasherize
|
58
|
+
dup.dasherize!
|
59
|
+
end
|
60
|
+
|
61
|
+
def dasherize!
|
62
|
+
tap do
|
63
|
+
underscore!
|
64
|
+
gsub!(/_/, '-')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module GitHooks
|
2
|
+
class Error < StandardError
|
3
|
+
class NotAGitRepo < GitHooks::Error; end
|
4
|
+
class Registration < GitHooks::Error; end
|
5
|
+
class TestsFailed < GitHooks::Error; end
|
6
|
+
class AlreadyAttached < GitHooks::Error; end
|
7
|
+
class NotAttached < GitHooks::Error; end
|
8
|
+
class InvalidPhase < GitHooks::Error; end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright (C) 2013 Carl P. Corliss
|
4
|
+
|
5
|
+
This program is free software; you can redistribute it and/or modify
|
6
|
+
it under the terms of the GNU General Public License as published by
|
7
|
+
the Free Software Foundation; either version 2 of the License, or
|
8
|
+
(at your option) any later version.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU General Public License along
|
16
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
17
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
18
|
+
=end
|
19
|
+
|
20
|
+
require_relative 'system_utils'
|
21
|
+
|
22
|
+
module GitHooks
|
23
|
+
class Hook
|
24
|
+
VALID_PHASES = %w{ pre-commit commit-msg }.freeze
|
25
|
+
|
26
|
+
@__phases__ = {}
|
27
|
+
@__mutex__ = Mutex.new
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def instances # rubocop:disable TrivialAccessors
|
31
|
+
@__phases__
|
32
|
+
end
|
33
|
+
alias_method :phases, :instances
|
34
|
+
|
35
|
+
def instance(phase = 'pre-commit')
|
36
|
+
phase = phase.to_s
|
37
|
+
unless VALID_PHASES.include? phase
|
38
|
+
valid_phases = VALID_PHASES.collect(&:inspect).join(', ')
|
39
|
+
fail ArgumentError, "Hook phase (#{phase.inspect}) must be one of #{valid_phases}"
|
40
|
+
end
|
41
|
+
|
42
|
+
return phases[phase] if phases[phase]
|
43
|
+
|
44
|
+
@__mutex__.synchronize do
|
45
|
+
return phases[phase] if phases[phase]
|
46
|
+
phases[phase] = new(phase)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
private :instance
|
50
|
+
|
51
|
+
alias_method :[], :instance
|
52
|
+
private :[]
|
53
|
+
|
54
|
+
def method_missing(method, *args, &block)
|
55
|
+
return super unless instance.public_methods.include? method
|
56
|
+
instance.public_send(method, *args, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def register(phase, &block)
|
60
|
+
fail ArgumentError, 'Missing required block to #register' unless block_given?
|
61
|
+
self[phase].instance_eval(&block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :sections, :phase, :repository, :repository_path
|
66
|
+
attr_accessor :args, :unstaged, :untracked
|
67
|
+
|
68
|
+
def initialize(phase)
|
69
|
+
@phase = phase
|
70
|
+
@sections = []
|
71
|
+
@commands = []
|
72
|
+
@args = []
|
73
|
+
@unstaged = false
|
74
|
+
@untracked = false
|
75
|
+
|
76
|
+
repository_path = Dir.getwd # rubocop:disable UselessAssignment
|
77
|
+
end
|
78
|
+
|
79
|
+
def repository_path=(path)
|
80
|
+
@repository = Repository.new(path)
|
81
|
+
end
|
82
|
+
|
83
|
+
def manifest(options = {})
|
84
|
+
@manifest ||= Manifest.new(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
def run
|
88
|
+
# only run sections that have actions matching files in the manifest
|
89
|
+
runable_sections = @sections.select { |section| !section.actions.empty? }
|
90
|
+
runable_sections.collect { |section| section.run }.all?
|
91
|
+
end
|
92
|
+
|
93
|
+
def method_missing(method, *args, &block)
|
94
|
+
command = find_command(method)
|
95
|
+
return super unless command
|
96
|
+
command.execute(*args, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup_command(name, options = {})
|
100
|
+
name = name.to_s.to_sym
|
101
|
+
|
102
|
+
@commands << SystemUtils::Command.new(
|
103
|
+
name,
|
104
|
+
path: options.delete(:path),
|
105
|
+
aliases: options.delete(:aliases) || options.delete(:alias)
|
106
|
+
)
|
107
|
+
end
|
108
|
+
private :setup_command
|
109
|
+
|
110
|
+
def find_command(name)
|
111
|
+
@commands.select { |command| command.aliases.include? name.to_s }.first
|
112
|
+
end
|
113
|
+
|
114
|
+
# DSL methods
|
115
|
+
|
116
|
+
def command(name, options = {})
|
117
|
+
setup_command name, options
|
118
|
+
end
|
119
|
+
|
120
|
+
def commands(*names)
|
121
|
+
return @commands if names.empty?
|
122
|
+
names.each { |name| command name }
|
123
|
+
end
|
124
|
+
|
125
|
+
def section(name, &block)
|
126
|
+
@sections << Section.new(name, self, &block)
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
class Manifest
|
131
|
+
attr_reader :hook
|
132
|
+
private :hook
|
133
|
+
|
134
|
+
def initialize(hook)
|
135
|
+
@hook = hook
|
136
|
+
end
|
137
|
+
|
138
|
+
def repo
|
139
|
+
@hook.repository
|
140
|
+
end
|
141
|
+
|
142
|
+
def manifest
|
143
|
+
@files ||= repo.manifest(
|
144
|
+
untracked: hook.untracked,
|
145
|
+
unstaged: hook.unstaged
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
def filter(limiters)
|
150
|
+
manifest.dup.tap do |files|
|
151
|
+
limiters.each do |limiter|
|
152
|
+
puts "Limiter [#{limiter.type}] -> (#{limiter.only.inspect}) match against: " if GitHooks.debug?
|
153
|
+
limiter.limit(files)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|