Patchwork [4,of,4] bundle2: part params

login
register
mail settings
Submitter Pierre-Yves David
Date March 27, 2014, 2:19 a.m.
Message ID <57ab0045d562efb1e6be.1395886785@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/4079/
State Superseded
Commit 9e9e3a4e9261d31318f0f177538636370c50fe57
Headers show

Comments

Pierre-Yves David - March 27, 2014, 2:19 a.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1395303885 25200
#      Thu Mar 20 01:24:45 2014 -0700
# Node ID 57ab0045d562efb1e6beb676bbbb3ba4e7b3ae8d
# Parent  00b0aeb4f82b8056395ed53ce3fadcf8d16bd02c
bundle2: part params

Patch

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -82,12 +82,35 @@  Binary format is as follow
     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.
+
+    :parameters:
+
+        Part's parameter may have arbitraty content, the binary structure is::
+
+            <mandatory-count><advisory-count><param-sizes><param-data>
+
+        :mandatory-count: 1 byte, number of mandatory parameters
+
+        :advisory-count:  1 byte, number of advisory parameters
+
+        :param-sizes:
+
+            N couple of Bytes, where N is the total number of parameters. each
+            couple if the size of the key and value for a parameters
+
+        :param-data:
+
+            A blob of bytes from which each parameter key and value can be
+            retrieved using the list of size couples stored in the previous
+            field.
+
+            Mandatory parameters comes first, then the advisory ones.
 
 :payload:
 
     payload is a series of `<chunksize><chunkdata>`.
 
@@ -113,10 +136,19 @@  from i18n import _
 
 _fstreamparamsize = '>H'
 _fpartheadersize = '>H'
 _fparttypesize = '>B'
 _fpayloadsize = '>I'
+_fpartparamcount = '>BB'
+
+def _makefpartparamsizes(nbparams):
+    """return a struct format to read part parameter sizes
+
+    The number parameters is variable so we need to build that format
+    dynamically.
+    """
+    return '>'+('BB'*nbparams)
 
 class bundle20(object):
     """represent an outgoing bundle2 container
 
     Use the `addparam` method to add stream level parameter. and `addpart` to
@@ -260,48 +292,92 @@  class unbundle20(object):
             _offset[0] = offset + size
             return data
         typesize = _unpack(_fparttypesize, fromheader(1))[0]
         parttype = fromheader(typesize)
         self.ui.debug('part type: "%s"\n' % parttype)
-        assert fromheader(2) == '\0\0' # no option for now
-        self.ui.debug('part parameters: 0\n')
+        ## reading parameters
+        # param count
+        mancount, advcount = _unpack(_fpartparamcount, fromheader(2))
+        self.ui.debug('part parameters: %i\n' % (mancount + advcount))
+        # param size
+        paramsizes = _unpack(_makefpartparamsizes(mancount + advcount),
+                             fromheader(2*(mancount + advcount)))
+        # make it a list of couple again
+        paramsizes = zip(paramsizes[::2], paramsizes[1::2])
+        # split mandatory from advisory
+        mansizes = paramsizes[:mancount]
+        advsizes = paramsizes[mancount:]
+        # retrive param value
+        manparams = []
+        for key, value in mansizes:
+            manparams.append((fromheader(key), fromheader(value)))
+        advparams = []
+        for key, value in advsizes:
+            advparams.append((fromheader(key), fromheader(value)))
+        ## part payload
         payload = []
         payloadsize = self._unpack(_fpayloadsize)[0]
         self.ui.debug('payload chunk size: %i\n' % payloadsize)
         while payloadsize:
             payload.append(self._readexact(payloadsize))
             payloadsize = self._unpack(_fpayloadsize)[0]
             self.ui.debug('payload chunk size: %i\n' % payloadsize)
         payload = ''.join(payload)
-        current = part(parttype, data=payload)
+        current = part(parttype, manparams, advparams, data=payload)
         return current
 
 
 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, data=''):
+    def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
+                 data=''):
         self.type = parttype
         self.data = data
+        self.mandatoryparams = mandatoryparams
+        self.advisoryparams = advisoryparams
 
     def getchunks(self):
-        ### header
+        #### header
+        ## parttype
         header = [_pack(_fparttypesize, len(self.type)),
                   self.type,
-                  '\0\0', # No option support for now.
                  ]
+        ## parameters
+        # count
+        manpar = self.mandatoryparams
+        advpar = self.advisoryparams
+        header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
+        # size
+        parsizes = []
+        for key, value in manpar:
+            parsizes.append(len(key))
+            parsizes.append(len(value))
+        for key, value in advpar:
+            parsizes.append(len(key))
+            parsizes.append(len(value))
+        paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
+        header.append(paramsizes)
+        # key, value
+        for key, value in manpar:
+            header.append(key)
+            header.append(value)
+        for key, value in advpar:
+            header.append(key)
+            header.append(value)
+        ## finalize header
         headerchunk = ''.join(header)
         yield _pack(_fpartheadersize, len(headerchunk))
         yield headerchunk
+        ## payload
         # we only support fixed size data now.
         # This will be improved in the future.
         yield _pack(_fpayloadsize, len(self.data))
         yield self.data
         if len(self.data):
             # end of payload
             yield _pack(_fpayloadsize, 0)
 
-
diff --git a/tests/test-bundle2.t b/tests/test-bundle2.t
--- a/tests/test-bundle2.t
+++ b/tests/test-bundle2.t
@@ -40,10 +40,15 @@  Create an extension to test bundle2 API
   >        # add a second one to make sure we handle multiple parts
   >        part = bundle2.part('test:empty')
   >        bundler.addpart(part)
   >        part = bundle2.part('test:song', data=ELEPHANTSSONG)
   >        bundler.addpart(part)
+  >        part = bundle2.part('test:math',
+  >                            [('pi', '3.14'), ('e', '2.72')],
+  >                            [('cooking', 'raw')],
+  >                            '42')
+  >        bundler.addpart(part)
   > 
   >     if path is None:
   >        file = sys.stdout
   >     else:
   >         file = open(path, 'w')
@@ -67,10 +72,12 @@  Create an extension to test bundle2 API
   >             ui.write('    %s\n' % value)
   >     parts = list(unbundler)
   >     ui.write('parts count:   %i\n' % len(parts))
   >     for p in parts:
   >         ui.write('  :%s:\n' % p.type)
+  >         ui.write('    mandatory: %i\n' % len(p.mandatoryparams))
+  >         ui.write('    advisory: %i\n' % len(p.advisoryparams))
   >         ui.write('    payload: %i bytes\n' % len(p.data))
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > bundle2=$TESTTMP/bundle2.py
@@ -245,29 +252,40 @@  Test part
   bundle parameter: 
   start of parts
   bundling part: "test:empty"
   bundling part: "test:empty"
   bundling part: "test:song"
+  bundling part: "test:math"
   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\x0c	test:song\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
   Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
-  Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+  Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00'	test:math\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
 
 
   $ hg unbundle2 < ../parts.hg2
   options count: 0
-  parts count:   3
+  parts count:   4
     :test:empty:
+      mandatory: 0
+      advisory: 0
       payload: 0 bytes
     :test:empty:
+      mandatory: 0
+      advisory: 0
       payload: 0 bytes
     :test:song:
+      mandatory: 0
+      advisory: 0
       payload: 178 bytes
+    :test:math:
+      mandatory: 2
+      advisory: 1
+      payload: 2 bytes
 
   $ hg unbundle2 --debug < ../parts.hg2
   start processing of HG20 stream
   reading bundle2 stream parameters
   options count: 0
@@ -283,14 +301,29 @@  Test part
   part header size: 12
   part type: "test:song"
   part parameters: 0
   payload chunk size: 178
   payload chunk size: 0
+  part header size: 39
+  part type: "test:math"
+  part parameters: 3
+  payload chunk size: 2
+  payload chunk size: 0
   part header size: 0
   end of bundle2 stream
-  parts count:   3
+  parts count:   4
     :test:empty:
+      mandatory: 0
+      advisory: 0
       payload: 0 bytes
     :test:empty:
+      mandatory: 0
+      advisory: 0
       payload: 0 bytes
     :test:song:
+      mandatory: 0
+      advisory: 0
       payload: 178 bytes
+    :test:math:
+      mandatory: 2
+      advisory: 1
+      payload: 2 bytes