git-fastclone 1.3.3 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cef4cf9ee8a10c9fb00ccb58382046fa71298ad8263985156f8e1b7c3dc85af4
4
- data.tar.gz: 53dbb091ce1e6f02db4ee9aa89b03791aae4f790d816e33b1f792b9912fde28f
3
+ metadata.gz: 7e915bc1276ac42ac35559122dc1c12f4a04778032ec4510fe96a64b37fc4a7a
4
+ data.tar.gz: 9fcb6e4a441106da6b09f2d4b39cf0e740942ae99de051712fba27656d962f04
5
5
  SHA512:
6
- metadata.gz: 184ddbefb71a17d2521f898ebf6f2f7887249d7f61423bb55b85e58ce1d3d637c5c7246fdf34058957274fbc3ee62e65be37bcb41b8e803143035c5ff0a13c4f
7
- data.tar.gz: 1d8b9882c5f1b3f48044cd4c71e8cae8d14c747f52ad7314a5897e137430a7e6af8bc215e8296b722cc5208fe4aac4d04f22d4bb7c1e173ca64719bbb76fad06
6
+ metadata.gz: fc52b41c18799ec2154000bc7762c8301ed50f28d2eb252e4adcdc82ff36cdc6e3fa772d85db4d52ea95782ea16e02ea0460f1488b5ce95a745ee8a451511a3d
7
+ data.tar.gz: f86877adef196e5416e69aac06c7129bfaf22a21a566a5afe05e4780da60cdc3f0e2379e81d1bc0b4e8bd129d18a893f925dbd7096a4057ef428f085a964b2f2
data/README.md CHANGED
@@ -74,6 +74,8 @@ keep the code as readable as possible.
74
74
  Before accepting any pull requests, we need you to sign an [Individual Contributor Agreement][2]
75
75
  (Google form).
76
76
 
77
+ Once landed, please reach out to any owner listed in https://rubygems.org/gems/git-fastclone and ask them to help publish the new version.
78
+
77
79
 
78
80
  Acknowledgements
79
81
  ----------------
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Version string for git-fastclone
4
4
  module GitFastCloneVersion
5
- VERSION = '1.3.3'
5
+ VERSION = '1.4.1'
6
6
  end
data/lib/git-fastclone.rb CHANGED
@@ -16,9 +16,8 @@
16
16
 
17
17
  require 'optparse'
18
18
  require 'fileutils'
19
- require 'logger'
20
- require 'terrapin'
21
19
  require 'timeout'
20
+ require_relative 'runner_execution'
22
21
 
23
22
  # Contains helper module UrlHelper and execution class GitFastClone::Runner
24
23
  module GitFastClone
@@ -68,13 +67,14 @@ module GitFastClone
68
67
  require 'colorize'
69
68
 
70
69
  include GitFastClone::UrlHelper
70
+ include RunnerExecution
71
71
 
72
72
  DEFAULT_REFERENCE_REPO_DIR = '/var/tmp/git-fastclone/reference'
73
73
 
74
74
  DEFAULT_GIT_ALLOW_PROTOCOL = 'file:git:http:https:ssh'
75
75
 
76
76
  attr_accessor :reference_dir, :prefetch_submodules, :reference_updated, :reference_mutex,
77
- :options, :logger, :abs_clone_path, :using_local_repo, :verbose, :color,
77
+ :options, :abs_clone_path, :using_local_repo, :verbose, :color,
78
78
  :flock_timeout_secs
79
79
 
80
80
  def initialize
@@ -94,8 +94,6 @@ module GitFastClone
94
94
 
95
95
  self.options = {}
96
96
 
97
- self.logger = nil # Only set in verbose mode
98
-
99
97
  self.abs_clone_path = Dir.pwd
100
98
 
101
99
  self.using_local_repo = false
@@ -119,8 +117,7 @@ module GitFastClone
119
117
  end
120
118
 
121
119
  puts "Cloning #{path_from_git_url(url)} to #{File.join(abs_clone_path, path)}"
122
- Terrapin::CommandLine.environment['GIT_ALLOW_PROTOCOL'] =
123
- ENV['GIT_ALLOW_PROTOCOL'] || DEFAULT_GIT_ALLOW_PROTOCOL
120
+ ENV['GIT_ALLOW_PROTOCOL'] ||= DEFAULT_GIT_ALLOW_PROTOCOL
124
121
  clone(url, options[:branch], path, options[:config])
125
122
  end
126
123
 
@@ -137,11 +134,6 @@ module GitFastClone
137
134
 
138
135
  opts.on('-v', '--verbose', 'Verbose mode') do
139
136
  self.verbose = true
140
- self.logger = Logger.new($stdout)
141
- logger.formatter = proc do |_severity, _datetime, _progname, msg|
142
- "#{msg}\n"
143
- end
144
- Terrapin::CommandLine.logger = logger
145
137
  end
146
138
 
147
139
  opts.on('-c', '--color', 'Display colored output') do
@@ -217,19 +209,16 @@ module GitFastClone
217
209
  with_git_mirror(url) do |mirror, attempt_number|
218
210
  clear_clone_dest_if_needed(attempt_number, clone_dest)
219
211
 
220
- clone_command = '--quiet --reference :mirror :url :path'
221
- clone_command += ' --config :config' unless config.nil?
222
- Terrapin::CommandLine.new('git clone', clone_command)
223
- .run(mirror: mirror.to_s,
224
- url: url.to_s,
225
- path: clone_dest,
226
- config: config.to_s)
212
+ clone_commands = ['git', 'clone', verbose ? '--verbose' : '--quiet']
213
+ clone_commands << '--reference' << mirror.to_s << url.to_s << clone_dest
214
+ clone_commands << '--config' << config.to_s unless config.nil?
215
+ fail_on_error(*clone_commands, quiet: !verbose)
227
216
  end
228
217
 
229
218
  # Only checkout if we're changing branches to a non-default branch
230
219
  if rev
231
220
  Dir.chdir(File.join(abs_clone_path, src_dir)) do
232
- Terrapin::CommandLine.new('git checkout', '--quiet :rev').run(rev: rev.to_s)
221
+ fail_on_error('git', 'checkout', '--quiet', rev.to_s, quiet: !verbose)
233
222
  end
234
223
  end
235
224
 
@@ -252,9 +241,12 @@ module GitFastClone
252
241
 
253
242
  threads = []
254
243
  submodule_url_list = []
244
+ output = ''
245
+ Dir.chdir(File.join(abs_clone_path, pwd).to_s) do
246
+ output = fail_on_error('git', 'submodule', 'init', quiet: !verbose)
247
+ end
255
248
 
256
- Terrapin::CommandLine.new('cd', ':path; git submodule init 2>&1')
257
- .run(path: File.join(abs_clone_path, pwd)).split("\n").each do |line|
249
+ output.split("\n").each do |line|
258
250
  submodule_path, submodule_url = parse_update_info(line)
259
251
  submodule_url_list << submodule_url
260
252
 
@@ -268,10 +260,11 @@ module GitFastClone
268
260
  def thread_update_submodule(submodule_url, submodule_path, threads, pwd)
269
261
  threads << Thread.new do
270
262
  with_git_mirror(submodule_url) do |mirror, _|
271
- Terrapin::CommandLine.new('cd', ':dir; git submodule update --quiet --reference :mirror :path')
272
- .run(dir: File.join(abs_clone_path, pwd).to_s,
273
- mirror: mirror.to_s,
274
- path: submodule_path.to_s)
263
+ Dir.chdir(File.join(abs_clone_path, pwd).to_s) do
264
+ cmd = ['git', 'submodule', verbose ? nil : '--quiet', 'update', '--reference', mirror.to_s,
265
+ submodule_path.to_s].compact
266
+ fail_on_error(*cmd, quiet: !verbose)
267
+ end
275
268
  end
276
269
 
277
270
  update_submodules(File.join(pwd, submodule_path), submodule_url)
@@ -343,43 +336,46 @@ module GitFastClone
343
336
  # that this repo has been updated on this run of fastclone
344
337
  def store_updated_repo(url, mirror, repo_name, fail_hard)
345
338
  unless Dir.exist?(mirror)
346
- Terrapin::CommandLine.new('git clone', '--mirror :url :mirror')
347
- .run(url: url.to_s, mirror: mirror.to_s)
339
+ fail_on_error('git', 'clone', verbose ? '--verbose' : '--quiet', '--mirror', url.to_s, mirror.to_s,
340
+ quiet: !verbose)
348
341
  end
349
342
 
350
- Terrapin::CommandLine.new('cd', ':path; git remote update --prune').run(path: mirror)
351
-
343
+ Dir.chdir(mirror) do
344
+ cmd = ['git', 'remote', verbose ? '--verbose' : nil, 'update', '--prune'].compact
345
+ fail_on_error(*cmd, quiet: !verbose)
346
+ end
352
347
  reference_updated[repo_name] = true
353
- rescue Terrapin::ExitStatusError => e
348
+ rescue RunnerExecutionRuntimeError => e
354
349
  # To avoid corruption of the cache, if we failed to update or check out we remove
355
350
  # the cache directory entirely. This may cause the current clone to fail, but if the
356
351
  # underlying error from git is transient it will not affect future clones.
357
- FileUtils.remove_entry_secure(mirror, force: true)
352
+ clear_cache(mirror, url)
358
353
  raise e if fail_hard
359
354
  end
360
355
 
361
356
  def retriable_error?(error)
362
357
  error_strings = [
363
- 'fatal: missing blob object',
364
- 'fatal: remote did not send all necessary objects',
365
- /fatal: packed object [a-z0-9]+ \(stored in .*?\) is corrupt/,
366
- /fatal: pack has \d+ unresolved delta/,
367
- 'error: unable to read sha1 file of ',
368
- 'fatal: did not receive expected object',
358
+ /^fatal: missing blob object/,
359
+ /^fatal: remote did not send all necessary objects/,
360
+ /^fatal: packed object [a-z0-9]+ \(stored in .*?\) is corrupt/,
361
+ /^fatal: pack has \d+ unresolved delta/,
362
+ /^error: unable to read sha1 file of /,
363
+ /^fatal: did not receive expected object/,
369
364
  /^fatal: unable to read tree [a-z0-9]+\n^warning: Clone succeeded, but checkout failed/
370
365
  ]
371
- error.to_s =~ /^STDERR:\n.*^#{Regexp.union(error_strings)}/m
366
+ error.to_s =~ /.*#{Regexp.union(error_strings)}/m
372
367
  end
373
368
 
374
369
  def print_formatted_error(error)
375
370
  indented_error = error.to_s.split("\n").map { |s| "> #{s}\n" }.join
376
- puts "Encountered a retriable error:\n#{indented_error}\n\nRemoving the fastclone cache."
371
+ puts "[INFO] Encountered a retriable error:\n#{indented_error}\n"
377
372
  end
378
373
 
379
374
  # To avoid corruption of the cache, if we failed to update or check out we remove
380
375
  # the cache directory entirely. This may cause the current clone to fail, but if the
381
376
  # underlying error from git is transient it will not affect future clones.
382
377
  def clear_cache(dir, url)
378
+ puts "[WARN] Removing the fastclone cache at #{dir}"
383
379
  FileUtils.remove_entry_secure(dir, force: true)
384
380
  reference_updated.delete(reference_repo_name(url))
385
381
  end
@@ -405,9 +401,9 @@ module GitFastClone
405
401
  with_reference_repo_lock(url) do
406
402
  yield dir, attempt_number
407
403
  end
408
- rescue Terrapin::ExitStatusError => e
409
- if retriable_error?(e)
410
- print_formatted_error(e)
404
+ rescue RunnerExecutionRuntimeError => e
405
+ if retriable_error?(e.output)
406
+ print_formatted_error(e.output)
411
407
  clear_cache(dir, url)
412
408
 
413
409
  if attempt_number < retries_allowed
@@ -416,7 +412,7 @@ module GitFastClone
416
412
  end
417
413
  end
418
414
 
419
- raise
415
+ raise e
420
416
  end
421
417
 
422
418
  def usage
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable all
3
+
4
+ require 'open3'
5
+ require 'logger'
6
+
7
+ # Execution primitives that force explicit error handling and never call the shell.
8
+ # Cargo-culted from internal BuildExecution code on top of public version: https://github.com/square/build_execution
9
+ module RunnerExecution
10
+ class RunnerExecutionRuntimeError < RuntimeError
11
+ attr_reader :status, :exitstatus, :command, :output
12
+
13
+ def initialize(status, command, output = nil)
14
+ @status = status
15
+ @exitstatus = status.exitstatus
16
+ @command = command
17
+ @output = output
18
+
19
+ super "#{status.inspect}\n#{command.inspect}"
20
+ end
21
+ end
22
+
23
+ # Runs a command that fails on error.
24
+ # Uses popen2e wrapper. Handles bad statuses with potential for retries.
25
+ def fail_on_error(*cmd, stdin_data: nil, binmode: false, quiet: false, **opts)
26
+ print_command('Running Shell Safe Command:', [cmd]) unless quiet
27
+ shell_safe_cmd = shell_safe(cmd)
28
+ retry_times = opts[:retry] || 0
29
+ opts.delete(:retry)
30
+
31
+ while retry_times >= 0
32
+ output, status = popen2e_wrapper(*shell_safe_cmd, stdin_data: stdin_data, binmode: binmode,
33
+ quiet: quiet, **opts)
34
+
35
+ break unless status.exitstatus != 0
36
+
37
+ logger.debug("Command failed with exit status #{status.exitstatus}, retrying #{retry_times} more time(s).") if retry_times > 0
38
+ retry_times -= 1
39
+ end
40
+
41
+ # Get out with the status, good or bad.
42
+ exit_on_status(output, [shell_safe_cmd], [status], quiet: quiet)
43
+ end
44
+ module_function :fail_on_error
45
+
46
+ # Wrapper around open3.popen2e
47
+ #
48
+ # We emulate open3.capture2e with the following changes in behavior:
49
+ # 1) The command is printed to stdout before execution.
50
+ # 2) Attempts to use the shell implicitly are blocked.
51
+ # 3) Nonzero return codes result in the process exiting.
52
+ # 4) Combined stdout/stderr goes to callers stdout
53
+ # (continuously streamed) and is returned as a string
54
+ #
55
+ # If you're looking for more process/stream control read the spawn
56
+ # documentation, and pass options directly here
57
+ def popen2e_wrapper(*shell_safe_cmd, stdin_data: nil, binmode: false,
58
+ quiet: false, **opts)
59
+
60
+ env = opts.delete(:env) { {} }
61
+ raise ArgumentError, "The :env option must be a hash, not #{env.inspect}" if !env.is_a?(Hash)
62
+
63
+ # Most of this is copied from Open3.capture2e in ruby/lib/open3.rb
64
+ _output, _status = Open3.popen2e(env, *shell_safe_cmd, opts) do |i, oe, t|
65
+ if binmode
66
+ i.binmode
67
+ oe.binmode
68
+ end
69
+
70
+ outerr_reader = Thread.new do
71
+ if quiet
72
+ oe.read
73
+ else
74
+ # Instead of oe.read, we redirect. Output from command goes to stdout
75
+ # and also is returned for processing if necessary.
76
+ tee(oe, STDOUT)
77
+ end
78
+ end
79
+
80
+ if stdin_data
81
+ begin
82
+ i.write stdin_data
83
+ rescue Errno::EPIPE
84
+ end
85
+ end
86
+
87
+ i.close
88
+ [outerr_reader.value, t.value]
89
+ end
90
+ end
91
+ module_function :popen2e_wrapper
92
+
93
+ # Look at a cmd list intended for spawn.
94
+ # determine if spawn will call the shell implicitly, fail in that case.
95
+ def shell_safe(cmd)
96
+ # Take the first string and change it to a list of [executable,argv0]
97
+ # This syntax for calling popen2e (and eventually spawn) avoids
98
+ # the shell in all cases
99
+ shell_safe_cmd = Array.new(cmd)
100
+ if shell_safe_cmd[0].class == String
101
+ shell_safe_cmd[0] = [shell_safe_cmd[0], shell_safe_cmd[0]]
102
+ end
103
+ shell_safe_cmd
104
+ end
105
+ module_function :shell_safe
106
+
107
+ def debug_print_cmd_list(cmd_list)
108
+ # Take a list of command argument lists like you'd sent to open3.pipeline or
109
+ # fail_on_error_pipe and print out a string that would do the same thing when
110
+ # entered at the shell.
111
+ #
112
+ # This is a converter from our internal representation of commands to a subset
113
+ # of bash that can be executed directly.
114
+ #
115
+ # Note this has problems if you specify env or opts
116
+ # TODO: make this remove those command parts
117
+ "\"" +
118
+ cmd_list.map do |cmd|
119
+ cmd.map do |arg|
120
+ arg.gsub("\"", "\\\"") # Escape all double quotes in command arguments
121
+ end.join("\" \"") # Fully quote all command parts, beginning and end.
122
+ end.join("\" | \"") + "\"" # Pipe commands to one another.
123
+ end
124
+ module_function :debug_print_cmd_list
125
+
126
+ # Prints a formatted string with command
127
+ def print_command(message, cmd)
128
+ logger.debug("#{message} #{debug_print_cmd_list(cmd)}\n")
129
+ end
130
+ module_function :print_command
131
+
132
+ # Takes in an input stream and an output stream
133
+ # Redirects data from one to the other until the input stream closes.
134
+ # Returns all data that passed through on return.
135
+ def tee(in_stream, out_stream)
136
+ alldata = ''
137
+ loop do
138
+ begin
139
+ data = in_stream.read_nonblock(4096)
140
+ alldata += data
141
+ out_stream.write(data)
142
+ out_stream.flush
143
+ rescue IO::WaitReadable
144
+ IO.select([in_stream])
145
+ retry
146
+ rescue IOError
147
+ break
148
+ end
149
+ end
150
+ alldata
151
+ end
152
+ module_function :tee
153
+
154
+ # If any of the statuses are bad, exits with the
155
+ # return code of the first one.
156
+ #
157
+ # Otherwise returns first argument (output)
158
+ def exit_on_status(output, cmd_list, status_list, quiet: false)
159
+ status_list.each_index do |index|
160
+ status = status_list[index]
161
+ cmd = cmd_list[index]
162
+ check_status(cmd, status, output: output, quiet: quiet)
163
+ end
164
+
165
+ output
166
+ end
167
+ module_function :exit_on_status
168
+
169
+ def check_status(cmd, status, output: nil, quiet: false)
170
+ return if status.exited? && status.exitstatus == 0
171
+
172
+ # If we exited nonzero or abnormally, print debugging info and explode.
173
+ if status.exited?
174
+ logger.debug("Process Exited normally. Exit status:#{status.exitstatus}") unless quiet
175
+ else
176
+ # This should only get executed if we're stopped or signaled
177
+ logger.debug("Process exited abnormally:\nProcessStatus: #{status.inspect}\n" \
178
+ "Raw POSIX Status: #{status.to_i}\n") unless quiet
179
+ end
180
+
181
+ raise RunnerExecutionRuntimeError.new(status, cmd, output)
182
+ end
183
+ module_function :check_status
184
+
185
+ DEFAULT_LOGGER = Logger.new(STDOUT)
186
+ private_constant :DEFAULT_LOGGER
187
+
188
+ def logger
189
+ DEFAULT_LOGGER
190
+ end
191
+ module_function :logger
192
+ end
193
+ # rubocop:enable all
@@ -36,6 +36,7 @@ describe GitFastClone::Runner do
36
36
 
37
37
  before do
38
38
  stub_const('ARGV', ['ssh://git@git.com/git-fastclone.git', 'test_reference_dir'])
39
+ allow($stdout).to receive(:puts)
39
40
  end
40
41
 
41
42
  let(:yielded) { [] }
@@ -50,7 +51,6 @@ describe GitFastClone::Runner do
50
51
  expect(subject.reference_mutex).to eq({})
51
52
  expect(subject.reference_updated).to eq({})
52
53
  expect(subject.options).to eq({})
53
- expect(subject.logger).to eq(nil)
54
54
  end
55
55
  end
56
56
 
@@ -87,10 +87,9 @@ describe GitFastClone::Runner do
87
87
  end
88
88
 
89
89
  describe '.clone' do
90
- let(:terrapin_commandline_double) { double('new_terrapin_commandline') }
90
+ let(:runner_execution_double) { double('runner_execution') }
91
91
  before(:each) do
92
- allow(terrapin_commandline_double).to receive(:run) {}
93
- expect(Time).to receive(:now).twice { 0 }
92
+ allow(runner_execution_double).to receive(:fail_on_error) {}
94
93
  allow(Dir).to receive(:pwd) { '/pwd' }
95
94
  allow(Dir).to receive(:chdir).and_yield
96
95
  allow(subject).to receive(:with_git_mirror).and_yield('/cache', 0)
@@ -98,36 +97,37 @@ describe GitFastClone::Runner do
98
97
  end
99
98
 
100
99
  it 'should clone correctly' do
101
- expect(Terrapin::CommandLine).to receive(:new).with(
102
- 'git clone',
103
- '--quiet --reference :mirror :url :path'
104
- ) { terrapin_commandline_double }
105
- expect(Terrapin::CommandLine).to receive(:new).with(
106
- 'git checkout',
107
- '--quiet :rev'
108
- ) { terrapin_commandline_double }
109
- expect(terrapin_commandline_double).to receive(:run).with(
110
- mirror: '/cache',
111
- url: placeholder_arg,
112
- path: '/pwd/.',
113
- config: ''
114
- )
115
- expect(terrapin_commandline_double).to receive(:run).with(rev: placeholder_arg)
100
+ expect(subject).to receive(:fail_on_error).with(
101
+ 'git', 'checkout', '--quiet', 'PH',
102
+ { quiet: true }
103
+ ) { runner_execution_double }
104
+ expect(subject).to receive(:fail_on_error).with(
105
+ 'git', 'clone', '--quiet', '--reference', '/cache', 'PH', '/pwd/.',
106
+ { quiet: true }
107
+ ) { runner_execution_double }
108
+
109
+ subject.clone(placeholder_arg, placeholder_arg, '.', nil)
110
+ end
111
+
112
+ it 'should clone correctly with verbose mode on' do
113
+ subject.verbose = true
114
+ expect(subject).to receive(:fail_on_error).with(
115
+ 'git', 'checkout', '--quiet', 'PH',
116
+ { quiet: false }
117
+ ) { runner_execution_double }
118
+ expect(subject).to receive(:fail_on_error).with(
119
+ 'git', 'clone', '--verbose', '--reference', '/cache', 'PH', '/pwd/.',
120
+ { quiet: false }
121
+ ) { runner_execution_double }
116
122
 
117
123
  subject.clone(placeholder_arg, placeholder_arg, '.', nil)
118
124
  end
119
125
 
120
126
  it 'should clone correctly with custom configs' do
121
- expect(Terrapin::CommandLine).to receive(:new).with(
122
- 'git clone',
123
- '--quiet --reference :mirror :url :path --config :config'
124
- ) { terrapin_commandline_double }
125
- expect(terrapin_commandline_double).to receive(:run).with(
126
- mirror: '/cache',
127
- url: placeholder_arg,
128
- path: '/pwd/.',
129
- config: 'config'
130
- )
127
+ expect(subject).to receive(:fail_on_error).with(
128
+ 'git', 'clone', '--quiet', '--reference', '/cache', 'PH', '/pwd/.', '--config', 'config',
129
+ { quiet: true }
130
+ ) { runner_execution_double }
131
131
 
132
132
  subject.clone(placeholder_arg, nil, '.', 'config')
133
133
  end
@@ -294,36 +294,35 @@ describe GitFastClone::Runner do
294
294
 
295
295
  describe '.store_updated_repo' do
296
296
  context 'when fail_hard is true' do
297
- it 'should raise a Terrapin error' do
298
- terrapin_commandline_double = double('new_terrapin_commandline')
299
- allow(terrapin_commandline_double).to receive(:run) { raise Terrapin::ExitStatusError }
300
- allow(Terrapin::CommandLine).to receive(:new) { terrapin_commandline_double }
297
+ it 'should raise a Runtime error and clear cache' do
298
+ status = double('status')
299
+ allow(status).to receive(:exitstatus).and_return(1)
300
+ ex = RunnerExecution::RunnerExecutionRuntimeError.new(status, 'cmd')
301
+ allow(subject).to receive(:fail_on_error) { raise ex }
301
302
  expect(FileUtils).to receive(:remove_entry_secure).with(placeholder_arg, force: true)
302
303
  expect do
303
304
  subject.store_updated_repo(placeholder_arg, placeholder_arg, placeholder_arg, true)
304
- end.to raise_error(Terrapin::ExitStatusError)
305
+ end.to raise_error(ex)
305
306
  end
306
307
  end
307
308
 
308
309
  context 'when fail_hard is false' do
309
- it 'should not raise a terrapin error' do
310
- terrapin_commandline_double = double('new_terrapin_commandline')
311
- allow(terrapin_commandline_double).to receive(:run) { raise Terrapin::ExitStatusError }
312
- allow(Terrapin::CommandLine).to receive(:new) { terrapin_commandline_double }
310
+ it 'should not raise a Runtime error but clear cache' do
311
+ status = double('status')
312
+ allow(status).to receive(:exitstatus).and_return(1)
313
+ ex = RunnerExecution::RunnerExecutionRuntimeError.new(status, 'cmd')
314
+ allow(subject).to receive(:fail_on_error) { raise ex }
313
315
  expect(FileUtils).to receive(:remove_entry_secure).with(placeholder_arg, force: true)
314
-
315
316
  expect do
316
317
  subject.store_updated_repo(placeholder_arg, placeholder_arg, placeholder_arg, false)
317
- end.not_to raise_error
318
+ end.to_not raise_error
318
319
  end
319
320
  end
320
321
 
321
322
  let(:placeholder_hash) { {} }
322
323
 
323
324
  it 'should correctly update the hash' do
324
- terrapin_commandline_double = double('new_terrapin_commandline')
325
- allow(terrapin_commandline_double).to receive(:run) {}
326
- allow(Terrapin::CommandLine).to receive(:new) { terrapin_commandline_double }
325
+ allow(subject).to receive(:fail_on_error)
327
326
  allow(Dir).to receive(:chdir) {}
328
327
 
329
328
  subject.reference_updated = placeholder_hash
@@ -335,10 +334,6 @@ describe GitFastClone::Runner do
335
334
  describe '.with_git_mirror' do
336
335
  def retriable_error
337
336
  %(
338
- STDOUT:
339
-
340
- STDERR:
341
-
342
337
  fatal: bad object ee35b1e14e7c3a53dcc14d82606e5b872f6a05a7
343
338
  fatal: remote did not send all necessary objects
344
339
  ).strip.split("\n").map(&:strip).join("\n")
@@ -351,7 +346,11 @@ describe GitFastClone::Runner do
351
346
  ->(url) { url }
352
347
  else
353
348
  # Simulate failed error response
354
- ->(_url) { raise Terrapin::ExitStatusError, response }
349
+ lambda { |_url|
350
+ status = double('status')
351
+ allow(status).to receive(:exitstatus).and_return(1)
352
+ raise RunnerExecution::RunnerExecutionRuntimeError.new(status, 'cmd', response)
353
+ }
355
354
  end
356
355
  end
357
356
 
@@ -366,19 +365,16 @@ describe GitFastClone::Runner do
366
365
  end
367
366
 
368
367
  let(:expected_commands) { [] }
369
- let(:expected_commands_args) { [] }
370
368
 
371
369
  before(:each) do
372
- expect(expected_commands.length).to eq(expected_commands_args.length)
373
- allow(Terrapin::CommandLine).to receive(:new) do |*command|
370
+ allow(subject).to receive(:fail_on_error) { |*params|
371
+ # last one is an argument `quiet:`
372
+ command = params.first(params.size - 1)
374
373
  expect(expected_commands.length).to be > 0
375
374
  expected_command = expected_commands.shift
376
- expected_args = expected_commands_args.shift
377
375
  expect(command).to eq(expected_command)
378
- stub = double(Terrapin::CommandLine)
379
- expect(stub).to receive(:run).with(expected_args)
380
- stub
381
- end
376
+ }
377
+ allow(Dir).to receive(:chdir).and_yield
382
378
 
383
379
  allow(subject).to receive(:print_formatted_error) {}
384
380
  allow(subject).to receive(:reference_repo_dir).and_return(test_reference_repo_dir)
@@ -389,39 +385,46 @@ describe GitFastClone::Runner do
389
385
  expect(expected_commands).to be_empty
390
386
  end
391
387
 
392
- def clone_cmds
388
+ def clone_cmds(verbose: false)
393
389
  [
394
- ['git clone', '--mirror :url :mirror'],
395
- ['cd', ':path; git remote update --prune']
396
- ]
397
- end
398
-
399
- def clone_args
400
- [
401
- {
402
- mirror: test_reference_repo_dir,
403
- url: test_url_valid
404
- },
405
- {
406
- path: test_reference_repo_dir
407
- }
390
+ ['git', 'clone', verbose ? '--verbose' : '--quiet', '--mirror', test_url_valid,
391
+ test_reference_repo_dir],
392
+ ['git', 'remote', verbose ? '--verbose' : nil, 'update', '--prune'].compact
408
393
  ]
409
394
  end
410
395
 
411
396
  context 'expecting 1 clone attempt' do
412
- let(:expected_commands) { clone_cmds }
413
- let(:expected_commands_args) { clone_args }
397
+ context 'with verbose mode on' do
398
+ before { subject.verbose = true }
399
+ let(:expected_commands) { clone_cmds(verbose: true) }
414
400
 
415
- it 'should succeed with a successful clone' do
416
- expect(subject).not_to receive(:clear_cache)
417
- try_with_git_mirror([true], [[test_reference_repo_dir, 0]])
401
+ it 'should succeed with a successful clone' do
402
+ expect(subject).not_to receive(:clear_cache)
403
+ try_with_git_mirror([true], [[test_reference_repo_dir, 0]])
404
+ end
405
+
406
+ it 'should fail after a non-retryable clone error' do
407
+ expect(subject).not_to receive(:clear_cache)
408
+ expect do
409
+ try_with_git_mirror(['Some unexpected error message'], [])
410
+ end.to raise_error(RunnerExecution::RunnerExecutionRuntimeError)
411
+ end
418
412
  end
419
413
 
420
- it 'should fail after a non-retryable clone error' do
421
- expect(subject).not_to receive(:clear_cache)
422
- expect do
423
- try_with_git_mirror(['Some unexpected error message'], [])
424
- end.to raise_error(Terrapin::ExitStatusError)
414
+ context 'with verbose mode off' do
415
+ let(:expected_commands) { clone_cmds }
416
+
417
+ it 'should succeed with a successful clone' do
418
+ expect(subject).not_to receive(:clear_cache)
419
+ try_with_git_mirror([true], [[test_reference_repo_dir, 0]])
420
+ end
421
+
422
+ it 'should fail after a non-retryable clone error' do
423
+ expect(subject).not_to receive(:clear_cache)
424
+ expect do
425
+ try_with_git_mirror(['Some unexpected error message'], [])
426
+ end.to raise_error(RunnerExecution::RunnerExecutionRuntimeError)
427
+ end
425
428
  end
426
429
  end
427
430
 
@@ -438,14 +441,14 @@ describe GitFastClone::Runner do
438
441
  expect(subject).to receive(:clear_cache).twice.and_call_original
439
442
  expect do
440
443
  try_with_git_mirror([retriable_error, retriable_error], [])
441
- end.to raise_error(Terrapin::ExitStatusError)
444
+ end.to raise_error(RunnerExecution::RunnerExecutionRuntimeError)
442
445
  end
443
446
  end
444
447
  end
445
448
 
446
449
  describe '.retriable_error?' do
447
450
  def format_error(error)
448
- error_wrapper = "STDOUT:\n\nSTDERR:\n#{error}"
451
+ error_wrapper = error.to_s
449
452
  error_wrapper.strip.lines.map(&:strip).join("\n")
450
453
  end
451
454
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-fastclone
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.3
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Tauraso
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-03-03 00:00:00.000000000 Z
12
+ date: 2023-05-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: colorize
@@ -25,20 +25,6 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: terrapin
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: 0.6.0
35
- type: :runtime
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: 0.6.0
42
28
  description: A git command that uses reference repositories and threading to quicklyand
43
29
  recursively clone repositories with many nested submodules
44
30
  email:
@@ -55,6 +41,7 @@ files:
55
41
  - bin/git-fastclone
56
42
  - lib/git-fastclone.rb
57
43
  - lib/git-fastclone/version.rb
44
+ - lib/runner_execution.rb
58
45
  - spec/git_fastclone_runner_spec.rb
59
46
  - spec/git_fastclone_url_helper_spec.rb
60
47
  - spec/spec_helper.rb