git-fastclone 1.2.7 → 1.3.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 +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 +76 -33
- data/spec/git_fastclone_runner_spec.rb +190 -182
- data/spec/git_fastclone_url_helper_spec.rb +1 -1
- metadata +7 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d83fead53c699b625667b994e06ab9cfe0c25ed62c01239c054ec1523109f986
|
4
|
+
data.tar.gz: 9e4513fe51d1e1deb357a1781fadd1df723997df527402c12beb436103ce7b2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecc286e3180698eab52e06e61d3bf62c27af6524a3ab398d86cf1bbbe336dd9c547609ec6744264be506ea9f41920cf68210b08d6fad752f5d8fc589b069e2be
|
7
|
+
data.tar.gz: 653a21b17f4a033ee23bd77f1953a72df9176006ebb320a33ed179e4f7f2a957133a4569f1d91722c65d2d638ee727eae15d41b6b73f5b133d138823365e6937
|
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,
|
@@ -121,7 +121,7 @@ module GitFastClone
|
|
121
121
|
puts "Cloning #{path_from_git_url(url)} to #{File.join(abs_clone_path, path)}"
|
122
122
|
Terrapin::CommandLine.environment['GIT_ALLOW_PROTOCOL'] =
|
123
123
|
ENV['GIT_ALLOW_PROTOCOL'] || DEFAULT_GIT_ALLOW_PROTOCOL
|
124
|
-
clone(url, options[:branch], path)
|
124
|
+
clone(url, options[:branch], path, options[:config])
|
125
125
|
end
|
126
126
|
|
127
127
|
def parse_options
|
@@ -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
|
@@ -148,6 +148,10 @@ module GitFastClone
|
|
148
148
|
self.color = true
|
149
149
|
end
|
150
150
|
|
151
|
+
opts.on('--config CONFIG', 'Git config applied to the cloned repo') do |config|
|
152
|
+
options[:config] = config
|
153
|
+
end
|
154
|
+
|
151
155
|
opts.on('--lock-timeout N', 'Timeout in seconds to acquire a lock on any reference repo.
|
152
156
|
Default is 0 which waits indefinitely.') do |timeout_secs|
|
153
157
|
self.flock_timeout_secs = timeout_secs
|
@@ -159,7 +163,7 @@ module GitFastClone
|
|
159
163
|
parse_options
|
160
164
|
|
161
165
|
unless ARGV[0]
|
162
|
-
|
166
|
+
warn usage
|
163
167
|
exit(129)
|
164
168
|
end
|
165
169
|
|
@@ -185,16 +189,41 @@ module GitFastClone
|
|
185
189
|
[url, path, options]
|
186
190
|
end
|
187
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
|
+
|
188
207
|
# Checkout to SOURCE_DIR. Update all submodules recursively. Use reference
|
189
208
|
# repos everywhere for speed.
|
190
|
-
def clone(url, rev, src_dir)
|
209
|
+
def clone(url, rev, src_dir, config)
|
210
|
+
clone_dest = File.join(abs_clone_path, src_dir).to_s
|
191
211
|
initial_time = Time.now
|
192
212
|
|
193
|
-
|
194
|
-
|
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
|
+
|
220
|
+
clone_command = '--quiet --reference :mirror :url :path'
|
221
|
+
clone_command += ' --config :config' unless config.nil?
|
222
|
+
Terrapin::CommandLine.new('git clone', clone_command)
|
195
223
|
.run(mirror: mirror.to_s,
|
196
224
|
url: url.to_s,
|
197
|
-
path:
|
225
|
+
path: clone_dest,
|
226
|
+
config: config.to_s)
|
198
227
|
end
|
199
228
|
|
200
229
|
# Only checkout if we're changing branches to a non-default branch
|
@@ -238,7 +267,7 @@ module GitFastClone
|
|
238
267
|
|
239
268
|
def thread_update_submodule(submodule_url, submodule_path, threads, pwd)
|
240
269
|
threads << Thread.new do
|
241
|
-
with_git_mirror(submodule_url) do |mirror|
|
270
|
+
with_git_mirror(submodule_url) do |mirror, _|
|
242
271
|
Terrapin::CommandLine.new('cd', ':dir; git submodule update --quiet --reference :mirror :path')
|
243
272
|
.run(dir: File.join(abs_clone_path, pwd).to_s,
|
244
273
|
mirror: mirror.to_s,
|
@@ -263,14 +292,12 @@ module GitFastClone
|
|
263
292
|
lockfile.close
|
264
293
|
end
|
265
294
|
|
266
|
-
def with_reference_repo_thread_lock(url)
|
295
|
+
def with_reference_repo_thread_lock(url, &block)
|
267
296
|
# We also need thread level locking because pre-fetch means multiple threads can
|
268
297
|
# attempt to update the same repository from a single git-fastclone process
|
269
298
|
# file locks in posix are tracked per process, not per userland thread.
|
270
299
|
# This gives us the equivalent of pthread_mutex around these accesses.
|
271
|
-
reference_mutex[reference_repo_name(url)].synchronize
|
272
|
-
yield
|
273
|
-
end
|
300
|
+
reference_mutex[reference_repo_name(url)].synchronize(&block)
|
274
301
|
end
|
275
302
|
|
276
303
|
def update_submodule_reference(url, submodule_url_list)
|
@@ -331,6 +358,31 @@ module GitFastClone
|
|
331
358
|
raise e if fail_hard
|
332
359
|
end
|
333
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)
|
383
|
+
FileUtils.remove_entry_secure(dir, force: true)
|
384
|
+
end
|
385
|
+
|
334
386
|
# This command will create and bring the mirror up-to-date on-demand,
|
335
387
|
# blocking any code passed in while the mirror is brought up-to-date
|
336
388
|
#
|
@@ -347,27 +399,18 @@ module GitFastClone
|
|
347
399
|
# This makes sure we have control and unlock when the block returns:
|
348
400
|
with_reference_repo_lock(url) do
|
349
401
|
dir = reference_repo_dir(url, reference_dir, using_local_repo)
|
350
|
-
|
402
|
+
retries_allowed = 1
|
403
|
+
attempt_number = 0
|
351
404
|
|
352
405
|
begin
|
353
|
-
yield dir
|
406
|
+
yield dir, attempt_number
|
354
407
|
rescue Terrapin::ExitStatusError => e
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
'fatal: did not receive expected object',
|
362
|
-
/^fatal: unable to read tree [a-z0-9]+\n^warning: Clone succeeded, but checkout failed/
|
363
|
-
]
|
364
|
-
if e.to_s =~ /^STDERR:\n.+^#{Regexp.union(error_strings)}/m
|
365
|
-
# To avoid corruption of the cache, if we failed to update or check out we remove
|
366
|
-
# the cache directory entirely. This may cause the current clone to fail, but if the
|
367
|
-
# underlying error from git is transient it will not affect future clones.
|
368
|
-
FileUtils.remove_entry_secure(dir, force: true)
|
369
|
-
if retries_left > 0
|
370
|
-
retries_left -= 1
|
408
|
+
if retriable_error?(e)
|
409
|
+
print_formatted_error(e)
|
410
|
+
clear_cache(dir)
|
411
|
+
|
412
|
+
if attempt_number < retries_allowed
|
413
|
+
attempt_number += 1
|
371
414
|
retry
|
372
415
|
end
|
373
416
|
end
|
@@ -56,11 +56,22 @@ describe GitFastClone::Runner do
|
|
56
56
|
let(:options) { { branch: placeholder_arg } }
|
57
57
|
|
58
58
|
it 'should run with the correct args' do
|
59
|
-
allow(subject).to receive(:parse_inputs) { [placeholder_arg, placeholder_arg, options] }
|
60
|
-
expect(subject).to receive(:clone).with(placeholder_arg, placeholder_arg, placeholder_arg)
|
59
|
+
allow(subject).to receive(:parse_inputs) { [placeholder_arg, placeholder_arg, options, nil] }
|
60
|
+
expect(subject).to receive(:clone).with(placeholder_arg, placeholder_arg, placeholder_arg, nil)
|
61
61
|
|
62
62
|
subject.run
|
63
63
|
end
|
64
|
+
|
65
|
+
describe 'with custom configs' do
|
66
|
+
let(:options) { { branch: placeholder_arg, config: 'conf' } }
|
67
|
+
|
68
|
+
it 'should clone correctly' do
|
69
|
+
allow(subject).to receive(:parse_inputs) { [placeholder_arg, placeholder_arg, options, 'conf'] }
|
70
|
+
expect(subject).to receive(:clone).with(placeholder_arg, placeholder_arg, placeholder_arg, 'conf')
|
71
|
+
|
72
|
+
subject.run
|
73
|
+
end
|
74
|
+
end
|
64
75
|
end
|
65
76
|
|
66
77
|
describe '.parse_inputs' do
|
@@ -74,17 +85,69 @@ describe GitFastClone::Runner do
|
|
74
85
|
end
|
75
86
|
|
76
87
|
describe '.clone' do
|
77
|
-
|
78
|
-
|
79
|
-
allow(subject).to receive(:with_git_mirror) {}
|
88
|
+
let(:terrapin_commandline_double) { double('new_terrapin_commandline') }
|
89
|
+
before(:each) do
|
80
90
|
allow(terrapin_commandline_double).to receive(:run) {}
|
81
|
-
allow(Terrapin::CommandLine).to receive(:new) { terrapin_commandline_double }
|
82
|
-
|
83
91
|
expect(Time).to receive(:now).twice { 0 }
|
84
|
-
|
85
|
-
|
92
|
+
allow(Dir).to receive(:pwd) { '/pwd' }
|
93
|
+
allow(Dir).to receive(:chdir).and_yield
|
94
|
+
allow(subject).to receive(:with_git_mirror).and_yield('/cache', 0)
|
95
|
+
expect(subject).to receive(:clear_clone_dest_if_needed).once {}
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should clone correctly' do
|
99
|
+
expect(Terrapin::CommandLine).to receive(:new).with(
|
100
|
+
'git clone',
|
101
|
+
'--quiet --reference :mirror :url :path'
|
102
|
+
) { terrapin_commandline_double }
|
103
|
+
expect(Terrapin::CommandLine).to receive(:new).with(
|
104
|
+
'git checkout',
|
105
|
+
'--quiet :rev'
|
106
|
+
) { terrapin_commandline_double }
|
107
|
+
expect(terrapin_commandline_double).to receive(:run).with(
|
108
|
+
mirror: '/cache',
|
109
|
+
url: placeholder_arg,
|
110
|
+
path: '/pwd/.',
|
111
|
+
config: ''
|
112
|
+
)
|
113
|
+
expect(terrapin_commandline_double).to receive(:run).with(rev: placeholder_arg)
|
114
|
+
|
115
|
+
subject.clone(placeholder_arg, placeholder_arg, '.', nil)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should clone correctly with custom configs' 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
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '.clear_clone_dest_if_needed' do
|
135
|
+
it 'does not clear on first attempt' do
|
136
|
+
expect(Dir).not_to receive(:glob)
|
137
|
+
expect(subject).not_to receive(:clear_clone_dest)
|
138
|
+
subject.clear_clone_dest_if_needed(0, '/some/path')
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'does not clear if the directory is only FNM_DOTMATCH self and parent refs' do
|
142
|
+
expect(Dir).to receive(:glob).and_return(%w[. ..])
|
143
|
+
expect(subject).not_to receive(:clear_clone_dest)
|
144
|
+
subject.clear_clone_dest_if_needed(1, '/some/path')
|
145
|
+
end
|
86
146
|
|
87
|
-
|
147
|
+
it 'does clear if the directory is not empty' do
|
148
|
+
expect(Dir).to receive(:glob).and_return(%w[. .. /some/path/file.txt])
|
149
|
+
expect(subject).to receive(:clear_clone_dest) {}
|
150
|
+
subject.clear_clone_dest_if_needed(1, '/some/path')
|
88
151
|
end
|
89
152
|
end
|
90
153
|
|
@@ -268,209 +331,154 @@ describe GitFastClone::Runner do
|
|
268
331
|
end
|
269
332
|
|
270
333
|
describe '.with_git_mirror' do
|
271
|
-
|
334
|
+
before(:each) do
|
272
335
|
allow(subject).to receive(:update_reference_repo) {}
|
273
|
-
|
336
|
+
allow(subject).to receive(:print_formatted_error) {}
|
337
|
+
expect(subject).to receive(:reference_repo_dir).and_return(test_reference_repo_dir)
|
274
338
|
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
339
|
+
end
|
275
340
|
|
276
|
-
|
277
|
-
|
278
|
-
|
341
|
+
def retriable_error
|
342
|
+
%(
|
343
|
+
STDOUT:
|
279
344
|
|
280
|
-
|
345
|
+
STDERR:
|
346
|
+
|
347
|
+
fatal: bad object ee35b1e14e7c3a53dcc14d82606e5b872f6a05a7
|
348
|
+
fatal: remote did not send all necessary objects
|
349
|
+
).strip.split("\n").map(&:strip).join("\n")
|
281
350
|
end
|
282
351
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
352
|
+
def try_with_git_mirror(responses, results)
|
353
|
+
lambdas = responses.map do |response|
|
354
|
+
if response == true
|
355
|
+
# Simulate successful response
|
356
|
+
->(url) { url }
|
357
|
+
else
|
358
|
+
# Simulate failed error response
|
359
|
+
->(_url) { raise Terrapin::ExitStatusError, response }
|
360
|
+
end
|
361
|
+
end
|
287
362
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
STDERR:
|
294
|
-
|
295
|
-
fatal: bad object ee35b1e14e7c3a53dcc14d82606e5b872f6a05a7
|
296
|
-
fatal: remote did not send all necessary objects
|
297
|
-
ERROR
|
298
|
-
},
|
299
|
-
->(url) { url }
|
300
|
-
]
|
301
|
-
subject.with_git_mirror(test_url_valid) do
|
302
|
-
yielded << responses.shift.call(test_url_valid)
|
363
|
+
subject.with_git_mirror(test_url_valid) do |url, attempt|
|
364
|
+
raise 'Not enough responses were provided!' if lambdas.empty?
|
365
|
+
|
366
|
+
yielded << [lambdas.shift.call(url), attempt]
|
303
367
|
end
|
304
368
|
|
305
|
-
expect(
|
306
|
-
expect(yielded).to eq(
|
369
|
+
expect(lambdas).to be_empty
|
370
|
+
expect(yielded).to eq(results)
|
307
371
|
end
|
308
372
|
|
309
|
-
it 'should
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
responses = [
|
315
|
-
lambda { |_url|
|
316
|
-
raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
|
317
|
-
STDOUT:
|
318
|
-
|
319
|
-
STDERR:
|
320
|
-
|
321
|
-
fatal: packed object 7c4d79704f8adf701f38a7bfb3e33ec5342542f1 (stored in /private/var/tmp/git-fastclone/reference/some-repo.git/objects/pack/pack-d37d7ed3e88d6e5f0ac141a7b0a2b32baf6e21a0.pack) is corrupt
|
322
|
-
warning: Clone succeeded, but checkout failed.
|
323
|
-
You can inspect what was checked out with 'git status' and retry with 'git restore --source=HEAD :/'
|
324
|
-
ERROR
|
325
|
-
},
|
326
|
-
->(url) { url }
|
327
|
-
]
|
328
|
-
subject.with_git_mirror(test_url_valid) do
|
329
|
-
yielded << responses.shift.call(test_url_valid)
|
330
|
-
end
|
373
|
+
it 'should yield properly' do
|
374
|
+
expect(subject).not_to receive(:clear_cache)
|
375
|
+
try_with_git_mirror([true], [[test_reference_repo_dir, 0]])
|
376
|
+
end
|
331
377
|
|
332
|
-
|
333
|
-
expect(
|
378
|
+
it 'should retry once for retriable errors' do
|
379
|
+
expect(subject).to receive(:clear_cache).once {}
|
380
|
+
try_with_git_mirror([retriable_error, true], [[test_reference_repo_dir, 1]])
|
334
381
|
end
|
335
382
|
|
336
|
-
it 'should retry
|
337
|
-
|
338
|
-
expect
|
339
|
-
|
383
|
+
it 'should only retry twice at most' do
|
384
|
+
expect(subject).to receive(:clear_cache).twice {}
|
385
|
+
expect do
|
386
|
+
try_with_git_mirror([retriable_error, retriable_error], [])
|
387
|
+
end.to raise_error(Terrapin::ExitStatusError)
|
388
|
+
end
|
340
389
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
error: Could not read 92cf57b8f07df010ab5f607b109c325e30e46235
|
349
|
-
fatal: unable to read tree 0c32c0521d3b0bfb4e74e4a39b97a84d1a3bb9a1
|
350
|
-
warning: Clone succeeded, but checkout failed.
|
351
|
-
You can inspect what was checked out with 'git status'
|
352
|
-
and retry with 'git restore --source=HEAD :/'
|
353
|
-
ERROR
|
354
|
-
},
|
355
|
-
->(url) { url }
|
356
|
-
]
|
357
|
-
subject.with_git_mirror(test_url_valid) do
|
358
|
-
yielded << responses.shift.call(test_url_valid)
|
359
|
-
end
|
390
|
+
it 'should not retry for non-retriable errors' do
|
391
|
+
expect(subject).not_to receive(:clear_cache)
|
392
|
+
expect do
|
393
|
+
try_with_git_mirror(['Some unexpected error message'], [])
|
394
|
+
end.to raise_error(Terrapin::ExitStatusError)
|
395
|
+
end
|
396
|
+
end
|
360
397
|
|
361
|
-
|
362
|
-
|
398
|
+
describe '.retriable_error?' do
|
399
|
+
def format_error(error)
|
400
|
+
error_wrapper = "STDOUT:\n\nSTDERR:\n#{error}"
|
401
|
+
error_wrapper.strip.lines.map(&:strip).join("\n")
|
363
402
|
end
|
364
403
|
|
365
|
-
it '
|
366
|
-
|
367
|
-
expect(subject).to receive(:reference_repo_dir)
|
368
|
-
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
404
|
+
it 'not for a random error message' do
|
405
|
+
error = format_error 'random error message'
|
369
406
|
|
370
|
-
|
371
|
-
|
372
|
-
raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
|
373
|
-
STDOUT:
|
374
|
-
|
375
|
-
STDERR:
|
376
|
-
|
377
|
-
error: Could not read f7fad86d06fee0678f9af7203b6031feabb40c3e
|
378
|
-
fatal: pack has 1 unresolved delta
|
379
|
-
fatal: index-pack failed
|
380
|
-
ERROR
|
381
|
-
},
|
382
|
-
->(url) { url }
|
383
|
-
]
|
384
|
-
subject.with_git_mirror(test_url_valid) do
|
385
|
-
yielded << responses.shift.call(test_url_valid)
|
386
|
-
end
|
407
|
+
expect(subject.retriable_error?(error)).to be_falsey
|
408
|
+
end
|
387
409
|
|
388
|
-
|
389
|
-
|
410
|
+
it 'when the cache looks corrupted' do
|
411
|
+
error = format_error <<-ERROR
|
412
|
+
fatal: bad object ee35b1e14e7c3a53dcc14d82606e5b872f6a05a7
|
413
|
+
fatal: remote did not send all necessary objects
|
414
|
+
ERROR
|
415
|
+
|
416
|
+
expect(subject.retriable_error?(error)).to be_truthy
|
390
417
|
end
|
391
418
|
|
392
|
-
it '
|
393
|
-
|
394
|
-
|
395
|
-
|
419
|
+
it 'when the clone succeeds but checkout fails with corrupt packed object' do
|
420
|
+
error = format_error <<-ERROR
|
421
|
+
fatal: packed object 7c4d79704f8adf701f38a7bfb3e33ec5342542f1 (stored in /private/var/tmp/git-fastclone/reference/some-repo.git/objects/pack/pack-d37d7ed3e88d6e5f0ac141a7b0a2b32baf6e21a0.pack) is corrupt
|
422
|
+
warning: Clone succeeded, but checkout failed.
|
423
|
+
You can inspect what was checked out with 'git status' and retry with 'git restore --source=HEAD :/'
|
424
|
+
ERROR
|
396
425
|
|
397
|
-
|
398
|
-
|
399
|
-
raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
|
400
|
-
STDOUT:
|
401
|
-
|
402
|
-
STDERR:
|
403
|
-
|
404
|
-
error: Could not read f7fad86d06fee0678f9af7203b6031feabb40c3e
|
405
|
-
fatal: pack has 138063 unresolved deltas
|
406
|
-
fatal: index-pack failed
|
407
|
-
ERROR
|
408
|
-
},
|
409
|
-
->(url) { url }
|
410
|
-
]
|
411
|
-
subject.with_git_mirror(test_url_valid) do
|
412
|
-
yielded << responses.shift.call(test_url_valid)
|
413
|
-
end
|
426
|
+
expect(subject.retriable_error?(error)).to be_truthy
|
427
|
+
end
|
414
428
|
|
415
|
-
|
416
|
-
|
429
|
+
it 'when the clone succeeds but checkout fails with unable to read tree' do
|
430
|
+
error = format_error <<-ERROR
|
431
|
+
error: Could not read 92cf57b8f07df010ab5f607b109c325e30e46235
|
432
|
+
fatal: unable to read tree 0c32c0521d3b0bfb4e74e4a39b97a84d1a3bb9a1
|
433
|
+
warning: Clone succeeded, but checkout failed.
|
434
|
+
You can inspect what was checked out with 'git status'
|
435
|
+
and retry with 'git restore --source=HEAD :/'
|
436
|
+
ERROR
|
437
|
+
|
438
|
+
expect(subject.retriable_error?(error)).to be_truthy
|
417
439
|
end
|
418
|
-
end
|
419
440
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
STDERR:
|
431
|
-
|
432
|
-
error: unable to read sha1 file of sqiosbuild/lib/action/action.rb (6113b739af82d8b07731de8a58d6e233301f80ab)
|
433
|
-
fatal: unable to checkout working tree
|
434
|
-
warning: Clone succeeded, but checkout failed.
|
435
|
-
You can inspect what was checked out with 'git status'
|
436
|
-
and retry with 'git restore --source=HEAD :/'
|
437
|
-
ERROR
|
438
|
-
},
|
439
|
-
->(url) { url }
|
440
|
-
]
|
441
|
-
subject.with_git_mirror(test_url_valid) do
|
442
|
-
yielded << responses.shift.call(test_url_valid)
|
443
|
-
end
|
444
|
-
|
445
|
-
expect(responses).to be_empty
|
446
|
-
expect(yielded).to eq([test_url_valid])
|
447
|
-
end
|
441
|
+
it 'when one delta is missing' do
|
442
|
+
error = format_error <<-ERROR
|
443
|
+
error: Could not read f7fad86d06fee0678f9af7203b6031feabb40c3e
|
444
|
+
fatal: pack has 1 unresolved delta
|
445
|
+
fatal: index-pack failed
|
446
|
+
ERROR
|
447
|
+
|
448
|
+
expect(subject.retriable_error?(error)).to be_truthy
|
449
|
+
end
|
448
450
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
451
|
+
it 'when deltas are missing' do
|
452
|
+
error = format_error <<-ERROR
|
453
|
+
error: Could not read f7fad86d06fee0678f9af7203b6031feabb40c3e
|
454
|
+
fatal: pack has 138063 unresolved deltas
|
455
|
+
fatal: index-pack failed
|
456
|
+
ERROR
|
453
457
|
|
454
|
-
|
455
|
-
|
456
|
-
raise Terrapin::ExitStatusError, <<-ERROR.gsub(/^ {12}/, '')
|
457
|
-
STDOUT:
|
458
|
+
expect(subject.retriable_error?(error)).to be_truthy
|
459
|
+
end
|
458
460
|
|
459
|
-
|
461
|
+
it 'when the cache errors with unable to read sha1 file' do
|
462
|
+
error = format_error <<-ERROR
|
463
|
+
error: unable to read sha1 file of sqiosbuild/lib/action/action.rb (6113b739af82d8b07731de8a58d6e233301f80ab)
|
464
|
+
fatal: unable to checkout working tree
|
465
|
+
warning: Clone succeeded, but checkout failed.
|
466
|
+
You can inspect what was checked out with 'git status'
|
467
|
+
and retry with 'git restore --source=HEAD :/'
|
468
|
+
ERROR
|
460
469
|
|
461
|
-
|
462
|
-
error: Could not read 0cd3703c23fa44c0043d97fbc26356a23939f31b
|
463
|
-
fatal: did not receive expected object 3c64c9dd49c79bd09aa13d4b05ac18263ca29ccd
|
464
|
-
fatal: index-pack failed
|
465
|
-
ERROR
|
466
|
-
},
|
467
|
-
->(url) { url }
|
468
|
-
]
|
469
|
-
subject.with_git_mirror(test_url_valid) do
|
470
|
-
yielded << responses.shift.call(test_url_valid)
|
470
|
+
expect(subject.retriable_error?(error)).to be_truthy
|
471
471
|
end
|
472
472
|
|
473
|
-
|
474
|
-
|
473
|
+
it 'when the cache errors with did not receive expected object' do
|
474
|
+
error = format_error <<-ERROR
|
475
|
+
error: Could not read 6682dfe81f66656436e60883dd795e7ec6735153
|
476
|
+
error: Could not read 0cd3703c23fa44c0043d97fbc26356a23939f31b
|
477
|
+
fatal: did not receive expected object 3c64c9dd49c79bd09aa13d4b05ac18263ca29ccd
|
478
|
+
fatal: index-pack failed
|
479
|
+
ERROR
|
480
|
+
|
481
|
+
expect(subject.retriable_error?(error)).to be_truthy
|
482
|
+
end
|
475
483
|
end
|
476
484
|
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.
|
4
|
+
version: 1.3.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:
|
12
|
+
date: 2022-12-08 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,18 +71,15 @@ 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
|
- - ">="
|
77
78
|
- !ruby/object:Gem::Version
|
78
79
|
version: '0'
|
79
80
|
requirements: []
|
80
|
-
rubygems_version: 3.
|
81
|
+
rubygems_version: 3.3.7
|
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: []
|