Merge lp:~jelmer/brz/git-incomplete into lp:brz

Proposed by Jelmer Vernooij
Status: Needs review
Proposed branch: lp:~jelmer/brz/git-incomplete
Merge into: lp:brz
Diff against target: 571 lines (+89/-83)
2 files modified
breezy/git/remote.py (+6/-1)
breezy/transport/http/urllib.py (+83/-82)
To merge this branch: bzr merge lp:~jelmer/brz/git-incomplete
Reviewer Review Type Date Requested Status
Breezy developers Pending
Review via email: mp+430421@code.launchpad.net

Description of the change

Handle incomplete reads.

To post a comment you must log in.

Unmerged revisions

7645. By Jelmer Vernooij

Handle incomplete reads.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/git/remote.py'
2--- breezy/git/remote.py 2022-09-14 19:01:01 +0000
3+++ breezy/git/remote.py 2022-09-26 03:17:36 +0000
4@@ -835,6 +835,7 @@
5 `redirect_location` properties, and `read` is a consumable read
6 method for the response data.
7 """
8+ from breezy.transport.http.urllib import IncompleteRead
9 if is_github_url(url):
10 headers['User-agent'] = user_agent_for_github()
11 headers["Pragma"] = "no-cache"
12@@ -851,7 +852,11 @@
13 raise GitProtocolError("unexpected http resp %d for %s" %
14 (response.status, url))
15
16- read = response.read
17+ def read(amt=None):
18+ try:
19+ return response.read(amt)
20+ except IncompleteRead as e:
21+ return e.partial
22
23 class WrapResponse(object):
24
25
26=== modified file 'breezy/transport/http/urllib.py'
27--- breezy/transport/http/urllib.py 2022-08-31 14:25:13 +0000
28+++ breezy/transport/http/urllib.py 2022-09-26 03:17:36 +0000
29@@ -35,9 +35,10 @@
30 import urllib
31 import weakref
32
33-import http.client as http_client
34+import http.client
35+from http.client import IncompleteRead
36
37-import urllib.request as urllib_request
38+import urllib.request
39
40 from urllib.parse import urljoin, urlencode, urlparse
41
42@@ -86,23 +87,23 @@
43 return host, None
44
45
46-class addinfourl(urllib_request.addinfourl):
47+class addinfourl(urllib.request.addinfourl):
48 '''Replacement addinfourl class compatible with python-2.7's xmlrpclib
49
50 In python-2.7, xmlrpclib expects that the response object that it receives
51- has a getheader method. http_client.HTTPResponse provides this but
52- urllib_request.addinfourl does not. Add the necessary functions here, ported to
53+ has a getheader method. http.client.HTTPResponse provides this but
54+ urllib.request.addinfourl does not. Add the necessary functions here, ported to
55 use the internal data structures of addinfourl.
56 '''
57
58 def getheader(self, name, default=None):
59 if self.headers is None:
60- raise http_client.ResponseNotReady()
61+ raise http.client.ResponseNotReady()
62 return self.headers.getheader(name, default)
63
64 def getheaders(self):
65 if self.headers is None:
66- raise http_client.ResponseNotReady()
67+ raise http.client.ResponseNotReady()
68 return list(self.headers.items())
69
70
71@@ -155,7 +156,7 @@
72 return s
73
74 def makefile(self, mode='r', bufsize=-1):
75- # http_client creates a fileobject that doesn't do buffering, which
76+ # http.client creates a fileobject that doesn't do buffering, which
77 # makes fp.readline() very expensive because it only reads one byte
78 # at a time. So we wrap the socket in an object that forces
79 # sock.makefile to make a buffered file.
80@@ -167,11 +168,11 @@
81 return getattr(self.sock, name)
82
83
84-# We define our own Response class to keep our http_client pipe clean
85-class Response(http_client.HTTPResponse):
86+# We define our own Response class to keep our http.client pipe clean
87+class Response(http.client.HTTPResponse):
88 """Custom HTTPResponse, to avoid the need to decorate.
89
90- http_client prefers to decorate the returned objects, rather
91+ http.client prefers to decorate the returned objects, rather
92 than using a custom object.
93 """
94
95@@ -192,12 +193,12 @@
96 def begin(self):
97 """Begin to read the response from the server.
98
99- http_client assumes that some responses get no content and do
100+ http.client assumes that some responses get no content and do
101 not even attempt to read the body in that case, leaving
102 the body in the socket, blocking the next request. Let's
103 try to workaround that.
104 """
105- http_client.HTTPResponse.begin(self)
106+ http.client.HTTPResponse.begin(self)
107 if self.status in self._body_ignored_responses:
108 if self.debuglevel >= 2:
109 print("For status: [%s], will ready body, length: %s" % (
110@@ -217,12 +218,12 @@
111 elif self.status == 200:
112 # Whatever the request is, it went ok, so we surely don't want to
113 # close the connection. Some cases are not correctly detected by
114- # http_client.HTTPConnection.getresponse (called by
115- # http_client.HTTPResponse.begin). The CONNECT response for the https
116+ # http.client.HTTPConnection.getresponse (called by
117+ # http.client.HTTPResponse.begin). The CONNECT response for the https
118 # through proxy case is one. Note: the 'will_close' below refers
119 # to the "true" socket between us and the server, whereas the
120 # 'close()' above refers to the copy of that socket created by
121- # http_client for the response itself. So, in the if above we close the
122+ # http.client for the response itself. So, in the if above we close the
123 # socket to indicate that we are done with the response whereas
124 # below we keep the socket with the server opened.
125 self.will_close = False
126@@ -253,7 +254,7 @@
127 return pending
128
129
130-# Not inheriting from 'object' because http_client.HTTPConnection doesn't.
131+# Not inheriting from 'object' because http.client.HTTPConnection doesn't.
132 class AbstractHTTPConnection:
133 """A custom HTTP(S) Connection, which can reset itself on a bad response"""
134
135@@ -276,7 +277,7 @@
136
137 def getresponse(self):
138 """Capture the response to be able to cleanup"""
139- self._response = http_client.HTTPConnection.getresponse(self)
140+ self._response = http.client.HTTPConnection.getresponse(self)
141 return self._response
142
143 def cleanup_pipe(self):
144@@ -307,7 +308,7 @@
145 # Preserve our preciousss
146 sock = self.sock
147 self.sock = None
148- # Let http_client.HTTPConnection do its housekeeping
149+ # Let http.client.HTTPConnection do its housekeeping
150 self.close()
151 # Restore our preciousss
152 self.sock = sock
153@@ -317,30 +318,30 @@
154 self.sock = _ReportingSocket(sock, self._report_activity)
155
156
157-class HTTPConnection(AbstractHTTPConnection, http_client.HTTPConnection):
158+class HTTPConnection(AbstractHTTPConnection, http.client.HTTPConnection):
159
160 # XXX: Needs refactoring at the caller level.
161 def __init__(self, host, port=None, proxied_host=None,
162 report_activity=None, ca_certs=None):
163 AbstractHTTPConnection.__init__(self, report_activity=report_activity)
164- http_client.HTTPConnection.__init__(self, host, port)
165+ http.client.HTTPConnection.__init__(self, host, port)
166 self.proxied_host = proxied_host
167 # ca_certs is ignored, it's only relevant for https
168
169 def connect(self):
170 if 'http' in debug.debug_flags:
171 self._mutter_connect()
172- http_client.HTTPConnection.connect(self)
173+ http.client.HTTPConnection.connect(self)
174 self._wrap_socket_for_reporting(self.sock)
175
176
177-class HTTPSConnection(AbstractHTTPConnection, http_client.HTTPSConnection):
178+class HTTPSConnection(AbstractHTTPConnection, http.client.HTTPSConnection):
179
180 def __init__(self, host, port=None, key_file=None, cert_file=None,
181 proxied_host=None,
182 report_activity=None, ca_certs=None):
183 AbstractHTTPConnection.__init__(self, report_activity=report_activity)
184- http_client.HTTPSConnection.__init__(
185+ http.client.HTTPSConnection.__init__(
186 self, host, port, key_file, cert_file)
187 self.proxied_host = proxied_host
188 self.ca_certs = ca_certs
189@@ -348,7 +349,7 @@
190 def connect(self):
191 if 'http' in debug.debug_flags:
192 self._mutter_connect()
193- http_client.HTTPConnection.connect(self)
194+ http.client.HTTPConnection.connect(self)
195 self._wrap_socket_for_reporting(self.sock)
196 if self.proxied_host is None:
197 self.connect_to_origin()
198@@ -397,10 +398,10 @@
199 self._wrap_socket_for_reporting(ssl_sock)
200
201
202-class Request(urllib_request.Request):
203+class Request(urllib.request.Request):
204 """A custom Request object.
205
206- urllib_request determines the request method heuristically (based on
207+ urllib.request determines the request method heuristically (based on
208 the presence or absence of data). We set the method
209 statically.
210
211@@ -414,7 +415,7 @@
212 def __init__(self, method, url, data=None, headers={},
213 origin_req_host=None, unverifiable=False,
214 connection=None, parent=None):
215- urllib_request.Request.__init__(
216+ urllib.request.Request.__init__(
217 self, url, data, headers,
218 origin_req_host, unverifiable)
219 self.method = method
220@@ -447,8 +448,8 @@
221 conn_class = HTTPConnection
222 port = conn_class.default_port
223 self.proxied_host = '%s:%s' % (host, port)
224- urllib_request.Request.set_proxy(self, proxy, type)
225- # When urllib_request makes a https request with our wrapper code and a proxy,
226+ urllib.request.Request.set_proxy(self, proxy, type)
227+ # When urllib.request makes a https request with our wrapper code and a proxy,
228 # it sets Host to the https proxy, not the host we want to talk to.
229 # I'm fairly sure this is our fault, but what is the cause is an open
230 # question. -- Robert Collins May 8 2010.
231@@ -463,7 +464,7 @@
232 :param request: the first request sent to the proxied host, already
233 processed by the opener (i.e. proxied_host is already set).
234 """
235- # We give a fake url and redefine selector or urllib_request will be
236+ # We give a fake url and redefine selector or urllib.request will be
237 # confused
238 Request.__init__(self, 'CONNECT', request.get_full_url(),
239 connection=request.connection)
240@@ -488,13 +489,13 @@
241 here. In fact, the connection is already established with proxy and we
242 just want to enable the SSL tunneling.
243 """
244- urllib_request.Request.set_proxy(self, proxy, type)
245-
246-
247-class ConnectionHandler(urllib_request.BaseHandler):
248+ urllib.request.Request.set_proxy(self, proxy, type)
249+
250+
251+class ConnectionHandler(urllib.request.BaseHandler):
252 """Provides connection-sharing by pre-processing requests.
253
254- urllib_request provides no way to access the HTTPConnection object
255+ urllib.request provides no way to access the HTTPConnection object
256 internally used. But we need it in order to achieve
257 connection sharing. So, we add it to the request just before
258 it is processed, and then we override the do_open method for
259@@ -521,8 +522,8 @@
260 host, proxied_host=request.proxied_host,
261 report_activity=self._report_activity,
262 ca_certs=self.ca_certs)
263- except http_client.InvalidURL as exception:
264- # There is only one occurrence of InvalidURL in http_client
265+ except http.client.InvalidURL as exception:
266+ # There is only one occurrence of InvalidURL in http.client
267 raise urlutils.InvalidURL(request.get_full_url(),
268 extra='nonnumeric port')
269
270@@ -558,16 +559,16 @@
271 return self.capture_connection(request, HTTPSConnection)
272
273
274-class AbstractHTTPHandler(urllib_request.AbstractHTTPHandler):
275+class AbstractHTTPHandler(urllib.request.AbstractHTTPHandler):
276 """A custom handler for HTTP(S) requests.
277
278- We overrive urllib_request.AbstractHTTPHandler to get a better
279+ We overrive urllib.request.AbstractHTTPHandler to get a better
280 control of the connection, the ability to implement new
281 request types and return a response able to cope with
282 persistent connections.
283 """
284
285- # We change our order to be before urllib_request HTTP[S]Handlers
286+ # We change our order to be before urllib.request HTTP[S]Handlers
287 # and be chosen instead of them (the first http_open called
288 # wins).
289 handler_order = 400
290@@ -580,7 +581,7 @@
291 }
292
293 def __init__(self):
294- urllib_request.AbstractHTTPHandler.__init__(self, debuglevel=DEBUG)
295+ urllib.request.AbstractHTTPHandler.__init__(self, debuglevel=DEBUG)
296
297 def http_request(self, request):
298 """Common headers setting"""
299@@ -595,13 +596,13 @@
300 def retry_or_raise(self, http_class, request, first_try):
301 """Retry the request (once) or raise the exception.
302
303- urllib_request raises exception of application level kind, we
304+ urllib.request raises exception of application level kind, we
305 just have to translate them.
306
307- http_client can raise exceptions of transport level (badly
308+ http.client can raise exceptions of transport level (badly
309 formatted dialog, loss of connexion or socket level
310 problems). In that case we should issue the request again
311- (http_client will close and reopen a new connection if
312+ (http.client will close and reopen a new connection if
313 needed).
314 """
315 # When an exception occurs, we give back the original
316@@ -613,8 +614,8 @@
317 raise errors.ConnectionError("Couldn't resolve host '%s'"
318 % origin_req_host,
319 orig_error=exc_val)
320- elif isinstance(exc_val, http_client.ImproperConnectionState):
321- # The http_client pipeline is in incorrect state, it's a bug in our
322+ elif isinstance(exc_val, http.client.ImproperConnectionState):
323+ # The http.client pipeline is in incorrect state, it's a bug in our
324 # implementation.
325 raise exc_val.with_traceback(exc_tb)
326 else:
327@@ -631,9 +632,9 @@
328 if self._debuglevel >= 2:
329 print('Received second exception: [%r]' % exc_val)
330 print(' On connection: [%r]' % request.connection)
331- if exc_type in (http_client.BadStatusLine, http_client.UnknownProtocol):
332- # http_client.BadStatusLine and
333- # http_client.UnknownProtocol indicates that a
334+ if exc_type in (http.client.BadStatusLine, http.client.UnknownProtocol):
335+ # http.client.BadStatusLine and
336+ # http.client.UnknownProtocol indicates that a
337 # bogus server was encountered or a bad
338 # connection (i.e. transient errors) is
339 # experimented, we have already retried once
340@@ -671,7 +672,7 @@
341 return response
342
343 def do_open(self, http_class, request, first_try=True):
344- """See urllib_request.AbstractHTTPHandler.do_open for the general idea.
345+ """See urllib.request.AbstractHTTPHandler.do_open for the general idea.
346
347 The request will be retried once if it fails.
348 """
349@@ -685,10 +686,10 @@
350 headers.update(request.header_items())
351 headers.update(request.unredirected_hdrs)
352 # Some servers or proxies will choke on headers not properly
353- # cased. http_client/urllib/urllib_request all use capitalize to get canonical
354- # header names, but only python2.5 urllib_request use title() to fix them just
355+ # cased. http.client/urllib/urllib.request all use capitalize to get canonical
356+ # header names, but only python2.5 urllib.request use title() to fix them just
357 # before sending the request. And not all versions of python 2.5 do
358- # that. Since we replace urllib_request.AbstractHTTPHandler.do_open we do it
359+ # that. Since we replace urllib.request.AbstractHTTPHandler.do_open we do it
360 # ourself below.
361 headers = {name.title(): val for name, val in headers.items()}
362
363@@ -720,8 +721,8 @@
364 # Something is wrong with either the certificate or the hostname,
365 # re-trying won't help
366 raise
367- except (socket.gaierror, http_client.BadStatusLine, http_client.UnknownProtocol,
368- socket.error, http_client.HTTPException):
369+ except (socket.gaierror, http.client.BadStatusLine, http.client.UnknownProtocol,
370+ socket.error, http.client.HTTPException):
371 response = self.retry_or_raise(http_class, request, first_try)
372 convert_to_addinfourl = False
373
374@@ -746,7 +747,7 @@
375 request.get_full_url()))
376
377 if convert_to_addinfourl:
378- # Shamelessly copied from urllib_request
379+ # Shamelessly copied from urllib.request
380 req = request
381 r = response
382 r.recv = r.read
383@@ -822,7 +823,7 @@
384 return self.do_open(HTTPSConnection, request)
385
386
387-class HTTPRedirectHandler(urllib_request.HTTPRedirectHandler):
388+class HTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
389 """Handles redirect requests.
390
391 We have to implement our own scheme because we use a specific
392@@ -839,9 +840,9 @@
393 # of the RFC if not its letter.
394
395 def redirect_request(self, req, fp, code, msg, headers, newurl):
396- """See urllib_request.HTTPRedirectHandler.redirect_request"""
397+ """See urllib.request.HTTPRedirectHandler.redirect_request"""
398 # We would have preferred to update the request instead
399- # of creating a new one, but the urllib_request.Request object
400+ # of creating a new one, but the urllib.request.Request object
401 # has a too complicated creation process to provide a
402 # simple enough equivalent update process. Instead, when
403 # redirecting, we only update the following request in
404@@ -883,13 +884,13 @@
405 parent=req,
406 )
407 else:
408- raise urllib_request.HTTPError(
409+ raise urllib.request.HTTPError(
410 req.get_full_url(), code, msg, headers, fp)
411
412 def http_error_302(self, req, fp, code, msg, headers):
413 """Requests the redirected to URI.
414
415- Copied from urllib_request to be able to clean the pipe of the associated
416+ Copied from urllib.request to be able to clean the pipe of the associated
417 connection, *before* issuing the redirected request but *after* having
418 eventually raised an error.
419 """
420@@ -915,7 +916,7 @@
421 req.redirected_to = newurl
422 return fp
423
424- # This call succeeds or raise an error. urllib_request returns
425+ # This call succeeds or raise an error. urllib.request returns
426 # if redirect_request returns None, but our
427 # redirect_request never returns None.
428 redirected_req = self.redirect_request(req, fp, code, msg, headers,
429@@ -927,7 +928,7 @@
430 visited = redirected_req.redirect_dict = req.redirect_dict
431 if (visited.get(newurl, 0) >= self.max_repeats or
432 len(visited) >= self.max_redirections):
433- raise urllib_request.HTTPError(req.get_full_url(), code,
434+ raise urllib.request.HTTPError(req.get_full_url(), code,
435 self.inf_msg + msg, headers, fp)
436 else:
437 visited = redirected_req.redirect_dict = req.redirect_dict = {}
438@@ -944,10 +945,10 @@
439 http_error_301 = http_error_303 = http_error_307 = http_error_308 = http_error_302
440
441
442-class ProxyHandler(urllib_request.ProxyHandler):
443+class ProxyHandler(urllib.request.ProxyHandler):
444 """Handles proxy setting.
445
446- Copied and modified from urllib_request to be able to modify the request during
447+ Copied and modified from urllib.request to be able to modify the request during
448 the request pre-processing instead of modifying it at _open time. As we
449 capture (or create) the connection object during request processing, _open
450 time was too late.
451@@ -958,7 +959,7 @@
452 Note: the proxy handling *may* modify the protocol used; the request may be
453 against an https server proxied through an http proxy. So, https_request
454 will be called, but later it's really http_open that will be called. This
455- explains why we don't have to call self.parent.open as the urllib_request did.
456+ explains why we don't have to call self.parent.open as the urllib.request did.
457 """
458
459 # Proxies must be in front
460@@ -966,8 +967,8 @@
461 _debuglevel = DEBUG
462
463 def __init__(self, proxies=None):
464- urllib_request.ProxyHandler.__init__(self, proxies)
465- # First, let's get rid of urllib_request implementation
466+ urllib.request.ProxyHandler.__init__(self, proxies)
467+ # First, let's get rid of urllib.request implementation
468 for type, proxy in self.proxies.items():
469 if self._debuglevel >= 3:
470 print('Will unbind %s_open for %r' % (type, proxy))
471@@ -1015,7 +1016,7 @@
472 if bypass is None:
473 # Nevertheless, there are platform-specific ways to
474 # ignore proxies...
475- return urllib_request.proxy_bypass(host)
476+ return urllib.request.proxy_bypass(host)
477 else:
478 return bypass
479
480@@ -1090,7 +1091,7 @@
481 return request
482
483
484-class AbstractAuthHandler(urllib_request.BaseHandler):
485+class AbstractAuthHandler(urllib.request.BaseHandler):
486 """A custom abstract authentication handler for all http authentications.
487
488 Provides the meat to handle authentication errors and
489@@ -1101,9 +1102,9 @@
490 digest authentications.
491
492 This provides an unified interface for all authentication handlers
493- (urllib_request provides far too many with different policies).
494+ (urllib.request provides far too many with different policies).
495
496- The interaction between this handler and the urllib_request
497+ The interaction between this handler and the urllib.request
498 framework is not obvious, it works as follow:
499
500 opener.open(request) is called:
501@@ -1509,8 +1510,8 @@
502 return False
503
504 # Put the requested authentication info into a dict
505- req_auth = urllib_request.parse_keqv_list(
506- urllib_request.parse_http_list(raw_auth))
507+ req_auth = urllib.request.parse_keqv_list(
508+ urllib.request.parse_http_list(raw_auth))
509
510 # Check that we can handle that authentication
511 qop = req_auth.get('qop', None)
512@@ -1624,7 +1625,7 @@
513
514 auth_required_header = 'proxy-authenticate'
515 # FIXME: the correct capitalization is Proxy-Authorization,
516- # but python-2.4 urllib_request.Request insist on using capitalize()
517+ # but python-2.4 urllib.request.Request insist on using capitalize()
518 # instead of title().
519 auth_header = 'Proxy-authorization'
520
521@@ -1674,7 +1675,7 @@
522 """Custom proxy negotiate authentication handler"""
523
524
525-class HTTPErrorProcessor(urllib_request.HTTPErrorProcessor):
526+class HTTPErrorProcessor(urllib.request.HTTPErrorProcessor):
527 """Process HTTP error responses.
528
529 We don't really process the errors, quite the contrary
530@@ -1713,7 +1714,7 @@
531 https_response = http_response
532
533
534-class HTTPDefaultErrorHandler(urllib_request.HTTPDefaultErrorHandler):
535+class HTTPDefaultErrorHandler(urllib.request.HTTPDefaultErrorHandler):
536 """Translate common errors into Breezy Exceptions"""
537
538 def http_error_default(self, req, fp, code, msg, hdrs):
539@@ -1729,7 +1730,7 @@
540
541
542 class Opener(object):
543- """A wrapper around urllib_request.build_opener
544+ """A wrapper around urllib.request.build_opener
545
546 Daughter classes can override to build their own specific opener
547 """
548@@ -1741,7 +1742,7 @@
549 error=HTTPErrorProcessor,
550 report_activity=None,
551 ca_certs=None):
552- self._opener = urllib_request.build_opener(
553+ self._opener = urllib.request.build_opener(
554 connection(report_activity=report_activity, ca_certs=ca_certs),
555 redirect, error,
556 ProxyHandler(),
557@@ -1869,12 +1870,12 @@
558
559 def getheader(self, name, default=None):
560 if self._actual.headers is None:
561- raise http_client.ResponseNotReady()
562+ raise http.client.ResponseNotReady()
563 return self._actual.headers.get(name, default)
564
565 def getheaders(self):
566 if self._actual.headers is None:
567- raise http_client.ResponseNotReady()
568+ raise http.client.ResponseNotReady()
569 return list(self._actual.headers.items())
570
571 @property

Subscribers

People subscribed via source and target branches