changeset 20802:520df53ad26a

bundle2: a very first version of bundle2 unbundler This changeset introduce an unbundler class to match the bundle2 bundler. It is currently able to unbundle an empty bundle2 only and will gain more feature at the same pace than the bundler. It also comes with its special extension command in test.
author Pierre-Yves David <pierre-yves.david@fb.com>
date Tue, 18 Mar 2014 14:28:42 -0700
parents 9c5183cb9bca
children 88db3e615319
files mercurial/bundle2.py tests/test-bundle2.t
diffstat 2 files changed, 64 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/bundle2.py	Tue Mar 18 14:00:50 2014 -0700
+++ b/mercurial/bundle2.py	Tue Mar 18 14:28:42 2014 -0700
@@ -60,6 +60,10 @@
   Currently forced to 0 in the current state of the implementation
 """
 
+import util
+import changegroup
+
+
 _magicstring = 'HG20'
 
 class bundle20(object):
@@ -82,3 +86,48 @@
         # to be obviously fixed soon.
         assert not self._parts
         yield '\0\0'
+
+class unbundle20(object):
+    """interpret a bundle2 stream
+
+    (this will eventually yield parts)"""
+
+    def __init__(self, fp):
+        # assume the magic string is ok and drop it
+        # to be obviously fixed soon.
+        self._fp = fp
+        self._readexact(4)
+
+    def _unpack(self, format):
+        """unpack this struct format from the stream"""
+        data = self._readexact(struct.calcsize(format))
+        return _unpack(format, data)
+
+    def _readexact(self, size):
+        """read exactly <size> bytes from the stream"""
+        return changegroup.readexactly(self._fp, size)
+
+    @util.propertycache
+    def params(self):
+        """dictionnary of stream level parameters"""
+        paramsize = self._readexact(2)
+        assert paramsize == '\0\0'
+        return {}
+
+    def __iter__(self):
+        """yield all parts contained in the stream"""
+        # make sure param have been loaded
+        self.params
+        part = self._readpart()
+        while part is not None:
+            yield part
+            part = self._readpart()
+
+    def _readpart(self):
+        """return None when an end of stream markers is reach"""
+        headersize = self._readexact(2)
+        assert headersize == '\0\0'
+        return None
+
+
+
--- a/tests/test-bundle2.t	Tue Mar 18 14:00:50 2014 -0700
+++ b/tests/test-bundle2.t	Tue Mar 18 14:28:42 2014 -0700
@@ -8,6 +8,7 @@
   > code. We still need to be able to test it while it grow up.
   > """
   > 
+  > import sys
   > from mercurial import cmdutil
   > from mercurial import bundle2
   > cmdtable = {}
@@ -19,6 +20,14 @@
   >     bundle = bundle2.bundle20()
   >     for chunk in bundle.getchunks():
   >         ui.write(chunk)
+  > 
+  > @command('unbundle2', [], '')
+  > def cmdunbundle2(ui, repo):
+  >     """read a bundle2 container from standard input"""
+  >     unbundler = bundle2.unbundle20(sys.stdin)
+  >     ui.write('options count: %i\n' % len(unbundler.params))
+  >     parts = list(unbundler)
+  >     ui.write('parts count:   %i\n' % len(parts))
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
@@ -34,3 +43,9 @@
 
   $ hg bundle2
   HG20\x00\x00\x00\x00 (no-eol) (esc)
+
+Test parsing of an empty bundle
+
+  $ hg bundle2 | hg unbundle2
+  options count: 0
+  parts count:   0