git-fastclone 1.3.3 → 1.4.1

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.
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