[PATCH stable] http: handle push of bundles > 2 GB again (issue3017)

Mads Kiilerich mads at kiilerich.com
Sun Sep 25 18:30:37 CDT 2011


# HG changeset patch
# User Mads Kiilerich <mads at kiilerich.com>
# Date 1316638320 -7200
# Branch stable
# Node ID 94b200a11cf7adfee75bb865d238a077ee75b79c
# Parent  ec222a29bdf0eafc9f281151e3221e6e0efecfad
http: handle push of bundles > 2 GB again (issue3017)

It was very elegant that httpsendfile implemented __len__ like a string. It was
however also dangerous because that protocol can't handle sizes bigger than 2 GB.
Mercurial tried to work around that, but it turned out to be too easy to
introduce new errors in this area.

With this change __len__ is no longer implemented at all and the code will work
the same way for short and long posts.

diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py
--- a/mercurial/httpconnection.py
+++ b/mercurial/httpconnection.py
@@ -22,8 +22,9 @@
 class httpsendfile(object):
     """This is a wrapper around the objects returned by python's "open".
 
-    Its purpose is to send file-like objects via HTTP and, to do so, it
-    defines a __len__ attribute to feed the Content-Length header.
+    Its purpose is to send file-like objects via HTTP.
+    It do however not define a __len__ attribute because the length
+    might be more than Py_ssize_t can handle.
     """
 
     def __init__(self, ui, *args, **kwargs):
@@ -35,9 +36,9 @@
         self.seek = self._data.seek
         self.close = self._data.close
         self.write = self._data.write
-        self._len = os.fstat(self._data.fileno()).st_size
+        self.length = os.fstat(self._data.fileno()).st_size
         self._pos = 0
-        self._total = self._len / 1024 * 2
+        self._total = self.length / 1024 * 2
 
     def read(self, *args, **kwargs):
         try:
@@ -54,9 +55,6 @@
                          unit=_('kb'), total=self._total)
         return ret
 
-    def __len__(self):
-        return self._len
-
 # moved here from url.py to avoid a cycle
 def readauthforuri(ui, uri, user):
     # Read configuration
diff --git a/mercurial/httprepo.py b/mercurial/httprepo.py
--- a/mercurial/httprepo.py
+++ b/mercurial/httprepo.py
@@ -74,9 +74,14 @@
         if cmd == 'pushkey':
             args['data'] = ''
         data = args.pop('data', None)
+        size = 0
+        if util.safehasattr(data, 'length'):
+            size = data.length
+        elif data is not None:
+            size = len(data)
         headers = args.pop('headers', {})
 
-        if data and self.ui.configbool('ui', 'usehttp2', False):
+        if size and self.ui.configbool('ui', 'usehttp2', False):
             headers['Expect'] = '100-Continue'
             headers['X-HgHttp2'] = '1'
 
@@ -105,9 +110,6 @@
         cu = "%s%s" % (self._url, qs)
         req = urllib2.Request(cu, data, headers)
         if data is not None:
-            # len(data) is broken if data doesn't fit into Py_ssize_t
-            # add the header ourself to avoid OverflowError
-            size = data.__len__()
             self.ui.debug("sending %s bytes\n" % size)
             req.add_unredirected_header('Content-Length', '%d' % size)
         try:
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -24,6 +24,10 @@
 def sha1(s):
     return _fastsha1(s)
 
+_notset = object()
+def safehasattr(thing, attr):
+    return getattr(thing, attr, _notset) is not _notset
+
 def _fastsha1(s):
     # This function will import sha1 from hashlib or sha (whichever is
     # available) and overwrite itself with it on the first call.


More information about the Mercurial-devel mailing list