Show More
@@ -7,6 +7,7 b'' | |||||
7 |
|
7 | |||
8 | from __future__ import absolute_import |
|
8 | from __future__ import absolute_import | |
9 |
|
9 | |||
|
10 | import struct | |||
10 | import time |
|
11 | import time | |
11 |
|
12 | |||
12 | from .i18n import _ |
|
13 | from .i18n import _ | |
@@ -236,6 +237,61 b' def generatev1wireproto(repo):' | |||||
236 | for chunk in it: |
|
237 | for chunk in it: | |
237 | yield chunk |
|
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 | def consumev1(repo, fp, filecount, bytecount): |
|
295 | def consumev1(repo, fp, filecount, bytecount): | |
240 | """Apply the contents from version 1 of a streaming clone file handle. |
|
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 | util.bytecount(bytecount / elapsed))) |
|
346 | util.bytecount(bytecount / elapsed))) | |
291 | finally: |
|
347 | finally: | |
292 | lock.release() |
|
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