rabbitt-githooks 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
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