git-fastclone 1.3.0 → 1.3.2
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 +4 -4
- data/README.md +4 -2
- data/bin/git-fastclone +1 -1
- data/lib/git-fastclone/version.rb +1 -1
- data/lib/git-fastclone.rb +73 -38
- data/spec/git_fastclone_runner_spec.rb +192 -180
- data/spec/git_fastclone_url_helper_spec.rb +1 -1
- metadata +6 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d5ae1cf32d0c0f13534a9e7d986cb4d1845cec980085b27332cf366829b0ccf
|
4
|
+
data.tar.gz: 909d9f98c4d09d9f52b70539749639b3fc57464f6b622ac9620d24509e690476
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
48
|
-
-c, --color
|
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__)
|
18
|
+
$LOAD_PATH.unshift(File.expand_path("#{File.dirname(__FILE__)}/../lib"))
|
19
19
|
|
20
20
|
require 'git-fastclone'
|
21
21
|
|
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,
|
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'
|
72
|
+
DEFAULT_REFERENCE_REPO_DIR = '/var/tmp/git-fastclone/reference'
|
73
73
|
|
74
|
-
DEFAULT_GIT_ALLOW_PROTOCOL = 'file:git:http:https:ssh'
|
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(
|
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
|
-
|
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
|
-
|
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:
|
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
|
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
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
316
|
-
|
317
|
-
|
318
|
-
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
336
|
+
def retriable_error
|
337
|
+
%(
|
338
|
+
STDOUT:
|
319
339
|
|
320
|
-
|
321
|
-
yielded << test_url_valid
|
322
|
-
end
|
340
|
+
STDERR:
|
323
341
|
|
324
|
-
|
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
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
raise Terrapin::ExitStatusError,
|
335
|
-
|
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
|
-
|
358
|
+
subject.with_git_mirror(test_url_valid) do |url, attempt|
|
359
|
+
raise 'Not enough responses were provided!' if lambdas.empty?
|
338
360
|
|
339
|
-
|
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(
|
350
|
-
expect(yielded).to eq(
|
364
|
+
expect(lambdas).to be_empty
|
365
|
+
expect(yielded).to eq(results)
|
351
366
|
end
|
352
367
|
|
353
|
-
|
354
|
-
|
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
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
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
|
-
|
377
|
-
|
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
|
-
|
381
|
-
|
382
|
-
|
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
|
-
|
392
|
+
def clone_cmds
|
393
|
+
[
|
394
|
+
['git clone', '--mirror :url :mirror'],
|
395
|
+
['cd', ':path; git remote update --prune']
|
396
|
+
]
|
397
|
+
end
|
391
398
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
ERROR
|
399
|
+
def clone_args
|
400
|
+
[
|
401
|
+
{
|
402
|
+
mirror: test_reference_repo_dir,
|
403
|
+
url: test_url_valid
|
398
404
|
},
|
399
|
-
|
405
|
+
{
|
406
|
+
path: test_reference_repo_dir
|
407
|
+
}
|
400
408
|
]
|
401
|
-
|
402
|
-
|
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
|
-
|
406
|
-
|
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
|
-
|
410
|
-
|
411
|
-
|
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
|
-
|
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
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
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
|
-
|
433
|
-
|
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 '
|
437
|
-
|
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
|
-
|
442
|
-
|
443
|
-
raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
|
444
|
-
STDOUT:
|
455
|
+
expect(subject.retriable_error?(error)).to be_falsey
|
456
|
+
end
|
445
457
|
|
446
|
-
|
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
|
-
|
449
|
-
|
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
|
-
|
460
|
-
|
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
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
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
|
-
|
494
|
-
|
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
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
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
|
-
|
496
|
+
expect(subject.retriable_error?(error)).to be_truthy
|
497
|
+
end
|
504
498
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
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
|
-
|
518
|
-
|
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
|
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.
|
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:
|
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: '
|
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: []
|