rabbitt-githooks 1.3.0 → 1.3.2

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 (39) hide show
  1. checksums.yaml +5 -13
  2. data/Gemfile.lock +29 -19
  3. data/Rakefile +20 -0
  4. data/bin/githooks +2 -1
  5. data/bin/githooks-runner +10 -2
  6. data/lib/githooks.rb +11 -7
  7. data/lib/githooks/action.rb +67 -53
  8. data/lib/githooks/cli.rb +9 -6
  9. data/lib/githooks/commands/config.rb +26 -19
  10. data/lib/githooks/core_ext.rb +2 -1
  11. data/lib/githooks/core_ext/array/extract_options.rb +5 -1
  12. data/lib/githooks/core_ext/array/min_max.rb +4 -4
  13. data/lib/githooks/core_ext/array/select_with_index.rb +1 -1
  14. data/lib/githooks/core_ext/colorize.rb +30 -0
  15. data/lib/githooks/core_ext/object.rb +8 -0
  16. data/lib/githooks/core_ext/pathname.rb +7 -6
  17. data/lib/githooks/core_ext/string.rb +1 -1
  18. data/lib/githooks/core_ext/string/sanitize.rb +51 -0
  19. data/lib/githooks/error.rb +1 -0
  20. data/lib/githooks/hook.rb +21 -26
  21. data/lib/githooks/repository.rb +31 -35
  22. data/lib/githooks/repository/config.rb +16 -24
  23. data/lib/githooks/repository/diff_index_entry.rb +3 -2
  24. data/lib/githooks/repository/file.rb +14 -7
  25. data/lib/githooks/runner.rb +38 -36
  26. data/lib/githooks/section.rb +13 -19
  27. data/lib/githooks/system_utils.rb +132 -47
  28. data/lib/githooks/version.rb +1 -1
  29. data/lib/tasks/dev.task +14 -0
  30. data/rabbitt-githooks.gemspec +18 -15
  31. metadata +58 -33
  32. data/.gitignore +0 -34
  33. data/.hooks/commit-messages.rb +0 -29
  34. data/.hooks/formatting.rb +0 -44
  35. data/.rubocop.yml +0 -87
  36. data/lib/githooks/core_ext/process.rb +0 -9
  37. data/lib/githooks/core_ext/string/strip_empty_lines.rb +0 -9
  38. data/lib/githooks/terminal_colors.rb +0 -62
  39. data/thoughts.txt +0 -56
@@ -16,8 +16,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
16
16
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
17
  =end
18
18
 
19
+ require_relative 'core_ext/object'
19
20
  require_relative 'core_ext/array'
21
+ require_relative 'core_ext/colorize'
20
22
  require_relative 'core_ext/numbers'
21
23
  require_relative 'core_ext/string'
22
24
  require_relative 'core_ext/pathname'
23
- require_relative 'core_ext/process'
@@ -1,5 +1,9 @@
1
1
  class Array
2
- def extract_options
2
+ def extract_options!
3
3
  last.is_a?(Hash) ? pop : {}
4
4
  end
5
+
6
+ def extract_options
7
+ last.is_a?(Hash) ? last : {}
8
+ end
5
9
  end
@@ -19,17 +19,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
19
19
  require_relative '../numbers/infinity'
20
20
 
21
21
  class Array
22
- def min(&block)
22
+ def minimum(&_block)
23
23
  collection = block_given? ? collect { |obj| yield obj } : self
24
- collection.inject(Infinity) do |min, num|
24
+ collection.inject(Infinity) do |min, num| # rubocop:disable Style/EachWithObject
25
25
  min = num < min ? num : min
26
26
  min
27
27
  end
28
28
  end
29
29
 
30
- def max(&block)
30
+ def maximum(&_block)
31
31
  collection = block_given? ? collect { |obj| yield obj } : self
32
- collection.inject(0) do |max, num|
32
+ collection.inject(0) do |max, num| # rubocop:disable Style/EachWithObject
33
33
  max = num > max ? num : max
34
34
  max
35
35
  end
@@ -5,7 +5,7 @@ class Array
5
5
  if regexp.is_a? Regexp
6
6
  collection << [index, node] if node =~ regexp
7
7
  elsif block_given?
8
- collection << [index, node] if yield(node)
8
+ collection << [index, node] if block.call(node)
9
9
  end
10
10
  end
11
11
  end
@@ -0,0 +1,30 @@
1
+ require 'colorize'
2
+
3
+ module Colorize
4
+ module ClassMethods
5
+ unless respond_to? :disable_colorization_without_tty_detection
6
+ alias_method :disable_colorization_without_tty_detection, :disable_colorization
7
+ end
8
+
9
+ def disable_colorization(value = nil)
10
+ # disable colorization when we don't have a tty on STDOUT
11
+ return true unless value || STDOUT.tty?
12
+ disable_colorization_without_tty_detection(value)
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+ def success!
18
+ light_green
19
+ end
20
+
21
+ def failure!
22
+ light_red
23
+ end
24
+
25
+ def unknown!
26
+ light_yellow
27
+ end
28
+ alias_method :warning!, :unknown!
29
+ end
30
+ end
@@ -0,0 +1,8 @@
1
+ class Object
2
+ def deep_dup
3
+ Marshal.load(Marshal.dump(self))
4
+ rescue TypeError => e
5
+ raise e unless e.message.include? "can't dump"
6
+ dup
7
+ end
8
+ end
@@ -3,10 +3,9 @@ require 'pathname'
3
3
  if RUBY_ENGINE == 'jruby'
4
4
  class Pathname
5
5
  def realpath(basedir = nil)
6
- unless (path = java_realpath(basedir)).exist?
7
- fail Errno::ENOENT, path.to_s
6
+ java_realpath(basedir).tap do |path|
7
+ fail Errno::ENOENT, path.to_s unless path.exist?
8
8
  end
9
- path
10
9
  end
11
10
 
12
11
  def realdirpath(basedir = nil)
@@ -14,11 +13,13 @@ if RUBY_ENGINE == 'jruby'
14
13
  end
15
14
 
16
15
  def java_realpath(basedir = nil)
17
- if basedir && !@path.start_with?('/')
18
- path = self.class.new(basedir).realpath + @path
16
+ # rubocop:disable ElseAlignment, EndAlignment
17
+ path = if basedir && @path[0] != '/'
18
+ Pathname.new(basedir).realpath.join(@path)
19
19
  else
20
- path = @path.to_s
20
+ @path.to_s
21
21
  end
22
+ # rubocop:enable ElseAlignment, EndAlignment
22
23
 
23
24
  self.class.new java.io.File.new(path.to_s).canonical_path
24
25
  end
@@ -1,3 +1,3 @@
1
1
  require_relative 'string/git_option_path_split'
2
2
  require_relative 'string/inflections'
3
- require_relative 'string/strip_empty_lines'
3
+ require_relative 'string/sanitize'
@@ -0,0 +1,51 @@
1
+ require_relative '../array/extract_options'
2
+
3
+ class String
4
+ def strip_empty_lines!
5
+ replace(strip_empty_lines)
6
+ end
7
+
8
+ def strip_empty_lines
9
+ split(/\n/).reject { |s| s !~ /\S/ }.join("\n")
10
+ end
11
+
12
+ def strip_non_printable!
13
+ replace(strip_non_printable)
14
+ end
15
+
16
+ def strip_non_printable
17
+ gsub(/[^[:print:] \n\t\x1b]/, '')
18
+ end
19
+
20
+ def strip_colors!
21
+ replace(strip_colors)
22
+ end
23
+
24
+ def strip_colors
25
+ gsub(/\x1b\[\d+(?:;\d+)?m/, '')
26
+ end
27
+
28
+ def sanitize!(*methods)
29
+ options = methods.extract_options!
30
+
31
+ map = {
32
+ strip: :strip!,
33
+ empty_lines: :strip_empty_lines!,
34
+ non_printable: :strip_non_printable!,
35
+ colors: :strip_colors!
36
+ }
37
+
38
+ methods = map.keys if methods.empty? || methods.include?(:all)
39
+ methods -= Array(options.delete(:except)) if options[:except]
40
+
41
+ methods.collect(&:to_sym).each do |method|
42
+ send(map[method]) if map[method]
43
+ end
44
+
45
+ self
46
+ end
47
+
48
+ def sanitize(*methods)
49
+ dup.sanitize!(*methods)
50
+ end
51
+ end
@@ -1,5 +1,6 @@
1
1
  module GitHooks
2
2
  class Error < StandardError
3
+ class CommandExecutionFailure < GitHooks::Error; end
3
4
  class NotAGitRepo < GitHooks::Error; end
4
5
  class Registration < GitHooks::Error; end
5
6
  class TestsFailed < GitHooks::Error; end
data/lib/githooks/hook.rb CHANGED
@@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
17
17
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
  =end
19
19
 
20
+ require_relative 'repository'
20
21
  require_relative 'system_utils'
21
22
 
22
23
  module GitHooks
@@ -27,24 +28,22 @@ module GitHooks
27
28
  @__mutex__ = Mutex.new
28
29
 
29
30
  class << self
30
- def instances # rubocop:disable TrivialAccessors
31
- @__phases__
32
- end
33
- alias_method :phases, :instances
31
+ attr_reader :__phases__
32
+ alias_method :phases, :__phases__
34
33
 
35
- def instance(phase = 'pre-commit')
34
+ def instance(phase = 'pre-commit') # rubocop:disable AbcSize
36
35
  phase = phase.to_s
37
36
  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}"
37
+ fail ArgumentError, "Hook phase (#{phase}) must be one of #{VALID_PHASES.join(', ')}"
40
38
  end
41
39
 
42
- return phases[phase] if phases[phase]
43
-
44
- @__mutex__.synchronize do
45
- return phases[phase] if phases[phase]
46
- phases[phase] = new(phase)
40
+ unless phases[phase]
41
+ @__mutex__.synchronize {
42
+ return phases[phase] if phases[phase]
43
+ phases[phase] = new(phase)
44
+ }
47
45
  end
46
+ phases[phase]
48
47
  end
49
48
  private :instance
50
49
 
@@ -57,8 +56,8 @@ module GitHooks
57
56
  end
58
57
 
59
58
  def register(phase, &block)
60
- fail ArgumentError, 'Missing required block to #register' unless block_given?
61
- self[phase].instance_eval(&block)
59
+ fail ArgumentError, 'expected block, received none' unless block_given?
60
+ instance(phase).instance_eval(&block)
62
61
  end
63
62
  end
64
63
 
@@ -66,7 +65,7 @@ module GitHooks
66
65
  attr_accessor :args, :staged, :untracked, :tracked
67
66
 
68
67
  def initialize(phase)
69
- @phase = phase
68
+ @phase = phase.to_s
70
69
  @sections = {}
71
70
  @commands = []
72
71
  @args = []
@@ -85,35 +84,31 @@ module GitHooks
85
84
  @repository = Repository.new(path)
86
85
  end
87
86
 
88
- def manifest(options = {})
87
+ def manifest
89
88
  @manifest ||= Manifest.new(self)
90
89
  end
91
90
 
92
91
  def run
93
92
  # only run sections that have actions matching files in the manifest
94
- runable_sections = sections.select { |section| !section.actions.empty? }
95
- runable_sections.collect { |section| section.run }.all?
93
+ sections.reject { |s| s.actions.empty? }.collect(&:run).all?
96
94
  end
97
95
 
98
96
  def method_missing(method, *args, &block)
99
- command = find_command(method)
100
- return super unless command
97
+ return super unless command = find_command(method) # rubocop:disable AssignmentInCondition
101
98
  command.execute(*args, &block)
102
99
  end
103
100
 
104
101
  def setup_command(name, options = {})
105
- name = name.to_s.to_sym
106
-
107
102
  @commands << SystemUtils::Command.new(
108
- name,
109
- path: options.delete(:path),
110
- aliases: options.delete(:aliases) || options.delete(:alias)
103
+ name.to_sym,
104
+ chdir: options.delete(:chdir),
105
+ bin_path: options.delete(:bin_path)
111
106
  )
112
107
  end
113
108
  private :setup_command
114
109
 
115
110
  def find_command(name)
116
- @commands.select { |command| command.aliases.include? name.to_s }.first
111
+ @commands.select { |command| command.name == name.to_s }.first
117
112
  end
118
113
 
119
114
  def sections
@@ -17,12 +17,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
17
17
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
  =end
19
19
 
20
- require 'ostruct'
21
- require 'singleton'
22
- require 'open3'
23
-
24
20
  module GitHooks
25
21
  class Repository # rubocop:disable ClassLength
22
+ extend SystemUtils
23
+
24
+ command :git
25
+
26
26
  autoload :Config, 'githooks/repository/config'
27
27
  autoload :File, 'githooks/repository/file'
28
28
  autoload :Limiter, 'githooks/repository/limiter'
@@ -38,11 +38,11 @@ module GitHooks
38
38
  }.freeze unless defined? CHANGE_TYPE_SYMBOLS
39
39
 
40
40
  CHANGE_TYPES = CHANGE_TYPE_SYMBOLS.invert.freeze unless defined? CHANGE_TYPES
41
-
42
41
  DEFAULT_DIFF_INDEX_OPTIONS = { staged: true } unless defined? DEFAULT_DIFF_INDEX_OPTIONS
43
42
 
44
43
  @__instance__ = {}
45
44
  @__mutex__ = Mutex.new
45
+
46
46
  def self.instance(path = Dir.getwd)
47
47
  path = Pathname.new(path).realpath
48
48
  strpath = path.to_s
@@ -70,12 +70,8 @@ module GitHooks
70
70
  @config ||= Repository::Config.new(root_path)
71
71
  end
72
72
 
73
- def git_command(*args)
74
- git.execute(*args.flatten)
75
- end
76
-
77
73
  def get_root_path(path)
78
- git_command('rev-parse', '--show-toplevel', path: path).tap do |result|
74
+ git('rev-parse', '--show-toplevel', chdir: path).tap do |result|
79
75
  unless result.status.success? && result.output !~ /not a git repository/i
80
76
  fail Error::NotAGitRepo, "Unable to find a valid git repo in #{path}"
81
77
  end
@@ -83,30 +79,33 @@ module GitHooks
83
79
  end
84
80
 
85
81
  def stash
86
- git_command(%w( stash -q --keep-index -a)).status.success?
82
+ git(*%w( stash -q --keep-index -a)).status.success?
87
83
  end
88
84
 
89
85
  def unstash
90
- git_command(%w(stash pop -q)).status.success?
86
+ git(*%w(stash pop -q)).status.success?
91
87
  end
92
88
 
93
- def manifest(options = {})
89
+ def manifest(options = {}) # rubocop:disable AbcSize
94
90
  ref = options.delete(:ref)
95
-
96
91
  return staged_manifest(ref: ref) if options.delete(:staged)
97
92
 
98
- files = unstaged_manifest(ref: ref)
93
+ manifest_list = unstaged_manifest(ref: ref)
99
94
 
100
- tracked_manifest(ref: ref).each do |file|
101
- files << file unless files.index { |f| f.path.to_s == file.path.to_s }
102
- end if options.delete(:tracked)
95
+ if options.delete(:tracked)
96
+ tracked_manifest(ref: ref).each_with_object(manifest_list) do |file, list|
97
+ list << file unless list.include?(file)
98
+ end
99
+ end
103
100
 
104
- untracked_manifest(ref: ref).each do |file|
105
- files << file unless files.index { |f| f.path.to_s == file.path.to_s }
106
- end if options.delete(:untracked)
101
+ if options.delete(:untracked)
102
+ untracked_manifest(ref: ref).each_with_object(manifest_list) do |file, list|
103
+ list << file unless list.include?(file)
104
+ end
105
+ end
107
106
 
108
- files.sort_by! { |f| f.path.to_s }
109
- files.uniq { |f| f.path.to_s }
107
+ manifest_list.sort!
108
+ manifest_list.uniq { |f| f.path.to_s }
110
109
  end
111
110
 
112
111
  def staged_manifest(options = {})
@@ -119,18 +118,18 @@ module GitHooks
119
118
  end
120
119
 
121
120
  def tracked_manifest(*)
122
- files = git_command('ls-files', '--exclude-standard').output.strip.split(/\s*\n\s*/)
121
+ files = git('ls-files', '--exclude-standard').output.strip.split(/\s*\n\s*/)
123
122
  files.collect { |path| DiffIndexEntry.from_file_path(path, true).to_repo_file }
124
123
  end
125
124
 
126
125
  def untracked_manifest(*)
127
- files = git_command('ls-files', '--others', '--exclude-standard').output.strip.split(/\s*\n\s*/)
126
+ files = git('ls-files', '--others', '--exclude-standard').output.strip.split(/\s*\n\s*/)
128
127
  files.collect { |path| DiffIndexEntry.from_file_path(path).to_repo_file }
129
128
  end
130
129
 
131
130
  private
132
131
 
133
- def diff_index(options = {})
132
+ def diff_index(options = {}) # rubocop:disable AbcSize
134
133
  options = DEFAULT_DIFF_INDEX_OPTIONS.merge(options)
135
134
 
136
135
  if $stdout.tty? && !options[:staged]
@@ -141,21 +140,18 @@ module GitHooks
141
140
  cmd << (options.delete(:ref) || 'HEAD')
142
141
  end
143
142
 
144
- cmd.compact!
145
-
146
- raw_output = git_command(*cmd.compact).output.strip
147
- raw_output.split(/\n/).collect { |data| DiffIndexEntry.new(data).to_repo_file }
148
- end
149
-
150
- def git
151
- @git ||= SystemUtils::Command.new('git')
143
+ git(*cmd.compact.compact).output_lines.collect do |diff_data|
144
+ DiffIndexEntry.new(diff_data).to_repo_file
145
+ end
146
+ rescue
147
+ exit! 1
152
148
  end
153
149
 
154
150
  def while_stashed(&block)
155
151
  fail ArgumentError, 'Missing required block' unless block_given?
156
152
  begin
157
153
  stash
158
- yield
154
+ block.call
159
155
  ensure
160
156
  unstash
161
157
  end
@@ -17,18 +17,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
17
17
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
  =end
19
19
 
20
- require 'ostruct'
21
- require 'singleton'
22
- require 'open3'
23
-
24
20
  module GitHooks
25
21
  class Repository
26
22
  class Config # rubocop:disable ClassLength
27
23
  OPTIONS = {
28
- 'path' => { type: :path, multiple: false },
29
- 'script' => { type: :path, multiple: false },
30
- 'pre-run-execute' => { type: :path, multiple: true },
31
- 'post-run-execute' => { type: :path, multiple: true }
24
+ 'path' => { type: :path, multiple: false },
25
+ 'script' => { type: :path, multiple: false },
26
+ 'pre-run-execute' => { type: :path, multiple: true },
27
+ 'post-run-execute' => { type: :path, multiple: true },
32
28
  }
33
29
 
34
30
  def initialize(path = Dir.getwd)
@@ -49,7 +45,7 @@ module GitHooks
49
45
  send(option.gsub('-', '_'))
50
46
  end
51
47
 
52
- def set(option, value, options = {}) # rubocop:disable CyclomaticComplexity, MethodLength
48
+ def set(option, value, options = {}) # rubocop:disable CyclomaticComplexity, MethodLength, PerceivedComplexity, AbcSize
53
49
  unless OPTIONS.keys.include? option
54
50
  fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
55
51
  end
@@ -82,11 +78,11 @@ module GitHooks
82
78
 
83
79
  if overwrite && !self[option].nil? && !self[option].empty?
84
80
  puts "Overwrite requested for option '#{option}'" if GitHooks.verbose
85
- unset(option, repo_path: repo, global: global)
81
+ unset(option, repo_chdir: repo, global: global)
86
82
  end
87
83
 
88
84
  option = "githooks.#{repo}.#{option}"
89
- git_command(global ? '--global' : '--local', var_type, add_type, option, value, path: repo).tap do |result|
85
+ git(global ? '--global' : '--local', var_type, add_type, option, value, chdir: repo).tap do |result|
90
86
  puts "Added option #{option} with value #{value}" if result.status.success?
91
87
  end
92
88
  end
@@ -95,10 +91,10 @@ module GitHooks
95
91
  repo = options.delete(:repo_path) || repo_path
96
92
  global = (opt = options.delete(:global)).nil? ? false : opt
97
93
  option = "githooks.#{repo}"
98
- git_command(global ? '--global' : '--local', '--remove-section', option, path: repo)
94
+ git(global ? '--global' : '--local', '--remove-section', option, chdir: repo)
99
95
  end
100
96
 
101
- def unset(option, *args) # rubocop:disable Style/CyclomaticComplexity
97
+ def unset(option, *args) # rubocop:disable CyclomaticComplexity, MethodLength, PerceivedComplexity, AbcSize
102
98
  unless OPTIONS.keys.include? option
103
99
  fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
104
100
  end
@@ -111,9 +107,9 @@ module GitHooks
111
107
  value_regex = args.first
112
108
 
113
109
  if options.delete(:all) || value_regex.nil?
114
- git_command(global ? '--global' : '--local', '--unset-all', option, path: repo)
110
+ git(global ? '--global' : '--local', '--unset-all', option, chdir: repo)
115
111
  else
116
- git_command(global ? '--global' : '--local', '--unset', option, value_regex, path: repo)
112
+ git(global ? '--global' : '--local', '--unset', option, value_regex, chdir: repo)
117
113
  end.tap do |result|
118
114
  puts "Unset option #{option.git_option_path_split.last}" if result.status.success?
119
115
  end
@@ -140,16 +136,16 @@ module GitHooks
140
136
  @repository.root_path
141
137
  end
142
138
 
143
- def git_command(*args)
139
+ def git(*args)
144
140
  args = ['config', *args].flatten
145
- @repository.git_command(*args)
141
+ @repository.git(*args)
146
142
  end
147
143
 
148
- def config(path = nil) # rubocop:disable MethodLength, CyclomaticComplexity
144
+ def config(path = nil) # rubocop:disable CyclomaticComplexity, MethodLength, PerceivedComplexity, AbcSize
149
145
  path ||= repo_path
150
146
 
151
- raw_config = git_command('--list', path: path).output.split("\n")
152
- raw_config.sort.uniq.inject({}) do |hash, line|
147
+ raw_config = git('--list', chdir: path).output.split("\n")
148
+ raw_config.sort.uniq.each_with_object({}) do |line, hash|
153
149
  key, value = line.split(/\s*=\s*/)
154
150
  key_parts = key.git_option_path_split
155
151
 
@@ -168,10 +164,6 @@ module GitHooks
168
164
  hash
169
165
  end
170
166
  end
171
-
172
- def git
173
- @repository.git
174
- end
175
167
  end
176
168
  end
177
169
  end