changeset 20856:8a6a86c9a5b5

bundle2: support bundling of empty part (with a type) Here start the work on bundle2 parts. Our first step is to be able to bundle a simplistic part that just have a type, no parameters, empty payload.
author Pierre-Yves David <pierre-yves.david@fb.com>
date Tue, 18 Mar 2014 14:29:33 -0700
parents dfad9bb23ab4
children 81d6dc8c3c63
files mercurial/bundle2.py tests/test-bundle2.t
diffstat 2 files changed, 89 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/bundle2.py	Wed Mar 26 18:51:49 2014 -0700
+++ b/mercurial/bundle2.py	Tue Mar 18 14:29:33 2014 -0700
@@ -21,9 +21,7 @@
  - payload parts (any number)
  - end of stream marker.
 
-The current implementation accept some stream level option but no part.
-
-Details on the Binary format
+the Binary format
 ============================
 
 All numbers are unsigned and big endian.
@@ -71,7 +69,28 @@
   The total number of Bytes used by the part headers. When the header is empty
   (size = 0) this is interpreted as the end of stream marker.
 
-  Currently forced to 0 in the current state of the implementation
+:header:
+
+    The header defines how to interpret the part. It contains two piece of
+    data: the part type, and the part parameters.
+
+    The part type is used to route an application level handler, that can
+    interpret payload.
+
+    Part parameters are passed to the application level handler.  They are
+    meant to convey information that will help the application level object to
+    interpret the part payload.
+
+    The binary format of the header is has follow
+
+    :typesize: (one byte)
+    :typename: alphanumerical part name
+    :option: we do not support option yet this denoted by two 16 bites zero.
+
+:payload:
+
+    The current payload is a 32bit integer with a value of 0. This is
+    considered an "empty" payload.
 """
 
 import util
@@ -88,15 +107,15 @@
 _magicstring = 'HG20'
 
 _fstreamparamsize = '>H'
+_fpartheadersize = '>H'
+_fparttypesize = '>B'
 
 class bundle20(object):
     """represent an outgoing bundle2 container
 
-    Use the `addparam` method to add stream level parameter. Then call
-    `getchunks` to retrieve all the binary chunks of datathat compose the
-    bundle2 container.
-
-    This object does not support payload part yet."""
+    Use the `addparam` method to add stream level parameter. and `addpart` to
+    populate it. Then call `getchunks` to retrieve all the binary chunks of
+    datathat compose the bundle2 container."""
 
     def __init__(self, ui):
         self.ui = ui
@@ -111,6 +130,12 @@
             raise ValueError('non letter first character: %r' % name)
         self._params.append((name, value))
 
+    def addpart(self, part):
+        """add a new part to the bundle2 container
+
+        Parts contains the actuall applicative payload."""
+        self._parts.append(part)
+
     def getchunks(self):
         self.ui.debug('start emission of %s stream\n' % _magicstring)
         yield _magicstring
@@ -120,9 +145,11 @@
         if param:
             yield param
 
-        # no support for parts
-        # to be obviously fixed soon.
-        assert not self._parts
+        self.ui.debug('start of parts\n')
+        for part in self._parts:
+            self.ui.debug('bundle part: "%s"\n' % part.type)
+            for chunk in part.getchunks():
+                yield chunk
         self.ui.debug('end of bundle\n')
         yield '\0\0'
 
@@ -217,5 +244,26 @@
         assert headersize == '\0\0'
         return None
 
+class part(object):
+    """A bundle2 part contains application level payload
+
+    The part `type` is used to route the part to the application level
+    handler.
+    """
+
+    def __init__(self, parttype):
+        self.type = parttype
+
+    def getchunks(self):
+        ### header
+        header = [_pack(_fparttypesize, len(self.type)),
+                  self.type,
+                  '\0\0', # No option support for now.
+                 ]
+        headerchunk = ''.join(header)
+        yield _pack(_fpartheadersize, len(headerchunk))
+        yield headerchunk
+        # force empty part for now
+        yield '\0\0\0\0'
 
 
--- a/tests/test-bundle2.t	Wed Mar 26 18:51:49 2014 -0700
+++ b/tests/test-bundle2.t	Tue Mar 18 14:29:33 2014 -0700
@@ -16,7 +16,8 @@
   > command = cmdutil.command(cmdtable)
   > 
   > @command('bundle2',
-  >          [('', 'param', [], 'stream level parameter'),],
+  >          [('', 'param', [], 'stream level parameter'),
+  >           ('', 'parts', False, 'include some arbitrary parts to the bundle'),],
   >          '[OUTPUTFILE]')
   > def cmdbundle2(ui, repo, path=None, **opts):
   >     """write a bundle2 container on standard ouput"""
@@ -28,6 +29,13 @@
   >         except ValueError, exc:
   >             raise util.Abort('%s' % exc)
   > 
+  >     if opts['parts']:
+  >        part = bundle2.part('test:empty')
+  >        bundler.addpart(part)
+  >        # add a second one to make sure we handle multiple parts
+  >        part = bundle2.part('test:empty')
+  >        bundler.addpart(part)
+  > 
   >     if path is None:
   >        file = sys.stdout
   >     else:
@@ -178,6 +186,7 @@
   $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
   start emission of HG20 stream
   bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
+  start of parts
   end of bundle
 
 file content is ok
@@ -215,3 +224,22 @@
   $ hg bundle2 --param 42babar
   abort: non letter first character: '42babar'
   [255]
+
+
+Test part
+=================
+
+  $ hg bundle2 --parts ../parts.hg2 --debug
+  start emission of HG20 stream
+  bundle parameter: 
+  start of parts
+  bundle part: "test:empty"
+  bundle part: "test:empty"
+  end of bundle
+
+  $ cat ../parts.hg2
+  HG20\x00\x00\x00\r (esc)
+  test:empty\x00\x00\x00\x00\x00\x00\x00\r (esc)
+  test:empty\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+
+