##// END OF EJS Templates
httppeer: add support for httppostargs when we're sending a file...
Augie Fackler -
r33820:3c91cc0c default
parent child Browse files
Show More
@@ -1,198 +1,201 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import cgi
11 11 import struct
12 12
13 13 from .common import (
14 14 HTTP_OK,
15 15 )
16 16
17 17 from .. import (
18 18 util,
19 19 wireproto,
20 20 )
21 21 stringio = util.stringio
22 22
23 23 urlerr = util.urlerr
24 24 urlreq = util.urlreq
25 25
26 26 HGTYPE = 'application/mercurial-0.1'
27 27 HGTYPE2 = 'application/mercurial-0.2'
28 28 HGERRTYPE = 'application/hg-error'
29 29
30 30 def decodevaluefromheaders(req, headerprefix):
31 31 """Decode a long value from multiple HTTP request headers."""
32 32 chunks = []
33 33 i = 1
34 34 while True:
35 35 v = req.env.get('HTTP_%s_%d' % (
36 36 headerprefix.upper().replace('-', '_'), i))
37 37 if v is None:
38 38 break
39 39 chunks.append(v)
40 40 i += 1
41 41
42 42 return ''.join(chunks)
43 43
44 44 class webproto(wireproto.abstractserverproto):
45 45 def __init__(self, req, ui):
46 46 self.req = req
47 47 self.response = ''
48 48 self.ui = ui
49 49 self.name = 'http'
50 50
51 51 def getargs(self, args):
52 52 knownargs = self._args()
53 53 data = {}
54 54 keys = args.split()
55 55 for k in keys:
56 56 if k == '*':
57 57 star = {}
58 58 for key in knownargs.keys():
59 59 if key != 'cmd' and key not in keys:
60 60 star[key] = knownargs[key][0]
61 61 data['*'] = star
62 62 else:
63 63 data[k] = knownargs[k][0]
64 64 return [data[k] for k in keys]
65 65 def _args(self):
66 66 args = self.req.form.copy()
67 67 postlen = int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
68 68 if postlen:
69 69 args.update(cgi.parse_qs(
70 70 self.req.read(postlen), keep_blank_values=True))
71 71 return args
72 72
73 73 argvalue = decodevaluefromheaders(self.req, 'X-HgArg')
74 74 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
75 75 return args
76 76 def getfile(self, fp):
77 77 length = int(self.req.env['CONTENT_LENGTH'])
78 # If httppostargs is used, we need to read Content-Length
79 # minus the amount that was consumed by args.
80 length -= int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
78 81 for s in util.filechunkiter(self.req, limit=length):
79 82 fp.write(s)
80 83 def redirect(self):
81 84 self.oldio = self.ui.fout, self.ui.ferr
82 85 self.ui.ferr = self.ui.fout = stringio()
83 86 def restore(self):
84 87 val = self.ui.fout.getvalue()
85 88 self.ui.ferr, self.ui.fout = self.oldio
86 89 return val
87 90
88 91 def _client(self):
89 92 return 'remote:%s:%s:%s' % (
90 93 self.req.env.get('wsgi.url_scheme') or 'http',
91 94 urlreq.quote(self.req.env.get('REMOTE_HOST', '')),
92 95 urlreq.quote(self.req.env.get('REMOTE_USER', '')))
93 96
94 97 def responsetype(self, v1compressible=False):
95 98 """Determine the appropriate response type and compression settings.
96 99
97 100 The ``v1compressible`` argument states whether the response with
98 101 application/mercurial-0.1 media types should be zlib compressed.
99 102
100 103 Returns a tuple of (mediatype, compengine, engineopts).
101 104 """
102 105 # For now, if it isn't compressible in the old world, it's never
103 106 # compressible. We can change this to send uncompressed 0.2 payloads
104 107 # later.
105 108 if not v1compressible:
106 109 return HGTYPE, None, None
107 110
108 111 # Determine the response media type and compression engine based
109 112 # on the request parameters.
110 113 protocaps = decodevaluefromheaders(self.req, 'X-HgProto').split(' ')
111 114
112 115 if '0.2' in protocaps:
113 116 # Default as defined by wire protocol spec.
114 117 compformats = ['zlib', 'none']
115 118 for cap in protocaps:
116 119 if cap.startswith('comp='):
117 120 compformats = cap[5:].split(',')
118 121 break
119 122
120 123 # Now find an agreed upon compression format.
121 124 for engine in wireproto.supportedcompengines(self.ui, self,
122 125 util.SERVERROLE):
123 126 if engine.wireprotosupport().name in compformats:
124 127 opts = {}
125 128 level = self.ui.configint('server',
126 129 '%slevel' % engine.name())
127 130 if level is not None:
128 131 opts['level'] = level
129 132
130 133 return HGTYPE2, engine, opts
131 134
132 135 # No mutually supported compression format. Fall back to the
133 136 # legacy protocol.
134 137
135 138 # Don't allow untrusted settings because disabling compression or
136 139 # setting a very high compression level could lead to flooding
137 140 # the server's network or CPU.
138 141 opts = {'level': self.ui.configint('server', 'zliblevel')}
139 142 return HGTYPE, util.compengines['zlib'], opts
140 143
141 144 def iscmd(cmd):
142 145 return cmd in wireproto.commands
143 146
144 147 def call(repo, req, cmd):
145 148 p = webproto(req, repo.ui)
146 149
147 150 def genversion2(gen, compress, engine, engineopts):
148 151 # application/mercurial-0.2 always sends a payload header
149 152 # identifying the compression engine.
150 153 name = engine.wireprotosupport().name
151 154 assert 0 < len(name) < 256
152 155 yield struct.pack('B', len(name))
153 156 yield name
154 157
155 158 if compress:
156 159 for chunk in engine.compressstream(gen, opts=engineopts):
157 160 yield chunk
158 161 else:
159 162 for chunk in gen:
160 163 yield chunk
161 164
162 165 rsp = wireproto.dispatch(repo, p, cmd)
163 166 if isinstance(rsp, str):
164 167 req.respond(HTTP_OK, HGTYPE, body=rsp)
165 168 return []
166 169 elif isinstance(rsp, wireproto.streamres):
167 170 if rsp.reader:
168 171 gen = iter(lambda: rsp.reader.read(32768), '')
169 172 else:
170 173 gen = rsp.gen
171 174
172 175 # This code for compression should not be streamres specific. It
173 176 # is here because we only compress streamres at the moment.
174 177 mediatype, engine, engineopts = p.responsetype(rsp.v1compressible)
175 178
176 179 if mediatype == HGTYPE and rsp.v1compressible:
177 180 gen = engine.compressstream(gen, engineopts)
178 181 elif mediatype == HGTYPE2:
179 182 gen = genversion2(gen, rsp.v1compressible, engine, engineopts)
180 183
181 184 req.respond(HTTP_OK, mediatype)
182 185 return gen
183 186 elif isinstance(rsp, wireproto.pushres):
184 187 val = p.restore()
185 188 rsp = '%d\n%s' % (rsp.res, val)
186 189 req.respond(HTTP_OK, HGTYPE, body=rsp)
187 190 return []
188 191 elif isinstance(rsp, wireproto.pusherr):
189 192 # drain the incoming bundle
190 193 req.drain()
191 194 p.restore()
192 195 rsp = '0\n%s\n' % rsp.res
193 196 req.respond(HTTP_OK, HGTYPE, body=rsp)
194 197 return []
195 198 elif isinstance(rsp, wireproto.ooberror):
196 199 rsp = rsp.message
197 200 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
198 201 return []
@@ -1,422 +1,464 b''
1 1 # httppeer.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 import io
12 13 import os
13 14 import socket
14 15 import struct
15 16 import tempfile
16 17
17 18 from .i18n import _
18 19 from .node import nullid
19 20 from . import (
20 21 bundle2,
21 22 error,
22 23 httpconnection,
23 24 pycompat,
24 25 statichttprepo,
25 26 url,
26 27 util,
27 28 wireproto,
28 29 )
29 30
30 31 httplib = util.httplib
31 32 urlerr = util.urlerr
32 33 urlreq = util.urlreq
33 34
34 35 def encodevalueinheaders(value, header, limit):
35 36 """Encode a string value into multiple HTTP headers.
36 37
37 38 ``value`` will be encoded into 1 or more HTTP headers with the names
38 39 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
39 40 name + value will be at most ``limit`` bytes long.
40 41
41 42 Returns an iterable of 2-tuples consisting of header names and values.
42 43 """
43 44 fmt = header + '-%s'
44 45 valuelen = limit - len(fmt % '000') - len(': \r\n')
45 46 result = []
46 47
47 48 n = 0
48 49 for i in xrange(0, len(value), valuelen):
49 50 n += 1
50 51 result.append((fmt % str(n), value[i:i + valuelen]))
51 52
52 53 return result
53 54
54 55 def _wraphttpresponse(resp):
55 56 """Wrap an HTTPResponse with common error handlers.
56 57
57 58 This ensures that any I/O from any consumer raises the appropriate
58 59 error and messaging.
59 60 """
60 61 origread = resp.read
61 62
62 63 class readerproxy(resp.__class__):
63 64 def read(self, size=None):
64 65 try:
65 66 return origread(size)
66 67 except httplib.IncompleteRead as e:
67 68 # e.expected is an integer if length known or None otherwise.
68 69 if e.expected:
69 70 msg = _('HTTP request error (incomplete response; '
70 71 'expected %d bytes got %d)') % (e.expected,
71 72 len(e.partial))
72 73 else:
73 74 msg = _('HTTP request error (incomplete response)')
74 75
75 76 raise error.PeerTransportError(
76 77 msg,
77 78 hint=_('this may be an intermittent network failure; '
78 79 'if the error persists, consider contacting the '
79 80 'network or server operator'))
80 81 except httplib.HTTPException as e:
81 82 raise error.PeerTransportError(
82 83 _('HTTP request error (%s)') % e,
83 84 hint=_('this may be an intermittent network failure; '
84 85 'if the error persists, consider contacting the '
85 86 'network or server operator'))
86 87
87 88 resp.__class__ = readerproxy
88 89
90 class _multifile(object):
91 def __init__(self, *fileobjs):
92 for f in fileobjs:
93 if not util.safehasattr(f, 'length'):
94 raise ValueError(
95 '_multifile only supports file objects that '
96 'have a length but this one does not:', type(f), f)
97 self._fileobjs = fileobjs
98 self._index = 0
99
100 @property
101 def length(self):
102 return sum(f.length for f in self._fileobjs)
103
104 def read(self, amt=None):
105 if amt <= 0:
106 return ''.join(f.read() for f in self._fileobjs)
107 parts = []
108 while amt and self._index < len(self._fileobjs):
109 parts.append(self._fileobjs[self._index].read(amt))
110 got = len(parts[-1])
111 if got < amt:
112 self._index += 1
113 amt -= got
114 return ''.join(parts)
115
116 def seek(self, offset, whence=os.SEEK_SET):
117 if whence != os.SEEK_SET:
118 raise NotImplementedError(
119 '_multifile does not support anything other'
120 ' than os.SEEK_SET for whence on seek()')
121 if offset != 0:
122 raise NotImplementedError(
123 '_multifile only supports seeking to start, but that '
124 'could be fixed if you need it')
125 for f in self._fileobjs:
126 f.seek(0)
127 self._index = 0
128
89 129 class httppeer(wireproto.wirepeer):
90 130 def __init__(self, ui, path):
91 131 self._path = path
92 132 self._caps = None
93 133 self._urlopener = None
94 134 self._requestbuilder = None
95 135 u = util.url(path)
96 136 if u.query or u.fragment:
97 137 raise error.Abort(_('unsupported URL component: "%s"') %
98 138 (u.query or u.fragment))
99 139
100 140 # urllib cannot handle URLs with embedded user or passwd
101 141 self._url, authinfo = u.authinfo()
102 142
103 143 self._ui = ui
104 144 ui.debug('using %s\n' % self._url)
105 145
106 146 self._urlopener = url.opener(ui, authinfo)
107 147 self._requestbuilder = urlreq.request
108 148
109 149 def __del__(self):
110 150 urlopener = getattr(self, '_urlopener', None)
111 151 if urlopener:
112 152 for h in urlopener.handlers:
113 153 h.close()
114 154 getattr(h, "close_all", lambda : None)()
115 155
116 156 # Begin of _basepeer interface.
117 157
118 158 @util.propertycache
119 159 def ui(self):
120 160 return self._ui
121 161
122 162 def url(self):
123 163 return self._path
124 164
125 165 def local(self):
126 166 return None
127 167
128 168 def peer(self):
129 169 return self
130 170
131 171 def canpush(self):
132 172 return True
133 173
134 174 def close(self):
135 175 pass
136 176
137 177 # End of _basepeer interface.
138 178
139 179 # Begin of _basewirepeer interface.
140 180
141 181 def capabilities(self):
142 182 if self._caps is None:
143 183 try:
144 184 self._fetchcaps()
145 185 except error.RepoError:
146 186 self._caps = set()
147 187 self.ui.debug('capabilities: %s\n' %
148 188 (' '.join(self._caps or ['none'])))
149 189 return self._caps
150 190
151 191 # End of _basewirepeer interface.
152 192
153 193 # look up capabilities only when needed
154 194
155 195 def _fetchcaps(self):
156 196 self._caps = set(self._call('capabilities').split())
157 197
158 198 def _callstream(self, cmd, _compressible=False, **args):
159 199 if cmd == 'pushkey':
160 200 args['data'] = ''
161 201 data = args.pop('data', None)
162 202 headers = args.pop('headers', {})
163 203
164 204 self.ui.debug("sending %s command\n" % cmd)
165 205 q = [('cmd', cmd)]
166 206 headersize = 0
167 207 varyheaders = []
168 208 # Important: don't use self.capable() here or else you end up
169 209 # with infinite recursion when trying to look up capabilities
170 210 # for the first time.
171 211 postargsok = self._caps is not None and 'httppostargs' in self._caps
172 # TODO: support for httppostargs when data is a file-like
173 # object rather than a basestring
174 canmungedata = not data or isinstance(data, basestring)
175 if postargsok and canmungedata:
212 if postargsok and args:
176 213 strargs = urlreq.urlencode(sorted(args.items()))
177 if strargs:
178 if not data:
179 data = strargs
180 elif isinstance(data, basestring):
181 data = strargs + data
182 headers['X-HgArgs-Post'] = len(strargs)
214 if not data:
215 data = strargs
216 else:
217 if isinstance(data, basestring):
218 i = io.BytesIO(data)
219 i.length = len(data)
220 data = i
221 argsio = io.BytesIO(strargs)
222 argsio.length = len(strargs)
223 data = _multifile(argsio, data)
224 headers['X-HgArgs-Post'] = len(strargs)
183 225 else:
184 226 if len(args) > 0:
185 227 httpheader = self.capable('httpheader')
186 228 if httpheader:
187 229 headersize = int(httpheader.split(',', 1)[0])
188 230 if headersize > 0:
189 231 # The headers can typically carry more data than the URL.
190 232 encargs = urlreq.urlencode(sorted(args.items()))
191 233 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
192 234 headersize):
193 235 headers[header] = value
194 236 varyheaders.append(header)
195 237 else:
196 238 q += sorted(args.items())
197 239 qs = '?%s' % urlreq.urlencode(q)
198 240 cu = "%s%s" % (self._url, qs)
199 241 size = 0
200 242 if util.safehasattr(data, 'length'):
201 243 size = data.length
202 244 elif data is not None:
203 245 size = len(data)
204 246 if size and self.ui.configbool('ui', 'usehttp2'):
205 247 headers['Expect'] = '100-Continue'
206 248 headers['X-HgHttp2'] = '1'
207 249 if data is not None and 'Content-Type' not in headers:
208 250 headers['Content-Type'] = 'application/mercurial-0.1'
209 251
210 252 # Tell the server we accept application/mercurial-0.2 and multiple
211 253 # compression formats if the server is capable of emitting those
212 254 # payloads.
213 255 protoparams = []
214 256
215 257 mediatypes = set()
216 258 if self._caps is not None:
217 259 mt = self.capable('httpmediatype')
218 260 if mt:
219 261 protoparams.append('0.1')
220 262 mediatypes = set(mt.split(','))
221 263
222 264 if '0.2tx' in mediatypes:
223 265 protoparams.append('0.2')
224 266
225 267 if '0.2tx' in mediatypes and self.capable('compression'):
226 268 # We /could/ compare supported compression formats and prune
227 269 # non-mutually supported or error if nothing is mutually supported.
228 270 # For now, send the full list to the server and have it error.
229 271 comps = [e.wireprotosupport().name for e in
230 272 util.compengines.supportedwireengines(util.CLIENTROLE)]
231 273 protoparams.append('comp=%s' % ','.join(comps))
232 274
233 275 if protoparams:
234 276 protoheaders = encodevalueinheaders(' '.join(protoparams),
235 277 'X-HgProto',
236 278 headersize or 1024)
237 279 for header, value in protoheaders:
238 280 headers[header] = value
239 281 varyheaders.append(header)
240 282
241 283 if varyheaders:
242 284 headers['Vary'] = ','.join(varyheaders)
243 285
244 286 req = self._requestbuilder(cu, data, headers)
245 287
246 288 if data is not None:
247 289 self.ui.debug("sending %s bytes\n" % size)
248 290 req.add_unredirected_header('Content-Length', '%d' % size)
249 291 try:
250 292 resp = self._urlopener.open(req)
251 293 except urlerr.httperror as inst:
252 294 if inst.code == 401:
253 295 raise error.Abort(_('authorization failed'))
254 296 raise
255 297 except httplib.HTTPException as inst:
256 298 self.ui.debug('http error while sending %s command\n' % cmd)
257 299 self.ui.traceback()
258 300 raise IOError(None, inst)
259 301
260 302 # Insert error handlers for common I/O failures.
261 303 _wraphttpresponse(resp)
262 304
263 305 # record the url we got redirected to
264 306 resp_url = resp.geturl()
265 307 if resp_url.endswith(qs):
266 308 resp_url = resp_url[:-len(qs)]
267 309 if self._url.rstrip('/') != resp_url.rstrip('/'):
268 310 if not self.ui.quiet:
269 311 self.ui.warn(_('real URL is %s\n') % resp_url)
270 312 self._url = resp_url
271 313 try:
272 314 proto = resp.getheader('content-type')
273 315 except AttributeError:
274 316 proto = resp.headers.get('content-type', '')
275 317
276 318 safeurl = util.hidepassword(self._url)
277 319 if proto.startswith('application/hg-error'):
278 320 raise error.OutOfBandError(resp.read())
279 321 # accept old "text/plain" and "application/hg-changegroup" for now
280 322 if not (proto.startswith('application/mercurial-') or
281 323 (proto.startswith('text/plain')
282 324 and not resp.headers.get('content-length')) or
283 325 proto.startswith('application/hg-changegroup')):
284 326 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
285 327 raise error.RepoError(
286 328 _("'%s' does not appear to be an hg repository:\n"
287 329 "---%%<--- (%s)\n%s\n---%%<---\n")
288 330 % (safeurl, proto or 'no content-type', resp.read(1024)))
289 331
290 332 if proto.startswith('application/mercurial-'):
291 333 try:
292 334 version = proto.split('-', 1)[1]
293 335 version_info = tuple([int(n) for n in version.split('.')])
294 336 except ValueError:
295 337 raise error.RepoError(_("'%s' sent a broken Content-Type "
296 338 "header (%s)") % (safeurl, proto))
297 339
298 340 # TODO consider switching to a decompression reader that uses
299 341 # generators.
300 342 if version_info == (0, 1):
301 343 if _compressible:
302 344 return util.compengines['zlib'].decompressorreader(resp)
303 345 return resp
304 346 elif version_info == (0, 2):
305 347 # application/mercurial-0.2 always identifies the compression
306 348 # engine in the payload header.
307 349 elen = struct.unpack('B', resp.read(1))[0]
308 350 ename = resp.read(elen)
309 351 engine = util.compengines.forwiretype(ename)
310 352 return engine.decompressorreader(resp)
311 353 else:
312 354 raise error.RepoError(_("'%s' uses newer protocol %s") %
313 355 (safeurl, version))
314 356
315 357 if _compressible:
316 358 return util.compengines['zlib'].decompressorreader(resp)
317 359
318 360 return resp
319 361
320 362 def _call(self, cmd, **args):
321 363 fp = self._callstream(cmd, **args)
322 364 try:
323 365 return fp.read()
324 366 finally:
325 367 # if using keepalive, allow connection to be reused
326 368 fp.close()
327 369
328 370 def _callpush(self, cmd, cg, **args):
329 371 # have to stream bundle to a temp file because we do not have
330 372 # http 1.1 chunked transfer.
331 373
332 374 types = self.capable('unbundle')
333 375 try:
334 376 types = types.split(',')
335 377 except AttributeError:
336 378 # servers older than d1b16a746db6 will send 'unbundle' as a
337 379 # boolean capability. They only support headerless/uncompressed
338 380 # bundles.
339 381 types = [""]
340 382 for x in types:
341 383 if x in bundle2.bundletypes:
342 384 type = x
343 385 break
344 386
345 387 tempname = bundle2.writebundle(self.ui, cg, None, type)
346 388 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
347 389 headers = {'Content-Type': 'application/mercurial-0.1'}
348 390
349 391 try:
350 392 r = self._call(cmd, data=fp, headers=headers, **args)
351 393 vals = r.split('\n', 1)
352 394 if len(vals) < 2:
353 395 raise error.ResponseError(_("unexpected response:"), r)
354 396 return vals
355 397 except socket.error as err:
356 398 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
357 399 raise error.Abort(_('push failed: %s') % err.args[1])
358 400 raise error.Abort(err.args[1])
359 401 finally:
360 402 fp.close()
361 403 os.unlink(tempname)
362 404
363 405 def _calltwowaystream(self, cmd, fp, **args):
364 406 fh = None
365 407 fp_ = None
366 408 filename = None
367 409 try:
368 410 # dump bundle to disk
369 411 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
370 412 fh = os.fdopen(fd, pycompat.sysstr("wb"))
371 413 d = fp.read(4096)
372 414 while d:
373 415 fh.write(d)
374 416 d = fp.read(4096)
375 417 fh.close()
376 418 # start http push
377 419 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
378 420 headers = {'Content-Type': 'application/mercurial-0.1'}
379 421 return self._callstream(cmd, data=fp_, headers=headers, **args)
380 422 finally:
381 423 if fp_ is not None:
382 424 fp_.close()
383 425 if fh is not None:
384 426 fh.close()
385 427 os.unlink(filename)
386 428
387 429 def _callcompressable(self, cmd, **args):
388 430 return self._callstream(cmd, _compressible=True, **args)
389 431
390 432 def _abort(self, exception):
391 433 raise exception
392 434
393 435 class httpspeer(httppeer):
394 436 def __init__(self, ui, path):
395 437 if not url.has_https:
396 438 raise error.Abort(_('Python support for SSL and HTTPS '
397 439 'is not installed'))
398 440 httppeer.__init__(self, ui, path)
399 441
400 442 def instance(ui, path, create):
401 443 if create:
402 444 raise error.Abort(_('cannot create new http repository'))
403 445 try:
404 446 if path.startswith('https:'):
405 447 inst = httpspeer(ui, path)
406 448 else:
407 449 inst = httppeer(ui, path)
408 450 try:
409 451 # Try to do useful work when checking compatibility.
410 452 # Usually saves a roundtrip since we want the caps anyway.
411 453 inst._fetchcaps()
412 454 except error.RepoError:
413 455 # No luck, try older compatibility check.
414 456 inst.between([(nullid, nullid)])
415 457 return inst
416 458 except error.RepoError as httpexception:
417 459 try:
418 460 r = statichttprepo.instance(ui, "static-" + path, create)
419 461 ui.note(_('(falling back to static-http)\n'))
420 462 return r
421 463 except error.RepoError:
422 464 raise httpexception # use the original http RepoError instead
General Comments 0
You need to be logged in to leave comments. Login now