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

login
register
mail settings
Submitter Pierre-Yves David
Date March 28, 2014, 2:14 a.m.
Message ID <f5be6f95d76a9369b441.1395972880@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/4097/
State Accepted
Commit 8a6a86c9a5b58ccc020de1ff0429e72dfa5599fc
Headers show

Comments

Pierre-Yves David - March 28, 2014, 2:14 a.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1395178173 25200
#      Tue Mar 18 14:29:33 2014 -0700
# Node ID f5be6f95d76a9369b441da680cf2a3dd959da1f7
# Parent  dfad9bb23ab49bd461544c1d5fab3318ab637d23
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.

Patch

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -19,13 +19,11 @@  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
+the Binary format
 ============================
 
 All numbers are unsigned and big endian.
 
 stream level parameters
@@ -69,11 +67,32 @@  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 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
 import struct
 import urllib
@@ -86,19 +105,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 +128,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('bundle 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 +242,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 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'
+
+
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
+  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)
+
+