Patchwork [2,of,5] bundle2: a very first version of bundle2 unbundler

login
register
mail settings
Submitter Pierre-Yves David
Date March 19, 2014, 10:02 p.m.
Message ID <cc3fc21c658c7ccd6af1.1395266523@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/3997/
State Superseded
Commit 520df53ad26a9105f5d4a7647243567bb7a0e697
Headers show

Comments

Pierre-Yves David - March 19, 2014, 10:02 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1395178122 25200
#      Tue Mar 18 14:28:42 2014 -0700
# Node ID cc3fc21c658c7ccd6af116853c09feaa8be87cae
# Parent  f77651bc816e79ae36b17641f13194edb92e5dcf
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.

Patch

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -58,10 +58,14 @@  Binary format is as follow
   (size = 0) this is interpreted as the end of stream marker.
 
   Currently forced to 0 in the current state of the implementation
 """
 
+import util
+import changegroup
+
+
 _magicstring = 'HG20'
 
 class bundler(object):
     """represent an outgoing bundle2 container
 
@@ -80,5 +84,44 @@  class bundler(object):
         yield '\0\0'
         # no support for parts
         # to be obviously fixed soon.
         assert not self._parts
         yield '\0\0'
+
+class unbundler(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 _readexact(self, n):
+        return changegroup.readexactly(self._fp, n)
+
+    @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
+
+
+
diff --git a/tests/test-bundle2.t b/tests/test-bundle2.t
--- a/tests/test-bundle2.t
+++ b/tests/test-bundle2.t
@@ -6,10 +6,11 @@  Create an extension to test bundle2 API
   > 
   > Current bundle2 implementation is far too limited to be used in any core
   > 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 = {}
   > command = cmdutil.command(cmdtable)
   > 
@@ -17,10 +18,18 @@  Create an extension to test bundle2 API
   > def cmdbundle2(ui, repo):
   >     """write a bundle2 container on standard ouput"""
   >     bundler = bundle2.bundler()
   >     for chunk in bundler.getchunks():
   >         ui.write(chunk)
+  > 
+  > @command('unbundle2', [], '')
+  > def cmdunbundle2(ui, repo):
+  >     """read a bundle2 container from standard input"""
+  >     unbundler = bundle2.unbundler(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]
   > bundle2=$TESTTMP/bundle2.py
   > EOF
@@ -32,5 +41,11 @@  The extension requires a repo (currently
 
 Test simple generation of empty bundle
 
   $ 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