rabbitt-githooks 1.2.7 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,79 +1,77 @@
1
1
  require 'ostruct'
2
2
  require 'pathname'
3
+ require 'githooks/repository/file'
3
4
 
4
5
  module GitHooks
5
- class Repository::DiffIndexEntry < OpenStruct
6
- DIFF_STRUCTURE_REGEXP = %r{
7
- ^:
8
- (?<original_mode>\d+)\s
9
- (?<new_mode>\d+)\s
10
- (?<original_sha>[a-f\d]+)\s
11
- (?<new_sha>[a-f\d]+)\s
12
- (?<change_type>.)
13
- (?:(?<score>\d+)?)\s
14
- (?<file_path>\S+)\s?
15
- (?<rename_path>\S+)?
16
- }xi unless defined? DIFF_STRUCTURE_REGEXP
6
+ class Repository
7
+ class DiffIndexEntry < OpenStruct
8
+ DIFF_STRUCTURE_REGEXP = %r{
9
+ ^:
10
+ (?<original_mode>\d+)\s
11
+ (?<new_mode>\d+)\s
12
+ (?<original_sha>[a-f\d]+)\.*\s
13
+ (?<new_sha>[a-f\d]+)\.*\s
14
+ (?<change_type>.)
15
+ (?:(?<score>\d+)?)\s
16
+ (?<file_path>\S+)\s?
17
+ (?<rename_path>\S+)?
18
+ }xi unless defined? DIFF_STRUCTURE_REGEXP
17
19
 
18
- def self.from_file_path(file_path)
19
- file_path = Pathname.new(file_path)
20
- new(
21
- [
22
- 0,
23
- file_path.stat.mode.to_s(8),
24
- 0x0,
25
- 0x0,
26
- '?',
27
- file_path.to_s
28
- ].join(' ').prepend(':')
29
- )
30
- end
20
+ def self.from_file_path(path, tracked = false)
21
+ path = Pathname.new(path)
22
+ entry_line = sprintf(":%06o %06o %040x %040x %s\t%s",
23
+ 0, path.stat.mode, 0, 0, (tracked ? '^' : '?'), path.to_s)
24
+ new(entry_line)
25
+ end
31
26
 
32
- def initialize(entry)
33
- unless entry =~ DIFF_STRUCTURE_REGEXP
34
- fail ArgumentError, 'Unable to parse incoming diff entry data: #{entry}'
27
+ def initialize(entry)
28
+ unless entry =~ DIFF_STRUCTURE_REGEXP
29
+ fail ArgumentError, "Unable to parse incoming diff entry data: #{entry}"
30
+ end
31
+ super parse_data(entry)
35
32
  end
36
- super parse_data(entry)
37
- end
38
33
 
39
- def parse_data(entry) # rubocop:disable MethodLength
40
- data = Hash[DIFF_STRUCTURE_REGEXP.names.collect(&:to_sym).zip(
41
- entry.match(DIFF_STRUCTURE_REGEXP).captures
42
- )]
34
+ def parse_data(entry) # rubocop:disable MethodLength
35
+ data = Hash[
36
+ DIFF_STRUCTURE_REGEXP.names.collect(&:to_sym).zip(
37
+ entry.match(DIFF_STRUCTURE_REGEXP).captures
38
+ )
39
+ ]
43
40
 
44
- {
45
- from: FileState.new(
46
- data[:original_mode].to_i(8),
47
- data[:original_sha],
48
- data[:file_path].nil? ? nil : Pathname.new(data[:file_path])
49
- ),
50
- to: FileState.new(
51
- data[:new_mode].to_i(8),
52
- data[:new_sha],
53
- data[:rename_path].nil? ? nil : Pathname.new(data[:rename_path])
54
- ),
55
- type: Repository::CHANGE_TYPES[data[:change_type]],
56
- score: data[:score].to_i
57
- }
58
- end
41
+ {
42
+ from: FileState.new(
43
+ data[:original_mode].to_i(8),
44
+ data[:original_sha],
45
+ data[:file_path].nil? ? nil : Pathname.new(data[:file_path])
46
+ ),
47
+ to: FileState.new(
48
+ data[:new_mode].to_i(8),
49
+ data[:new_sha],
50
+ data[:rename_path].nil? ? nil : Pathname.new(data[:rename_path])
51
+ ),
52
+ type: Repository::CHANGE_TYPES[data[:change_type]],
53
+ score: data[:score].to_i
54
+ }
55
+ end
59
56
 
60
- def to_repo_file
61
- Repository::File.new(self)
62
- end
57
+ def to_repo_file
58
+ Repository::File.new(self)
59
+ end
63
60
 
64
- class FileState
65
- attr_reader :mode, :sha, :path
61
+ class FileState
62
+ attr_reader :mode, :sha, :path
66
63
 
67
- def initialize(mode, sha, path)
68
- @mode, @sha, @path = mode, sha, path
69
- end
64
+ def initialize(mode, sha, path)
65
+ @mode, @sha, @path = mode, sha, path
66
+ end
70
67
 
71
- def inspect
72
- "#<#{self.class.name.split('::').last} mode=#{mode.to_s(8)} path=#{path.to_s.inspect} sha=#{sha.inspect}>"
73
- end
68
+ def inspect
69
+ "#<#{self.class.name.split('::').last} mode=#{mode.to_s(8)} path=#{path.to_s.inspect} sha=#{sha.inspect}>"
70
+ end
74
71
 
75
- def to_path
76
- Pathname.new(@path)
72
+ def to_path
73
+ Pathname.new(@path)
74
+ end
77
75
  end
78
76
  end
79
77
  end
@@ -26,100 +26,102 @@ unless defined? DiffIndexEntryDelegateClass
26
26
  end
27
27
 
28
28
  module GitHooks
29
- class Repository::File < DiffIndexEntryDelegateClass
30
- def initialize(entry)
31
- unless entry.is_a? Repository::DiffIndexEntry
32
- fail ArgumentError, "Expected a Repository::DiffIndexEntry but got a '#{entry.class.name}'"
29
+ class Repository
30
+ class File < DiffIndexEntryDelegateClass
31
+ def initialize(entry)
32
+ unless entry.is_a? Repository::DiffIndexEntry
33
+ fail ArgumentError, "Expected a Repository::DiffIndexEntry but got a '#{entry.class.name}'"
34
+ end
35
+ @file = entry
33
36
  end
34
- @file = entry
35
- end
36
37
 
37
- def __getobj__ # rubocop:disable TrivialAccessors
38
- @file
39
- end
38
+ def __getobj__ # rubocop:disable TrivialAccessors
39
+ @file
40
+ end
40
41
 
41
- def inspect
42
- attributes = [:name, :path, :type, :mode, :sha, :score].collect do |name|
43
- "#{name}=#{attribute_value(name).inspect}"
42
+ def inspect
43
+ attributes = [:name, :path, :type, :mode, :sha, :score].collect do |name|
44
+ "#{name}=#{attribute_value(name).inspect}"
45
+ end
46
+ "#<#{self.class.name} #{attributes.join(' ')} >"
44
47
  end
45
- "#<#{self.class.name} #{attributes.join(' ')} >"
46
- end
47
48
 
48
- def path
49
- to.path || from.path
50
- end
49
+ def path
50
+ to.path || from.path
51
+ end
51
52
 
52
- def name
53
- path.basename.to_s
54
- end
53
+ def name
54
+ path.basename.to_s
55
+ end
55
56
 
56
- # rubocop:disable CyclomaticComplexity
57
- def attribute_value(attribute)
58
- case attribute
59
- when :name then name
60
- when :path then path.to_s
61
- when :type then type
62
- when :mode then to.mode
63
- when :sha then to.sha
64
- when :score then score
65
- else fail ArgumentError,
66
- "Invalid attribute type '#{attribute}' - expected: :name, :path, :type, :mode, :sha, or :score"
57
+ # rubocop:disable CyclomaticComplexity
58
+ def attribute_value(attribute)
59
+ case attribute
60
+ when :name then name
61
+ when :path then path.to_s
62
+ when :type then type
63
+ when :mode then to.mode
64
+ when :sha then to.sha
65
+ when :score then score
66
+ else fail ArgumentError,
67
+ "Invalid attribute type '#{attribute}' - expected: :name, :path, :type, :mode, :sha, or :score"
68
+ end
67
69
  end
68
- end
69
70
 
70
- def match(type, _match)
71
- value = attribute_value(type)
72
- return _match.call(value) if _match.respond_to? :call
73
-
74
- case type
75
- when :name then _match.is_a?(Regexp) ? value =~ _match : value == _match
76
- when :path then _match.is_a?(Regexp) ? value =~ _match : value == _match
77
- when :type then _match.is_a?(Array) ? _match.include?(value) : _match == value
78
- when :mode then _match & value == _match
79
- when :sha then _match == value
80
- when :score then _match == value
71
+ def match(type, selector)
72
+ value = attribute_value(type)
73
+ return selector.call(value) if selector.respond_to? :call
74
+
75
+ case type
76
+ when :name then selector.is_a?(Regexp) ? value =~ selector : value == selector
77
+ when :path then selector.is_a?(Regexp) ? value =~ selector : value == selector
78
+ when :type then [*selector].include?(:any) ? true : [*selector].include?(value)
79
+ when :mode then selector & value == selector
80
+ when :sha then selector == value
81
+ when :score then selector == value
82
+ end
81
83
  end
82
- end
83
- # rubocop:enable CyclomaticComplexity
84
+ # rubocop:enable CyclomaticComplexity
84
85
 
85
- def fd
86
- case type
87
- when :deleted, :deletion then nil
88
- else path.open
86
+ def fd
87
+ case type
88
+ when :deleted, :deletion then nil
89
+ else path.open
90
+ end
89
91
  end
90
- end
91
92
 
92
- def realpath
93
- case type
94
- when :deleted, :deletion then path
95
- else path.realpath
93
+ def realpath
94
+ case type
95
+ when :deleted, :deletion then path
96
+ else path.realpath
97
+ end
96
98
  end
97
- end
98
99
 
99
- def contains?(string_or_regexp)
100
- if string_or_regexp.is_a?(Regexp)
101
- contents =~ string_or_regexp
102
- else
103
- contents.include? string_or_regexp
100
+ def contains?(string_or_regexp)
101
+ if string_or_regexp.is_a?(Regexp)
102
+ contents =~ string_or_regexp
103
+ else
104
+ contents.include? string_or_regexp
105
+ end
104
106
  end
105
- end
106
107
 
107
- def grep(regexp)
108
- lines(true).select_with_index { |line|
109
- line =~ regexp
110
- }.collect { |num, line|
111
- [num + 1, line] # line numbers start from 1, not 0
112
- }
113
- end
108
+ def grep(regexp)
109
+ lines(true).select_with_index { |line|
110
+ line =~ regexp
111
+ }.collect { |num, line|
112
+ [num + 1, line] # line numbers start from 1, not 0
113
+ }
114
+ end
114
115
 
115
- def contents
116
- return unless fd
117
- fd.read
118
- end
116
+ def contents
117
+ return unless fd
118
+ fd.read
119
+ end
119
120
 
120
- def lines(strip_newlines = false)
121
- return [] unless fd
122
- strip_newlines ? fd.readlines.collect(&:chomp!) : fd.readlines
121
+ def lines(strip_newlines = false)
122
+ return [] unless fd
123
+ strip_newlines ? fd.readlines.collect(&:chomp!) : fd.readlines
124
+ end
123
125
  end
124
126
  end
125
127
  end
@@ -17,38 +17,47 @@ with this program; if not, write to the Free Software Foundation, Inc.,
17
17
  =end
18
18
 
19
19
  module GitHooks
20
- class Repository::Limiter
21
- attr_reader :type, :only
20
+ class Repository
21
+ class Limiter
22
+ attr_reader :type, :only
22
23
 
23
- def initialize(type, options = {})
24
- @type = type
25
- @only = options.delete(:only) || options.delete(:to)
26
- end
24
+ def initialize(type, options = {})
25
+ @type = type
26
+ @only = options.delete(:only) || options.delete(:to)
27
+ @inverted = false
28
+ end
27
29
 
28
- def only(*args)
29
- return @only if args.empty?
30
- @only = args.flatten
31
- end
32
- alias_method :to, :only
33
-
34
- def limit(files)
35
- files.select! do |file|
36
- match_file(file, @only).tap do |result|
37
- if GitHooks.debug?
38
- result = (result ? 'success' : 'failure')
39
- puts " #{file.path.to_s} (#{file.attribute_value(@type).inspect}) was a #{result}"
30
+ def only(*args)
31
+ return @only if args.empty?
32
+ @only = args.flatten
33
+ self
34
+ end
35
+ alias_method :to, :only
36
+
37
+ def inverted
38
+ @inverted = true
39
+ end
40
+ alias_method :invert, :inverted
41
+
42
+ def limit(files)
43
+ files.select! do |file|
44
+ match_file(file, @only).tap do |result|
45
+ if GitHooks.debug?
46
+ result = (result ? 'success' : 'failure')
47
+ puts " #{file.path} (#{file.attribute_value(@type).inspect}) was a #{result}"
48
+ end
40
49
  end
41
50
  end
42
51
  end
43
- end
44
52
 
45
- private
53
+ private
46
54
 
47
- def match_file(file, match_value)
48
- if match_value.is_a? Array
49
- match_value.any? { |value| file.match(@type, value) }
50
- else
51
- file.match(@type, match_value)
55
+ def match_file(file, match_value)
56
+ if @inverted
57
+ [*match_value].none? { |value| file.match(@type, value) }
58
+ else
59
+ [*match_value].any? { |value| file.match(@type, value) }
60
+ end
52
61
  end
53
62
  end
54
63
  end
@@ -49,7 +49,7 @@ module GitHooks
49
49
  end
50
50
 
51
51
  if script && !(options['ignore-script'] || GitHooks.ignore_script)
52
- command = "#{script} #{Pathname.new($0).to_s} #{Shellwords.join(ARGV)};"
52
+ command = "#{script} #{Pathname.new($0)} #{Shellwords.join(ARGV)};"
53
53
  puts "Kernel#exec(#{command.inspect})" if GitHooks.verbose
54
54
  exec(command)
55
55
  elsif libpath
@@ -106,7 +106,7 @@ module GitHooks
106
106
 
107
107
  hook_phases.each do |hook|
108
108
  hook = (repo_hooks + hook).to_s
109
- puts "Linking #{gitrunner.to_s} -> #{hook}" if GitHooks.verbose
109
+ puts "Linking #{gitrunner} -> #{hook}" if GitHooks.verbose
110
110
  FileUtils.ln_sf gitrunner.to_s, hook
111
111
  end
112
112
  end
@@ -120,10 +120,9 @@ module GitHooks
120
120
  repo = Repository.instance(repo_path)
121
121
 
122
122
  hook_phases.each do |hook|
123
- if (repo_hook = repo_hooks + hook).symlink?
124
- puts "Removing hook '#{hook}' from repository at: #{repo_path}" if GitHooks.verbose
125
- FileUtils.rm_f repo_hook
126
- end
123
+ next unless (repo_hook = (repo_hooks + hook)).symlink?
124
+ puts "Removing hook '#{hook}' from repository at: #{repo_path}" if GitHooks.verbose
125
+ FileUtils.rm_f repo_hook
127
126
  end
128
127
 
129
128
  active_hooks = Hook::VALID_PHASES.select { |hook| (repo_hooks + hook).exist? }
@@ -137,7 +136,7 @@ module GitHooks
137
136
  end
138
137
  module_function :detach
139
138
 
140
- def list(repo_path)
139
+ def list(repo_path) # rubocop:disable Style/CyclomaticComplexity, Style/MethodLength
141
140
  repo_path ||= Pathname.new(Repository.root_path)
142
141
 
143
142
  repo = Repository.instance(repo_path)
@@ -223,14 +222,15 @@ module GitHooks
223
222
  end
224
223
  module_function :run_externals
225
224
 
226
- def start(options = {}) # rubocop:disable MethodLength
225
+ def start(options = {}) # rubocop:disable Style/CyclomaticComplexity, Style/MethodLength
227
226
  phase = options[:hook] || GitHooks.hook_name || 'pre-commit'
228
227
  puts "PHASE: #{phase}" if GitHooks.debug
229
228
 
230
- if active_hook = Hook.phases[phase]
229
+ if (active_hook = Hook.phases[phase])
231
230
  active_hook.args = options.delete(:args)
232
- active_hook.unstaged = options.delete(:unstaged)
231
+ active_hook.staged = options.delete(:staged)
233
232
  active_hook.untracked = options.delete(:untracked)
233
+ active_hook.tracked = options.delete(:tracked)
234
234
  active_hook.repository_path = options.delete(:repo)
235
235
  else
236
236
  fail Error::InvalidPhase, "Hook '#{phase}' is not defined - have you registered any tests for this hook yet?"
@@ -242,10 +242,10 @@ module GitHooks
242
242
 
243
243
  sections.each do |section|
244
244
  hash_tail_length = (section_length - section.title.length)
245
- printf "===== %s %s=====\n", section.colored_name(phase), ('=' * hash_tail_length)
245
+ printf "===== %s %s===== (%ds)\n", section.colored_name(phase), ('=' * hash_tail_length), section.benchmark
246
246
 
247
247
  section.actions.each_with_index do |action, index|
248
- printf " %d. [ %s ] %s\n", (index + 1), action.state_symbol, action.colored_title
248
+ printf " %d. [ %s ] %s (%ds)\n", (index + 1), action.state_symbol, action.colored_title, action.benchmark
249
249
 
250
250
  action.errors.each do |error|
251
251
  printf " %s %s\n", color_bright_red(MARK_FAILURE), error
@@ -270,9 +270,9 @@ module GitHooks
270
270
  end
271
271
  module_function :start
272
272
 
273
- def load_tests(path, skip_bundler = false) # rubocop:disable MethodLength
273
+ def load_tests(path, skip_bundler = false) # rubocop:disable MethodLength,Style/CyclomaticComplexity
274
274
  hooks_root = Pathname.new(path).realpath
275
- hooks_path = hooks_root + 'hooks'
275
+ hooks_path = (p = (hooks_root + 'hooks')).exist? ? p : (hooks_root + '.hooks')
276
276
  hooks_libs = hooks_root + 'libs'
277
277
  gemfile = hooks_root + 'Gemfile'
278
278
 
@@ -285,18 +285,15 @@ module GitHooks
285
285
  # stupid RVM polluting my environment without asking via it's
286
286
  # executable-hooks gem preloading bundler. hence the following ...
287
287
  if defined? Bundler
288
- [:@bundle_path, :@configured, :@definition, :@load].each do |var|
288
+ [:@settings, :@bundle_path, :@configured, :@definition, :@load].each do |var|
289
289
  Bundler.instance_variable_set(var, nil)
290
290
  end
291
- # bundler tests for @settings using defined? - which means we need
292
- # to forcibly remove it.
293
- Bundler.send(:remove_instance_variable, :@settings)
294
291
  else
295
292
  require 'bundler'
296
293
  end
297
294
  Bundler.require(:default)
298
295
  rescue LoadError
299
- puts 'Unable to load bundler - please make sure it\'s installed.'
296
+ puts %Q|Unable to load bundler - please make sure it's installed.|
300
297
  raise # rubocop:disable SignalException
301
298
  rescue Bundler::GemNotFound => e
302
299
  puts "Error: #{e.message}"
@@ -306,6 +303,7 @@ module GitHooks
306
303
  end
307
304
 
308
305
  $LOAD_PATH.unshift hooks_libs.to_s
306
+
309
307
  Dir["#{hooks_path}/**/*.rb"].each do |lib|
310
308
  lib.gsub!('.rb', '')
311
309
  puts "Loading: #{lib}" if GitHooks.verbose