rabbitt-githooks 1.3.2 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/githooks/hook.rb CHANGED
@@ -22,7 +22,7 @@ require_relative 'system_utils'
22
22
 
23
23
  module GitHooks
24
24
  class Hook
25
- VALID_PHASES = %w{ pre-commit commit-msg }.freeze
25
+ VALID_PHASES = %w{ pre-commit commit-msg }.freeze unless defined? VALID_PHASES
26
26
 
27
27
  @__phases__ = {}
28
28
  @__mutex__ = Mutex.new
@@ -61,19 +61,19 @@ module GitHooks
61
61
  end
62
62
  end
63
63
 
64
- attr_reader :sections, :phase, :repository, :repository_path
64
+ attr_reader :sections, :phase, :repository, :repository_path, :limiters
65
65
  attr_accessor :args, :staged, :untracked, :tracked
66
66
 
67
67
  def initialize(phase)
68
- @phase = phase.to_s
69
- @sections = {}
70
- @commands = []
71
- @args = []
72
- @staged = true
73
- @tracked = false
74
- @untracked = false
75
-
76
- repository_path = Dir.getwd # rubocop:disable UselessAssignment
68
+ @phase = phase.to_s
69
+ @sections = {}
70
+ @limiters = {}
71
+ @commands = []
72
+ @args = []
73
+ @staged = true
74
+ @tracked = false
75
+ @untracked = false
76
+ @repository = Repository.new(Dir.getwd)
77
77
  end
78
78
 
79
79
  def [](name)
@@ -117,6 +117,29 @@ module GitHooks
117
117
 
118
118
  # DSL methods
119
119
 
120
+ def config_path
121
+ GitHooks.hooks_root.join('configs')
122
+ end
123
+
124
+ def config_file(*path_components)
125
+ config_path.join(*path_components)
126
+ end
127
+
128
+ def lib_path
129
+ GitHooks.hooks_root.join('lib')
130
+ end
131
+
132
+ def lib_file(*path_components)
133
+ lib_path.join(*path_components)
134
+ end
135
+
136
+ def limit(type)
137
+ unless @limiters.include? type
138
+ @limiters[type] ||= Repository::Limiter.new(type)
139
+ end
140
+ @limiters[type]
141
+ end
142
+
120
143
  def command(name, options = {})
121
144
  setup_command name, options
122
145
  end
@@ -160,8 +183,8 @@ module GitHooks
160
183
 
161
184
  def filter(limiters)
162
185
  manifest.dup.tap do |files|
163
- limiters.each do |limiter|
164
- puts "Limiter [#{limiter.type}] -> (#{limiter.only.inspect}) match against: " if GitHooks.debug?
186
+ limiters.each do |type, limiter|
187
+ puts "Limiter [#{type}] -> (#{limiter.only.inspect}) match against: " if GitHooks.debug?
165
188
  limiter.limit(files)
166
189
  end
167
190
  end
@@ -133,7 +133,7 @@ module GitHooks
133
133
  private
134
134
 
135
135
  def repo_path
136
- @repository.root_path
136
+ @repository.path
137
137
  end
138
138
 
139
139
  def git(*args)
@@ -18,8 +18,8 @@ module GitHooks
18
18
 
19
19
  def self.from_file_path(path, tracked = false)
20
20
  path = Pathname.new(path)
21
- entry_line = sprintf(":%06o %06o %040x %040x %s\t%s",
22
- 0, path.stat.mode, 0, 0, (tracked ? '^' : '?'), path.to_s)
21
+ entry_line = format(":%06o %06o %040x %040x %s\t%s",
22
+ 0, path.stat.mode, 0, 0, (tracked ? '^' : '?'), path.to_s)
23
23
  new(entry_line)
24
24
  end
25
25
 
@@ -59,15 +59,17 @@ module GitHooks
59
59
  instance.public_send(method, *args, &block)
60
60
  end
61
61
 
62
- attr_reader :root_path
62
+ attr_reader :path, :hooks
63
+ alias_method :path, :path
63
64
 
64
65
  def initialize(path = Dir.getwd)
65
- @root_path = get_root_path(path)
66
+ @path = get_root_path(path)
67
+ @hooks = Pathname.new(@path).join('.git', 'hooks')
66
68
  end
67
69
  protected :initialize
68
70
 
69
71
  def config
70
- @config ||= Repository::Config.new(root_path)
72
+ @config ||= Repository::Config.new(path)
71
73
  end
72
74
 
73
75
  def get_root_path(path)
@@ -140,10 +142,13 @@ module GitHooks
140
142
  cmd << (options.delete(:ref) || 'HEAD')
141
143
  end
142
144
 
143
- git(*cmd.compact.compact).output_lines.collect do |diff_data|
145
+ git(*cmd.flatten.compact).output_lines.collect do |diff_data|
144
146
  DiffIndexEntry.new(diff_data).to_repo_file
145
147
  end
146
- rescue
148
+ rescue StandardError => e
149
+ puts 'Error Encountered while acquiring manifest'
150
+ puts "Command: git #{cmd.flatten.compact.join(' ')}"
151
+ puts "Error: #{e.class.name}: #{e.message}: #{e.backtrace[0..5].join("\n\t")}"
147
152
  exit! 1
148
153
  end
149
154
 
@@ -27,75 +27,68 @@ require_relative 'repository'
27
27
  require_relative 'system_utils'
28
28
 
29
29
  module GitHooks
30
- module Runner
31
- # rubocop:disable CyclomaticComplexity, MethodLength, AbcSize, PerceivedComplexity
32
- def run(options = {})
33
- options = Thor::CoreExt::HashWithIndifferentAccess.new(options)
34
-
35
- options['staged'] = options['staged'].nil? ? true : options['staged']
30
+ class Runner # rubocop:disable Metrics/ClassLength
31
+ attr_reader :repository, :script, :hook_path, :repo_path, :options
32
+ private :repository, :script, :hook_path, :repo_path, :options
36
33
 
37
- repo = options['repo'] ||= Repository.root_path
38
- script = options['script'] ||= Repository.instance(repo).config.script
39
- libpath = options['path'] ||= Repository.instance(repo).config.path
40
- args = options['args'] ||= []
34
+ def initialize(options = {}) # rubocop:disable Metrics/AbcSize
35
+ @repo_path = Pathname.new(options.delete('repo') || Repository.path)
36
+ @repository = Repository.instance(@repo_path)
37
+ @hook_path = acquire_hooks_path(options.delete('path') || @repository.config.path || @repository.path)
38
+ @script = options.delete('script') || @repository.config.script
39
+ @options = IndifferentAccessOpenStruct.new(options)
41
40
 
42
41
  GitHooks.verbose = !!ENV['GITHOOKS_VERBOSE']
43
42
  GitHooks.debug = !!ENV['GITHOOKS_DEBUG']
43
+ end
44
44
 
45
- if options['skip-pre']
45
+ # rubocop:disable CyclomaticComplexity, MethodLength, AbcSize, PerceivedComplexity
46
+ def run
47
+ options.staged = options.staged.nil? ? true : options.staged
48
+
49
+ if options.skip_pre
46
50
  puts 'Skipping PreRun Executables'
47
51
  else
48
- run_externals('pre-run-execute', repo, args)
52
+ run_externals('pre-run-execute')
49
53
  end
50
54
 
51
- if script && !(options['ignore-script'] || GitHooks.ignore_script)
55
+ if script && !(options.ignore_script || GitHooks.ignore_script)
52
56
  command = "#{script} #{Pathname.new($0)} #{Shellwords.join(ARGV)};"
53
57
  puts "Kernel#exec(#{command.inspect})" if GitHooks.verbose
54
58
  exec(command)
55
- elsif libpath
56
- load_tests(libpath, options['skip-bundler'])
57
- start(options)
59
+ elsif hook_path
60
+ load_tests(hook_path, options.skip_bundler)
61
+ start
58
62
  else
59
- puts %q"I can't figure out what to run - specify either path or script to give me a hint..."
63
+ puts %q"I can't figure out what to run! Specify either path or script to give me a hint..."
60
64
  end
61
65
 
62
- if options['skip-post']
66
+ if options.skip_post
63
67
  puts 'Skipping PostRun Executables'
64
68
  else
65
- run_externals('post-run-execute', repo, args)
69
+ run_externals('post-run-execute')
66
70
  end
67
71
  rescue GitHooks::Error::NotAGitRepo => e
68
72
  puts "Unable to find a valid git repo in #{repo}."
69
73
  puts 'Please specify path to repo via --repo <path>' if GitHooks::SCRIPT_NAME == 'githooks'
70
74
  raise e
71
75
  end
72
- module_function :run
73
-
74
- def attach(options = {})
75
- repo_path = options[:repo] || Repository.root_path
76
- repo_path = Pathname.new(repo_path) unless repo_path.nil?
77
- repo_hooks = repo_path.join('.git', 'hooks')
78
- entry_path = options[:script] || options[:path]
79
76
 
80
- hook_phases = options[:hooks]
81
- hook_phases ||= Hook::VALID_PHASES
82
-
83
- bootstrapper = options[:bootstrap]
84
- bootstrapper = Pathname.new(bootstrapper).realpath unless bootstrapper.nil?
85
- entry_path = Pathname.new(entry_path).realdirpath
86
-
87
- repo = Repository.instance(repo_path)
77
+ def attach
78
+ entry_path = Pathname.new(options.script || options.path).realdirpath
79
+ hook_phases = options.hooks || Hook::VALID_PHASES
80
+ bootstrapper = Pathname.new(options.bootstrap).realpath if options.bootstrap
88
81
 
89
82
  if entry_path.directory?
90
- if path = repo.config['path'] # rubocop:disable AssignmentInCondition
83
+ if path = repository.config['path'] # rubocop:disable AssignmentInCondition
91
84
  fail Error::AlreadyAttached, "Repository [#{repo_path}] already attached to path #{path} - Detach to continue."
92
85
  end
93
- repo.config.set('path', entry_path)
86
+ repository.config.set('path', entry_path)
94
87
  elsif entry_path.executable?
95
- if path = repo.config['script'] # rubocop:disable AssignmentInCondition
88
+ if path = repository.config['script'] # rubocop:disable AssignmentInCondition
96
89
  fail Error::AlreadyAttached, "Repository [#{repo_path}] already attached to script #{path}. Detach to continue."
97
90
  end
98
- repo.config.set('script', entry_path)
91
+ repository.config.set('script', entry_path)
99
92
  else
100
93
  fail ArgumentError, "Provided path '#{entry_path}' is neither a directory nor an executable file."
101
94
  end
@@ -105,49 +98,35 @@ module GitHooks
105
98
  gitrunner ||= (GitHooks::BIN_PATH + 'githooks-runner').realpath
106
99
 
107
100
  hook_phases.each do |hook|
108
- hook = (repo_hooks + hook).to_s
101
+ hook = (@repository.hooks + hook).to_s
109
102
  puts "Linking #{gitrunner} -> #{hook}" if GitHooks.verbose
110
103
  FileUtils.ln_sf gitrunner.to_s, hook
111
104
  end
112
105
  end
113
- module_function :attach
114
-
115
- def detach(repo_path, hook_phases)
116
- repo_path ||= Repository.root_path
117
- repo_hooks = Pathname.new(repo_path).join('.git', 'hooks')
118
- hook_phases ||= Hook::VALID_PHASES
119
106
 
120
- repo = Repository.instance(repo_path)
121
-
122
- hook_phases.each do |hook|
123
- next unless (repo_hook = (repo_hooks + hook)).symlink?
124
- puts "Removing hook '#{hook}' from repository at: #{repo_path}" if GitHooks.verbose
107
+ def detach(hook_phases = nil)
108
+ (hook_phases || Hook::VALID_PHASES).each do |hook|
109
+ next unless (repo_hook = (@repository.hooks + hook)).symlink?
110
+ puts "Removing hook '#{hook}' from repository at: #{repository.path}" if GitHooks.verbose
125
111
  FileUtils.rm_f repo_hook
126
112
  end
127
113
 
128
- active_hooks = Hook::VALID_PHASES.select { |hook| (repo_hooks + hook).exist? }
114
+ active_hooks = Hook::VALID_PHASES.select { |hook| (@repository.hooks + hook).exist? }
129
115
 
130
116
  if active_hooks.empty?
131
117
  puts 'All hooks detached. Removing configuration section.'
132
- repo.config.remove_section(repo_path: repo_path)
118
+ repo.config.remove_section(repo_path: repository.path)
133
119
  else
134
120
  puts "Keeping configuration for active hooks: #{active_hooks.join(', ')}"
135
121
  end
136
122
  end
137
- module_function :detach
138
-
139
- def list(repo_path)
140
- repo_path ||= Pathname.new(Repository.root_path)
141
123
 
142
- repo = Repository.instance(repo_path)
143
- script = repo.config.script
144
- libpath = repo.config.path
145
-
146
- unless script || libpath
124
+ def list
125
+ unless script || hook_path
147
126
  fail Error::NotAttached, 'Repository currently not configured. Usage attach to setup for use with githooks.'
148
127
  end
149
128
 
150
- if (executables = repo.config.pre_run_execute).size > 0
129
+ if (executables = repository.config.pre_run_execute).size > 0
151
130
  puts 'PreRun Executables (in execution order):'
152
131
  puts executables.collect { |exe| " #{exe}" }.join("\n")
153
132
  puts
@@ -159,13 +138,13 @@ module GitHooks
159
138
  puts
160
139
  end
161
140
 
162
- if libpath
141
+ if hook_path
163
142
  puts 'Main Testing Library with Tests (in execution order):'
164
143
  puts ' Tests loaded from:'
165
- puts " #{libpath}"
144
+ puts " #{hook_path}"
166
145
  puts
167
146
 
168
- SystemUtils.quiet { load_tests(libpath, true) }
147
+ SystemUtils.quiet { load_tests(hook_path, true) }
169
148
 
170
149
  %w{ pre-commit commit-msg }.each do |phase|
171
150
  next unless Hook.phases[phase]
@@ -173,7 +152,7 @@ module GitHooks
173
152
  puts " Phase #{phase.camelize}:"
174
153
  Hook.phases[phase].sections.each_with_index do |section, section_index|
175
154
  printf " %3d: %s\n", section_index + 1, section.title
176
- section.all.each_with_index do |action, action_index|
155
+ section.actions.each_with_index do |action, action_index|
177
156
  printf " %3d: %s\n", action_index + 1, action.title
178
157
  action.limiters.each_with_index do |limiter, limiter_index|
179
158
  type, value = limiter.type.inspect, limiter.only
@@ -187,7 +166,7 @@ module GitHooks
187
166
  puts
188
167
  end
189
168
 
190
- if (executables = repo.config.post_run_execute).size > 0
169
+ if (executables = repository.config.post_run_execute).size > 0
191
170
  puts 'PostRun Executables (in execution order):'
192
171
  executables.each do |exe|
193
172
  puts " #{exe}"
@@ -195,16 +174,26 @@ module GitHooks
195
174
  puts
196
175
  end
197
176
  rescue Error::NotAGitRepo
198
- puts "Unable to find a valid git repo in #{repo}."
177
+ puts "Unable to find a valid git repo in #{repository}."
199
178
  puts 'Please specify path to repo via --repo <path>' if GitHooks::SCRIPT_NAME == 'githooks'
200
179
  raise
201
180
  end
202
- module_function :list
203
181
 
204
182
  private
205
183
 
206
- def run_externals(which, repo_path, args)
207
- Repository.instance(repo_path).config[which].all? do |executable|
184
+ def acquire_hooks_path(path)
185
+ path = Pathname.new(path) unless path.is_a? Pathname
186
+ path.tap do # return input path by default
187
+ return path if path.include? 'hooks'
188
+ return path if path.include? '.hooks'
189
+ return p if (p = path.join('hooks')).exist? # rubocop:disable Lint/UselessAssignment
190
+ return p if (p = path.join('.hooks')).exist? # rubocop:disable Lint/UselessAssignment
191
+ end
192
+ end
193
+
194
+ def run_externals(which)
195
+ args = options.args || []
196
+ repository.config[which].all? { |executable|
208
197
  command = SystemUtils::Command.new(File.basename(executable), bin_path: executable)
209
198
 
210
199
  puts "#{which.camelize}: #{command.build_command(args)}" if GitHooks.verbose
@@ -217,20 +206,19 @@ module GitHooks
217
206
  end
218
207
  end
219
208
  r.status.success?
220
- end || fail(TestsFailed, "Failed #{which.camelize} executables - giving up")
209
+ } || fail(TestsFailed, "Failed #{which.camelize} executables - giving up")
221
210
  end
222
- module_function :run_externals
223
211
 
224
- def start(options = {}) # rubocop:disable CyclomaticComplexity, MethodLength
225
- phase = options[:hook] || GitHooks.hook_name || 'pre-commit'
212
+ def start # rubocop:disable CyclomaticComplexity, MethodLength
213
+ phase = options.hook || GitHooks.hook_name || 'pre-commit'
226
214
  puts "PHASE: #{phase}" if GitHooks.debug
227
215
 
228
216
  if (active_hook = Hook.phases[phase])
229
- active_hook.args = options.delete(:args)
230
- active_hook.staged = options.delete(:staged)
231
- active_hook.untracked = options.delete(:untracked)
232
- active_hook.tracked = options.delete(:tracked)
233
- active_hook.repository_path = options.delete(:repo)
217
+ active_hook.args = options.args
218
+ active_hook.staged = options.staged
219
+ active_hook.untracked = options.untracked
220
+ active_hook.tracked = options.tracked
221
+ active_hook.repository_path = repository.path
234
222
  else
235
223
  fail Error::InvalidPhase, "Hook '#{phase}' is not defined - have you registered any tests for this hook yet?"
236
224
  end
@@ -268,26 +256,39 @@ module GitHooks
268
256
 
269
257
  exit(success ? 0 : 1)
270
258
  end
271
- module_function :start
272
259
 
273
- def load_tests(path, skip_bundler = false)
274
- hooks_root = Pathname.new(path).realpath
275
- hooks_path = (p = (hooks_root.join('hooks'))).exist? ? p : (hooks_root.join('.hooks'))
276
- hooks_libs = hooks_root.join('libs')
277
- gemfile = hooks_root.join('Gemfile')
260
+ def load_tests(hooks_path, skip_bundler = false)
261
+ # hooks stored locally in the repo_root should have their libs, init and
262
+ # gemfile stored in the hooks directory itself, whereas hooks stored
263
+ # in a separate repository should have have them all stored relative
264
+ # to the separate hooks directory.
265
+ if hook_path.to_s.start_with? repository.path
266
+ hook_data_path = acquire_hooks_path(repository.path)
267
+ else
268
+ hook_data_path = acquire_hooks_path(hook_path)
269
+ end
270
+
271
+ hooks_libs = hook_data_path.join('lib')
272
+ hooks_init = hook_data_path.join('hooks_init.rb')
273
+ gemfile = hook_data_path.join('Gemfile')
274
+
275
+ GitHooks.hooks_root = hook_data_path
278
276
 
279
- if gemfile.exist? && !skip_bundler
277
+ if gemfile.exist? && !(skip_bundler.nil? ? ENV.include?('GITHOOKS_SKIP_BUNDLER') : skip_bundler)
280
278
  puts "loading Gemfile from: #{gemfile}" if GitHooks.verbose
281
279
 
282
280
  begin
283
- ENV['BUNDLE_GEMFILE'] = (hooks_root + 'Gemfile').to_s
281
+ ENV['BUNDLE_GEMFILE'] = gemfile.to_s
284
282
 
285
283
  # stupid RVM polluting my environment without asking via it's
286
284
  # executable-hooks gem preloading bundler. hence the following ...
287
285
  if defined? Bundler
288
- [:@settings, :@bundle_path, :@configured, :@definition, :@load].each do |var|
286
+ [:@bundle_path, :@configured, :@definition, :@load].each do |var|
289
287
  ::Bundler.instance_variable_set(var, nil)
290
288
  end
289
+ # bundler tests for @settings using defined? - which means we need
290
+ # to forcibly remove it.
291
+ Bundler.send(:remove_instance_variable, :@settings)
291
292
  else
292
293
  require 'bundler'
293
294
  end
@@ -304,13 +305,18 @@ module GitHooks
304
305
 
305
306
  $LOAD_PATH.unshift hooks_libs.to_s
306
307
 
307
- Dir["#{hooks_path}/**/*.rb"].each do |lib|
308
- lib.gsub!('.rb', '')
309
- puts "Loading: #{lib}" if GitHooks.verbose
310
- require lib
308
+ if hooks_init.exist?
309
+ puts "Loading hooks from #{hooks_init} ..." if GitHooks.verbose?
310
+ require hooks_init.sub_ext('').to_s
311
+ else
312
+ puts 'Loading hooks brute-force style ...' if GitHooks.verbose?
313
+ Dir["#{hooks_path}/**/*.rb"].each do |lib|
314
+ lib.gsub!('.rb', '')
315
+ puts " -> #{lib}" if GitHooks.verbose
316
+ require lib
317
+ end
311
318
  end
312
319
  end
313
- module_function :load_tests
314
320
 
315
321
  # rubocop:enable CyclomaticComplexity, MethodLength, AbcSize, PerceivedComplexity
316
322
  end
@@ -21,10 +21,10 @@ require 'delegate'
21
21
 
22
22
  module GitHooks
23
23
  class Section < DelegateClass(Array)
24
- attr_reader :name, :hook, :success, :actions, :benchmark
24
+ attr_reader :name, :hook, :success, :actions, :benchmark, :limiters
25
+
25
26
  alias_method :title, :name
26
27
  alias_method :success?, :success
27
- alias_method :all, :actions
28
28
 
29
29
  class << self
30
30
  def key_from_name(name)
@@ -36,6 +36,7 @@ module GitHooks
36
36
  @name = name.to_s.titleize
37
37
  @success = true
38
38
  @actions = []
39
+ @limiters = hook.limiters
39
40
  @hook = hook
40
41
  @benchmark = 0
41
42
 
@@ -85,12 +86,6 @@ module GitHooks
85
86
  success? ? text.success! : text.failure!
86
87
  end
87
88
 
88
- def action(title, &block)
89
- fail ArgumentError, 'expected block, received none' unless block_given?
90
- @actions << Action.new(title, self, &block)
91
- self
92
- end
93
-
94
89
  def run
95
90
  running!
96
91
  begin
@@ -101,5 +96,36 @@ module GitHooks
101
96
  finished!
102
97
  end
103
98
  end
99
+
100
+ ## DSL
101
+
102
+ def config_path
103
+ GitHooks.hooks_root.join('configs')
104
+ end
105
+
106
+ def config_file(*path_components)
107
+ config_path.join(*path_components)
108
+ end
109
+
110
+ def lib_path
111
+ GitHooks.hooks_root.join('lib')
112
+ end
113
+
114
+ def lib_file(*path_components)
115
+ lib_path.join(*path_components)
116
+ end
117
+
118
+ def limit(type)
119
+ unless @limiters.include? type
120
+ @limiters[type] ||= Repository::Limiter.new(type)
121
+ end
122
+ @limiters[type]
123
+ end
124
+
125
+ def action(title, &block)
126
+ fail ArgumentError, 'expected block, received none' unless block_given?
127
+ @actions << Action.new(title, self, &block)
128
+ self
129
+ end
104
130
  end
105
131
  end
@@ -14,8 +14,8 @@ module GitHooks
14
14
 
15
15
  def find_bin(name)
16
16
  # rubocop:disable MultilineBlockChain, Blocks
17
- ENV['PATH'].split(/:/).collect {
18
- |path| Pathname.new(path) + name.to_s
17
+ ENV['PATH'].split(/:/).collect { |path|
18
+ Pathname.new(path) + name.to_s
19
19
  }.select { |path|
20
20
  path.exist? && path.executable?
21
21
  }.collect(&:to_s)
@@ -67,10 +67,12 @@ module GitHooks
67
67
  LANG LC_ALL SHELL SHLVL TERM TMPDIR USER
68
68
  SSH_USER SSH_AUTH_SOCK
69
69
  GEM_HOME GEM_PATH MY_RUBY_HOME
70
- )
70
+ GIT_DIR GIT_AUTHOR_DATE GIT_INDEX_FILE GIT_AUTHOR_NAME GIT_PREFIX GIT_AUTHOR_EMAIL
71
+ ) unless defined? ENV_WHITELIST
71
72
 
72
73
  class Result
73
- attr_reader :output, :error, :status
74
+ attr_accessor :output, :error
75
+ attr_reader :status
74
76
  def initialize(output, error, status)
75
77
  @output = output.strip
76
78
  @error = error.strip
@@ -135,7 +137,7 @@ module GitHooks
135
137
  def prep_env(env = {})
136
138
  Hash[env].each_with_object([]) do |(k, v), array|
137
139
  array << %Q|#{k}="#{v}"| if ENV_WHITELIST.include? k
138
- end.join(' ')
140
+ end
139
141
  end
140
142
 
141
143
  def execute(*args, &_block) # rubocop:disable MethodLength, CyclomaticComplexity, AbcSize, PerceivedComplexity
@@ -154,27 +156,39 @@ module GitHooks
154
156
  command.push options.delete(:post_run) if options[:post_run]
155
157
  command = shellwords(command.flatten.join(';'))
156
158
 
157
- environment = prep_env(options.delete(:env) || ENV)
159
+ environment = prep_env(options.delete(:env) || ENV).join(' ')
158
160
 
159
161
  error_file = Tempfile.new('ghstderr')
162
+
163
+ script_file = Tempfile.new('ghscript')
164
+ script_file.puts "exec 2>#{error_file.path}"
165
+ script_file.puts command.join(' ')
166
+
167
+ script_file.rewind
168
+
160
169
  begin
161
- real_command = %Q{
162
- /usr/bin/env -i #{environment} bash -c '
163
- ( #{command.join(' ').gsub("'", %q|'"'"'|)}) 2>#{error_file.path}
164
- '
165
- }
170
+ real_command = "/usr/bin/env -i #{environment} bash #{script_file.path}"
166
171
 
167
- $stderr.puts real_command if GitHooks.debug?
172
+ if GitHooks.verbose?
173
+ $stderr.puts "Command Line :\n----\n#{real_command}\n----\n"
174
+ $stderr.puts "Command Script:\n----\n#{script_file.read}\n----\n"
175
+ end
168
176
 
169
177
  output = %x{ #{real_command} }
170
178
  result = Result.new(output, error_file.read, $?)
171
179
 
172
- if GitHooks.verbose? && result.failure?
173
- STDERR.puts "Command failed with exit code [#{result.status.exitstatus}]",
174
- "ENVIRONMENT:\n\t#{environment}\n\n",
175
- "COMMAND:\n\t#{command.join(' ')}\n\n",
176
- "OUTPUT:\n-----\n#{result.output}\n-----\n\n",
177
- "ERROR:\n-----\n#{result.error}\n-----\n\n"
180
+ if GitHooks.verbose?
181
+ if result.failure?
182
+ STDERR.puts "Command failed with exit code [#{result.status.exitstatus}]",
183
+ "ENVIRONMENT:\n\t#{environment}\n\n",
184
+ "COMMAND:\n\t#{command.join(' ')}\n\n",
185
+ "OUTPUT:\n-----\n#{result.output}\n-----\n\n",
186
+ "ERROR:\n-----\n#{result.error}\n-----\n\n"
187
+ else
188
+ STDERR.puts "Command succeeded with exit code [#{result.status.exitstatus}]",
189
+ "OUTPUT:\n-----\n#{result.output}\n-----\n\n",
190
+ "ERROR:\n-----\n#{result.error}\n-----\n\n"
191
+ end
178
192
  end
179
193
 
180
194
  sanitize = [ :strip, :non_printable ]
@@ -182,8 +196,11 @@ module GitHooks
182
196
  sanitize << :empty_lines if options.delete(:strip_empty_lines)
183
197
  result.sanitize!(*sanitize)
184
198
 
185
- block_given? ? yield(result) : result
199
+ result.tap { yield(result) if block_given? }
186
200
  ensure
201
+ script_file.close
202
+ script_file.unlink
203
+
187
204
  error_file.close
188
205
  error_file.unlink
189
206
  end
@@ -18,5 +18,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
18
18
  =end
19
19
 
20
20
  module GitHooks
21
- VERSION = '1.3.2'
21
+ VERSION = '1.4.1' unless defined? VERSION
22
22
  end