Show More
@@ -7,6 +7,7 b'' | |||
|
7 | 7 | |
|
8 | 8 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | import struct | |
|
10 | 11 | import time |
|
11 | 12 | |
|
12 | 13 | from .i18n import _ |
@@ -236,6 +237,61 b' def generatev1wireproto(repo):' | |||
|
236 | 237 | for chunk in it: |
|
237 | 238 | yield chunk |
|
238 | 239 | |
|
240 | def generatebundlev1(repo, compression='UN'): | |
|
241 | """Emit content for version 1 of a stream clone bundle. | |
|
242 | ||
|
243 | The first 4 bytes of the output ("HGS1") denote this as stream clone | |
|
244 | bundle version 1. | |
|
245 | ||
|
246 | The next 2 bytes indicate the compression type. Only "UN" is currently | |
|
247 | supported. | |
|
248 | ||
|
249 | The next 16 bytes are two 64-bit big endian unsigned integers indicating | |
|
250 | file count and byte count, respectively. | |
|
251 | ||
|
252 | The next 2 bytes is a 16-bit big endian unsigned short declaring the length | |
|
253 | of the requirements string, including a trailing \0. The following N bytes | |
|
254 | are the requirements string, which is ASCII containing a comma-delimited | |
|
255 | list of repo requirements that are needed to support the data. | |
|
256 | ||
|
257 | The remaining content is the output of ``generatev1()`` (which may be | |
|
258 | compressed in the future). | |
|
259 | ||
|
260 | Returns a tuple of (requirements, data generator). | |
|
261 | """ | |
|
262 | if compression != 'UN': | |
|
263 | raise ValueError('we do not support the compression argument yet') | |
|
264 | ||
|
265 | requirements = repo.requirements & repo.supportedformats | |
|
266 | requires = ','.join(sorted(requirements)) | |
|
267 | ||
|
268 | def gen(): | |
|
269 | yield 'HGS1' | |
|
270 | yield compression | |
|
271 | ||
|
272 | filecount, bytecount, it = generatev1(repo) | |
|
273 | repo.ui.status(_('writing %d bytes for %d files\n') % | |
|
274 | (bytecount, filecount)) | |
|
275 | ||
|
276 | yield struct.pack('>QQ', filecount, bytecount) | |
|
277 | yield struct.pack('>H', len(requires) + 1) | |
|
278 | yield requires + '\0' | |
|
279 | ||
|
280 | # This is where we'll add compression in the future. | |
|
281 | assert compression == 'UN' | |
|
282 | ||
|
283 | seen = 0 | |
|
284 | repo.ui.progress(_('bundle'), 0, total=bytecount) | |
|
285 | ||
|
286 | for chunk in it: | |
|
287 | seen += len(chunk) | |
|
288 | repo.ui.progress(_('bundle'), seen, total=bytecount) | |
|
289 | yield chunk | |
|
290 | ||
|
291 | repo.ui.progress(_('bundle'), None) | |
|
292 | ||
|
293 | return requirements, gen() | |
|
294 | ||
|
239 | 295 | def consumev1(repo, fp, filecount, bytecount): |
|
240 | 296 | """Apply the contents from version 1 of a streaming clone file handle. |
|
241 | 297 | |
@@ -290,3 +346,47 b' def consumev1(repo, fp, filecount, bytec' | |||
|
290 | 346 | util.bytecount(bytecount / elapsed))) |
|
291 | 347 | finally: |
|
292 | 348 | lock.release() |
|
349 | ||
|
350 | def applybundlev1(repo, fp): | |
|
351 | """Apply the content from a stream clone bundle version 1. | |
|
352 | ||
|
353 | We assume the 4 byte header has been read and validated and the file handle | |
|
354 | is at the 2 byte compression identifier. | |
|
355 | """ | |
|
356 | if len(repo): | |
|
357 | raise error.Abort(_('cannot apply stream clone bundle on non-empty ' | |
|
358 | 'repo')) | |
|
359 | ||
|
360 | compression = fp.read(2) | |
|
361 | if compression != 'UN': | |
|
362 | raise error.Abort(_('only uncompressed stream clone bundles are ' | |
|
363 | 'supported; got %s') % compression) | |
|
364 | ||
|
365 | filecount, bytecount = struct.unpack('>QQ', fp.read(16)) | |
|
366 | requireslen = struct.unpack('>H', fp.read(2))[0] | |
|
367 | requires = fp.read(requireslen) | |
|
368 | ||
|
369 | if not requires.endswith('\0'): | |
|
370 | raise error.Abort(_('malformed stream clone bundle: ' | |
|
371 | 'requirements not properly encoded')) | |
|
372 | ||
|
373 | requirements = set(requires.rstrip('\0').split(',')) | |
|
374 | missingreqs = requirements - repo.supportedformats | |
|
375 | if missingreqs: | |
|
376 | raise error.Abort(_('unable to apply stream clone: ' | |
|
377 | 'unsupported format: %s') % | |
|
378 | ', '.join(sorted(missingreqs))) | |
|
379 | ||
|
380 | consumev1(repo, fp, filecount, bytecount) | |
|
381 | ||
|
382 | class streamcloneapplier(object): | |
|
383 | """Class to manage applying streaming clone bundles. | |
|
384 | ||
|
385 | We need to wrap ``applybundlev1()`` in a dedicated type to enable bundle | |
|
386 | readers to perform bundle type-specific functionality. | |
|
387 | """ | |
|
388 | def __init__(self, fh): | |
|
389 | self._fh = fh | |
|
390 | ||
|
391 | def apply(self, repo): | |
|
392 | return applybundlev1(repo, self._fh) |
General Comments 0
You need to be logged in to leave comments.
Login now