rabbitt-githooks 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|