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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/.rubocop.yml +73 -0
  4. data/Gemfile +21 -0
  5. data/Gemfile.lock +45 -0
  6. data/LICENSE.txt +339 -0
  7. data/README.md +4 -0
  8. data/Rakefile +19 -0
  9. data/bin/githooks +15 -0
  10. data/bin/githooks-runner +17 -0
  11. data/hooks/commit-messages.rb +29 -0
  12. data/hooks/formatting.rb +53 -0
  13. data/lib/githooks.rb +89 -0
  14. data/lib/githooks/action.rb +150 -0
  15. data/lib/githooks/cli.rb +93 -0
  16. data/lib/githooks/commands/config.rb +107 -0
  17. data/lib/githooks/core_ext.rb +23 -0
  18. data/lib/githooks/core_ext/array.rb +3 -0
  19. data/lib/githooks/core_ext/array/extract_options.rb +5 -0
  20. data/lib/githooks/core_ext/array/min_max.rb +37 -0
  21. data/lib/githooks/core_ext/array/select_with_index.rb +13 -0
  22. data/lib/githooks/core_ext/numbers.rb +1 -0
  23. data/lib/githooks/core_ext/numbers/infinity.rb +19 -0
  24. data/lib/githooks/core_ext/pathname.rb +27 -0
  25. data/lib/githooks/core_ext/process.rb +7 -0
  26. data/lib/githooks/core_ext/string.rb +3 -0
  27. data/lib/githooks/core_ext/string/git_option_path_split.rb +6 -0
  28. data/lib/githooks/core_ext/string/inflections.rb +67 -0
  29. data/lib/githooks/core_ext/string/strip_empty_lines.rb +9 -0
  30. data/lib/githooks/error.rb +10 -0
  31. data/lib/githooks/hook.rb +159 -0
  32. data/lib/githooks/repository.rb +152 -0
  33. data/lib/githooks/repository/config.rb +170 -0
  34. data/lib/githooks/repository/diff_index_entry.rb +80 -0
  35. data/lib/githooks/repository/file.rb +125 -0
  36. data/lib/githooks/repository/limiter.rb +55 -0
  37. data/lib/githooks/runner.rb +317 -0
  38. data/lib/githooks/section.rb +98 -0
  39. data/lib/githooks/system_utils.rb +109 -0
  40. data/lib/githooks/terminal_colors.rb +63 -0
  41. data/lib/githooks/version.rb +22 -0
  42. data/rabbitt-githooks.gemspec +49 -0
  43. data/thoughts.txt +56 -0
  44. 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,3 @@
1
+ require_relative 'array/min_max'
2
+ require_relative 'array/select_with_index'
3
+ require_relative 'array/extract_options'
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def extract_options
3
+ last.is_a?(Hash) ? pop : {}
4
+ end
5
+ end
@@ -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,7 @@
1
+ class Process::Status
2
+ def failed?
3
+ !success?
4
+ end
5
+ alias_method :fail?, :failed?
6
+ alias_method :failure?, :failed?
7
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'string/git_option_path_split'
2
+ require_relative 'string/inflections'
3
+ require_relative 'string/strip_empty_lines'
@@ -0,0 +1,6 @@
1
+ class String
2
+ def git_option_path_split
3
+ section, *subsection, option = split('.')
4
+ [section, subsection.join('.'), option]
5
+ end
6
+ 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,9 @@
1
+ class String
2
+ def strip_empty_lines!
3
+ replace(split(/\n/).reject(&:empty?).join("\n"))
4
+ end
5
+
6
+ def strip_empty_lines
7
+ dup.strip_empty_lines!
8
+ end
9
+ 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