git-fastclone 1.3.0 → 1.3.2

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: 3e6bcf21a8d3b9c1b30b93efc17487fa8dfa0c319b8636efc1f96a430e27c876
4
- data.tar.gz: 7720184507344bc94ad63e45b135e0948aab6776d371867159a4cd84dcfd2a0d
3
+ metadata.gz: 9d5ae1cf32d0c0f13534a9e7d986cb4d1845cec980085b27332cf366829b0ccf
4
+ data.tar.gz: 909d9f98c4d09d9f52b70539749639b3fc57464f6b622ac9620d24509e690476
5
5
  SHA512:
6
- metadata.gz: 13e48d37e5162a8288da830232476fb12ba7b8bb01c0ef0834a8eb372a224c0a2f8430d10002a9c54c1ec7fc7713db384ef32d0da9f1707366663bbe10e9ff04
7
- data.tar.gz: 1f74af758cfbd3f47f98f6d4fc1a47e24bf574f9e7b58116b9aa3f6186ab334e66c66e413416641b7ce6494b3099b45158142fbd88b4c1bb3a0bb48a86c9b912
6
+ metadata.gz: 8f20b952bdfe79605012ae2b365dfb01b3621239f75e2e9e40aef76d807c9a2eb1af5b876dbd34032311efeea0ae1a9990c18295c3256ea8d7a622f99b1e1032
7
+ data.tar.gz: e9fa4c99ce664a1c1780b76a5a952cdef57b1d43ddf92ea870bba0c29a76fce2e2b0281b6698af48c5a60c1446427c66614fb226cb159f3870b614e06b2d897b
data/README.md CHANGED
@@ -44,8 +44,10 @@ Usage
44
44
  git fastclone [options] <git-repo-url>
45
45
 
46
46
  -b, --branch <branch> Clone a specific branch
47
- -v, --verbose Shows more info
48
- -c, --color Pretty colors!
47
+ -v, --verbose Verbose mode
48
+ -c, --color Display colored output
49
+ --config CONFIG Git config applied to the cloned repo
50
+ --lock-timeout N Timeout in seconds to acquire a lock on any reference repo.
49
51
 
50
52
  Change the default `REFERENCE_REPO_DIR` environment variable if necessary.
51
53
 
data/bin/git-fastclone CHANGED
@@ -15,7 +15,7 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
18
+ $LOAD_PATH.unshift(File.expand_path("#{File.dirname(__FILE__)}/../lib"))
19
19
 
20
20
  require 'git-fastclone'
21
21
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Version string for git-fastclone
4
4
  module GitFastCloneVersion
5
- VERSION = '1.3.0'.freeze
5
+ VERSION = '1.3.2'
6
6
  end
data/lib/git-fastclone.rb CHANGED
@@ -41,7 +41,7 @@ module GitFastClone
41
41
 
42
42
  def reference_repo_dir(url, reference_dir, using_local_repo)
43
43
  if using_local_repo
44
- File.join(reference_dir, 'local' + reference_repo_name(url))
44
+ File.join(reference_dir, "local#{reference_repo_name(url)}")
45
45
  else
46
46
  File.join(reference_dir, reference_repo_name(url))
47
47
  end
@@ -69,9 +69,9 @@ module GitFastClone
69
69
 
70
70
  include GitFastClone::UrlHelper
71
71
 
72
- DEFAULT_REFERENCE_REPO_DIR = '/var/tmp/git-fastclone/reference'.freeze
72
+ DEFAULT_REFERENCE_REPO_DIR = '/var/tmp/git-fastclone/reference'
73
73
 
74
- DEFAULT_GIT_ALLOW_PROTOCOL = 'file:git:http:https:ssh'.freeze
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
77
  :options, :logger, :abs_clone_path, :using_local_repo, :verbose, :color,
@@ -137,7 +137,7 @@ module GitFastClone
137
137
 
138
138
  opts.on('-v', '--verbose', 'Verbose mode') do
139
139
  self.verbose = true
140
- self.logger = Logger.new(STDOUT)
140
+ self.logger = Logger.new($stdout)
141
141
  logger.formatter = proc do |_severity, _datetime, _progname, msg|
142
142
  "#{msg}\n"
143
143
  end
@@ -163,7 +163,7 @@ module GitFastClone
163
163
  parse_options
164
164
 
165
165
  unless ARGV[0]
166
- STDERR.puts usage
166
+ warn usage
167
167
  exit(129)
168
168
  end
169
169
 
@@ -189,18 +189,40 @@ module GitFastClone
189
189
  [url, path, options]
190
190
  end
191
191
 
192
+ def clear_clone_dest_if_needed(attempt_number, clone_dest)
193
+ return unless attempt_number.positive?
194
+
195
+ dest_with_dotfiles = Dir.glob("#{clone_dest}/*", File::FNM_DOTMATCH)
196
+ dest_files = dest_with_dotfiles.reject { |f| %w[. ..].include?(File.basename(f)) }
197
+ return if dest_files.empty?
198
+
199
+ clear_clone_dest(dest_files)
200
+ end
201
+
202
+ def clear_clone_dest(dest_files)
203
+ puts 'Non-empty clone directory found, clearing its content now.'
204
+ FileUtils.rm_rf(dest_files)
205
+ end
206
+
192
207
  # Checkout to SOURCE_DIR. Update all submodules recursively. Use reference
193
208
  # repos everywhere for speed.
194
209
  def clone(url, rev, src_dir, config)
210
+ clone_dest = File.join(abs_clone_path, src_dir).to_s
195
211
  initial_time = Time.now
196
212
 
197
- with_git_mirror(url) do |mirror|
213
+ if Dir.exist?(clone_dest) && !Dir.empty?(clone_dest)
214
+ raise "Can't clone into an existing non-empty path: #{clone_dest}"
215
+ end
216
+
217
+ with_git_mirror(url) do |mirror, attempt_number|
218
+ clear_clone_dest_if_needed(attempt_number, clone_dest)
219
+
198
220
  clone_command = '--quiet --reference :mirror :url :path'
199
221
  clone_command += ' --config :config' unless config.nil?
200
222
  Terrapin::CommandLine.new('git clone', clone_command)
201
223
  .run(mirror: mirror.to_s,
202
224
  url: url.to_s,
203
- path: File.join(abs_clone_path, src_dir).to_s,
225
+ path: clone_dest,
204
226
  config: config.to_s)
205
227
  end
206
228
 
@@ -245,7 +267,7 @@ module GitFastClone
245
267
 
246
268
  def thread_update_submodule(submodule_url, submodule_path, threads, pwd)
247
269
  threads << Thread.new do
248
- with_git_mirror(submodule_url) do |mirror|
270
+ with_git_mirror(submodule_url) do |mirror, _|
249
271
  Terrapin::CommandLine.new('cd', ':dir; git submodule update --quiet --reference :mirror :path')
250
272
  .run(dir: File.join(abs_clone_path, pwd).to_s,
251
273
  mirror: mirror.to_s,
@@ -270,14 +292,12 @@ module GitFastClone
270
292
  lockfile.close
271
293
  end
272
294
 
273
- def with_reference_repo_thread_lock(url)
295
+ def with_reference_repo_thread_lock(url, &block)
274
296
  # We also need thread level locking because pre-fetch means multiple threads can
275
297
  # attempt to update the same repository from a single git-fastclone process
276
298
  # file locks in posix are tracked per process, not per userland thread.
277
299
  # This gives us the equivalent of pthread_mutex around these accesses.
278
- reference_mutex[reference_repo_name(url)].synchronize do
279
- yield
280
- end
300
+ reference_mutex[reference_repo_name(url)].synchronize(&block)
281
301
  end
282
302
 
283
303
  def update_submodule_reference(url, submodule_url_list)
@@ -338,6 +358,32 @@ module GitFastClone
338
358
  raise e if fail_hard
339
359
  end
340
360
 
361
+ def retriable_error?(error)
362
+ 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',
369
+ /^fatal: unable to read tree [a-z0-9]+\n^warning: Clone succeeded, but checkout failed/
370
+ ]
371
+ error.to_s =~ /^STDERR:\n.*^#{Regexp.union(error_strings)}/m
372
+ end
373
+
374
+ def print_formatted_error(error)
375
+ 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."
377
+ end
378
+
379
+ # To avoid corruption of the cache, if we failed to update or check out we remove
380
+ # the cache directory entirely. This may cause the current clone to fail, but if the
381
+ # underlying error from git is transient it will not affect future clones.
382
+ def clear_cache(dir, url)
383
+ FileUtils.remove_entry_secure(dir, force: true)
384
+ reference_updated.delete(reference_repo_name(url))
385
+ end
386
+
341
387
  # This command will create and bring the mirror up-to-date on-demand,
342
388
  # blocking any code passed in while the mirror is brought up-to-date
343
389
  #
@@ -346,42 +392,31 @@ module GitFastClone
346
392
  # moment means we only need to synchronize our own threads in case a single
347
393
  # submodule url is included twice via multiple dependency paths
348
394
  def with_git_mirror(url)
395
+ retries_allowed ||= 1
396
+ attempt_number ||= 0
397
+
349
398
  update_reference_repo(url, true)
399
+ dir = reference_repo_dir(url, reference_dir, using_local_repo)
350
400
 
351
401
  # Sometimes remote updates involve re-packing objects on a different thread
352
402
  # We grab the reference repo lock here just to make sure whatever thread
353
403
  # ended up doing the update is done with its housekeeping.
354
404
  # This makes sure we have control and unlock when the block returns:
355
405
  with_reference_repo_lock(url) do
356
- dir = reference_repo_dir(url, reference_dir, using_local_repo)
357
- retries_left = 1
358
-
359
- begin
360
- yield dir
361
- rescue Terrapin::ExitStatusError => e
362
- 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',
369
- /^fatal: unable to read tree [a-z0-9]+\n^warning: Clone succeeded, but checkout failed/
370
- ]
371
- if e.to_s =~ /^STDERR:\n.+^#{Regexp.union(error_strings)}/m
372
- # To avoid corruption of the cache, if we failed to update or check out we remove
373
- # the cache directory entirely. This may cause the current clone to fail, but if the
374
- # underlying error from git is transient it will not affect future clones.
375
- FileUtils.remove_entry_secure(dir, force: true)
376
- if retries_left > 0
377
- retries_left -= 1
378
- retry
379
- end
380
- end
406
+ yield dir, attempt_number
407
+ end
408
+ rescue Terrapin::ExitStatusError => e
409
+ if retriable_error?(e)
410
+ print_formatted_error(e)
411
+ clear_cache(dir, url)
381
412
 
382
- raise
413
+ if attempt_number < retries_allowed
414
+ attempt_number += 1
415
+ retry
383
416
  end
384
417
  end
418
+
419
+ raise
385
420
  end
386
421
 
387
422
  def usage
@@ -24,7 +24,7 @@ describe GitFastClone::Runner do
24
24
  let(:test_reference_repo_dir) { '/var/tmp/git-fastclone/reference/test_reference_dir' }
25
25
  let(:placeholder_arg) { 'PH' }
26
26
 
27
- let(:lockfile) do
27
+ def create_lockfile_double
28
28
  lockfile = double
29
29
  expect(lockfile).to receive(:flock).with(File::LOCK_EX).once
30
30
  expect(lockfile).to receive(:flock).with(File::LOCK_UN).once
@@ -32,6 +32,8 @@ describe GitFastClone::Runner do
32
32
  lockfile
33
33
  end
34
34
 
35
+ let(:lockfile) { create_lockfile_double }
36
+
35
37
  before do
36
38
  stub_const('ARGV', ['ssh://git@git.com/git-fastclone.git', 'test_reference_dir'])
37
39
  end
@@ -91,7 +93,8 @@ describe GitFastClone::Runner do
91
93
  expect(Time).to receive(:now).twice { 0 }
92
94
  allow(Dir).to receive(:pwd) { '/pwd' }
93
95
  allow(Dir).to receive(:chdir).and_yield
94
- allow(subject).to receive(:with_git_mirror).and_yield('/cache')
96
+ allow(subject).to receive(:with_git_mirror).and_yield('/cache', 0)
97
+ expect(subject).to receive(:clear_clone_dest_if_needed).once {}
95
98
  end
96
99
 
97
100
  it 'should clone correctly' do
@@ -114,21 +117,39 @@ describe GitFastClone::Runner do
114
117
  subject.clone(placeholder_arg, placeholder_arg, '.', nil)
115
118
  end
116
119
 
117
- describe 'with custom configs' do
118
- it 'should clone correctly' do
119
- expect(Terrapin::CommandLine).to receive(:new).with(
120
- 'git clone',
121
- '--quiet --reference :mirror :url :path --config :config'
122
- ) { terrapin_commandline_double }
123
- expect(terrapin_commandline_double).to receive(:run).with(
124
- mirror: '/cache',
125
- url: placeholder_arg,
126
- path: '/pwd/.',
127
- config: 'config'
128
- )
129
-
130
- subject.clone(placeholder_arg, nil, '.', 'config')
131
- end
120
+ 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
+ )
131
+
132
+ subject.clone(placeholder_arg, nil, '.', 'config')
133
+ end
134
+ end
135
+
136
+ describe '.clear_clone_dest_if_needed' do
137
+ it 'does not clear on first attempt' do
138
+ expect(Dir).not_to receive(:glob)
139
+ expect(subject).not_to receive(:clear_clone_dest)
140
+ subject.clear_clone_dest_if_needed(0, '/some/path')
141
+ end
142
+
143
+ it 'does not clear if the directory is only FNM_DOTMATCH self and parent refs' do
144
+ expect(Dir).to receive(:glob).and_return(%w[. ..])
145
+ expect(subject).not_to receive(:clear_clone_dest)
146
+ subject.clear_clone_dest_if_needed(1, '/some/path')
147
+ end
148
+
149
+ it 'does clear if the directory is not empty' do
150
+ expect(Dir).to receive(:glob).and_return(%w[. .. /some/path/file.txt])
151
+ expect(subject).to receive(:clear_clone_dest) {}
152
+ subject.clear_clone_dest_if_needed(1, '/some/path')
132
153
  end
133
154
  end
134
155
 
@@ -312,209 +333,200 @@ describe GitFastClone::Runner do
312
333
  end
313
334
 
314
335
  describe '.with_git_mirror' do
315
- it 'should yield properly' do
316
- allow(subject).to receive(:update_reference_repo) {}
317
- expect(subject).to receive(:reference_repo_dir)
318
- expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
336
+ def retriable_error
337
+ %(
338
+ STDOUT:
319
339
 
320
- subject.with_git_mirror(test_url_valid) do
321
- yielded << test_url_valid
322
- end
340
+ STDERR:
323
341
 
324
- expect(yielded).to eq([test_url_valid])
342
+ fatal: bad object ee35b1e14e7c3a53dcc14d82606e5b872f6a05a7
343
+ fatal: remote did not send all necessary objects
344
+ ).strip.split("\n").map(&:strip).join("\n")
325
345
  end
326
346
 
327
- it 'should retry when the cache looks corrupted' do
328
- allow(subject).to receive(:update_reference_repo) {}
329
- expect(subject).to receive(:reference_repo_dir)
330
- expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
331
-
332
- responses = [
333
- lambda { |_url|
334
- raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
335
- STDOUT:
347
+ def try_with_git_mirror(responses, results)
348
+ lambdas = responses.map do |response|
349
+ if response == true
350
+ # Simulate successful response
351
+ ->(url) { url }
352
+ else
353
+ # Simulate failed error response
354
+ ->(_url) { raise Terrapin::ExitStatusError, response }
355
+ end
356
+ end
336
357
 
337
- STDERR:
358
+ subject.with_git_mirror(test_url_valid) do |url, attempt|
359
+ raise 'Not enough responses were provided!' if lambdas.empty?
338
360
 
339
- fatal: bad object ee35b1e14e7c3a53dcc14d82606e5b872f6a05a7
340
- fatal: remote did not send all necessary objects
341
- ERROR
342
- },
343
- ->(url) { url }
344
- ]
345
- subject.with_git_mirror(test_url_valid) do
346
- yielded << responses.shift.call(test_url_valid)
361
+ yielded << [lambdas.shift.call(url), attempt]
347
362
  end
348
363
 
349
- expect(responses).to be_empty
350
- expect(yielded).to eq([test_url_valid])
364
+ expect(lambdas).to be_empty
365
+ expect(yielded).to eq(results)
351
366
  end
352
367
 
353
- it 'should retry when the clone succeeds but checkout fails with corrupt packed object' do
354
- allow(subject).to receive(:update_reference_repo) {}
355
- expect(subject).to receive(:reference_repo_dir)
356
- expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
357
-
358
- responses = [
359
- lambda { |_url|
360
- raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
361
- STDOUT:
368
+ let(:expected_commands) { [] }
369
+ let(:expected_commands_args) { [] }
362
370
 
363
- STDERR:
364
-
365
- fatal: packed object 7c4d79704f8adf701f38a7bfb3e33ec5342542f1 (stored in /private/var/tmp/git-fastclone/reference/some-repo.git/objects/pack/pack-d37d7ed3e88d6e5f0ac141a7b0a2b32baf6e21a0.pack) is corrupt
366
- warning: Clone succeeded, but checkout failed.
367
- You can inspect what was checked out with 'git status' and retry with 'git restore --source=HEAD :/'
368
- ERROR
369
- },
370
- ->(url) { url }
371
- ]
372
- subject.with_git_mirror(test_url_valid) do
373
- yielded << responses.shift.call(test_url_valid)
371
+ before(:each) do
372
+ expect(expected_commands.length).to eq(expected_commands_args.length)
373
+ allow(Terrapin::CommandLine).to receive(:new) do |*command|
374
+ expect(expected_commands.length).to be > 0
375
+ expected_command = expected_commands.shift
376
+ expected_args = expected_commands_args.shift
377
+ expect(command).to eq(expected_command)
378
+ stub = double(Terrapin::CommandLine)
379
+ expect(stub).to receive(:run).with(expected_args)
380
+ stub
374
381
  end
375
382
 
376
- expect(responses).to be_empty
377
- expect(yielded).to eq([test_url_valid])
383
+ allow(subject).to receive(:print_formatted_error) {}
384
+ allow(subject).to receive(:reference_repo_dir).and_return(test_reference_repo_dir)
385
+ allow(subject).to receive(:reference_repo_lock_file) { create_lockfile_double }
378
386
  end
379
387
 
380
- it 'should retry when the clone succeeds but checkout fails with unable to read tree' do
381
- allow(subject).to receive(:update_reference_repo) {}
382
- expect(subject).to receive(:reference_repo_dir)
383
- expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
384
-
385
- responses = [
386
- lambda { |_url|
387
- raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
388
- STDOUT:
388
+ after(:each) do
389
+ expect(expected_commands).to be_empty
390
+ end
389
391
 
390
- STDERR:
392
+ def clone_cmds
393
+ [
394
+ ['git clone', '--mirror :url :mirror'],
395
+ ['cd', ':path; git remote update --prune']
396
+ ]
397
+ end
391
398
 
392
- error: Could not read 92cf57b8f07df010ab5f607b109c325e30e46235
393
- fatal: unable to read tree 0c32c0521d3b0bfb4e74e4a39b97a84d1a3bb9a1
394
- warning: Clone succeeded, but checkout failed.
395
- You can inspect what was checked out with 'git status'
396
- and retry with 'git restore --source=HEAD :/'
397
- ERROR
399
+ def clone_args
400
+ [
401
+ {
402
+ mirror: test_reference_repo_dir,
403
+ url: test_url_valid
398
404
  },
399
- ->(url) { url }
405
+ {
406
+ path: test_reference_repo_dir
407
+ }
400
408
  ]
401
- subject.with_git_mirror(test_url_valid) do
402
- yielded << responses.shift.call(test_url_valid)
409
+ end
410
+
411
+ context 'expecting 1 clone attempt' do
412
+ let(:expected_commands) { clone_cmds }
413
+ let(:expected_commands_args) { clone_args }
414
+
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]])
403
418
  end
404
419
 
405
- expect(responses).to be_empty
406
- expect(yielded).to eq([test_url_valid])
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)
425
+ end
407
426
  end
408
427
 
409
- it 'should retry when one delta is missing' do
410
- allow(subject).to receive(:update_reference_repo) {}
411
- expect(subject).to receive(:reference_repo_dir)
412
- expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
413
-
414
- responses = [
415
- lambda { |_url|
416
- raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
417
- STDOUT:
428
+ context 'expecting 2 clone attempts' do
429
+ let(:expected_commands) { clone_cmds + clone_cmds }
430
+ let(:expected_commands_args) { clone_args + clone_args }
418
431
 
419
- STDERR:
432
+ it 'should succeed after a single retryable clone failure' do
433
+ expect(subject).to receive(:clear_cache).and_call_original
434
+ try_with_git_mirror([retriable_error, true], [[test_reference_repo_dir, 1]])
435
+ end
420
436
 
421
- error: Could not read f7fad86d06fee0678f9af7203b6031feabb40c3e
422
- fatal: pack has 1 unresolved delta
423
- fatal: index-pack failed
424
- ERROR
425
- },
426
- ->(url) { url }
427
- ]
428
- subject.with_git_mirror(test_url_valid) do
429
- yielded << responses.shift.call(test_url_valid)
437
+ it 'should fail after two retryable clone failures' do
438
+ expect(subject).to receive(:clear_cache).twice.and_call_original
439
+ expect do
440
+ try_with_git_mirror([retriable_error, retriable_error], [])
441
+ end.to raise_error(Terrapin::ExitStatusError)
430
442
  end
443
+ end
444
+ end
431
445
 
432
- expect(responses).to be_empty
433
- expect(yielded).to eq([test_url_valid])
446
+ describe '.retriable_error?' do
447
+ def format_error(error)
448
+ error_wrapper = "STDOUT:\n\nSTDERR:\n#{error}"
449
+ error_wrapper.strip.lines.map(&:strip).join("\n")
434
450
  end
435
451
 
436
- it 'should retry when deltas are missing' do
437
- allow(subject).to receive(:update_reference_repo) {}
438
- expect(subject).to receive(:reference_repo_dir)
439
- expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
452
+ it 'not for a random error message' do
453
+ error = format_error 'random error message'
440
454
 
441
- responses = [
442
- lambda { |_url|
443
- raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
444
- STDOUT:
455
+ expect(subject.retriable_error?(error)).to be_falsey
456
+ end
445
457
 
446
- STDERR:
458
+ it 'when the cache looks corrupted' do
459
+ error = format_error <<-ERROR
460
+ fatal: bad object ee35b1e14e7c3a53dcc14d82606e5b872f6a05a7
461
+ fatal: remote did not send all necessary objects
462
+ ERROR
447
463
 
448
- error: Could not read f7fad86d06fee0678f9af7203b6031feabb40c3e
449
- fatal: pack has 138063 unresolved deltas
450
- fatal: index-pack failed
451
- ERROR
452
- },
453
- ->(url) { url }
454
- ]
455
- subject.with_git_mirror(test_url_valid) do
456
- yielded << responses.shift.call(test_url_valid)
457
- end
464
+ expect(subject.retriable_error?(error)).to be_truthy
465
+ end
458
466
 
459
- expect(responses).to be_empty
460
- expect(yielded).to eq([test_url_valid])
467
+ it 'when the clone succeeds but checkout fails with corrupt packed object' do
468
+ error = format_error <<-ERROR
469
+ fatal: packed object 7c4d79704f8adf701f38a7bfb3e33ec5342542f1 (stored in /private/var/tmp/git-fastclone/reference/some-repo.git/objects/pack/pack-d37d7ed3e88d6e5f0ac141a7b0a2b32baf6e21a0.pack) is corrupt
470
+ warning: Clone succeeded, but checkout failed.
471
+ You can inspect what was checked out with 'git status' and retry with 'git restore --source=HEAD :/'
472
+ ERROR
473
+
474
+ expect(subject.retriable_error?(error)).to be_truthy
461
475
  end
462
- end
463
476
 
464
- it 'should retry when the cache errors with unable to read sha1 file' do
465
- allow(subject).to receive(:update_reference_repo) {}
466
- expect(subject).to receive(:reference_repo_dir)
467
- expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
468
-
469
- responses = [
470
- lambda { |_url|
471
- raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
472
- STDOUT:
473
-
474
- STDERR:
475
-
476
- error: unable to read sha1 file of sqiosbuild/lib/action/action.rb (6113b739af82d8b07731de8a58d6e233301f80ab)
477
- fatal: unable to checkout working tree
478
- warning: Clone succeeded, but checkout failed.
479
- You can inspect what was checked out with 'git status'
480
- and retry with 'git restore --source=HEAD :/'
481
- ERROR
482
- },
483
- ->(url) { url }
484
- ]
485
- subject.with_git_mirror(test_url_valid) do
486
- yielded << responses.shift.call(test_url_valid)
487
- end
488
-
489
- expect(responses).to be_empty
490
- expect(yielded).to eq([test_url_valid])
491
- end
477
+ it 'when the clone succeeds but checkout fails with unable to read tree' do
478
+ error = format_error <<-ERROR
479
+ error: Could not read 92cf57b8f07df010ab5f607b109c325e30e46235
480
+ fatal: unable to read tree 0c32c0521d3b0bfb4e74e4a39b97a84d1a3bb9a1
481
+ warning: Clone succeeded, but checkout failed.
482
+ You can inspect what was checked out with 'git status'
483
+ and retry with 'git restore --source=HEAD :/'
484
+ ERROR
492
485
 
493
- it 'should retry when the cache errors with did not receive expected object' do
494
- allow(subject).to receive(:update_reference_repo) {}
495
- expect(subject).to receive(:reference_repo_dir)
496
- expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
486
+ expect(subject.retriable_error?(error)).to be_truthy
487
+ end
497
488
 
498
- responses = [
499
- lambda { |_url|
500
- raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
501
- STDOUT:
489
+ it 'when one delta is missing' do
490
+ error = format_error <<-ERROR
491
+ error: Could not read f7fad86d06fee0678f9af7203b6031feabb40c3e
492
+ fatal: pack has 1 unresolved delta
493
+ fatal: index-pack failed
494
+ ERROR
502
495
 
503
- STDERR:
496
+ expect(subject.retriable_error?(error)).to be_truthy
497
+ end
504
498
 
505
- error: Could not read 6682dfe81f66656436e60883dd795e7ec6735153
506
- error: Could not read 0cd3703c23fa44c0043d97fbc26356a23939f31b
507
- fatal: did not receive expected object 3c64c9dd49c79bd09aa13d4b05ac18263ca29ccd
508
- fatal: index-pack failed
509
- ERROR
510
- },
511
- ->(url) { url }
512
- ]
513
- subject.with_git_mirror(test_url_valid) do
514
- yielded << responses.shift.call(test_url_valid)
499
+ it 'when deltas are missing' do
500
+ error = format_error <<-ERROR
501
+ error: Could not read f7fad86d06fee0678f9af7203b6031feabb40c3e
502
+ fatal: pack has 138063 unresolved deltas
503
+ fatal: index-pack failed
504
+ ERROR
505
+
506
+ expect(subject.retriable_error?(error)).to be_truthy
515
507
  end
516
508
 
517
- expect(responses).to be_empty
518
- expect(yielded).to eq([test_url_valid])
509
+ it 'when the cache errors with unable to read sha1 file' do
510
+ error = format_error <<-ERROR
511
+ error: unable to read sha1 file of sqiosbuild/lib/action/action.rb (6113b739af82d8b07731de8a58d6e233301f80ab)
512
+ fatal: unable to checkout working tree
513
+ warning: Clone succeeded, but checkout failed.
514
+ You can inspect what was checked out with 'git status'
515
+ and retry with 'git restore --source=HEAD :/'
516
+ ERROR
517
+
518
+ expect(subject.retriable_error?(error)).to be_truthy
519
+ end
520
+
521
+ it 'when the cache errors with did not receive expected object' do
522
+ error = format_error <<-ERROR
523
+ error: Could not read 6682dfe81f66656436e60883dd795e7ec6735153
524
+ error: Could not read 0cd3703c23fa44c0043d97fbc26356a23939f31b
525
+ fatal: did not receive expected object 3c64c9dd49c79bd09aa13d4b05ac18263ca29ccd
526
+ fatal: index-pack failed
527
+ ERROR
528
+
529
+ expect(subject.retriable_error?(error)).to be_truthy
530
+ end
519
531
  end
520
532
  end
@@ -62,7 +62,7 @@ describe GitFastClone::UrlHelper do
62
62
  allow(subject).to receive(:reference_repo_name) { test_reference_dir }
63
63
 
64
64
  expect(subject.reference_repo_dir(test_url_valid, test_reference_dir, false))
65
- .to eq(test_reference_dir + '/' + test_reference_dir)
65
+ .to eq("#{test_reference_dir}/#{test_reference_dir}")
66
66
  end
67
67
  end
68
68
 
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.0
4
+ version: 1.3.2
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: 2022-12-03 00:00:00.000000000 Z
12
+ date: 2023-01-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: colorize
@@ -61,7 +61,8 @@ files:
61
61
  homepage: http://square.github.io/git-fastclone/
62
62
  licenses:
63
63
  - Apache
64
- metadata: {}
64
+ metadata:
65
+ rubygems_mfa_required: 'true'
65
66
  post_install_message:
66
67
  rdoc_options: []
67
68
  require_paths:
@@ -70,7 +71,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
71
  requirements:
71
72
  - - ">="
72
73
  - !ruby/object:Gem::Version
73
- version: '0'
74
+ version: '2.7'
74
75
  required_rubygems_version: !ruby/object:Gem::Requirement
75
76
  requirements:
76
77
  - - ">="
@@ -81,7 +82,4 @@ rubygems_version: 3.1.6
81
82
  signing_key:
82
83
  specification_version: 4
83
84
  summary: git-clone --recursive on steroids!
84
- test_files:
85
- - spec/git_fastclone_runner_spec.rb
86
- - spec/git_fastclone_url_helper_spec.rb
87
- - spec/spec_helper.rb
85
+ test_files: []