[PATCH 1 of 4] bundle2: support bundling of empty part (with a type)

pierre-yves.david at ens-lyon.org pierre-yves.david at ens-lyon.org
Wed Mar 26 21:19:42 CDT 2014


# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at fb.com>
# Date 1395178173 25200
#      Tue Mar 18 14:29:33 2014 -0700
# Node ID 447f461fcdc476a60412f7e91b29e38d5b46de22
# Parent  5c6341690ef3465393bc77b59aecffc201ebd9b7
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.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -19,12 +19,10 @@ The format is architectured as follow
  - magic string
  - stream level parameters
  - payload parts (any number)
  - end of stream marker.
 
-The current implementation accept some stream level option but no part.
-
 Details on the Binary format
 ============================
 
 All numbers are unsigned and big endian.
 
@@ -69,11 +67,31 @@ Binary format is as follow
 :header size: (16 bits inter)
 
   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 contains data about how to interpret the part. it contains two
+    piece of data: the part type, and the part parameters.
+
+    The part type will be used to route the part to the proper application
+    level object that will interpret the part paylod.
+
+    The part option will be handed as argument this application level object.
+    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 payload is currently alway empty this is denoted by a 32bits zero.
 """
 
 import util
 import struct
 import urllib
@@ -86,19 +104,19 @@ from i18n import _
 _unpack = struct.unpack
 
 _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
         self._params = []
         self._parts = []
@@ -109,22 +127,30 @@ class bundle20(object):
             raise ValueError('empty parameter name')
         if name[0] not in string.letters:
             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
         param = self._paramchunk()
         self.ui.debug('bundle parameter: %s\n' % param)
         yield _pack(_fstreamparamsize, len(param))
         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('bundling part: "%s"\n' % part.type)
+            for chunk in part.getchunks():
+                yield chunk
         self.ui.debug('end of bundle\n')
         yield '\0\0'
 
     def _paramchunk(self):
         """return a encoded version of all stream parameters"""
@@ -215,7 +241,28 @@ class unbundle20(object):
         """return None when an end of stream markers is reach"""
         headersize = self._readexact(2)
         assert headersize == '\0\0'
         return None
 
+class part(object):
+    """A bundle2 part contains actual application level payload
 
+    The part have `type` used to route it to proper application level object
+    that interpret its content.
+    """
 
+    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'
+
+
diff --git a/tests/test-bundle2.t b/tests/test-bundle2.t
--- a/tests/test-bundle2.t
+++ b/tests/test-bundle2.t
@@ -14,11 +14,12 @@ Create an extension to test bundle2 API
   > from mercurial import bundle2
   > cmdtable = {}
   > 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"""
   >     bundler = bundle2.bundle20(ui)
   >     for p in opts['param']:
@@ -26,10 +27,17 @@ Create an extension to test bundle2 API
   >         try:
   >             bundler.addparam(*p)
   >         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:
   >         file = open(path, 'w')
   > 
@@ -176,10 +184,11 @@ Test debug output
 bundling debug
 
   $ 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
 
   $ cat ../out.hg2
@@ -213,5 +222,24 @@ empty parameter name
 bad parameter name
 
   $ 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
+  bundling part: "test:empty"
+  bundling 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)
+
+


More information about the Mercurial-devel mailing list