Patchwork [1,of,3] bundle2: add separate handling for error part creation

login
register
mail settings
Submitter Siddharth Agarwal
Date April 4, 2017, 11:43 p.m.
Message ID <5c84da513a5ff56a973a.1491349399@devvm002.prn1.facebook.com>
Download mbox | patch
Permalink /patch/19965/
State Superseded
Headers show

Comments

Siddharth Agarwal - April 4, 2017, 11:43 p.m.
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1491348154 25200
#      Tue Apr 04 16:22:34 2017 -0700
# Node ID 5c84da513a5ff56a973a89631f266c6fcf9b18cd
# Parent  04ec317b81280c189fcea33a05c8cbbac3c186b1
bundle2: add separate handling for error part creation

This will be used in upcoming diffs to handle error parts.

See the included docstrings for why this is necessary.

Patch

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -177,6 +177,7 @@  urlreq = util.urlreq
 _fpartid = '>I'
 _fpayloadsize = '>i'
 _fpartparamcount = '>BB'
+_ferrorlongparamsize = '>I'
 
 preferedchunksize = 4096
 
@@ -557,6 +558,20 @@  class bundle20(object):
         self.addpart(part)
         return part
 
+    def newerrorpart(self, typeid, *args, **kwargs):
+        """create a new error part and add it to the containers
+
+        This is only required for the following error parts that are recognized
+        by Mercurial 4.1 or lower.
+        - error:abort
+        - error:pushraced
+        - error:unsupportedcontent
+
+        See bundleerrorpart for more."""
+        part = bundleerrorpart(typeid, *args, **kwargs)
+        self.addpart(part)
+        return part
+
     # methods used to generate the bundle2 stream
     def getchunks(self):
         if self.ui.debugflag:
@@ -1026,6 +1041,54 @@  class bundlepart(object):
         elif len(self.data):
             yield self.data
 
+class bundleerrorpart(bundlepart):
+    """
+    This is only required for the following error parts that are recognized
+    by Mercurial versions below 4.2.
+    - error:abort
+    - error:pushraced
+    - error:unsupportedcontent
+
+    In Mercurial below 4.2, error messages for these parts were stored as
+    part parameters. Since parameter sizes can be 255 at most, this meant that
+    error messages longer than 255 bytes would crash the Mercurial server.
+
+    To avoid this issue while still retaining backwards compatibility with older
+    versions of Mercurial, this class of parts:
+    * truncates long params to 255 bytes
+    * adds such params to the payload instead.
+
+    Any newer error parts should instead:
+    - use newpart
+    - take care not to set up any params with potentially unbounded length
+    - send the unbounded param(s) (e.g. error message) as part of the
+      payload
+    """
+    def __init__(self, *args, **kwargs):
+        super(bundleerrorpart, self).__init__(*args, **kwargs)
+        self._errordata = []
+        self.addparam('longparams', 'payload', mandatory=False)
+
+    def addlongparam(self, name, value='', mandatory=True):
+        vparam = value
+        if len(value) > 255:
+            # truncate value so that it doesn't crash
+            vparam = value[:252] + '...'
+        self.addparam(name, vparam, mandatory=mandatory)
+        # encode the full error message as
+        # (len(name), name, len(value), value)
+        # mandatory is enforced by the param
+        data = [struct.pack(_ferrorlongparamsize, len(name)), name,
+                struct.pack(_ferrorlongparamsize, len(value)), value]
+        self._errordata.append(''.join(data))
+
+    def getchunks(self, ui):
+        # data = number of long params + all the params
+        numparams = len(self._errordata)
+        errordata = ([struct.pack(_ferrorlongparamsize, numparams)] +
+                     self._errordata)
+        self._data = ''.join(errordata)
+        return super(bundleerrorpart, self).getchunks(ui)
 
 flaginterrupt = -1