##// END OF EJS Templates
wireprotov2: client support for following content redirects...
Gregory Szorc -
r40062:7e807b8a default
parent child Browse files
Show More
@@ -1,976 +1,978
1 # httppeer.py - HTTP repository proxy classes for mercurial
1 # httppeer.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import io
12 import io
13 import os
13 import os
14 import socket
14 import socket
15 import struct
15 import struct
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 bundle2,
20 bundle2,
21 error,
21 error,
22 httpconnection,
22 httpconnection,
23 pycompat,
23 pycompat,
24 repository,
24 repository,
25 statichttprepo,
25 statichttprepo,
26 url as urlmod,
26 url as urlmod,
27 util,
27 util,
28 wireprotoframing,
28 wireprotoframing,
29 wireprototypes,
29 wireprototypes,
30 wireprotov1peer,
30 wireprotov1peer,
31 wireprotov2peer,
31 wireprotov2peer,
32 wireprotov2server,
32 wireprotov2server,
33 )
33 )
34 from .utils import (
34 from .utils import (
35 cborutil,
35 cborutil,
36 interfaceutil,
36 interfaceutil,
37 stringutil,
37 stringutil,
38 )
38 )
39
39
40 httplib = util.httplib
40 httplib = util.httplib
41 urlerr = util.urlerr
41 urlerr = util.urlerr
42 urlreq = util.urlreq
42 urlreq = util.urlreq
43
43
44 def encodevalueinheaders(value, header, limit):
44 def encodevalueinheaders(value, header, limit):
45 """Encode a string value into multiple HTTP headers.
45 """Encode a string value into multiple HTTP headers.
46
46
47 ``value`` will be encoded into 1 or more HTTP headers with the names
47 ``value`` will be encoded into 1 or more HTTP headers with the names
48 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
48 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
49 name + value will be at most ``limit`` bytes long.
49 name + value will be at most ``limit`` bytes long.
50
50
51 Returns an iterable of 2-tuples consisting of header names and
51 Returns an iterable of 2-tuples consisting of header names and
52 values as native strings.
52 values as native strings.
53 """
53 """
54 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
54 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
55 # not bytes. This function always takes bytes in as arguments.
55 # not bytes. This function always takes bytes in as arguments.
56 fmt = pycompat.strurl(header) + r'-%s'
56 fmt = pycompat.strurl(header) + r'-%s'
57 # Note: it is *NOT* a bug that the last bit here is a bytestring
57 # Note: it is *NOT* a bug that the last bit here is a bytestring
58 # and not a unicode: we're just getting the encoded length anyway,
58 # and not a unicode: we're just getting the encoded length anyway,
59 # and using an r-string to make it portable between Python 2 and 3
59 # and using an r-string to make it portable between Python 2 and 3
60 # doesn't work because then the \r is a literal backslash-r
60 # doesn't work because then the \r is a literal backslash-r
61 # instead of a carriage return.
61 # instead of a carriage return.
62 valuelen = limit - len(fmt % r'000') - len(': \r\n')
62 valuelen = limit - len(fmt % r'000') - len(': \r\n')
63 result = []
63 result = []
64
64
65 n = 0
65 n = 0
66 for i in pycompat.xrange(0, len(value), valuelen):
66 for i in pycompat.xrange(0, len(value), valuelen):
67 n += 1
67 n += 1
68 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
68 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
69
69
70 return result
70 return result
71
71
72 class _multifile(object):
72 class _multifile(object):
73 def __init__(self, *fileobjs):
73 def __init__(self, *fileobjs):
74 for f in fileobjs:
74 for f in fileobjs:
75 if not util.safehasattr(f, 'length'):
75 if not util.safehasattr(f, 'length'):
76 raise ValueError(
76 raise ValueError(
77 '_multifile only supports file objects that '
77 '_multifile only supports file objects that '
78 'have a length but this one does not:', type(f), f)
78 'have a length but this one does not:', type(f), f)
79 self._fileobjs = fileobjs
79 self._fileobjs = fileobjs
80 self._index = 0
80 self._index = 0
81
81
82 @property
82 @property
83 def length(self):
83 def length(self):
84 return sum(f.length for f in self._fileobjs)
84 return sum(f.length for f in self._fileobjs)
85
85
86 def read(self, amt=None):
86 def read(self, amt=None):
87 if amt <= 0:
87 if amt <= 0:
88 return ''.join(f.read() for f in self._fileobjs)
88 return ''.join(f.read() for f in self._fileobjs)
89 parts = []
89 parts = []
90 while amt and self._index < len(self._fileobjs):
90 while amt and self._index < len(self._fileobjs):
91 parts.append(self._fileobjs[self._index].read(amt))
91 parts.append(self._fileobjs[self._index].read(amt))
92 got = len(parts[-1])
92 got = len(parts[-1])
93 if got < amt:
93 if got < amt:
94 self._index += 1
94 self._index += 1
95 amt -= got
95 amt -= got
96 return ''.join(parts)
96 return ''.join(parts)
97
97
98 def seek(self, offset, whence=os.SEEK_SET):
98 def seek(self, offset, whence=os.SEEK_SET):
99 if whence != os.SEEK_SET:
99 if whence != os.SEEK_SET:
100 raise NotImplementedError(
100 raise NotImplementedError(
101 '_multifile does not support anything other'
101 '_multifile does not support anything other'
102 ' than os.SEEK_SET for whence on seek()')
102 ' than os.SEEK_SET for whence on seek()')
103 if offset != 0:
103 if offset != 0:
104 raise NotImplementedError(
104 raise NotImplementedError(
105 '_multifile only supports seeking to start, but that '
105 '_multifile only supports seeking to start, but that '
106 'could be fixed if you need it')
106 'could be fixed if you need it')
107 for f in self._fileobjs:
107 for f in self._fileobjs:
108 f.seek(0)
108 f.seek(0)
109 self._index = 0
109 self._index = 0
110
110
111 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
111 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
112 repobaseurl, cmd, args):
112 repobaseurl, cmd, args):
113 """Make an HTTP request to run a command for a version 1 client.
113 """Make an HTTP request to run a command for a version 1 client.
114
114
115 ``caps`` is a set of known server capabilities. The value may be
115 ``caps`` is a set of known server capabilities. The value may be
116 None if capabilities are not yet known.
116 None if capabilities are not yet known.
117
117
118 ``capablefn`` is a function to evaluate a capability.
118 ``capablefn`` is a function to evaluate a capability.
119
119
120 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
120 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
121 raw data to pass to it.
121 raw data to pass to it.
122 """
122 """
123 if cmd == 'pushkey':
123 if cmd == 'pushkey':
124 args['data'] = ''
124 args['data'] = ''
125 data = args.pop('data', None)
125 data = args.pop('data', None)
126 headers = args.pop('headers', {})
126 headers = args.pop('headers', {})
127
127
128 ui.debug("sending %s command\n" % cmd)
128 ui.debug("sending %s command\n" % cmd)
129 q = [('cmd', cmd)]
129 q = [('cmd', cmd)]
130 headersize = 0
130 headersize = 0
131 # Important: don't use self.capable() here or else you end up
131 # Important: don't use self.capable() here or else you end up
132 # with infinite recursion when trying to look up capabilities
132 # with infinite recursion when trying to look up capabilities
133 # for the first time.
133 # for the first time.
134 postargsok = caps is not None and 'httppostargs' in caps
134 postargsok = caps is not None and 'httppostargs' in caps
135
135
136 # Send arguments via POST.
136 # Send arguments via POST.
137 if postargsok and args:
137 if postargsok and args:
138 strargs = urlreq.urlencode(sorted(args.items()))
138 strargs = urlreq.urlencode(sorted(args.items()))
139 if not data:
139 if not data:
140 data = strargs
140 data = strargs
141 else:
141 else:
142 if isinstance(data, bytes):
142 if isinstance(data, bytes):
143 i = io.BytesIO(data)
143 i = io.BytesIO(data)
144 i.length = len(data)
144 i.length = len(data)
145 data = i
145 data = i
146 argsio = io.BytesIO(strargs)
146 argsio = io.BytesIO(strargs)
147 argsio.length = len(strargs)
147 argsio.length = len(strargs)
148 data = _multifile(argsio, data)
148 data = _multifile(argsio, data)
149 headers[r'X-HgArgs-Post'] = len(strargs)
149 headers[r'X-HgArgs-Post'] = len(strargs)
150 elif args:
150 elif args:
151 # Calling self.capable() can infinite loop if we are calling
151 # Calling self.capable() can infinite loop if we are calling
152 # "capabilities". But that command should never accept wire
152 # "capabilities". But that command should never accept wire
153 # protocol arguments. So this should never happen.
153 # protocol arguments. So this should never happen.
154 assert cmd != 'capabilities'
154 assert cmd != 'capabilities'
155 httpheader = capablefn('httpheader')
155 httpheader = capablefn('httpheader')
156 if httpheader:
156 if httpheader:
157 headersize = int(httpheader.split(',', 1)[0])
157 headersize = int(httpheader.split(',', 1)[0])
158
158
159 # Send arguments via HTTP headers.
159 # Send arguments via HTTP headers.
160 if headersize > 0:
160 if headersize > 0:
161 # The headers can typically carry more data than the URL.
161 # The headers can typically carry more data than the URL.
162 encargs = urlreq.urlencode(sorted(args.items()))
162 encargs = urlreq.urlencode(sorted(args.items()))
163 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
163 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
164 headersize):
164 headersize):
165 headers[header] = value
165 headers[header] = value
166 # Send arguments via query string (Mercurial <1.9).
166 # Send arguments via query string (Mercurial <1.9).
167 else:
167 else:
168 q += sorted(args.items())
168 q += sorted(args.items())
169
169
170 qs = '?%s' % urlreq.urlencode(q)
170 qs = '?%s' % urlreq.urlencode(q)
171 cu = "%s%s" % (repobaseurl, qs)
171 cu = "%s%s" % (repobaseurl, qs)
172 size = 0
172 size = 0
173 if util.safehasattr(data, 'length'):
173 if util.safehasattr(data, 'length'):
174 size = data.length
174 size = data.length
175 elif data is not None:
175 elif data is not None:
176 size = len(data)
176 size = len(data)
177 if data is not None and r'Content-Type' not in headers:
177 if data is not None and r'Content-Type' not in headers:
178 headers[r'Content-Type'] = r'application/mercurial-0.1'
178 headers[r'Content-Type'] = r'application/mercurial-0.1'
179
179
180 # Tell the server we accept application/mercurial-0.2 and multiple
180 # Tell the server we accept application/mercurial-0.2 and multiple
181 # compression formats if the server is capable of emitting those
181 # compression formats if the server is capable of emitting those
182 # payloads.
182 # payloads.
183 # Note: Keep this set empty by default, as client advertisement of
183 # Note: Keep this set empty by default, as client advertisement of
184 # protocol parameters should only occur after the handshake.
184 # protocol parameters should only occur after the handshake.
185 protoparams = set()
185 protoparams = set()
186
186
187 mediatypes = set()
187 mediatypes = set()
188 if caps is not None:
188 if caps is not None:
189 mt = capablefn('httpmediatype')
189 mt = capablefn('httpmediatype')
190 if mt:
190 if mt:
191 protoparams.add('0.1')
191 protoparams.add('0.1')
192 mediatypes = set(mt.split(','))
192 mediatypes = set(mt.split(','))
193
193
194 protoparams.add('partial-pull')
194 protoparams.add('partial-pull')
195
195
196 if '0.2tx' in mediatypes:
196 if '0.2tx' in mediatypes:
197 protoparams.add('0.2')
197 protoparams.add('0.2')
198
198
199 if '0.2tx' in mediatypes and capablefn('compression'):
199 if '0.2tx' in mediatypes and capablefn('compression'):
200 # We /could/ compare supported compression formats and prune
200 # We /could/ compare supported compression formats and prune
201 # non-mutually supported or error if nothing is mutually supported.
201 # non-mutually supported or error if nothing is mutually supported.
202 # For now, send the full list to the server and have it error.
202 # For now, send the full list to the server and have it error.
203 comps = [e.wireprotosupport().name for e in
203 comps = [e.wireprotosupport().name for e in
204 util.compengines.supportedwireengines(util.CLIENTROLE)]
204 util.compengines.supportedwireengines(util.CLIENTROLE)]
205 protoparams.add('comp=%s' % ','.join(comps))
205 protoparams.add('comp=%s' % ','.join(comps))
206
206
207 if protoparams:
207 if protoparams:
208 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
208 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
209 'X-HgProto',
209 'X-HgProto',
210 headersize or 1024)
210 headersize or 1024)
211 for header, value in protoheaders:
211 for header, value in protoheaders:
212 headers[header] = value
212 headers[header] = value
213
213
214 varyheaders = []
214 varyheaders = []
215 for header in headers:
215 for header in headers:
216 if header.lower().startswith(r'x-hg'):
216 if header.lower().startswith(r'x-hg'):
217 varyheaders.append(header)
217 varyheaders.append(header)
218
218
219 if varyheaders:
219 if varyheaders:
220 headers[r'Vary'] = r','.join(sorted(varyheaders))
220 headers[r'Vary'] = r','.join(sorted(varyheaders))
221
221
222 req = requestbuilder(pycompat.strurl(cu), data, headers)
222 req = requestbuilder(pycompat.strurl(cu), data, headers)
223
223
224 if data is not None:
224 if data is not None:
225 ui.debug("sending %d bytes\n" % size)
225 ui.debug("sending %d bytes\n" % size)
226 req.add_unredirected_header(r'Content-Length', r'%d' % size)
226 req.add_unredirected_header(r'Content-Length', r'%d' % size)
227
227
228 return req, cu, qs
228 return req, cu, qs
229
229
230 def _reqdata(req):
230 def _reqdata(req):
231 """Get request data, if any. If no data, returns None."""
231 """Get request data, if any. If no data, returns None."""
232 if pycompat.ispy3:
232 if pycompat.ispy3:
233 return req.data
233 return req.data
234 if not req.has_data():
234 if not req.has_data():
235 return None
235 return None
236 return req.get_data()
236 return req.get_data()
237
237
238 def sendrequest(ui, opener, req):
238 def sendrequest(ui, opener, req):
239 """Send a prepared HTTP request.
239 """Send a prepared HTTP request.
240
240
241 Returns the response object.
241 Returns the response object.
242 """
242 """
243 dbg = ui.debug
243 dbg = ui.debug
244 if (ui.debugflag
244 if (ui.debugflag
245 and ui.configbool('devel', 'debug.peer-request')):
245 and ui.configbool('devel', 'debug.peer-request')):
246 line = 'devel-peer-request: %s\n'
246 line = 'devel-peer-request: %s\n'
247 dbg(line % '%s %s' % (pycompat.bytesurl(req.get_method()),
247 dbg(line % '%s %s' % (pycompat.bytesurl(req.get_method()),
248 pycompat.bytesurl(req.get_full_url())))
248 pycompat.bytesurl(req.get_full_url())))
249 hgargssize = None
249 hgargssize = None
250
250
251 for header, value in sorted(req.header_items()):
251 for header, value in sorted(req.header_items()):
252 header = pycompat.bytesurl(header)
252 header = pycompat.bytesurl(header)
253 value = pycompat.bytesurl(value)
253 value = pycompat.bytesurl(value)
254 if header.startswith('X-hgarg-'):
254 if header.startswith('X-hgarg-'):
255 if hgargssize is None:
255 if hgargssize is None:
256 hgargssize = 0
256 hgargssize = 0
257 hgargssize += len(value)
257 hgargssize += len(value)
258 else:
258 else:
259 dbg(line % ' %s %s' % (header, value))
259 dbg(line % ' %s %s' % (header, value))
260
260
261 if hgargssize is not None:
261 if hgargssize is not None:
262 dbg(line % ' %d bytes of commands arguments in headers'
262 dbg(line % ' %d bytes of commands arguments in headers'
263 % hgargssize)
263 % hgargssize)
264 data = _reqdata(req)
264 data = _reqdata(req)
265 if data is not None:
265 if data is not None:
266 length = getattr(data, 'length', None)
266 length = getattr(data, 'length', None)
267 if length is None:
267 if length is None:
268 length = len(data)
268 length = len(data)
269 dbg(line % ' %d bytes of data' % length)
269 dbg(line % ' %d bytes of data' % length)
270
270
271 start = util.timer()
271 start = util.timer()
272
272
273 res = None
273 res = None
274 try:
274 try:
275 res = opener.open(req)
275 res = opener.open(req)
276 except urlerr.httperror as inst:
276 except urlerr.httperror as inst:
277 if inst.code == 401:
277 if inst.code == 401:
278 raise error.Abort(_('authorization failed'))
278 raise error.Abort(_('authorization failed'))
279 raise
279 raise
280 except httplib.HTTPException as inst:
280 except httplib.HTTPException as inst:
281 ui.debug('http error requesting %s\n' %
281 ui.debug('http error requesting %s\n' %
282 util.hidepassword(req.get_full_url()))
282 util.hidepassword(req.get_full_url()))
283 ui.traceback()
283 ui.traceback()
284 raise IOError(None, inst)
284 raise IOError(None, inst)
285 finally:
285 finally:
286 if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
286 if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
287 code = res.code if res else -1
287 code = res.code if res else -1
288 dbg(line % ' finished in %.4f seconds (%d)'
288 dbg(line % ' finished in %.4f seconds (%d)'
289 % (util.timer() - start, code))
289 % (util.timer() - start, code))
290
290
291 # Insert error handlers for common I/O failures.
291 # Insert error handlers for common I/O failures.
292 urlmod.wrapresponse(res)
292 urlmod.wrapresponse(res)
293
293
294 return res
294 return res
295
295
296 class RedirectedRepoError(error.RepoError):
296 class RedirectedRepoError(error.RepoError):
297 def __init__(self, msg, respurl):
297 def __init__(self, msg, respurl):
298 super(RedirectedRepoError, self).__init__(msg)
298 super(RedirectedRepoError, self).__init__(msg)
299 self.respurl = respurl
299 self.respurl = respurl
300
300
301 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,
301 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,
302 allowcbor=False):
302 allowcbor=False):
303 # record the url we got redirected to
303 # record the url we got redirected to
304 redirected = False
304 redirected = False
305 respurl = pycompat.bytesurl(resp.geturl())
305 respurl = pycompat.bytesurl(resp.geturl())
306 if respurl.endswith(qs):
306 if respurl.endswith(qs):
307 respurl = respurl[:-len(qs)]
307 respurl = respurl[:-len(qs)]
308 qsdropped = False
308 qsdropped = False
309 else:
309 else:
310 qsdropped = True
310 qsdropped = True
311
311
312 if baseurl.rstrip('/') != respurl.rstrip('/'):
312 if baseurl.rstrip('/') != respurl.rstrip('/'):
313 redirected = True
313 redirected = True
314 if not ui.quiet:
314 if not ui.quiet:
315 ui.warn(_('real URL is %s\n') % respurl)
315 ui.warn(_('real URL is %s\n') % respurl)
316
316
317 try:
317 try:
318 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
318 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
319 except AttributeError:
319 except AttributeError:
320 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
320 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
321
321
322 safeurl = util.hidepassword(baseurl)
322 safeurl = util.hidepassword(baseurl)
323 if proto.startswith('application/hg-error'):
323 if proto.startswith('application/hg-error'):
324 raise error.OutOfBandError(resp.read())
324 raise error.OutOfBandError(resp.read())
325
325
326 # Pre 1.0 versions of Mercurial used text/plain and
326 # Pre 1.0 versions of Mercurial used text/plain and
327 # application/hg-changegroup. We don't support such old servers.
327 # application/hg-changegroup. We don't support such old servers.
328 if not proto.startswith('application/mercurial-'):
328 if not proto.startswith('application/mercurial-'):
329 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
329 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
330 msg = _("'%s' does not appear to be an hg repository:\n"
330 msg = _("'%s' does not appear to be an hg repository:\n"
331 "---%%<--- (%s)\n%s\n---%%<---\n") % (
331 "---%%<--- (%s)\n%s\n---%%<---\n") % (
332 safeurl, proto or 'no content-type', resp.read(1024))
332 safeurl, proto or 'no content-type', resp.read(1024))
333
333
334 # Some servers may strip the query string from the redirect. We
334 # Some servers may strip the query string from the redirect. We
335 # raise a special error type so callers can react to this specially.
335 # raise a special error type so callers can react to this specially.
336 if redirected and qsdropped:
336 if redirected and qsdropped:
337 raise RedirectedRepoError(msg, respurl)
337 raise RedirectedRepoError(msg, respurl)
338 else:
338 else:
339 raise error.RepoError(msg)
339 raise error.RepoError(msg)
340
340
341 try:
341 try:
342 subtype = proto.split('-', 1)[1]
342 subtype = proto.split('-', 1)[1]
343
343
344 # Unless we end up supporting CBOR in the legacy wire protocol,
344 # Unless we end up supporting CBOR in the legacy wire protocol,
345 # this should ONLY be encountered for the initial capabilities
345 # this should ONLY be encountered for the initial capabilities
346 # request during handshake.
346 # request during handshake.
347 if subtype == 'cbor':
347 if subtype == 'cbor':
348 if allowcbor:
348 if allowcbor:
349 return respurl, proto, resp
349 return respurl, proto, resp
350 else:
350 else:
351 raise error.RepoError(_('unexpected CBOR response from '
351 raise error.RepoError(_('unexpected CBOR response from '
352 'server'))
352 'server'))
353
353
354 version_info = tuple([int(n) for n in subtype.split('.')])
354 version_info = tuple([int(n) for n in subtype.split('.')])
355 except ValueError:
355 except ValueError:
356 raise error.RepoError(_("'%s' sent a broken Content-Type "
356 raise error.RepoError(_("'%s' sent a broken Content-Type "
357 "header (%s)") % (safeurl, proto))
357 "header (%s)") % (safeurl, proto))
358
358
359 # TODO consider switching to a decompression reader that uses
359 # TODO consider switching to a decompression reader that uses
360 # generators.
360 # generators.
361 if version_info == (0, 1):
361 if version_info == (0, 1):
362 if compressible:
362 if compressible:
363 resp = util.compengines['zlib'].decompressorreader(resp)
363 resp = util.compengines['zlib'].decompressorreader(resp)
364
364
365 elif version_info == (0, 2):
365 elif version_info == (0, 2):
366 # application/mercurial-0.2 always identifies the compression
366 # application/mercurial-0.2 always identifies the compression
367 # engine in the payload header.
367 # engine in the payload header.
368 elen = struct.unpack('B', util.readexactly(resp, 1))[0]
368 elen = struct.unpack('B', util.readexactly(resp, 1))[0]
369 ename = util.readexactly(resp, elen)
369 ename = util.readexactly(resp, elen)
370 engine = util.compengines.forwiretype(ename)
370 engine = util.compengines.forwiretype(ename)
371
371
372 resp = engine.decompressorreader(resp)
372 resp = engine.decompressorreader(resp)
373 else:
373 else:
374 raise error.RepoError(_("'%s' uses newer protocol %s") %
374 raise error.RepoError(_("'%s' uses newer protocol %s") %
375 (safeurl, subtype))
375 (safeurl, subtype))
376
376
377 return respurl, proto, resp
377 return respurl, proto, resp
378
378
379 class httppeer(wireprotov1peer.wirepeer):
379 class httppeer(wireprotov1peer.wirepeer):
380 def __init__(self, ui, path, url, opener, requestbuilder, caps):
380 def __init__(self, ui, path, url, opener, requestbuilder, caps):
381 self.ui = ui
381 self.ui = ui
382 self._path = path
382 self._path = path
383 self._url = url
383 self._url = url
384 self._caps = caps
384 self._caps = caps
385 self._urlopener = opener
385 self._urlopener = opener
386 self._requestbuilder = requestbuilder
386 self._requestbuilder = requestbuilder
387
387
388 def __del__(self):
388 def __del__(self):
389 for h in self._urlopener.handlers:
389 for h in self._urlopener.handlers:
390 h.close()
390 h.close()
391 getattr(h, "close_all", lambda: None)()
391 getattr(h, "close_all", lambda: None)()
392
392
393 # Begin of ipeerconnection interface.
393 # Begin of ipeerconnection interface.
394
394
395 def url(self):
395 def url(self):
396 return self._path
396 return self._path
397
397
398 def local(self):
398 def local(self):
399 return None
399 return None
400
400
401 def peer(self):
401 def peer(self):
402 return self
402 return self
403
403
404 def canpush(self):
404 def canpush(self):
405 return True
405 return True
406
406
407 def close(self):
407 def close(self):
408 pass
408 pass
409
409
410 # End of ipeerconnection interface.
410 # End of ipeerconnection interface.
411
411
412 # Begin of ipeercommands interface.
412 # Begin of ipeercommands interface.
413
413
414 def capabilities(self):
414 def capabilities(self):
415 return self._caps
415 return self._caps
416
416
417 # End of ipeercommands interface.
417 # End of ipeercommands interface.
418
418
419 def _callstream(self, cmd, _compressible=False, **args):
419 def _callstream(self, cmd, _compressible=False, **args):
420 args = pycompat.byteskwargs(args)
420 args = pycompat.byteskwargs(args)
421
421
422 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
422 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
423 self._caps, self.capable,
423 self._caps, self.capable,
424 self._url, cmd, args)
424 self._url, cmd, args)
425
425
426 resp = sendrequest(self.ui, self._urlopener, req)
426 resp = sendrequest(self.ui, self._urlopener, req)
427
427
428 self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
428 self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
429 resp, _compressible)
429 resp, _compressible)
430
430
431 return resp
431 return resp
432
432
433 def _call(self, cmd, **args):
433 def _call(self, cmd, **args):
434 fp = self._callstream(cmd, **args)
434 fp = self._callstream(cmd, **args)
435 try:
435 try:
436 return fp.read()
436 return fp.read()
437 finally:
437 finally:
438 # if using keepalive, allow connection to be reused
438 # if using keepalive, allow connection to be reused
439 fp.close()
439 fp.close()
440
440
441 def _callpush(self, cmd, cg, **args):
441 def _callpush(self, cmd, cg, **args):
442 # have to stream bundle to a temp file because we do not have
442 # have to stream bundle to a temp file because we do not have
443 # http 1.1 chunked transfer.
443 # http 1.1 chunked transfer.
444
444
445 types = self.capable('unbundle')
445 types = self.capable('unbundle')
446 try:
446 try:
447 types = types.split(',')
447 types = types.split(',')
448 except AttributeError:
448 except AttributeError:
449 # servers older than d1b16a746db6 will send 'unbundle' as a
449 # servers older than d1b16a746db6 will send 'unbundle' as a
450 # boolean capability. They only support headerless/uncompressed
450 # boolean capability. They only support headerless/uncompressed
451 # bundles.
451 # bundles.
452 types = [""]
452 types = [""]
453 for x in types:
453 for x in types:
454 if x in bundle2.bundletypes:
454 if x in bundle2.bundletypes:
455 type = x
455 type = x
456 break
456 break
457
457
458 tempname = bundle2.writebundle(self.ui, cg, None, type)
458 tempname = bundle2.writebundle(self.ui, cg, None, type)
459 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
459 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
460 headers = {r'Content-Type': r'application/mercurial-0.1'}
460 headers = {r'Content-Type': r'application/mercurial-0.1'}
461
461
462 try:
462 try:
463 r = self._call(cmd, data=fp, headers=headers, **args)
463 r = self._call(cmd, data=fp, headers=headers, **args)
464 vals = r.split('\n', 1)
464 vals = r.split('\n', 1)
465 if len(vals) < 2:
465 if len(vals) < 2:
466 raise error.ResponseError(_("unexpected response:"), r)
466 raise error.ResponseError(_("unexpected response:"), r)
467 return vals
467 return vals
468 except urlerr.httperror:
468 except urlerr.httperror:
469 # Catch and re-raise these so we don't try and treat them
469 # Catch and re-raise these so we don't try and treat them
470 # like generic socket errors. They lack any values in
470 # like generic socket errors. They lack any values in
471 # .args on Python 3 which breaks our socket.error block.
471 # .args on Python 3 which breaks our socket.error block.
472 raise
472 raise
473 except socket.error as err:
473 except socket.error as err:
474 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
474 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
475 raise error.Abort(_('push failed: %s') % err.args[1])
475 raise error.Abort(_('push failed: %s') % err.args[1])
476 raise error.Abort(err.args[1])
476 raise error.Abort(err.args[1])
477 finally:
477 finally:
478 fp.close()
478 fp.close()
479 os.unlink(tempname)
479 os.unlink(tempname)
480
480
481 def _calltwowaystream(self, cmd, fp, **args):
481 def _calltwowaystream(self, cmd, fp, **args):
482 fh = None
482 fh = None
483 fp_ = None
483 fp_ = None
484 filename = None
484 filename = None
485 try:
485 try:
486 # dump bundle to disk
486 # dump bundle to disk
487 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
487 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
488 fh = os.fdopen(fd, r"wb")
488 fh = os.fdopen(fd, r"wb")
489 d = fp.read(4096)
489 d = fp.read(4096)
490 while d:
490 while d:
491 fh.write(d)
491 fh.write(d)
492 d = fp.read(4096)
492 d = fp.read(4096)
493 fh.close()
493 fh.close()
494 # start http push
494 # start http push
495 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
495 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
496 headers = {r'Content-Type': r'application/mercurial-0.1'}
496 headers = {r'Content-Type': r'application/mercurial-0.1'}
497 return self._callstream(cmd, data=fp_, headers=headers, **args)
497 return self._callstream(cmd, data=fp_, headers=headers, **args)
498 finally:
498 finally:
499 if fp_ is not None:
499 if fp_ is not None:
500 fp_.close()
500 fp_.close()
501 if fh is not None:
501 if fh is not None:
502 fh.close()
502 fh.close()
503 os.unlink(filename)
503 os.unlink(filename)
504
504
505 def _callcompressable(self, cmd, **args):
505 def _callcompressable(self, cmd, **args):
506 return self._callstream(cmd, _compressible=True, **args)
506 return self._callstream(cmd, _compressible=True, **args)
507
507
508 def _abort(self, exception):
508 def _abort(self, exception):
509 raise exception
509 raise exception
510
510
511 def sendv2request(ui, opener, requestbuilder, apiurl, permission, requests,
511 def sendv2request(ui, opener, requestbuilder, apiurl, permission, requests,
512 redirect):
512 redirect):
513 reactor = wireprotoframing.clientreactor(hasmultiplesend=False,
513 reactor = wireprotoframing.clientreactor(hasmultiplesend=False,
514 buffersends=True)
514 buffersends=True)
515
515
516 handler = wireprotov2peer.clienthandler(ui, reactor)
516 handler = wireprotov2peer.clienthandler(ui, reactor,
517 opener=opener,
518 requestbuilder=requestbuilder)
517
519
518 url = '%s/%s' % (apiurl, permission)
520 url = '%s/%s' % (apiurl, permission)
519
521
520 if len(requests) > 1:
522 if len(requests) > 1:
521 url += '/multirequest'
523 url += '/multirequest'
522 else:
524 else:
523 url += '/%s' % requests[0][0]
525 url += '/%s' % requests[0][0]
524
526
525 ui.debug('sending %d commands\n' % len(requests))
527 ui.debug('sending %d commands\n' % len(requests))
526 for command, args, f in requests:
528 for command, args, f in requests:
527 ui.debug('sending command %s: %s\n' % (
529 ui.debug('sending command %s: %s\n' % (
528 command, stringutil.pprint(args, indent=2)))
530 command, stringutil.pprint(args, indent=2)))
529 assert not list(handler.callcommand(command, args, f,
531 assert not list(handler.callcommand(command, args, f,
530 redirect=redirect))
532 redirect=redirect))
531
533
532 # TODO stream this.
534 # TODO stream this.
533 body = b''.join(map(bytes, handler.flushcommands()))
535 body = b''.join(map(bytes, handler.flushcommands()))
534
536
535 # TODO modify user-agent to reflect v2
537 # TODO modify user-agent to reflect v2
536 headers = {
538 headers = {
537 r'Accept': wireprotov2server.FRAMINGTYPE,
539 r'Accept': wireprotov2server.FRAMINGTYPE,
538 r'Content-Type': wireprotov2server.FRAMINGTYPE,
540 r'Content-Type': wireprotov2server.FRAMINGTYPE,
539 }
541 }
540
542
541 req = requestbuilder(pycompat.strurl(url), body, headers)
543 req = requestbuilder(pycompat.strurl(url), body, headers)
542 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
544 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
543
545
544 try:
546 try:
545 res = opener.open(req)
547 res = opener.open(req)
546 except urlerr.httperror as e:
548 except urlerr.httperror as e:
547 if e.code == 401:
549 if e.code == 401:
548 raise error.Abort(_('authorization failed'))
550 raise error.Abort(_('authorization failed'))
549
551
550 raise
552 raise
551 except httplib.HTTPException as e:
553 except httplib.HTTPException as e:
552 ui.traceback()
554 ui.traceback()
553 raise IOError(None, e)
555 raise IOError(None, e)
554
556
555 return handler, res
557 return handler, res
556
558
557 class queuedcommandfuture(pycompat.futures.Future):
559 class queuedcommandfuture(pycompat.futures.Future):
558 """Wraps result() on command futures to trigger submission on call."""
560 """Wraps result() on command futures to trigger submission on call."""
559
561
560 def result(self, timeout=None):
562 def result(self, timeout=None):
561 if self.done():
563 if self.done():
562 return pycompat.futures.Future.result(self, timeout)
564 return pycompat.futures.Future.result(self, timeout)
563
565
564 self._peerexecutor.sendcommands()
566 self._peerexecutor.sendcommands()
565
567
566 # sendcommands() will restore the original __class__ and self.result
568 # sendcommands() will restore the original __class__ and self.result
567 # will resolve to Future.result.
569 # will resolve to Future.result.
568 return self.result(timeout)
570 return self.result(timeout)
569
571
570 @interfaceutil.implementer(repository.ipeercommandexecutor)
572 @interfaceutil.implementer(repository.ipeercommandexecutor)
571 class httpv2executor(object):
573 class httpv2executor(object):
572 def __init__(self, ui, opener, requestbuilder, apiurl, descriptor,
574 def __init__(self, ui, opener, requestbuilder, apiurl, descriptor,
573 redirect):
575 redirect):
574 self._ui = ui
576 self._ui = ui
575 self._opener = opener
577 self._opener = opener
576 self._requestbuilder = requestbuilder
578 self._requestbuilder = requestbuilder
577 self._apiurl = apiurl
579 self._apiurl = apiurl
578 self._descriptor = descriptor
580 self._descriptor = descriptor
579 self._redirect = redirect
581 self._redirect = redirect
580 self._sent = False
582 self._sent = False
581 self._closed = False
583 self._closed = False
582 self._neededpermissions = set()
584 self._neededpermissions = set()
583 self._calls = []
585 self._calls = []
584 self._futures = weakref.WeakSet()
586 self._futures = weakref.WeakSet()
585 self._responseexecutor = None
587 self._responseexecutor = None
586 self._responsef = None
588 self._responsef = None
587
589
588 def __enter__(self):
590 def __enter__(self):
589 return self
591 return self
590
592
591 def __exit__(self, exctype, excvalue, exctb):
593 def __exit__(self, exctype, excvalue, exctb):
592 self.close()
594 self.close()
593
595
594 def callcommand(self, command, args):
596 def callcommand(self, command, args):
595 if self._sent:
597 if self._sent:
596 raise error.ProgrammingError('callcommand() cannot be used after '
598 raise error.ProgrammingError('callcommand() cannot be used after '
597 'commands are sent')
599 'commands are sent')
598
600
599 if self._closed:
601 if self._closed:
600 raise error.ProgrammingError('callcommand() cannot be used after '
602 raise error.ProgrammingError('callcommand() cannot be used after '
601 'close()')
603 'close()')
602
604
603 # The service advertises which commands are available. So if we attempt
605 # The service advertises which commands are available. So if we attempt
604 # to call an unknown command or pass an unknown argument, we can screen
606 # to call an unknown command or pass an unknown argument, we can screen
605 # for this.
607 # for this.
606 if command not in self._descriptor['commands']:
608 if command not in self._descriptor['commands']:
607 raise error.ProgrammingError(
609 raise error.ProgrammingError(
608 'wire protocol command %s is not available' % command)
610 'wire protocol command %s is not available' % command)
609
611
610 cmdinfo = self._descriptor['commands'][command]
612 cmdinfo = self._descriptor['commands'][command]
611 unknownargs = set(args.keys()) - set(cmdinfo.get('args', {}))
613 unknownargs = set(args.keys()) - set(cmdinfo.get('args', {}))
612
614
613 if unknownargs:
615 if unknownargs:
614 raise error.ProgrammingError(
616 raise error.ProgrammingError(
615 'wire protocol command %s does not accept argument: %s' % (
617 'wire protocol command %s does not accept argument: %s' % (
616 command, ', '.join(sorted(unknownargs))))
618 command, ', '.join(sorted(unknownargs))))
617
619
618 self._neededpermissions |= set(cmdinfo['permissions'])
620 self._neededpermissions |= set(cmdinfo['permissions'])
619
621
620 # TODO we /could/ also validate types here, since the API descriptor
622 # TODO we /could/ also validate types here, since the API descriptor
621 # includes types...
623 # includes types...
622
624
623 f = pycompat.futures.Future()
625 f = pycompat.futures.Future()
624
626
625 # Monkeypatch it so result() triggers sendcommands(), otherwise result()
627 # Monkeypatch it so result() triggers sendcommands(), otherwise result()
626 # could deadlock.
628 # could deadlock.
627 f.__class__ = queuedcommandfuture
629 f.__class__ = queuedcommandfuture
628 f._peerexecutor = self
630 f._peerexecutor = self
629
631
630 self._futures.add(f)
632 self._futures.add(f)
631 self._calls.append((command, args, f))
633 self._calls.append((command, args, f))
632
634
633 return f
635 return f
634
636
635 def sendcommands(self):
637 def sendcommands(self):
636 if self._sent:
638 if self._sent:
637 return
639 return
638
640
639 if not self._calls:
641 if not self._calls:
640 return
642 return
641
643
642 self._sent = True
644 self._sent = True
643
645
644 # Unhack any future types so caller sees a clean type and so we
646 # Unhack any future types so caller sees a clean type and so we
645 # break reference cycle.
647 # break reference cycle.
646 for f in self._futures:
648 for f in self._futures:
647 if isinstance(f, queuedcommandfuture):
649 if isinstance(f, queuedcommandfuture):
648 f.__class__ = pycompat.futures.Future
650 f.__class__ = pycompat.futures.Future
649 f._peerexecutor = None
651 f._peerexecutor = None
650
652
651 # Mark the future as running and filter out cancelled futures.
653 # Mark the future as running and filter out cancelled futures.
652 calls = [(command, args, f)
654 calls = [(command, args, f)
653 for command, args, f in self._calls
655 for command, args, f in self._calls
654 if f.set_running_or_notify_cancel()]
656 if f.set_running_or_notify_cancel()]
655
657
656 # Clear out references, prevent improper object usage.
658 # Clear out references, prevent improper object usage.
657 self._calls = None
659 self._calls = None
658
660
659 if not calls:
661 if not calls:
660 return
662 return
661
663
662 permissions = set(self._neededpermissions)
664 permissions = set(self._neededpermissions)
663
665
664 if 'push' in permissions and 'pull' in permissions:
666 if 'push' in permissions and 'pull' in permissions:
665 permissions.remove('pull')
667 permissions.remove('pull')
666
668
667 if len(permissions) > 1:
669 if len(permissions) > 1:
668 raise error.RepoError(_('cannot make request requiring multiple '
670 raise error.RepoError(_('cannot make request requiring multiple '
669 'permissions: %s') %
671 'permissions: %s') %
670 _(', ').join(sorted(permissions)))
672 _(', ').join(sorted(permissions)))
671
673
672 permission = {
674 permission = {
673 'push': 'rw',
675 'push': 'rw',
674 'pull': 'ro',
676 'pull': 'ro',
675 }[permissions.pop()]
677 }[permissions.pop()]
676
678
677 handler, resp = sendv2request(
679 handler, resp = sendv2request(
678 self._ui, self._opener, self._requestbuilder, self._apiurl,
680 self._ui, self._opener, self._requestbuilder, self._apiurl,
679 permission, calls, self._redirect)
681 permission, calls, self._redirect)
680
682
681 # TODO we probably want to validate the HTTP code, media type, etc.
683 # TODO we probably want to validate the HTTP code, media type, etc.
682
684
683 self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)
685 self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)
684 self._responsef = self._responseexecutor.submit(self._handleresponse,
686 self._responsef = self._responseexecutor.submit(self._handleresponse,
685 handler, resp)
687 handler, resp)
686
688
687 def close(self):
689 def close(self):
688 if self._closed:
690 if self._closed:
689 return
691 return
690
692
691 self.sendcommands()
693 self.sendcommands()
692
694
693 self._closed = True
695 self._closed = True
694
696
695 if not self._responsef:
697 if not self._responsef:
696 return
698 return
697
699
698 # TODO ^C here may not result in immediate program termination.
700 # TODO ^C here may not result in immediate program termination.
699
701
700 try:
702 try:
701 self._responsef.result()
703 self._responsef.result()
702 finally:
704 finally:
703 self._responseexecutor.shutdown(wait=True)
705 self._responseexecutor.shutdown(wait=True)
704 self._responsef = None
706 self._responsef = None
705 self._responseexecutor = None
707 self._responseexecutor = None
706
708
707 # If any of our futures are still in progress, mark them as
709 # If any of our futures are still in progress, mark them as
708 # errored, otherwise a result() could wait indefinitely.
710 # errored, otherwise a result() could wait indefinitely.
709 for f in self._futures:
711 for f in self._futures:
710 if not f.done():
712 if not f.done():
711 f.set_exception(error.ResponseError(
713 f.set_exception(error.ResponseError(
712 _('unfulfilled command response')))
714 _('unfulfilled command response')))
713
715
714 self._futures = None
716 self._futures = None
715
717
716 def _handleresponse(self, handler, resp):
718 def _handleresponse(self, handler, resp):
717 # Called in a thread to read the response.
719 # Called in a thread to read the response.
718
720
719 while handler.readdata(resp):
721 while handler.readdata(resp):
720 pass
722 pass
721
723
722 # TODO implement interface for version 2 peers
724 # TODO implement interface for version 2 peers
723 @interfaceutil.implementer(repository.ipeerconnection,
725 @interfaceutil.implementer(repository.ipeerconnection,
724 repository.ipeercapabilities,
726 repository.ipeercapabilities,
725 repository.ipeerrequests)
727 repository.ipeerrequests)
726 class httpv2peer(object):
728 class httpv2peer(object):
727 def __init__(self, ui, repourl, apipath, opener, requestbuilder,
729 def __init__(self, ui, repourl, apipath, opener, requestbuilder,
728 apidescriptor):
730 apidescriptor):
729 self.ui = ui
731 self.ui = ui
730
732
731 if repourl.endswith('/'):
733 if repourl.endswith('/'):
732 repourl = repourl[:-1]
734 repourl = repourl[:-1]
733
735
734 self._url = repourl
736 self._url = repourl
735 self._apipath = apipath
737 self._apipath = apipath
736 self._apiurl = '%s/%s' % (repourl, apipath)
738 self._apiurl = '%s/%s' % (repourl, apipath)
737 self._opener = opener
739 self._opener = opener
738 self._requestbuilder = requestbuilder
740 self._requestbuilder = requestbuilder
739 self._descriptor = apidescriptor
741 self._descriptor = apidescriptor
740
742
741 self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor)
743 self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor)
742
744
743 # Start of ipeerconnection.
745 # Start of ipeerconnection.
744
746
745 def url(self):
747 def url(self):
746 return self._url
748 return self._url
747
749
748 def local(self):
750 def local(self):
749 return None
751 return None
750
752
751 def peer(self):
753 def peer(self):
752 return self
754 return self
753
755
754 def canpush(self):
756 def canpush(self):
755 # TODO change once implemented.
757 # TODO change once implemented.
756 return False
758 return False
757
759
758 def close(self):
760 def close(self):
759 pass
761 pass
760
762
761 # End of ipeerconnection.
763 # End of ipeerconnection.
762
764
763 # Start of ipeercapabilities.
765 # Start of ipeercapabilities.
764
766
765 def capable(self, name):
767 def capable(self, name):
766 # The capabilities used internally historically map to capabilities
768 # The capabilities used internally historically map to capabilities
767 # advertised from the "capabilities" wire protocol command. However,
769 # advertised from the "capabilities" wire protocol command. However,
768 # version 2 of that command works differently.
770 # version 2 of that command works differently.
769
771
770 # Maps to commands that are available.
772 # Maps to commands that are available.
771 if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):
773 if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):
772 return True
774 return True
773
775
774 # Other concepts.
776 # Other concepts.
775 if name in ('bundle2'):
777 if name in ('bundle2'):
776 return True
778 return True
777
779
778 # Alias command-* to presence of command of that name.
780 # Alias command-* to presence of command of that name.
779 if name.startswith('command-'):
781 if name.startswith('command-'):
780 return name[len('command-'):] in self._descriptor['commands']
782 return name[len('command-'):] in self._descriptor['commands']
781
783
782 return False
784 return False
783
785
784 def requirecap(self, name, purpose):
786 def requirecap(self, name, purpose):
785 if self.capable(name):
787 if self.capable(name):
786 return
788 return
787
789
788 raise error.CapabilityError(
790 raise error.CapabilityError(
789 _('cannot %s; client or remote repository does not support the %r '
791 _('cannot %s; client or remote repository does not support the %r '
790 'capability') % (purpose, name))
792 'capability') % (purpose, name))
791
793
792 # End of ipeercapabilities.
794 # End of ipeercapabilities.
793
795
794 def _call(self, name, **args):
796 def _call(self, name, **args):
795 with self.commandexecutor() as e:
797 with self.commandexecutor() as e:
796 return e.callcommand(name, args).result()
798 return e.callcommand(name, args).result()
797
799
798 def commandexecutor(self):
800 def commandexecutor(self):
799 return httpv2executor(self.ui, self._opener, self._requestbuilder,
801 return httpv2executor(self.ui, self._opener, self._requestbuilder,
800 self._apiurl, self._descriptor, self._redirect)
802 self._apiurl, self._descriptor, self._redirect)
801
803
802 # Registry of API service names to metadata about peers that handle it.
804 # Registry of API service names to metadata about peers that handle it.
803 #
805 #
804 # The following keys are meaningful:
806 # The following keys are meaningful:
805 #
807 #
806 # init
808 # init
807 # Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
809 # Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
808 # apidescriptor) to create a peer.
810 # apidescriptor) to create a peer.
809 #
811 #
810 # priority
812 # priority
811 # Integer priority for the service. If we could choose from multiple
813 # Integer priority for the service. If we could choose from multiple
812 # services, we choose the one with the highest priority.
814 # services, we choose the one with the highest priority.
813 API_PEERS = {
815 API_PEERS = {
814 wireprototypes.HTTP_WIREPROTO_V2: {
816 wireprototypes.HTTP_WIREPROTO_V2: {
815 'init': httpv2peer,
817 'init': httpv2peer,
816 'priority': 50,
818 'priority': 50,
817 },
819 },
818 }
820 }
819
821
820 def performhandshake(ui, url, opener, requestbuilder):
822 def performhandshake(ui, url, opener, requestbuilder):
821 # The handshake is a request to the capabilities command.
823 # The handshake is a request to the capabilities command.
822
824
823 caps = None
825 caps = None
824 def capable(x):
826 def capable(x):
825 raise error.ProgrammingError('should not be called')
827 raise error.ProgrammingError('should not be called')
826
828
827 args = {}
829 args = {}
828
830
829 # The client advertises support for newer protocols by adding an
831 # The client advertises support for newer protocols by adding an
830 # X-HgUpgrade-* header with a list of supported APIs and an
832 # X-HgUpgrade-* header with a list of supported APIs and an
831 # X-HgProto-* header advertising which serializing formats it supports.
833 # X-HgProto-* header advertising which serializing formats it supports.
832 # We only support the HTTP version 2 transport and CBOR responses for
834 # We only support the HTTP version 2 transport and CBOR responses for
833 # now.
835 # now.
834 advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')
836 advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')
835
837
836 if advertisev2:
838 if advertisev2:
837 args['headers'] = {
839 args['headers'] = {
838 r'X-HgProto-1': r'cbor',
840 r'X-HgProto-1': r'cbor',
839 }
841 }
840
842
841 args['headers'].update(
843 args['headers'].update(
842 encodevalueinheaders(' '.join(sorted(API_PEERS)),
844 encodevalueinheaders(' '.join(sorted(API_PEERS)),
843 'X-HgUpgrade',
845 'X-HgUpgrade',
844 # We don't know the header limit this early.
846 # We don't know the header limit this early.
845 # So make it small.
847 # So make it small.
846 1024))
848 1024))
847
849
848 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
850 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
849 capable, url, 'capabilities',
851 capable, url, 'capabilities',
850 args)
852 args)
851 resp = sendrequest(ui, opener, req)
853 resp = sendrequest(ui, opener, req)
852
854
853 # The server may redirect us to the repo root, stripping the
855 # The server may redirect us to the repo root, stripping the
854 # ?cmd=capabilities query string from the URL. The server would likely
856 # ?cmd=capabilities query string from the URL. The server would likely
855 # return HTML in this case and ``parsev1commandresponse()`` would raise.
857 # return HTML in this case and ``parsev1commandresponse()`` would raise.
856 # We catch this special case and re-issue the capabilities request against
858 # We catch this special case and re-issue the capabilities request against
857 # the new URL.
859 # the new URL.
858 #
860 #
859 # We should ideally not do this, as a redirect that drops the query
861 # We should ideally not do this, as a redirect that drops the query
860 # string from the URL is arguably a server bug. (Garbage in, garbage out).
862 # string from the URL is arguably a server bug. (Garbage in, garbage out).
861 # However, Mercurial clients for several years appeared to handle this
863 # However, Mercurial clients for several years appeared to handle this
862 # issue without behavior degradation. And according to issue 5860, it may
864 # issue without behavior degradation. And according to issue 5860, it may
863 # be a longstanding bug in some server implementations. So we allow a
865 # be a longstanding bug in some server implementations. So we allow a
864 # redirect that drops the query string to "just work."
866 # redirect that drops the query string to "just work."
865 try:
867 try:
866 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
868 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
867 compressible=False,
869 compressible=False,
868 allowcbor=advertisev2)
870 allowcbor=advertisev2)
869 except RedirectedRepoError as e:
871 except RedirectedRepoError as e:
870 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
872 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
871 capable, e.respurl,
873 capable, e.respurl,
872 'capabilities', args)
874 'capabilities', args)
873 resp = sendrequest(ui, opener, req)
875 resp = sendrequest(ui, opener, req)
874 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
876 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
875 compressible=False,
877 compressible=False,
876 allowcbor=advertisev2)
878 allowcbor=advertisev2)
877
879
878 try:
880 try:
879 rawdata = resp.read()
881 rawdata = resp.read()
880 finally:
882 finally:
881 resp.close()
883 resp.close()
882
884
883 if not ct.startswith('application/mercurial-'):
885 if not ct.startswith('application/mercurial-'):
884 raise error.ProgrammingError('unexpected content-type: %s' % ct)
886 raise error.ProgrammingError('unexpected content-type: %s' % ct)
885
887
886 if advertisev2:
888 if advertisev2:
887 if ct == 'application/mercurial-cbor':
889 if ct == 'application/mercurial-cbor':
888 try:
890 try:
889 info = cborutil.decodeall(rawdata)[0]
891 info = cborutil.decodeall(rawdata)[0]
890 except cborutil.CBORDecodeError:
892 except cborutil.CBORDecodeError:
891 raise error.Abort(_('error decoding CBOR from remote server'),
893 raise error.Abort(_('error decoding CBOR from remote server'),
892 hint=_('try again and consider contacting '
894 hint=_('try again and consider contacting '
893 'the server operator'))
895 'the server operator'))
894
896
895 # We got a legacy response. That's fine.
897 # We got a legacy response. That's fine.
896 elif ct in ('application/mercurial-0.1', 'application/mercurial-0.2'):
898 elif ct in ('application/mercurial-0.1', 'application/mercurial-0.2'):
897 info = {
899 info = {
898 'v1capabilities': set(rawdata.split())
900 'v1capabilities': set(rawdata.split())
899 }
901 }
900
902
901 else:
903 else:
902 raise error.RepoError(
904 raise error.RepoError(
903 _('unexpected response type from server: %s') % ct)
905 _('unexpected response type from server: %s') % ct)
904 else:
906 else:
905 info = {
907 info = {
906 'v1capabilities': set(rawdata.split())
908 'v1capabilities': set(rawdata.split())
907 }
909 }
908
910
909 return respurl, info
911 return respurl, info
910
912
911 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
913 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
912 """Construct an appropriate HTTP peer instance.
914 """Construct an appropriate HTTP peer instance.
913
915
914 ``opener`` is an ``url.opener`` that should be used to establish
916 ``opener`` is an ``url.opener`` that should be used to establish
915 connections, perform HTTP requests.
917 connections, perform HTTP requests.
916
918
917 ``requestbuilder`` is the type used for constructing HTTP requests.
919 ``requestbuilder`` is the type used for constructing HTTP requests.
918 It exists as an argument so extensions can override the default.
920 It exists as an argument so extensions can override the default.
919 """
921 """
920 u = util.url(path)
922 u = util.url(path)
921 if u.query or u.fragment:
923 if u.query or u.fragment:
922 raise error.Abort(_('unsupported URL component: "%s"') %
924 raise error.Abort(_('unsupported URL component: "%s"') %
923 (u.query or u.fragment))
925 (u.query or u.fragment))
924
926
925 # urllib cannot handle URLs with embedded user or passwd.
927 # urllib cannot handle URLs with embedded user or passwd.
926 url, authinfo = u.authinfo()
928 url, authinfo = u.authinfo()
927 ui.debug('using %s\n' % url)
929 ui.debug('using %s\n' % url)
928
930
929 opener = opener or urlmod.opener(ui, authinfo)
931 opener = opener or urlmod.opener(ui, authinfo)
930
932
931 respurl, info = performhandshake(ui, url, opener, requestbuilder)
933 respurl, info = performhandshake(ui, url, opener, requestbuilder)
932
934
933 # Given the intersection of APIs that both we and the server support,
935 # Given the intersection of APIs that both we and the server support,
934 # sort by their advertised priority and pick the first one.
936 # sort by their advertised priority and pick the first one.
935 #
937 #
936 # TODO consider making this request-based and interface driven. For
938 # TODO consider making this request-based and interface driven. For
937 # example, the caller could say "I want a peer that does X." It's quite
939 # example, the caller could say "I want a peer that does X." It's quite
938 # possible that not all peers would do that. Since we know the service
940 # possible that not all peers would do that. Since we know the service
939 # capabilities, we could filter out services not meeting the
941 # capabilities, we could filter out services not meeting the
940 # requirements. Possibly by consulting the interfaces defined by the
942 # requirements. Possibly by consulting the interfaces defined by the
941 # peer type.
943 # peer type.
942 apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())
944 apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())
943
945
944 preferredchoices = sorted(apipeerchoices,
946 preferredchoices = sorted(apipeerchoices,
945 key=lambda x: API_PEERS[x]['priority'],
947 key=lambda x: API_PEERS[x]['priority'],
946 reverse=True)
948 reverse=True)
947
949
948 for service in preferredchoices:
950 for service in preferredchoices:
949 apipath = '%s/%s' % (info['apibase'].rstrip('/'), service)
951 apipath = '%s/%s' % (info['apibase'].rstrip('/'), service)
950
952
951 return API_PEERS[service]['init'](ui, respurl, apipath, opener,
953 return API_PEERS[service]['init'](ui, respurl, apipath, opener,
952 requestbuilder,
954 requestbuilder,
953 info['apis'][service])
955 info['apis'][service])
954
956
955 # Failed to construct an API peer. Fall back to legacy.
957 # Failed to construct an API peer. Fall back to legacy.
956 return httppeer(ui, path, respurl, opener, requestbuilder,
958 return httppeer(ui, path, respurl, opener, requestbuilder,
957 info['v1capabilities'])
959 info['v1capabilities'])
958
960
959 def instance(ui, path, create, intents=None, createopts=None):
961 def instance(ui, path, create, intents=None, createopts=None):
960 if create:
962 if create:
961 raise error.Abort(_('cannot create new http repository'))
963 raise error.Abort(_('cannot create new http repository'))
962 try:
964 try:
963 if path.startswith('https:') and not urlmod.has_https:
965 if path.startswith('https:') and not urlmod.has_https:
964 raise error.Abort(_('Python support for SSL and HTTPS '
966 raise error.Abort(_('Python support for SSL and HTTPS '
965 'is not installed'))
967 'is not installed'))
966
968
967 inst = makepeer(ui, path)
969 inst = makepeer(ui, path)
968
970
969 return inst
971 return inst
970 except error.RepoError as httpexception:
972 except error.RepoError as httpexception:
971 try:
973 try:
972 r = statichttprepo.instance(ui, "static-" + path, create)
974 r = statichttprepo.instance(ui, "static-" + path, create)
973 ui.note(_('(falling back to static-http)\n'))
975 ui.note(_('(falling back to static-http)\n'))
974 return r
976 return r
975 except error.RepoError:
977 except error.RepoError:
976 raise httpexception # use the original http RepoError instead
978 raise httpexception # use the original http RepoError instead
@@ -1,372 +1,498
1 # wireprotov2peer.py - client side code for wire protocol version 2
1 # wireprotov2peer.py - client side code for wire protocol version 2
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import threading
10 import threading
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 encoding,
14 encoding,
15 error,
15 error,
16 pycompat,
16 sslutil,
17 sslutil,
18 url as urlmod,
19 util,
17 wireprotoframing,
20 wireprotoframing,
21 wireprototypes,
18 )
22 )
19 from .utils import (
23 from .utils import (
20 cborutil,
24 cborutil,
21 )
25 )
22
26
23 def formatrichmessage(atoms):
27 def formatrichmessage(atoms):
24 """Format an encoded message from the framing protocol."""
28 """Format an encoded message from the framing protocol."""
25
29
26 chunks = []
30 chunks = []
27
31
28 for atom in atoms:
32 for atom in atoms:
29 msg = _(atom[b'msg'])
33 msg = _(atom[b'msg'])
30
34
31 if b'args' in atom:
35 if b'args' in atom:
32 msg = msg % tuple(atom[b'args'])
36 msg = msg % tuple(atom[b'args'])
33
37
34 chunks.append(msg)
38 chunks.append(msg)
35
39
36 return b''.join(chunks)
40 return b''.join(chunks)
37
41
38 SUPPORTED_REDIRECT_PROTOCOLS = {
42 SUPPORTED_REDIRECT_PROTOCOLS = {
39 b'http',
43 b'http',
40 b'https',
44 b'https',
41 }
45 }
42
46
43 SUPPORTED_CONTENT_HASHES = {
47 SUPPORTED_CONTENT_HASHES = {
44 b'sha1',
48 b'sha1',
45 b'sha256',
49 b'sha256',
46 }
50 }
47
51
48 def redirecttargetsupported(ui, target):
52 def redirecttargetsupported(ui, target):
49 """Determine whether a redirect target entry is supported.
53 """Determine whether a redirect target entry is supported.
50
54
51 ``target`` should come from the capabilities data structure emitted by
55 ``target`` should come from the capabilities data structure emitted by
52 the server.
56 the server.
53 """
57 """
54 if target.get(b'protocol') not in SUPPORTED_REDIRECT_PROTOCOLS:
58 if target.get(b'protocol') not in SUPPORTED_REDIRECT_PROTOCOLS:
55 ui.note(_('(remote redirect target %s uses unsupported protocol: %s)\n')
59 ui.note(_('(remote redirect target %s uses unsupported protocol: %s)\n')
56 % (target[b'name'], target.get(b'protocol', b'')))
60 % (target[b'name'], target.get(b'protocol', b'')))
57 return False
61 return False
58
62
59 if target.get(b'snirequired') and not sslutil.hassni:
63 if target.get(b'snirequired') and not sslutil.hassni:
60 ui.note(_('(redirect target %s requires SNI, which is unsupported)\n') %
64 ui.note(_('(redirect target %s requires SNI, which is unsupported)\n') %
61 target[b'name'])
65 target[b'name'])
62 return False
66 return False
63
67
64 if b'tlsversions' in target:
68 if b'tlsversions' in target:
65 tlsversions = set(target[b'tlsversions'])
69 tlsversions = set(target[b'tlsversions'])
66 supported = set()
70 supported = set()
67
71
68 for v in sslutil.supportedprotocols:
72 for v in sslutil.supportedprotocols:
69 assert v.startswith(b'tls')
73 assert v.startswith(b'tls')
70 supported.add(v[3:])
74 supported.add(v[3:])
71
75
72 if not tlsversions & supported:
76 if not tlsversions & supported:
73 ui.note(_('(remote redirect target %s requires unsupported TLS '
77 ui.note(_('(remote redirect target %s requires unsupported TLS '
74 'versions: %s)\n') % (
78 'versions: %s)\n') % (
75 target[b'name'], b', '.join(sorted(tlsversions))))
79 target[b'name'], b', '.join(sorted(tlsversions))))
76 return False
80 return False
77
81
78 ui.note(_('(remote redirect target %s is compatible)\n') % target[b'name'])
82 ui.note(_('(remote redirect target %s is compatible)\n') % target[b'name'])
79
83
80 return True
84 return True
81
85
82 def supportedredirects(ui, apidescriptor):
86 def supportedredirects(ui, apidescriptor):
83 """Resolve the "redirect" command request key given an API descriptor.
87 """Resolve the "redirect" command request key given an API descriptor.
84
88
85 Given an API descriptor returned by the server, returns a data structure
89 Given an API descriptor returned by the server, returns a data structure
86 that can be used in hte "redirect" field of command requests to advertise
90 that can be used in hte "redirect" field of command requests to advertise
87 support for compatible redirect targets.
91 support for compatible redirect targets.
88
92
89 Returns None if no redirect targets are remotely advertised or if none are
93 Returns None if no redirect targets are remotely advertised or if none are
90 supported.
94 supported.
91 """
95 """
92 if not apidescriptor or b'redirect' not in apidescriptor:
96 if not apidescriptor or b'redirect' not in apidescriptor:
93 return None
97 return None
94
98
95 targets = [t[b'name'] for t in apidescriptor[b'redirect'][b'targets']
99 targets = [t[b'name'] for t in apidescriptor[b'redirect'][b'targets']
96 if redirecttargetsupported(ui, t)]
100 if redirecttargetsupported(ui, t)]
97
101
98 hashes = [h for h in apidescriptor[b'redirect'][b'hashes']
102 hashes = [h for h in apidescriptor[b'redirect'][b'hashes']
99 if h in SUPPORTED_CONTENT_HASHES]
103 if h in SUPPORTED_CONTENT_HASHES]
100
104
101 return {
105 return {
102 b'targets': targets,
106 b'targets': targets,
103 b'hashes': hashes,
107 b'hashes': hashes,
104 }
108 }
105
109
106 class commandresponse(object):
110 class commandresponse(object):
107 """Represents the response to a command request.
111 """Represents the response to a command request.
108
112
109 Instances track the state of the command and hold its results.
113 Instances track the state of the command and hold its results.
110
114
111 An external entity is required to update the state of the object when
115 An external entity is required to update the state of the object when
112 events occur.
116 events occur.
113 """
117 """
114
118
115 def __init__(self, requestid, command):
119 def __init__(self, requestid, command, fromredirect=False):
116 self.requestid = requestid
120 self.requestid = requestid
117 self.command = command
121 self.command = command
122 self.fromredirect = fromredirect
118
123
119 # Whether all remote input related to this command has been
124 # Whether all remote input related to this command has been
120 # received.
125 # received.
121 self._inputcomplete = False
126 self._inputcomplete = False
122
127
123 # We have a lock that is acquired when important object state is
128 # We have a lock that is acquired when important object state is
124 # mutated. This is to prevent race conditions between 1 thread
129 # mutated. This is to prevent race conditions between 1 thread
125 # sending us new data and another consuming it.
130 # sending us new data and another consuming it.
126 self._lock = threading.RLock()
131 self._lock = threading.RLock()
127
132
128 # An event is set when state of the object changes. This event
133 # An event is set when state of the object changes. This event
129 # is waited on by the generator emitting objects.
134 # is waited on by the generator emitting objects.
130 self._serviceable = threading.Event()
135 self._serviceable = threading.Event()
131
136
132 self._pendingevents = []
137 self._pendingevents = []
133 self._decoder = cborutil.bufferingdecoder()
138 self._decoder = cborutil.bufferingdecoder()
134 self._seeninitial = False
139 self._seeninitial = False
140 self._redirect = None
135
141
136 def _oninputcomplete(self):
142 def _oninputcomplete(self):
137 with self._lock:
143 with self._lock:
138 self._inputcomplete = True
144 self._inputcomplete = True
139 self._serviceable.set()
145 self._serviceable.set()
140
146
141 def _onresponsedata(self, data):
147 def _onresponsedata(self, data):
142 available, readcount, wanted = self._decoder.decode(data)
148 available, readcount, wanted = self._decoder.decode(data)
143
149
144 if not available:
150 if not available:
145 return
151 return
146
152
147 with self._lock:
153 with self._lock:
148 for o in self._decoder.getavailable():
154 for o in self._decoder.getavailable():
149 if not self._seeninitial:
155 if not self._seeninitial and not self.fromredirect:
150 self._handleinitial(o)
156 self._handleinitial(o)
151 continue
157 continue
152
158
159 # We should never see an object after a content redirect,
160 # as the spec says the main status object containing the
161 # content redirect is the only object in the stream. Fail
162 # if we see a misbehaving server.
163 if self._redirect:
164 raise error.Abort(_('received unexpected response data '
165 'after content redirect; the remote is '
166 'buggy'))
167
153 self._pendingevents.append(o)
168 self._pendingevents.append(o)
154
169
155 self._serviceable.set()
170 self._serviceable.set()
156
171
157 def _handleinitial(self, o):
172 def _handleinitial(self, o):
158 self._seeninitial = True
173 self._seeninitial = True
159 if o[b'status'] == b'ok':
174 if o[b'status'] == b'ok':
160 return
175 return
161
176
162 elif o[b'status'] == b'redirect':
177 elif o[b'status'] == b'redirect':
163 raise error.Abort(_('redirect responses not yet supported'))
178 l = o[b'location']
179 self._redirect = wireprototypes.alternatelocationresponse(
180 url=l[b'url'],
181 mediatype=l[b'mediatype'],
182 size=l.get(b'size'),
183 fullhashes=l.get(b'fullhashes'),
184 fullhashseed=l.get(b'fullhashseed'),
185 serverdercerts=l.get(b'serverdercerts'),
186 servercadercerts=l.get(b'servercadercerts'))
187 return
164
188
165 atoms = [{'msg': o[b'error'][b'message']}]
189 atoms = [{'msg': o[b'error'][b'message']}]
166 if b'args' in o[b'error']:
190 if b'args' in o[b'error']:
167 atoms[0]['args'] = o[b'error'][b'args']
191 atoms[0]['args'] = o[b'error'][b'args']
168
192
169 raise error.RepoError(formatrichmessage(atoms))
193 raise error.RepoError(formatrichmessage(atoms))
170
194
171 def objects(self):
195 def objects(self):
172 """Obtained decoded objects from this response.
196 """Obtained decoded objects from this response.
173
197
174 This is a generator of data structures that were decoded from the
198 This is a generator of data structures that were decoded from the
175 command response.
199 command response.
176
200
177 Obtaining the next member of the generator may block due to waiting
201 Obtaining the next member of the generator may block due to waiting
178 on external data to become available.
202 on external data to become available.
179
203
180 If the server encountered an error in the middle of serving the data
204 If the server encountered an error in the middle of serving the data
181 or if another error occurred, an exception may be raised when
205 or if another error occurred, an exception may be raised when
182 advancing the generator.
206 advancing the generator.
183 """
207 """
184 while True:
208 while True:
185 # TODO this can infinite loop if self._inputcomplete is never
209 # TODO this can infinite loop if self._inputcomplete is never
186 # set. We likely want to tie the lifetime of this object/state
210 # set. We likely want to tie the lifetime of this object/state
187 # to that of the background thread receiving frames and updating
211 # to that of the background thread receiving frames and updating
188 # our state.
212 # our state.
189 self._serviceable.wait(1.0)
213 self._serviceable.wait(1.0)
190
214
191 with self._lock:
215 with self._lock:
192 self._serviceable.clear()
216 self._serviceable.clear()
193
217
194 # Make copies because objects could be mutated during
218 # Make copies because objects could be mutated during
195 # iteration.
219 # iteration.
196 stop = self._inputcomplete
220 stop = self._inputcomplete
197 pending = list(self._pendingevents)
221 pending = list(self._pendingevents)
198 self._pendingevents[:] = []
222 self._pendingevents[:] = []
199
223
200 for o in pending:
224 for o in pending:
201 yield o
225 yield o
202
226
203 if stop:
227 if stop:
204 break
228 break
205
229
206 class clienthandler(object):
230 class clienthandler(object):
207 """Object to handle higher-level client activities.
231 """Object to handle higher-level client activities.
208
232
209 The ``clientreactor`` is used to hold low-level state about the frame-based
233 The ``clientreactor`` is used to hold low-level state about the frame-based
210 protocol, such as which requests and streams are active. This type is used
234 protocol, such as which requests and streams are active. This type is used
211 for higher-level operations, such as reading frames from a socket, exposing
235 for higher-level operations, such as reading frames from a socket, exposing
212 and managing a higher-level primitive for representing command responses,
236 and managing a higher-level primitive for representing command responses,
213 etc. This class is what peers should probably use to bridge wire activity
237 etc. This class is what peers should probably use to bridge wire activity
214 with the higher-level peer API.
238 with the higher-level peer API.
215 """
239 """
216
240
217 def __init__(self, ui, clientreactor):
241 def __init__(self, ui, clientreactor, opener=None,
242 requestbuilder=util.urlreq.request):
218 self._ui = ui
243 self._ui = ui
219 self._reactor = clientreactor
244 self._reactor = clientreactor
220 self._requests = {}
245 self._requests = {}
221 self._futures = {}
246 self._futures = {}
222 self._responses = {}
247 self._responses = {}
248 self._redirects = []
223 self._frameseof = False
249 self._frameseof = False
250 self._opener = opener or urlmod.opener(ui)
251 self._requestbuilder = requestbuilder
224
252
225 def callcommand(self, command, args, f, redirect=None):
253 def callcommand(self, command, args, f, redirect=None):
226 """Register a request to call a command.
254 """Register a request to call a command.
227
255
228 Returns an iterable of frames that should be sent over the wire.
256 Returns an iterable of frames that should be sent over the wire.
229 """
257 """
230 request, action, meta = self._reactor.callcommand(command, args,
258 request, action, meta = self._reactor.callcommand(command, args,
231 redirect=redirect)
259 redirect=redirect)
232
260
233 if action != 'noop':
261 if action != 'noop':
234 raise error.ProgrammingError('%s not yet supported' % action)
262 raise error.ProgrammingError('%s not yet supported' % action)
235
263
236 rid = request.requestid
264 rid = request.requestid
237 self._requests[rid] = request
265 self._requests[rid] = request
238 self._futures[rid] = f
266 self._futures[rid] = f
239 # TODO we need some kind of lifetime on response instances otherwise
267 # TODO we need some kind of lifetime on response instances otherwise
240 # objects() may deadlock.
268 # objects() may deadlock.
241 self._responses[rid] = commandresponse(rid, command)
269 self._responses[rid] = commandresponse(rid, command)
242
270
243 return iter(())
271 return iter(())
244
272
245 def flushcommands(self):
273 def flushcommands(self):
246 """Flush all queued commands.
274 """Flush all queued commands.
247
275
248 Returns an iterable of frames that should be sent over the wire.
276 Returns an iterable of frames that should be sent over the wire.
249 """
277 """
250 action, meta = self._reactor.flushcommands()
278 action, meta = self._reactor.flushcommands()
251
279
252 if action != 'sendframes':
280 if action != 'sendframes':
253 raise error.ProgrammingError('%s not yet supported' % action)
281 raise error.ProgrammingError('%s not yet supported' % action)
254
282
255 return meta['framegen']
283 return meta['framegen']
256
284
257 def readdata(self, framefh):
285 def readdata(self, framefh):
258 """Attempt to read data and do work.
286 """Attempt to read data and do work.
259
287
260 Returns None if no data was read. Presumably this means we're
288 Returns None if no data was read. Presumably this means we're
261 done with all read I/O.
289 done with all read I/O.
262 """
290 """
263 if not self._frameseof:
291 if not self._frameseof:
264 frame = wireprotoframing.readframe(framefh)
292 frame = wireprotoframing.readframe(framefh)
265 if frame is None:
293 if frame is None:
266 # TODO tell reactor?
294 # TODO tell reactor?
267 self._frameseof = True
295 self._frameseof = True
268 else:
296 else:
269 self._ui.note(_('received %r\n') % frame)
297 self._ui.note(_('received %r\n') % frame)
270 self._processframe(frame)
298 self._processframe(frame)
271
299
272 if self._frameseof:
300 # Also try to read the first redirect.
301 if self._redirects:
302 if not self._processredirect(*self._redirects[0]):
303 self._redirects.pop(0)
304
305 if self._frameseof and not self._redirects:
273 return None
306 return None
274
307
275 return True
308 return True
276
309
277 def _processframe(self, frame):
310 def _processframe(self, frame):
278 """Process a single read frame."""
311 """Process a single read frame."""
279
312
280 action, meta = self._reactor.onframerecv(frame)
313 action, meta = self._reactor.onframerecv(frame)
281
314
282 if action == 'error':
315 if action == 'error':
283 e = error.RepoError(meta['message'])
316 e = error.RepoError(meta['message'])
284
317
285 if frame.requestid in self._responses:
318 if frame.requestid in self._responses:
286 self._responses[frame.requestid]._oninputcomplete()
319 self._responses[frame.requestid]._oninputcomplete()
287
320
288 if frame.requestid in self._futures:
321 if frame.requestid in self._futures:
289 self._futures[frame.requestid].set_exception(e)
322 self._futures[frame.requestid].set_exception(e)
290 del self._futures[frame.requestid]
323 del self._futures[frame.requestid]
291 else:
324 else:
292 raise e
325 raise e
293
326
294 return
327 return
295
328
296 if frame.requestid not in self._requests:
329 if frame.requestid not in self._requests:
297 raise error.ProgrammingError(
330 raise error.ProgrammingError(
298 'received frame for unknown request; this is either a bug in '
331 'received frame for unknown request; this is either a bug in '
299 'the clientreactor not screening for this or this instance was '
332 'the clientreactor not screening for this or this instance was '
300 'never told about this request: %r' % frame)
333 'never told about this request: %r' % frame)
301
334
302 response = self._responses[frame.requestid]
335 response = self._responses[frame.requestid]
303
336
304 if action == 'responsedata':
337 if action == 'responsedata':
305 # Any failures processing this frame should bubble up to the
338 # Any failures processing this frame should bubble up to the
306 # future tracking the request.
339 # future tracking the request.
307 try:
340 try:
308 self._processresponsedata(frame, meta, response)
341 self._processresponsedata(frame, meta, response)
309 except BaseException as e:
342 except BaseException as e:
310 self._futures[frame.requestid].set_exception(e)
343 self._futures[frame.requestid].set_exception(e)
311 del self._futures[frame.requestid]
344 del self._futures[frame.requestid]
312 response._oninputcomplete()
345 response._oninputcomplete()
313 else:
346 else:
314 raise error.ProgrammingError(
347 raise error.ProgrammingError(
315 'unhandled action from clientreactor: %s' % action)
348 'unhandled action from clientreactor: %s' % action)
316
349
317 def _processresponsedata(self, frame, meta, response):
350 def _processresponsedata(self, frame, meta, response):
318 # This can raise. The caller can handle it.
351 # This can raise. The caller can handle it.
319 response._onresponsedata(meta['data'])
352 response._onresponsedata(meta['data'])
320
353
354 # If we got a content redirect response, we want to fetch it and
355 # expose the data as if we received it inline. But we also want to
356 # keep our internal request accounting in order. Our strategy is to
357 # basically put meaningful response handling on pause until EOS occurs
358 # and the stream accounting is in a good state. At that point, we follow
359 # the redirect and replace the response object with its data.
360
361 redirect = response._redirect
362 handlefuture = False if redirect else True
363
321 if meta['eos']:
364 if meta['eos']:
322 response._oninputcomplete()
365 response._oninputcomplete()
323 del self._requests[frame.requestid]
366 del self._requests[frame.requestid]
324
367
368 if redirect:
369 self._followredirect(frame.requestid, redirect)
370 return
371
372 if not handlefuture:
373 return
374
325 # If the command has a decoder, we wait until all input has been
375 # If the command has a decoder, we wait until all input has been
326 # received before resolving the future. Otherwise we resolve the
376 # received before resolving the future. Otherwise we resolve the
327 # future immediately.
377 # future immediately.
328 if frame.requestid not in self._futures:
378 if frame.requestid not in self._futures:
329 return
379 return
330
380
331 if response.command not in COMMAND_DECODERS:
381 if response.command not in COMMAND_DECODERS:
332 self._futures[frame.requestid].set_result(response.objects())
382 self._futures[frame.requestid].set_result(response.objects())
333 del self._futures[frame.requestid]
383 del self._futures[frame.requestid]
334 elif response._inputcomplete:
384 elif response._inputcomplete:
335 decoded = COMMAND_DECODERS[response.command](response.objects())
385 decoded = COMMAND_DECODERS[response.command](response.objects())
336 self._futures[frame.requestid].set_result(decoded)
386 self._futures[frame.requestid].set_result(decoded)
337 del self._futures[frame.requestid]
387 del self._futures[frame.requestid]
338
388
389 def _followredirect(self, requestid, redirect):
390 """Called to initiate redirect following for a request."""
391 self._ui.note(_('(following redirect to %s)\n') % redirect.url)
392
393 # TODO handle framed responses.
394 if redirect.mediatype != b'application/mercurial-cbor':
395 raise error.Abort(_('cannot handle redirects for the %s media type')
396 % redirect.mediatype)
397
398 if redirect.fullhashes:
399 self._ui.warn(_('(support for validating hashes on content '
400 'redirects not supported)\n'))
401
402 if redirect.serverdercerts or redirect.servercadercerts:
403 self._ui.warn(_('(support for pinning server certificates on '
404 'content redirects not supported)\n'))
405
406 headers = {
407 r'Accept': redirect.mediatype,
408 }
409
410 req = self._requestbuilder(pycompat.strurl(redirect.url), None, headers)
411
412 try:
413 res = self._opener.open(req)
414 except util.urlerr.httperror as e:
415 if e.code == 401:
416 raise error.Abort(_('authorization failed'))
417 raise
418 except util.httplib.HTTPException as e:
419 self._ui.debug('http error requesting %s\n' % req.get_full_url())
420 self._ui.traceback()
421 raise IOError(None, e)
422
423 urlmod.wrapresponse(res)
424
425 # The existing response object is associated with frame data. Rather
426 # than try to normalize its state, just create a new object.
427 oldresponse = self._responses[requestid]
428 self._responses[requestid] = commandresponse(requestid,
429 oldresponse.command,
430 fromredirect=True)
431
432 self._redirects.append((requestid, res))
433
434 def _processredirect(self, rid, res):
435 """Called to continue processing a response from a redirect."""
436 response = self._responses[rid]
437
438 try:
439 data = res.read(32768)
440 response._onresponsedata(data)
441
442 # We're at end of stream.
443 if not data:
444 response._oninputcomplete()
445
446 if rid not in self._futures:
447 return
448
449 if response.command not in COMMAND_DECODERS:
450 self._futures[rid].set_result(response.objects())
451 del self._futures[rid]
452 elif response._inputcomplete:
453 decoded = COMMAND_DECODERS[response.command](response.objects())
454 self._futures[rid].set_result(decoded)
455 del self._futures[rid]
456
457 return bool(data)
458
459 except BaseException as e:
460 self._futures[rid].set_exception(e)
461 del self._futures[rid]
462 response._oninputcomplete()
463 return False
464
339 def decodebranchmap(objs):
465 def decodebranchmap(objs):
340 # Response should be a single CBOR map of branch name to array of nodes.
466 # Response should be a single CBOR map of branch name to array of nodes.
341 bm = next(objs)
467 bm = next(objs)
342
468
343 return {encoding.tolocal(k): v for k, v in bm.items()}
469 return {encoding.tolocal(k): v for k, v in bm.items()}
344
470
345 def decodeheads(objs):
471 def decodeheads(objs):
346 # Array of node bytestrings.
472 # Array of node bytestrings.
347 return next(objs)
473 return next(objs)
348
474
349 def decodeknown(objs):
475 def decodeknown(objs):
350 # Bytestring where each byte is a 0 or 1.
476 # Bytestring where each byte is a 0 or 1.
351 raw = next(objs)
477 raw = next(objs)
352
478
353 return [True if c == '1' else False for c in raw]
479 return [True if c == '1' else False for c in raw]
354
480
355 def decodelistkeys(objs):
481 def decodelistkeys(objs):
356 # Map with bytestring keys and values.
482 # Map with bytestring keys and values.
357 return next(objs)
483 return next(objs)
358
484
359 def decodelookup(objs):
485 def decodelookup(objs):
360 return next(objs)
486 return next(objs)
361
487
362 def decodepushkey(objs):
488 def decodepushkey(objs):
363 return next(objs)
489 return next(objs)
364
490
365 COMMAND_DECODERS = {
491 COMMAND_DECODERS = {
366 'branchmap': decodebranchmap,
492 'branchmap': decodebranchmap,
367 'heads': decodeheads,
493 'heads': decodeheads,
368 'known': decodeknown,
494 'known': decodeknown,
369 'listkeys': decodelistkeys,
495 'listkeys': decodelistkeys,
370 'lookup': decodelookup,
496 'lookup': decodelookup,
371 'pushkey': decodepushkey,
497 'pushkey': decodepushkey,
372 }
498 }
@@ -1,1369 +1,1394
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ cat >> $HGRCPATH << EOF
3 $ cat >> $HGRCPATH << EOF
4 > [extensions]
4 > [extensions]
5 > blackbox =
5 > blackbox =
6 > [blackbox]
6 > [blackbox]
7 > track = simplecache
7 > track = simplecache
8 > EOF
8 > EOF
9
9
10 $ hg init server
10 $ hg init server
11 $ enablehttpv2 server
11 $ enablehttpv2 server
12 $ cd server
12 $ cd server
13 $ cat >> .hg/hgrc << EOF
13 $ cat >> .hg/hgrc << EOF
14 > [extensions]
14 > [extensions]
15 > simplecache = $TESTDIR/wireprotosimplecache.py
15 > simplecache = $TESTDIR/wireprotosimplecache.py
16 > [simplecache]
16 > [simplecache]
17 > cacheapi = true
17 > cacheapi = true
18 > EOF
18 > EOF
19
19
20 $ echo a0 > a
20 $ echo a0 > a
21 $ echo b0 > b
21 $ echo b0 > b
22 $ hg -q commit -A -m 'commit 0'
22 $ hg -q commit -A -m 'commit 0'
23 $ echo a1 > a
23 $ echo a1 > a
24 $ hg commit -m 'commit 1'
24 $ hg commit -m 'commit 1'
25
25
26 $ hg --debug debugindex -m
26 $ hg --debug debugindex -m
27 rev linkrev nodeid p1 p2
27 rev linkrev nodeid p1 p2
28 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
28 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
29 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
29 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
30
30
31 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
31 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
32 $ cat hg.pid > $DAEMON_PIDS
32 $ cat hg.pid > $DAEMON_PIDS
33
33
34 $ cat > redirects.py << EOF
34 $ cat > redirects.py << EOF
35 > [
35 > [
36 > {
36 > {
37 > b'name': b'target-a',
37 > b'name': b'target-a',
38 > b'protocol': b'http',
38 > b'protocol': b'http',
39 > b'snirequired': False,
39 > b'snirequired': False,
40 > b'tlsversions': [b'1.2', b'1.3'],
40 > b'tlsversions': [b'1.2', b'1.3'],
41 > b'uris': [b'http://example.com/'],
41 > b'uris': [b'http://example.com/'],
42 > },
42 > },
43 > ]
43 > ]
44 > EOF
44 > EOF
45
45
46 Redirect targets advertised when configured
46 Redirect targets advertised when configured
47
47
48 $ sendhttpv2peerhandshake << EOF
48 $ sendhttpv2peerhandshake << EOF
49 > command capabilities
49 > command capabilities
50 > EOF
50 > EOF
51 creating http peer for wire protocol version 2
51 creating http peer for wire protocol version 2
52 s> GET /?cmd=capabilities HTTP/1.1\r\n
52 s> GET /?cmd=capabilities HTTP/1.1\r\n
53 s> Accept-Encoding: identity\r\n
53 s> Accept-Encoding: identity\r\n
54 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
54 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
55 s> x-hgproto-1: cbor\r\n
55 s> x-hgproto-1: cbor\r\n
56 s> x-hgupgrade-1: exp-http-v2-0002\r\n
56 s> x-hgupgrade-1: exp-http-v2-0002\r\n
57 s> accept: application/mercurial-0.1\r\n
57 s> accept: application/mercurial-0.1\r\n
58 s> host: $LOCALIP:$HGPORT\r\n (glob)
58 s> host: $LOCALIP:$HGPORT\r\n (glob)
59 s> user-agent: Mercurial debugwireproto\r\n
59 s> user-agent: Mercurial debugwireproto\r\n
60 s> \r\n
60 s> \r\n
61 s> makefile('rb', None)
61 s> makefile('rb', None)
62 s> HTTP/1.1 200 OK\r\n
62 s> HTTP/1.1 200 OK\r\n
63 s> Server: testing stub value\r\n
63 s> Server: testing stub value\r\n
64 s> Date: $HTTP_DATE$\r\n
64 s> Date: $HTTP_DATE$\r\n
65 s> Content-Type: application/mercurial-cbor\r\n
65 s> Content-Type: application/mercurial-cbor\r\n
66 s> Content-Length: 1970\r\n
66 s> Content-Length: 1970\r\n
67 s> \r\n
67 s> \r\n
68 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
68 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
69 (remote redirect target target-a is compatible)
69 (remote redirect target target-a is compatible)
70 sending capabilities command
70 sending capabilities command
71 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
71 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
72 s> Accept-Encoding: identity\r\n
72 s> Accept-Encoding: identity\r\n
73 s> accept: application/mercurial-exp-framing-0005\r\n
73 s> accept: application/mercurial-exp-framing-0005\r\n
74 s> content-type: application/mercurial-exp-framing-0005\r\n
74 s> content-type: application/mercurial-exp-framing-0005\r\n
75 s> content-length: 75\r\n
75 s> content-length: 75\r\n
76 s> host: $LOCALIP:$HGPORT\r\n (glob)
76 s> host: $LOCALIP:$HGPORT\r\n (glob)
77 s> user-agent: Mercurial debugwireproto\r\n
77 s> user-agent: Mercurial debugwireproto\r\n
78 s> \r\n
78 s> \r\n
79 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
79 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
80 s> makefile('rb', None)
80 s> makefile('rb', None)
81 s> HTTP/1.1 200 OK\r\n
81 s> HTTP/1.1 200 OK\r\n
82 s> Server: testing stub value\r\n
82 s> Server: testing stub value\r\n
83 s> Date: $HTTP_DATE$\r\n
83 s> Date: $HTTP_DATE$\r\n
84 s> Content-Type: application/mercurial-exp-framing-0005\r\n
84 s> Content-Type: application/mercurial-exp-framing-0005\r\n
85 s> Transfer-Encoding: chunked\r\n
85 s> Transfer-Encoding: chunked\r\n
86 s> \r\n
86 s> \r\n
87 s> 13\r\n
87 s> 13\r\n
88 s> \x0b\x00\x00\x01\x00\x02\x011
88 s> \x0b\x00\x00\x01\x00\x02\x011
89 s> \xa1FstatusBok
89 s> \xa1FstatusBok
90 s> \r\n
90 s> \r\n
91 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
91 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
92 s> 5ab\r\n
92 s> 5ab\r\n
93 s> \xa3\x05\x00\x01\x00\x02\x001
93 s> \xa3\x05\x00\x01\x00\x02\x001
94 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
94 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
95 s> \r\n
95 s> \r\n
96 received frame(size=1443; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
96 received frame(size=1443; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
97 s> 8\r\n
97 s> 8\r\n
98 s> \x00\x00\x00\x01\x00\x02\x002
98 s> \x00\x00\x00\x01\x00\x02\x002
99 s> \r\n
99 s> \r\n
100 s> 0\r\n
100 s> 0\r\n
101 s> \r\n
101 s> \r\n
102 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
102 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
103 response: gen[
103 response: gen[
104 {
104 {
105 b'commands': {
105 b'commands': {
106 b'branchmap': {
106 b'branchmap': {
107 b'args': {},
107 b'args': {},
108 b'permissions': [
108 b'permissions': [
109 b'pull'
109 b'pull'
110 ]
110 ]
111 },
111 },
112 b'capabilities': {
112 b'capabilities': {
113 b'args': {},
113 b'args': {},
114 b'permissions': [
114 b'permissions': [
115 b'pull'
115 b'pull'
116 ]
116 ]
117 },
117 },
118 b'changesetdata': {
118 b'changesetdata': {
119 b'args': {
119 b'args': {
120 b'fields': {
120 b'fields': {
121 b'default': set([]),
121 b'default': set([]),
122 b'required': False,
122 b'required': False,
123 b'type': b'set',
123 b'type': b'set',
124 b'validvalues': set([
124 b'validvalues': set([
125 b'bookmarks',
125 b'bookmarks',
126 b'parents',
126 b'parents',
127 b'phase',
127 b'phase',
128 b'revision'
128 b'revision'
129 ])
129 ])
130 },
130 },
131 b'noderange': {
131 b'noderange': {
132 b'default': None,
132 b'default': None,
133 b'required': False,
133 b'required': False,
134 b'type': b'list'
134 b'type': b'list'
135 },
135 },
136 b'nodes': {
136 b'nodes': {
137 b'default': None,
137 b'default': None,
138 b'required': False,
138 b'required': False,
139 b'type': b'list'
139 b'type': b'list'
140 },
140 },
141 b'nodesdepth': {
141 b'nodesdepth': {
142 b'default': None,
142 b'default': None,
143 b'required': False,
143 b'required': False,
144 b'type': b'int'
144 b'type': b'int'
145 }
145 }
146 },
146 },
147 b'permissions': [
147 b'permissions': [
148 b'pull'
148 b'pull'
149 ]
149 ]
150 },
150 },
151 b'filedata': {
151 b'filedata': {
152 b'args': {
152 b'args': {
153 b'fields': {
153 b'fields': {
154 b'default': set([]),
154 b'default': set([]),
155 b'required': False,
155 b'required': False,
156 b'type': b'set',
156 b'type': b'set',
157 b'validvalues': set([
157 b'validvalues': set([
158 b'parents',
158 b'parents',
159 b'revision'
159 b'revision'
160 ])
160 ])
161 },
161 },
162 b'haveparents': {
162 b'haveparents': {
163 b'default': False,
163 b'default': False,
164 b'required': False,
164 b'required': False,
165 b'type': b'bool'
165 b'type': b'bool'
166 },
166 },
167 b'nodes': {
167 b'nodes': {
168 b'required': True,
168 b'required': True,
169 b'type': b'list'
169 b'type': b'list'
170 },
170 },
171 b'path': {
171 b'path': {
172 b'required': True,
172 b'required': True,
173 b'type': b'bytes'
173 b'type': b'bytes'
174 }
174 }
175 },
175 },
176 b'permissions': [
176 b'permissions': [
177 b'pull'
177 b'pull'
178 ]
178 ]
179 },
179 },
180 b'heads': {
180 b'heads': {
181 b'args': {
181 b'args': {
182 b'publiconly': {
182 b'publiconly': {
183 b'default': False,
183 b'default': False,
184 b'required': False,
184 b'required': False,
185 b'type': b'bool'
185 b'type': b'bool'
186 }
186 }
187 },
187 },
188 b'permissions': [
188 b'permissions': [
189 b'pull'
189 b'pull'
190 ]
190 ]
191 },
191 },
192 b'known': {
192 b'known': {
193 b'args': {
193 b'args': {
194 b'nodes': {
194 b'nodes': {
195 b'default': [],
195 b'default': [],
196 b'required': False,
196 b'required': False,
197 b'type': b'list'
197 b'type': b'list'
198 }
198 }
199 },
199 },
200 b'permissions': [
200 b'permissions': [
201 b'pull'
201 b'pull'
202 ]
202 ]
203 },
203 },
204 b'listkeys': {
204 b'listkeys': {
205 b'args': {
205 b'args': {
206 b'namespace': {
206 b'namespace': {
207 b'required': True,
207 b'required': True,
208 b'type': b'bytes'
208 b'type': b'bytes'
209 }
209 }
210 },
210 },
211 b'permissions': [
211 b'permissions': [
212 b'pull'
212 b'pull'
213 ]
213 ]
214 },
214 },
215 b'lookup': {
215 b'lookup': {
216 b'args': {
216 b'args': {
217 b'key': {
217 b'key': {
218 b'required': True,
218 b'required': True,
219 b'type': b'bytes'
219 b'type': b'bytes'
220 }
220 }
221 },
221 },
222 b'permissions': [
222 b'permissions': [
223 b'pull'
223 b'pull'
224 ]
224 ]
225 },
225 },
226 b'manifestdata': {
226 b'manifestdata': {
227 b'args': {
227 b'args': {
228 b'fields': {
228 b'fields': {
229 b'default': set([]),
229 b'default': set([]),
230 b'required': False,
230 b'required': False,
231 b'type': b'set',
231 b'type': b'set',
232 b'validvalues': set([
232 b'validvalues': set([
233 b'parents',
233 b'parents',
234 b'revision'
234 b'revision'
235 ])
235 ])
236 },
236 },
237 b'haveparents': {
237 b'haveparents': {
238 b'default': False,
238 b'default': False,
239 b'required': False,
239 b'required': False,
240 b'type': b'bool'
240 b'type': b'bool'
241 },
241 },
242 b'nodes': {
242 b'nodes': {
243 b'required': True,
243 b'required': True,
244 b'type': b'list'
244 b'type': b'list'
245 },
245 },
246 b'tree': {
246 b'tree': {
247 b'required': True,
247 b'required': True,
248 b'type': b'bytes'
248 b'type': b'bytes'
249 }
249 }
250 },
250 },
251 b'permissions': [
251 b'permissions': [
252 b'pull'
252 b'pull'
253 ]
253 ]
254 },
254 },
255 b'pushkey': {
255 b'pushkey': {
256 b'args': {
256 b'args': {
257 b'key': {
257 b'key': {
258 b'required': True,
258 b'required': True,
259 b'type': b'bytes'
259 b'type': b'bytes'
260 },
260 },
261 b'namespace': {
261 b'namespace': {
262 b'required': True,
262 b'required': True,
263 b'type': b'bytes'
263 b'type': b'bytes'
264 },
264 },
265 b'new': {
265 b'new': {
266 b'required': True,
266 b'required': True,
267 b'type': b'bytes'
267 b'type': b'bytes'
268 },
268 },
269 b'old': {
269 b'old': {
270 b'required': True,
270 b'required': True,
271 b'type': b'bytes'
271 b'type': b'bytes'
272 }
272 }
273 },
273 },
274 b'permissions': [
274 b'permissions': [
275 b'push'
275 b'push'
276 ]
276 ]
277 }
277 }
278 },
278 },
279 b'compression': [
279 b'compression': [
280 {
280 {
281 b'name': b'zstd'
281 b'name': b'zstd'
282 },
282 },
283 {
283 {
284 b'name': b'zlib'
284 b'name': b'zlib'
285 }
285 }
286 ],
286 ],
287 b'framingmediatypes': [
287 b'framingmediatypes': [
288 b'application/mercurial-exp-framing-0005'
288 b'application/mercurial-exp-framing-0005'
289 ],
289 ],
290 b'pathfilterprefixes': set([
290 b'pathfilterprefixes': set([
291 b'path:',
291 b'path:',
292 b'rootfilesin:'
292 b'rootfilesin:'
293 ]),
293 ]),
294 b'rawrepoformats': [
294 b'rawrepoformats': [
295 b'generaldelta',
295 b'generaldelta',
296 b'revlogv1'
296 b'revlogv1'
297 ],
297 ],
298 b'redirect': {
298 b'redirect': {
299 b'hashes': [
299 b'hashes': [
300 b'sha256',
300 b'sha256',
301 b'sha1'
301 b'sha1'
302 ],
302 ],
303 b'targets': [
303 b'targets': [
304 {
304 {
305 b'name': b'target-a',
305 b'name': b'target-a',
306 b'protocol': b'http',
306 b'protocol': b'http',
307 b'snirequired': False,
307 b'snirequired': False,
308 b'tlsversions': [
308 b'tlsversions': [
309 b'1.2',
309 b'1.2',
310 b'1.3'
310 b'1.3'
311 ],
311 ],
312 b'uris': [
312 b'uris': [
313 b'http://example.com/'
313 b'http://example.com/'
314 ]
314 ]
315 }
315 }
316 ]
316 ]
317 }
317 }
318 }
318 }
319 ]
319 ]
320
320
321 Unknown protocol is filtered from compatible targets
321 Unknown protocol is filtered from compatible targets
322
322
323 $ cat > redirects.py << EOF
323 $ cat > redirects.py << EOF
324 > [
324 > [
325 > {
325 > {
326 > b'name': b'target-a',
326 > b'name': b'target-a',
327 > b'protocol': b'http',
327 > b'protocol': b'http',
328 > b'uris': [b'http://example.com/'],
328 > b'uris': [b'http://example.com/'],
329 > },
329 > },
330 > {
330 > {
331 > b'name': b'target-b',
331 > b'name': b'target-b',
332 > b'protocol': b'unknown',
332 > b'protocol': b'unknown',
333 > b'uris': [b'unknown://example.com/'],
333 > b'uris': [b'unknown://example.com/'],
334 > },
334 > },
335 > ]
335 > ]
336 > EOF
336 > EOF
337
337
338 $ sendhttpv2peerhandshake << EOF
338 $ sendhttpv2peerhandshake << EOF
339 > command capabilities
339 > command capabilities
340 > EOF
340 > EOF
341 creating http peer for wire protocol version 2
341 creating http peer for wire protocol version 2
342 s> GET /?cmd=capabilities HTTP/1.1\r\n
342 s> GET /?cmd=capabilities HTTP/1.1\r\n
343 s> Accept-Encoding: identity\r\n
343 s> Accept-Encoding: identity\r\n
344 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
344 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
345 s> x-hgproto-1: cbor\r\n
345 s> x-hgproto-1: cbor\r\n
346 s> x-hgupgrade-1: exp-http-v2-0002\r\n
346 s> x-hgupgrade-1: exp-http-v2-0002\r\n
347 s> accept: application/mercurial-0.1\r\n
347 s> accept: application/mercurial-0.1\r\n
348 s> host: $LOCALIP:$HGPORT\r\n (glob)
348 s> host: $LOCALIP:$HGPORT\r\n (glob)
349 s> user-agent: Mercurial debugwireproto\r\n
349 s> user-agent: Mercurial debugwireproto\r\n
350 s> \r\n
350 s> \r\n
351 s> makefile('rb', None)
351 s> makefile('rb', None)
352 s> HTTP/1.1 200 OK\r\n
352 s> HTTP/1.1 200 OK\r\n
353 s> Server: testing stub value\r\n
353 s> Server: testing stub value\r\n
354 s> Date: $HTTP_DATE$\r\n
354 s> Date: $HTTP_DATE$\r\n
355 s> Content-Type: application/mercurial-cbor\r\n
355 s> Content-Type: application/mercurial-cbor\r\n
356 s> Content-Length: 1997\r\n
356 s> Content-Length: 1997\r\n
357 s> \r\n
357 s> \r\n
358 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
358 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
359 (remote redirect target target-a is compatible)
359 (remote redirect target target-a is compatible)
360 (remote redirect target target-b uses unsupported protocol: unknown)
360 (remote redirect target target-b uses unsupported protocol: unknown)
361 sending capabilities command
361 sending capabilities command
362 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
362 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
363 s> Accept-Encoding: identity\r\n
363 s> Accept-Encoding: identity\r\n
364 s> accept: application/mercurial-exp-framing-0005\r\n
364 s> accept: application/mercurial-exp-framing-0005\r\n
365 s> content-type: application/mercurial-exp-framing-0005\r\n
365 s> content-type: application/mercurial-exp-framing-0005\r\n
366 s> content-length: 75\r\n
366 s> content-length: 75\r\n
367 s> host: $LOCALIP:$HGPORT\r\n (glob)
367 s> host: $LOCALIP:$HGPORT\r\n (glob)
368 s> user-agent: Mercurial debugwireproto\r\n
368 s> user-agent: Mercurial debugwireproto\r\n
369 s> \r\n
369 s> \r\n
370 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
370 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
371 s> makefile('rb', None)
371 s> makefile('rb', None)
372 s> HTTP/1.1 200 OK\r\n
372 s> HTTP/1.1 200 OK\r\n
373 s> Server: testing stub value\r\n
373 s> Server: testing stub value\r\n
374 s> Date: $HTTP_DATE$\r\n
374 s> Date: $HTTP_DATE$\r\n
375 s> Content-Type: application/mercurial-exp-framing-0005\r\n
375 s> Content-Type: application/mercurial-exp-framing-0005\r\n
376 s> Transfer-Encoding: chunked\r\n
376 s> Transfer-Encoding: chunked\r\n
377 s> \r\n
377 s> \r\n
378 s> 13\r\n
378 s> 13\r\n
379 s> \x0b\x00\x00\x01\x00\x02\x011
379 s> \x0b\x00\x00\x01\x00\x02\x011
380 s> \xa1FstatusBok
380 s> \xa1FstatusBok
381 s> \r\n
381 s> \r\n
382 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
382 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
383 s> 5c6\r\n
383 s> 5c6\r\n
384 s> \xbe\x05\x00\x01\x00\x02\x001
384 s> \xbe\x05\x00\x01\x00\x02\x001
385 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
385 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
386 s> \r\n
386 s> \r\n
387 received frame(size=1470; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
387 received frame(size=1470; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
388 s> 8\r\n
388 s> 8\r\n
389 s> \x00\x00\x00\x01\x00\x02\x002
389 s> \x00\x00\x00\x01\x00\x02\x002
390 s> \r\n
390 s> \r\n
391 s> 0\r\n
391 s> 0\r\n
392 s> \r\n
392 s> \r\n
393 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
393 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
394 response: gen[
394 response: gen[
395 {
395 {
396 b'commands': {
396 b'commands': {
397 b'branchmap': {
397 b'branchmap': {
398 b'args': {},
398 b'args': {},
399 b'permissions': [
399 b'permissions': [
400 b'pull'
400 b'pull'
401 ]
401 ]
402 },
402 },
403 b'capabilities': {
403 b'capabilities': {
404 b'args': {},
404 b'args': {},
405 b'permissions': [
405 b'permissions': [
406 b'pull'
406 b'pull'
407 ]
407 ]
408 },
408 },
409 b'changesetdata': {
409 b'changesetdata': {
410 b'args': {
410 b'args': {
411 b'fields': {
411 b'fields': {
412 b'default': set([]),
412 b'default': set([]),
413 b'required': False,
413 b'required': False,
414 b'type': b'set',
414 b'type': b'set',
415 b'validvalues': set([
415 b'validvalues': set([
416 b'bookmarks',
416 b'bookmarks',
417 b'parents',
417 b'parents',
418 b'phase',
418 b'phase',
419 b'revision'
419 b'revision'
420 ])
420 ])
421 },
421 },
422 b'noderange': {
422 b'noderange': {
423 b'default': None,
423 b'default': None,
424 b'required': False,
424 b'required': False,
425 b'type': b'list'
425 b'type': b'list'
426 },
426 },
427 b'nodes': {
427 b'nodes': {
428 b'default': None,
428 b'default': None,
429 b'required': False,
429 b'required': False,
430 b'type': b'list'
430 b'type': b'list'
431 },
431 },
432 b'nodesdepth': {
432 b'nodesdepth': {
433 b'default': None,
433 b'default': None,
434 b'required': False,
434 b'required': False,
435 b'type': b'int'
435 b'type': b'int'
436 }
436 }
437 },
437 },
438 b'permissions': [
438 b'permissions': [
439 b'pull'
439 b'pull'
440 ]
440 ]
441 },
441 },
442 b'filedata': {
442 b'filedata': {
443 b'args': {
443 b'args': {
444 b'fields': {
444 b'fields': {
445 b'default': set([]),
445 b'default': set([]),
446 b'required': False,
446 b'required': False,
447 b'type': b'set',
447 b'type': b'set',
448 b'validvalues': set([
448 b'validvalues': set([
449 b'parents',
449 b'parents',
450 b'revision'
450 b'revision'
451 ])
451 ])
452 },
452 },
453 b'haveparents': {
453 b'haveparents': {
454 b'default': False,
454 b'default': False,
455 b'required': False,
455 b'required': False,
456 b'type': b'bool'
456 b'type': b'bool'
457 },
457 },
458 b'nodes': {
458 b'nodes': {
459 b'required': True,
459 b'required': True,
460 b'type': b'list'
460 b'type': b'list'
461 },
461 },
462 b'path': {
462 b'path': {
463 b'required': True,
463 b'required': True,
464 b'type': b'bytes'
464 b'type': b'bytes'
465 }
465 }
466 },
466 },
467 b'permissions': [
467 b'permissions': [
468 b'pull'
468 b'pull'
469 ]
469 ]
470 },
470 },
471 b'heads': {
471 b'heads': {
472 b'args': {
472 b'args': {
473 b'publiconly': {
473 b'publiconly': {
474 b'default': False,
474 b'default': False,
475 b'required': False,
475 b'required': False,
476 b'type': b'bool'
476 b'type': b'bool'
477 }
477 }
478 },
478 },
479 b'permissions': [
479 b'permissions': [
480 b'pull'
480 b'pull'
481 ]
481 ]
482 },
482 },
483 b'known': {
483 b'known': {
484 b'args': {
484 b'args': {
485 b'nodes': {
485 b'nodes': {
486 b'default': [],
486 b'default': [],
487 b'required': False,
487 b'required': False,
488 b'type': b'list'
488 b'type': b'list'
489 }
489 }
490 },
490 },
491 b'permissions': [
491 b'permissions': [
492 b'pull'
492 b'pull'
493 ]
493 ]
494 },
494 },
495 b'listkeys': {
495 b'listkeys': {
496 b'args': {
496 b'args': {
497 b'namespace': {
497 b'namespace': {
498 b'required': True,
498 b'required': True,
499 b'type': b'bytes'
499 b'type': b'bytes'
500 }
500 }
501 },
501 },
502 b'permissions': [
502 b'permissions': [
503 b'pull'
503 b'pull'
504 ]
504 ]
505 },
505 },
506 b'lookup': {
506 b'lookup': {
507 b'args': {
507 b'args': {
508 b'key': {
508 b'key': {
509 b'required': True,
509 b'required': True,
510 b'type': b'bytes'
510 b'type': b'bytes'
511 }
511 }
512 },
512 },
513 b'permissions': [
513 b'permissions': [
514 b'pull'
514 b'pull'
515 ]
515 ]
516 },
516 },
517 b'manifestdata': {
517 b'manifestdata': {
518 b'args': {
518 b'args': {
519 b'fields': {
519 b'fields': {
520 b'default': set([]),
520 b'default': set([]),
521 b'required': False,
521 b'required': False,
522 b'type': b'set',
522 b'type': b'set',
523 b'validvalues': set([
523 b'validvalues': set([
524 b'parents',
524 b'parents',
525 b'revision'
525 b'revision'
526 ])
526 ])
527 },
527 },
528 b'haveparents': {
528 b'haveparents': {
529 b'default': False,
529 b'default': False,
530 b'required': False,
530 b'required': False,
531 b'type': b'bool'
531 b'type': b'bool'
532 },
532 },
533 b'nodes': {
533 b'nodes': {
534 b'required': True,
534 b'required': True,
535 b'type': b'list'
535 b'type': b'list'
536 },
536 },
537 b'tree': {
537 b'tree': {
538 b'required': True,
538 b'required': True,
539 b'type': b'bytes'
539 b'type': b'bytes'
540 }
540 }
541 },
541 },
542 b'permissions': [
542 b'permissions': [
543 b'pull'
543 b'pull'
544 ]
544 ]
545 },
545 },
546 b'pushkey': {
546 b'pushkey': {
547 b'args': {
547 b'args': {
548 b'key': {
548 b'key': {
549 b'required': True,
549 b'required': True,
550 b'type': b'bytes'
550 b'type': b'bytes'
551 },
551 },
552 b'namespace': {
552 b'namespace': {
553 b'required': True,
553 b'required': True,
554 b'type': b'bytes'
554 b'type': b'bytes'
555 },
555 },
556 b'new': {
556 b'new': {
557 b'required': True,
557 b'required': True,
558 b'type': b'bytes'
558 b'type': b'bytes'
559 },
559 },
560 b'old': {
560 b'old': {
561 b'required': True,
561 b'required': True,
562 b'type': b'bytes'
562 b'type': b'bytes'
563 }
563 }
564 },
564 },
565 b'permissions': [
565 b'permissions': [
566 b'push'
566 b'push'
567 ]
567 ]
568 }
568 }
569 },
569 },
570 b'compression': [
570 b'compression': [
571 {
571 {
572 b'name': b'zstd'
572 b'name': b'zstd'
573 },
573 },
574 {
574 {
575 b'name': b'zlib'
575 b'name': b'zlib'
576 }
576 }
577 ],
577 ],
578 b'framingmediatypes': [
578 b'framingmediatypes': [
579 b'application/mercurial-exp-framing-0005'
579 b'application/mercurial-exp-framing-0005'
580 ],
580 ],
581 b'pathfilterprefixes': set([
581 b'pathfilterprefixes': set([
582 b'path:',
582 b'path:',
583 b'rootfilesin:'
583 b'rootfilesin:'
584 ]),
584 ]),
585 b'rawrepoformats': [
585 b'rawrepoformats': [
586 b'generaldelta',
586 b'generaldelta',
587 b'revlogv1'
587 b'revlogv1'
588 ],
588 ],
589 b'redirect': {
589 b'redirect': {
590 b'hashes': [
590 b'hashes': [
591 b'sha256',
591 b'sha256',
592 b'sha1'
592 b'sha1'
593 ],
593 ],
594 b'targets': [
594 b'targets': [
595 {
595 {
596 b'name': b'target-a',
596 b'name': b'target-a',
597 b'protocol': b'http',
597 b'protocol': b'http',
598 b'uris': [
598 b'uris': [
599 b'http://example.com/'
599 b'http://example.com/'
600 ]
600 ]
601 },
601 },
602 {
602 {
603 b'name': b'target-b',
603 b'name': b'target-b',
604 b'protocol': b'unknown',
604 b'protocol': b'unknown',
605 b'uris': [
605 b'uris': [
606 b'unknown://example.com/'
606 b'unknown://example.com/'
607 ]
607 ]
608 }
608 }
609 ]
609 ]
610 }
610 }
611 }
611 }
612 ]
612 ]
613
613
614 Missing SNI support filters targets that require SNI
614 Missing SNI support filters targets that require SNI
615
615
616 $ cat > nosni.py << EOF
616 $ cat > nosni.py << EOF
617 > from mercurial import sslutil
617 > from mercurial import sslutil
618 > sslutil.hassni = False
618 > sslutil.hassni = False
619 > EOF
619 > EOF
620 $ cat >> $HGRCPATH << EOF
620 $ cat >> $HGRCPATH << EOF
621 > [extensions]
621 > [extensions]
622 > nosni=`pwd`/nosni.py
622 > nosni=`pwd`/nosni.py
623 > EOF
623 > EOF
624
624
625 $ cat > redirects.py << EOF
625 $ cat > redirects.py << EOF
626 > [
626 > [
627 > {
627 > {
628 > b'name': b'target-bad-tls',
628 > b'name': b'target-bad-tls',
629 > b'protocol': b'https',
629 > b'protocol': b'https',
630 > b'uris': [b'https://example.com/'],
630 > b'uris': [b'https://example.com/'],
631 > b'snirequired': True,
631 > b'snirequired': True,
632 > },
632 > },
633 > ]
633 > ]
634 > EOF
634 > EOF
635
635
636 $ sendhttpv2peerhandshake << EOF
636 $ sendhttpv2peerhandshake << EOF
637 > command capabilities
637 > command capabilities
638 > EOF
638 > EOF
639 creating http peer for wire protocol version 2
639 creating http peer for wire protocol version 2
640 s> GET /?cmd=capabilities HTTP/1.1\r\n
640 s> GET /?cmd=capabilities HTTP/1.1\r\n
641 s> Accept-Encoding: identity\r\n
641 s> Accept-Encoding: identity\r\n
642 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
642 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
643 s> x-hgproto-1: cbor\r\n
643 s> x-hgproto-1: cbor\r\n
644 s> x-hgupgrade-1: exp-http-v2-0002\r\n
644 s> x-hgupgrade-1: exp-http-v2-0002\r\n
645 s> accept: application/mercurial-0.1\r\n
645 s> accept: application/mercurial-0.1\r\n
646 s> host: $LOCALIP:$HGPORT\r\n (glob)
646 s> host: $LOCALIP:$HGPORT\r\n (glob)
647 s> user-agent: Mercurial debugwireproto\r\n
647 s> user-agent: Mercurial debugwireproto\r\n
648 s> \r\n
648 s> \r\n
649 s> makefile('rb', None)
649 s> makefile('rb', None)
650 s> HTTP/1.1 200 OK\r\n
650 s> HTTP/1.1 200 OK\r\n
651 s> Server: testing stub value\r\n
651 s> Server: testing stub value\r\n
652 s> Date: $HTTP_DATE$\r\n
652 s> Date: $HTTP_DATE$\r\n
653 s> Content-Type: application/mercurial-cbor\r\n
653 s> Content-Type: application/mercurial-cbor\r\n
654 s> Content-Length: 1957\r\n
654 s> Content-Length: 1957\r\n
655 s> \r\n
655 s> \r\n
656 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
656 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
657 (redirect target target-bad-tls requires SNI, which is unsupported)
657 (redirect target target-bad-tls requires SNI, which is unsupported)
658 sending capabilities command
658 sending capabilities command
659 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
659 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
660 s> Accept-Encoding: identity\r\n
660 s> Accept-Encoding: identity\r\n
661 s> accept: application/mercurial-exp-framing-0005\r\n
661 s> accept: application/mercurial-exp-framing-0005\r\n
662 s> content-type: application/mercurial-exp-framing-0005\r\n
662 s> content-type: application/mercurial-exp-framing-0005\r\n
663 s> content-length: 66\r\n
663 s> content-length: 66\r\n
664 s> host: $LOCALIP:$HGPORT\r\n (glob)
664 s> host: $LOCALIP:$HGPORT\r\n (glob)
665 s> user-agent: Mercurial debugwireproto\r\n
665 s> user-agent: Mercurial debugwireproto\r\n
666 s> \r\n
666 s> \r\n
667 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
667 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
668 s> makefile('rb', None)
668 s> makefile('rb', None)
669 s> HTTP/1.1 200 OK\r\n
669 s> HTTP/1.1 200 OK\r\n
670 s> Server: testing stub value\r\n
670 s> Server: testing stub value\r\n
671 s> Date: $HTTP_DATE$\r\n
671 s> Date: $HTTP_DATE$\r\n
672 s> Content-Type: application/mercurial-exp-framing-0005\r\n
672 s> Content-Type: application/mercurial-exp-framing-0005\r\n
673 s> Transfer-Encoding: chunked\r\n
673 s> Transfer-Encoding: chunked\r\n
674 s> \r\n
674 s> \r\n
675 s> 13\r\n
675 s> 13\r\n
676 s> \x0b\x00\x00\x01\x00\x02\x011
676 s> \x0b\x00\x00\x01\x00\x02\x011
677 s> \xa1FstatusBok
677 s> \xa1FstatusBok
678 s> \r\n
678 s> \r\n
679 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
679 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
680 s> 59e\r\n
680 s> 59e\r\n
681 s> \x96\x05\x00\x01\x00\x02\x001
681 s> \x96\x05\x00\x01\x00\x02\x001
682 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
682 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
683 s> \r\n
683 s> \r\n
684 received frame(size=1430; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
684 received frame(size=1430; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
685 s> 8\r\n
685 s> 8\r\n
686 s> \x00\x00\x00\x01\x00\x02\x002
686 s> \x00\x00\x00\x01\x00\x02\x002
687 s> \r\n
687 s> \r\n
688 s> 0\r\n
688 s> 0\r\n
689 s> \r\n
689 s> \r\n
690 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
690 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
691 response: gen[
691 response: gen[
692 {
692 {
693 b'commands': {
693 b'commands': {
694 b'branchmap': {
694 b'branchmap': {
695 b'args': {},
695 b'args': {},
696 b'permissions': [
696 b'permissions': [
697 b'pull'
697 b'pull'
698 ]
698 ]
699 },
699 },
700 b'capabilities': {
700 b'capabilities': {
701 b'args': {},
701 b'args': {},
702 b'permissions': [
702 b'permissions': [
703 b'pull'
703 b'pull'
704 ]
704 ]
705 },
705 },
706 b'changesetdata': {
706 b'changesetdata': {
707 b'args': {
707 b'args': {
708 b'fields': {
708 b'fields': {
709 b'default': set([]),
709 b'default': set([]),
710 b'required': False,
710 b'required': False,
711 b'type': b'set',
711 b'type': b'set',
712 b'validvalues': set([
712 b'validvalues': set([
713 b'bookmarks',
713 b'bookmarks',
714 b'parents',
714 b'parents',
715 b'phase',
715 b'phase',
716 b'revision'
716 b'revision'
717 ])
717 ])
718 },
718 },
719 b'noderange': {
719 b'noderange': {
720 b'default': None,
720 b'default': None,
721 b'required': False,
721 b'required': False,
722 b'type': b'list'
722 b'type': b'list'
723 },
723 },
724 b'nodes': {
724 b'nodes': {
725 b'default': None,
725 b'default': None,
726 b'required': False,
726 b'required': False,
727 b'type': b'list'
727 b'type': b'list'
728 },
728 },
729 b'nodesdepth': {
729 b'nodesdepth': {
730 b'default': None,
730 b'default': None,
731 b'required': False,
731 b'required': False,
732 b'type': b'int'
732 b'type': b'int'
733 }
733 }
734 },
734 },
735 b'permissions': [
735 b'permissions': [
736 b'pull'
736 b'pull'
737 ]
737 ]
738 },
738 },
739 b'filedata': {
739 b'filedata': {
740 b'args': {
740 b'args': {
741 b'fields': {
741 b'fields': {
742 b'default': set([]),
742 b'default': set([]),
743 b'required': False,
743 b'required': False,
744 b'type': b'set',
744 b'type': b'set',
745 b'validvalues': set([
745 b'validvalues': set([
746 b'parents',
746 b'parents',
747 b'revision'
747 b'revision'
748 ])
748 ])
749 },
749 },
750 b'haveparents': {
750 b'haveparents': {
751 b'default': False,
751 b'default': False,
752 b'required': False,
752 b'required': False,
753 b'type': b'bool'
753 b'type': b'bool'
754 },
754 },
755 b'nodes': {
755 b'nodes': {
756 b'required': True,
756 b'required': True,
757 b'type': b'list'
757 b'type': b'list'
758 },
758 },
759 b'path': {
759 b'path': {
760 b'required': True,
760 b'required': True,
761 b'type': b'bytes'
761 b'type': b'bytes'
762 }
762 }
763 },
763 },
764 b'permissions': [
764 b'permissions': [
765 b'pull'
765 b'pull'
766 ]
766 ]
767 },
767 },
768 b'heads': {
768 b'heads': {
769 b'args': {
769 b'args': {
770 b'publiconly': {
770 b'publiconly': {
771 b'default': False,
771 b'default': False,
772 b'required': False,
772 b'required': False,
773 b'type': b'bool'
773 b'type': b'bool'
774 }
774 }
775 },
775 },
776 b'permissions': [
776 b'permissions': [
777 b'pull'
777 b'pull'
778 ]
778 ]
779 },
779 },
780 b'known': {
780 b'known': {
781 b'args': {
781 b'args': {
782 b'nodes': {
782 b'nodes': {
783 b'default': [],
783 b'default': [],
784 b'required': False,
784 b'required': False,
785 b'type': b'list'
785 b'type': b'list'
786 }
786 }
787 },
787 },
788 b'permissions': [
788 b'permissions': [
789 b'pull'
789 b'pull'
790 ]
790 ]
791 },
791 },
792 b'listkeys': {
792 b'listkeys': {
793 b'args': {
793 b'args': {
794 b'namespace': {
794 b'namespace': {
795 b'required': True,
795 b'required': True,
796 b'type': b'bytes'
796 b'type': b'bytes'
797 }
797 }
798 },
798 },
799 b'permissions': [
799 b'permissions': [
800 b'pull'
800 b'pull'
801 ]
801 ]
802 },
802 },
803 b'lookup': {
803 b'lookup': {
804 b'args': {
804 b'args': {
805 b'key': {
805 b'key': {
806 b'required': True,
806 b'required': True,
807 b'type': b'bytes'
807 b'type': b'bytes'
808 }
808 }
809 },
809 },
810 b'permissions': [
810 b'permissions': [
811 b'pull'
811 b'pull'
812 ]
812 ]
813 },
813 },
814 b'manifestdata': {
814 b'manifestdata': {
815 b'args': {
815 b'args': {
816 b'fields': {
816 b'fields': {
817 b'default': set([]),
817 b'default': set([]),
818 b'required': False,
818 b'required': False,
819 b'type': b'set',
819 b'type': b'set',
820 b'validvalues': set([
820 b'validvalues': set([
821 b'parents',
821 b'parents',
822 b'revision'
822 b'revision'
823 ])
823 ])
824 },
824 },
825 b'haveparents': {
825 b'haveparents': {
826 b'default': False,
826 b'default': False,
827 b'required': False,
827 b'required': False,
828 b'type': b'bool'
828 b'type': b'bool'
829 },
829 },
830 b'nodes': {
830 b'nodes': {
831 b'required': True,
831 b'required': True,
832 b'type': b'list'
832 b'type': b'list'
833 },
833 },
834 b'tree': {
834 b'tree': {
835 b'required': True,
835 b'required': True,
836 b'type': b'bytes'
836 b'type': b'bytes'
837 }
837 }
838 },
838 },
839 b'permissions': [
839 b'permissions': [
840 b'pull'
840 b'pull'
841 ]
841 ]
842 },
842 },
843 b'pushkey': {
843 b'pushkey': {
844 b'args': {
844 b'args': {
845 b'key': {
845 b'key': {
846 b'required': True,
846 b'required': True,
847 b'type': b'bytes'
847 b'type': b'bytes'
848 },
848 },
849 b'namespace': {
849 b'namespace': {
850 b'required': True,
850 b'required': True,
851 b'type': b'bytes'
851 b'type': b'bytes'
852 },
852 },
853 b'new': {
853 b'new': {
854 b'required': True,
854 b'required': True,
855 b'type': b'bytes'
855 b'type': b'bytes'
856 },
856 },
857 b'old': {
857 b'old': {
858 b'required': True,
858 b'required': True,
859 b'type': b'bytes'
859 b'type': b'bytes'
860 }
860 }
861 },
861 },
862 b'permissions': [
862 b'permissions': [
863 b'push'
863 b'push'
864 ]
864 ]
865 }
865 }
866 },
866 },
867 b'compression': [
867 b'compression': [
868 {
868 {
869 b'name': b'zstd'
869 b'name': b'zstd'
870 },
870 },
871 {
871 {
872 b'name': b'zlib'
872 b'name': b'zlib'
873 }
873 }
874 ],
874 ],
875 b'framingmediatypes': [
875 b'framingmediatypes': [
876 b'application/mercurial-exp-framing-0005'
876 b'application/mercurial-exp-framing-0005'
877 ],
877 ],
878 b'pathfilterprefixes': set([
878 b'pathfilterprefixes': set([
879 b'path:',
879 b'path:',
880 b'rootfilesin:'
880 b'rootfilesin:'
881 ]),
881 ]),
882 b'rawrepoformats': [
882 b'rawrepoformats': [
883 b'generaldelta',
883 b'generaldelta',
884 b'revlogv1'
884 b'revlogv1'
885 ],
885 ],
886 b'redirect': {
886 b'redirect': {
887 b'hashes': [
887 b'hashes': [
888 b'sha256',
888 b'sha256',
889 b'sha1'
889 b'sha1'
890 ],
890 ],
891 b'targets': [
891 b'targets': [
892 {
892 {
893 b'name': b'target-bad-tls',
893 b'name': b'target-bad-tls',
894 b'protocol': b'https',
894 b'protocol': b'https',
895 b'snirequired': True,
895 b'snirequired': True,
896 b'uris': [
896 b'uris': [
897 b'https://example.com/'
897 b'https://example.com/'
898 ]
898 ]
899 }
899 }
900 ]
900 ]
901 }
901 }
902 }
902 }
903 ]
903 ]
904
904
905 $ cat >> $HGRCPATH << EOF
905 $ cat >> $HGRCPATH << EOF
906 > [extensions]
906 > [extensions]
907 > nosni=!
907 > nosni=!
908 > EOF
908 > EOF
909
909
910 Unknown tls value is filtered from compatible targets
910 Unknown tls value is filtered from compatible targets
911
911
912 $ cat > redirects.py << EOF
912 $ cat > redirects.py << EOF
913 > [
913 > [
914 > {
914 > {
915 > b'name': b'target-bad-tls',
915 > b'name': b'target-bad-tls',
916 > b'protocol': b'https',
916 > b'protocol': b'https',
917 > b'uris': [b'https://example.com/'],
917 > b'uris': [b'https://example.com/'],
918 > b'tlsversions': [b'42', b'39'],
918 > b'tlsversions': [b'42', b'39'],
919 > },
919 > },
920 > ]
920 > ]
921 > EOF
921 > EOF
922
922
923 $ sendhttpv2peerhandshake << EOF
923 $ sendhttpv2peerhandshake << EOF
924 > command capabilities
924 > command capabilities
925 > EOF
925 > EOF
926 creating http peer for wire protocol version 2
926 creating http peer for wire protocol version 2
927 s> GET /?cmd=capabilities HTTP/1.1\r\n
927 s> GET /?cmd=capabilities HTTP/1.1\r\n
928 s> Accept-Encoding: identity\r\n
928 s> Accept-Encoding: identity\r\n
929 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
929 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
930 s> x-hgproto-1: cbor\r\n
930 s> x-hgproto-1: cbor\r\n
931 s> x-hgupgrade-1: exp-http-v2-0002\r\n
931 s> x-hgupgrade-1: exp-http-v2-0002\r\n
932 s> accept: application/mercurial-0.1\r\n
932 s> accept: application/mercurial-0.1\r\n
933 s> host: $LOCALIP:$HGPORT\r\n (glob)
933 s> host: $LOCALIP:$HGPORT\r\n (glob)
934 s> user-agent: Mercurial debugwireproto\r\n
934 s> user-agent: Mercurial debugwireproto\r\n
935 s> \r\n
935 s> \r\n
936 s> makefile('rb', None)
936 s> makefile('rb', None)
937 s> HTTP/1.1 200 OK\r\n
937 s> HTTP/1.1 200 OK\r\n
938 s> Server: testing stub value\r\n
938 s> Server: testing stub value\r\n
939 s> Date: $HTTP_DATE$\r\n
939 s> Date: $HTTP_DATE$\r\n
940 s> Content-Type: application/mercurial-cbor\r\n
940 s> Content-Type: application/mercurial-cbor\r\n
941 s> Content-Length: 1963\r\n
941 s> Content-Length: 1963\r\n
942 s> \r\n
942 s> \r\n
943 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
943 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd8batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
944 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
944 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
945 sending capabilities command
945 sending capabilities command
946 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
946 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
947 s> Accept-Encoding: identity\r\n
947 s> Accept-Encoding: identity\r\n
948 s> accept: application/mercurial-exp-framing-0005\r\n
948 s> accept: application/mercurial-exp-framing-0005\r\n
949 s> content-type: application/mercurial-exp-framing-0005\r\n
949 s> content-type: application/mercurial-exp-framing-0005\r\n
950 s> content-length: 66\r\n
950 s> content-length: 66\r\n
951 s> host: $LOCALIP:$HGPORT\r\n (glob)
951 s> host: $LOCALIP:$HGPORT\r\n (glob)
952 s> user-agent: Mercurial debugwireproto\r\n
952 s> user-agent: Mercurial debugwireproto\r\n
953 s> \r\n
953 s> \r\n
954 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
954 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
955 s> makefile('rb', None)
955 s> makefile('rb', None)
956 s> HTTP/1.1 200 OK\r\n
956 s> HTTP/1.1 200 OK\r\n
957 s> Server: testing stub value\r\n
957 s> Server: testing stub value\r\n
958 s> Date: $HTTP_DATE$\r\n
958 s> Date: $HTTP_DATE$\r\n
959 s> Content-Type: application/mercurial-exp-framing-0005\r\n
959 s> Content-Type: application/mercurial-exp-framing-0005\r\n
960 s> Transfer-Encoding: chunked\r\n
960 s> Transfer-Encoding: chunked\r\n
961 s> \r\n
961 s> \r\n
962 s> 13\r\n
962 s> 13\r\n
963 s> \x0b\x00\x00\x01\x00\x02\x011
963 s> \x0b\x00\x00\x01\x00\x02\x011
964 s> \xa1FstatusBok
964 s> \xa1FstatusBok
965 s> \r\n
965 s> \r\n
966 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
966 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
967 s> 5a4\r\n
967 s> 5a4\r\n
968 s> \x9c\x05\x00\x01\x00\x02\x001
968 s> \x9c\x05\x00\x01\x00\x02\x001
969 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
969 s> \xa6Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
970 s> \r\n
970 s> \r\n
971 received frame(size=1436; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
971 received frame(size=1436; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
972 s> 8\r\n
972 s> 8\r\n
973 s> \x00\x00\x00\x01\x00\x02\x002
973 s> \x00\x00\x00\x01\x00\x02\x002
974 s> \r\n
974 s> \r\n
975 s> 0\r\n
975 s> 0\r\n
976 s> \r\n
976 s> \r\n
977 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
977 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
978 response: gen[
978 response: gen[
979 {
979 {
980 b'commands': {
980 b'commands': {
981 b'branchmap': {
981 b'branchmap': {
982 b'args': {},
982 b'args': {},
983 b'permissions': [
983 b'permissions': [
984 b'pull'
984 b'pull'
985 ]
985 ]
986 },
986 },
987 b'capabilities': {
987 b'capabilities': {
988 b'args': {},
988 b'args': {},
989 b'permissions': [
989 b'permissions': [
990 b'pull'
990 b'pull'
991 ]
991 ]
992 },
992 },
993 b'changesetdata': {
993 b'changesetdata': {
994 b'args': {
994 b'args': {
995 b'fields': {
995 b'fields': {
996 b'default': set([]),
996 b'default': set([]),
997 b'required': False,
997 b'required': False,
998 b'type': b'set',
998 b'type': b'set',
999 b'validvalues': set([
999 b'validvalues': set([
1000 b'bookmarks',
1000 b'bookmarks',
1001 b'parents',
1001 b'parents',
1002 b'phase',
1002 b'phase',
1003 b'revision'
1003 b'revision'
1004 ])
1004 ])
1005 },
1005 },
1006 b'noderange': {
1006 b'noderange': {
1007 b'default': None,
1007 b'default': None,
1008 b'required': False,
1008 b'required': False,
1009 b'type': b'list'
1009 b'type': b'list'
1010 },
1010 },
1011 b'nodes': {
1011 b'nodes': {
1012 b'default': None,
1012 b'default': None,
1013 b'required': False,
1013 b'required': False,
1014 b'type': b'list'
1014 b'type': b'list'
1015 },
1015 },
1016 b'nodesdepth': {
1016 b'nodesdepth': {
1017 b'default': None,
1017 b'default': None,
1018 b'required': False,
1018 b'required': False,
1019 b'type': b'int'
1019 b'type': b'int'
1020 }
1020 }
1021 },
1021 },
1022 b'permissions': [
1022 b'permissions': [
1023 b'pull'
1023 b'pull'
1024 ]
1024 ]
1025 },
1025 },
1026 b'filedata': {
1026 b'filedata': {
1027 b'args': {
1027 b'args': {
1028 b'fields': {
1028 b'fields': {
1029 b'default': set([]),
1029 b'default': set([]),
1030 b'required': False,
1030 b'required': False,
1031 b'type': b'set',
1031 b'type': b'set',
1032 b'validvalues': set([
1032 b'validvalues': set([
1033 b'parents',
1033 b'parents',
1034 b'revision'
1034 b'revision'
1035 ])
1035 ])
1036 },
1036 },
1037 b'haveparents': {
1037 b'haveparents': {
1038 b'default': False,
1038 b'default': False,
1039 b'required': False,
1039 b'required': False,
1040 b'type': b'bool'
1040 b'type': b'bool'
1041 },
1041 },
1042 b'nodes': {
1042 b'nodes': {
1043 b'required': True,
1043 b'required': True,
1044 b'type': b'list'
1044 b'type': b'list'
1045 },
1045 },
1046 b'path': {
1046 b'path': {
1047 b'required': True,
1047 b'required': True,
1048 b'type': b'bytes'
1048 b'type': b'bytes'
1049 }
1049 }
1050 },
1050 },
1051 b'permissions': [
1051 b'permissions': [
1052 b'pull'
1052 b'pull'
1053 ]
1053 ]
1054 },
1054 },
1055 b'heads': {
1055 b'heads': {
1056 b'args': {
1056 b'args': {
1057 b'publiconly': {
1057 b'publiconly': {
1058 b'default': False,
1058 b'default': False,
1059 b'required': False,
1059 b'required': False,
1060 b'type': b'bool'
1060 b'type': b'bool'
1061 }
1061 }
1062 },
1062 },
1063 b'permissions': [
1063 b'permissions': [
1064 b'pull'
1064 b'pull'
1065 ]
1065 ]
1066 },
1066 },
1067 b'known': {
1067 b'known': {
1068 b'args': {
1068 b'args': {
1069 b'nodes': {
1069 b'nodes': {
1070 b'default': [],
1070 b'default': [],
1071 b'required': False,
1071 b'required': False,
1072 b'type': b'list'
1072 b'type': b'list'
1073 }
1073 }
1074 },
1074 },
1075 b'permissions': [
1075 b'permissions': [
1076 b'pull'
1076 b'pull'
1077 ]
1077 ]
1078 },
1078 },
1079 b'listkeys': {
1079 b'listkeys': {
1080 b'args': {
1080 b'args': {
1081 b'namespace': {
1081 b'namespace': {
1082 b'required': True,
1082 b'required': True,
1083 b'type': b'bytes'
1083 b'type': b'bytes'
1084 }
1084 }
1085 },
1085 },
1086 b'permissions': [
1086 b'permissions': [
1087 b'pull'
1087 b'pull'
1088 ]
1088 ]
1089 },
1089 },
1090 b'lookup': {
1090 b'lookup': {
1091 b'args': {
1091 b'args': {
1092 b'key': {
1092 b'key': {
1093 b'required': True,
1093 b'required': True,
1094 b'type': b'bytes'
1094 b'type': b'bytes'
1095 }
1095 }
1096 },
1096 },
1097 b'permissions': [
1097 b'permissions': [
1098 b'pull'
1098 b'pull'
1099 ]
1099 ]
1100 },
1100 },
1101 b'manifestdata': {
1101 b'manifestdata': {
1102 b'args': {
1102 b'args': {
1103 b'fields': {
1103 b'fields': {
1104 b'default': set([]),
1104 b'default': set([]),
1105 b'required': False,
1105 b'required': False,
1106 b'type': b'set',
1106 b'type': b'set',
1107 b'validvalues': set([
1107 b'validvalues': set([
1108 b'parents',
1108 b'parents',
1109 b'revision'
1109 b'revision'
1110 ])
1110 ])
1111 },
1111 },
1112 b'haveparents': {
1112 b'haveparents': {
1113 b'default': False,
1113 b'default': False,
1114 b'required': False,
1114 b'required': False,
1115 b'type': b'bool'
1115 b'type': b'bool'
1116 },
1116 },
1117 b'nodes': {
1117 b'nodes': {
1118 b'required': True,
1118 b'required': True,
1119 b'type': b'list'
1119 b'type': b'list'
1120 },
1120 },
1121 b'tree': {
1121 b'tree': {
1122 b'required': True,
1122 b'required': True,
1123 b'type': b'bytes'
1123 b'type': b'bytes'
1124 }
1124 }
1125 },
1125 },
1126 b'permissions': [
1126 b'permissions': [
1127 b'pull'
1127 b'pull'
1128 ]
1128 ]
1129 },
1129 },
1130 b'pushkey': {
1130 b'pushkey': {
1131 b'args': {
1131 b'args': {
1132 b'key': {
1132 b'key': {
1133 b'required': True,
1133 b'required': True,
1134 b'type': b'bytes'
1134 b'type': b'bytes'
1135 },
1135 },
1136 b'namespace': {
1136 b'namespace': {
1137 b'required': True,
1137 b'required': True,
1138 b'type': b'bytes'
1138 b'type': b'bytes'
1139 },
1139 },
1140 b'new': {
1140 b'new': {
1141 b'required': True,
1141 b'required': True,
1142 b'type': b'bytes'
1142 b'type': b'bytes'
1143 },
1143 },
1144 b'old': {
1144 b'old': {
1145 b'required': True,
1145 b'required': True,
1146 b'type': b'bytes'
1146 b'type': b'bytes'
1147 }
1147 }
1148 },
1148 },
1149 b'permissions': [
1149 b'permissions': [
1150 b'push'
1150 b'push'
1151 ]
1151 ]
1152 }
1152 }
1153 },
1153 },
1154 b'compression': [
1154 b'compression': [
1155 {
1155 {
1156 b'name': b'zstd'
1156 b'name': b'zstd'
1157 },
1157 },
1158 {
1158 {
1159 b'name': b'zlib'
1159 b'name': b'zlib'
1160 }
1160 }
1161 ],
1161 ],
1162 b'framingmediatypes': [
1162 b'framingmediatypes': [
1163 b'application/mercurial-exp-framing-0005'
1163 b'application/mercurial-exp-framing-0005'
1164 ],
1164 ],
1165 b'pathfilterprefixes': set([
1165 b'pathfilterprefixes': set([
1166 b'path:',
1166 b'path:',
1167 b'rootfilesin:'
1167 b'rootfilesin:'
1168 ]),
1168 ]),
1169 b'rawrepoformats': [
1169 b'rawrepoformats': [
1170 b'generaldelta',
1170 b'generaldelta',
1171 b'revlogv1'
1171 b'revlogv1'
1172 ],
1172 ],
1173 b'redirect': {
1173 b'redirect': {
1174 b'hashes': [
1174 b'hashes': [
1175 b'sha256',
1175 b'sha256',
1176 b'sha1'
1176 b'sha1'
1177 ],
1177 ],
1178 b'targets': [
1178 b'targets': [
1179 {
1179 {
1180 b'name': b'target-bad-tls',
1180 b'name': b'target-bad-tls',
1181 b'protocol': b'https',
1181 b'protocol': b'https',
1182 b'tlsversions': [
1182 b'tlsversions': [
1183 b'42',
1183 b'42',
1184 b'39'
1184 b'39'
1185 ],
1185 ],
1186 b'uris': [
1186 b'uris': [
1187 b'https://example.com/'
1187 b'https://example.com/'
1188 ]
1188 ]
1189 }
1189 }
1190 ]
1190 ]
1191 }
1191 }
1192 }
1192 }
1193 ]
1193 ]
1194
1194
1195 Set up the server to issue content redirects to its built-in API server.
1195 Set up the server to issue content redirects to its built-in API server.
1196
1196
1197 $ cat > redirects.py << EOF
1197 $ cat > redirects.py << EOF
1198 > [
1198 > [
1199 > {
1199 > {
1200 > b'name': b'local',
1200 > b'name': b'local',
1201 > b'protocol': b'http',
1201 > b'protocol': b'http',
1202 > b'uris': [b'http://example.com/'],
1202 > b'uris': [b'http://example.com/'],
1203 > },
1203 > },
1204 > ]
1204 > ]
1205 > EOF
1205 > EOF
1206
1206
1207 Request to eventual cache URL should return 404 (validating the cache server works)
1207 Request to eventual cache URL should return 404 (validating the cache server works)
1208
1208
1209 $ sendhttpraw << EOF
1209 $ sendhttpraw << EOF
1210 > httprequest GET api/simplecache/missingkey
1210 > httprequest GET api/simplecache/missingkey
1211 > user-agent: test
1211 > user-agent: test
1212 > EOF
1212 > EOF
1213 using raw connection to peer
1213 using raw connection to peer
1214 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1214 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1215 s> Accept-Encoding: identity\r\n
1215 s> Accept-Encoding: identity\r\n
1216 s> user-agent: test\r\n
1216 s> user-agent: test\r\n
1217 s> host: $LOCALIP:$HGPORT\r\n (glob)
1217 s> host: $LOCALIP:$HGPORT\r\n (glob)
1218 s> \r\n
1218 s> \r\n
1219 s> makefile('rb', None)
1219 s> makefile('rb', None)
1220 s> HTTP/1.1 404 Not Found\r\n
1220 s> HTTP/1.1 404 Not Found\r\n
1221 s> Server: testing stub value\r\n
1221 s> Server: testing stub value\r\n
1222 s> Date: $HTTP_DATE$\r\n
1222 s> Date: $HTTP_DATE$\r\n
1223 s> Content-Type: text/plain\r\n
1223 s> Content-Type: text/plain\r\n
1224 s> Content-Length: 22\r\n
1224 s> Content-Length: 22\r\n
1225 s> \r\n
1225 s> \r\n
1226 s> key not found in cache
1226 s> key not found in cache
1227
1227
1228 Send a cacheable request
1228 Send a cacheable request
1229
1229
1230 $ sendhttpv2peer << EOF
1230 $ sendhttpv2peer << EOF
1231 > command manifestdata
1231 > command manifestdata
1232 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1232 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1233 > tree eval:b''
1233 > tree eval:b''
1234 > fields eval:[b'parents']
1234 > fields eval:[b'parents']
1235 > EOF
1235 > EOF
1236 creating http peer for wire protocol version 2
1236 creating http peer for wire protocol version 2
1237 sending manifestdata command
1237 sending manifestdata command
1238 s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
1238 s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
1239 s> Accept-Encoding: identity\r\n
1239 s> Accept-Encoding: identity\r\n
1240 s> accept: application/mercurial-exp-framing-0005\r\n
1240 s> accept: application/mercurial-exp-framing-0005\r\n
1241 s> content-type: application/mercurial-exp-framing-0005\r\n
1241 s> content-type: application/mercurial-exp-framing-0005\r\n
1242 s> content-length: 128\r\n
1242 s> content-length: 128\r\n
1243 s> host: $LOCALIP:$HGPORT\r\n (glob)
1243 s> host: $LOCALIP:$HGPORT\r\n (glob)
1244 s> user-agent: Mercurial debugwireproto\r\n
1244 s> user-agent: Mercurial debugwireproto\r\n
1245 s> \r\n
1245 s> \r\n
1246 s> x\x00\x00\x01\x00\x01\x01\x11\xa3Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdataHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Elocal
1246 s> x\x00\x00\x01\x00\x01\x01\x11\xa3Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdataHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Elocal
1247 s> makefile('rb', None)
1247 s> makefile('rb', None)
1248 s> HTTP/1.1 200 OK\r\n
1248 s> HTTP/1.1 200 OK\r\n
1249 s> Server: testing stub value\r\n
1249 s> Server: testing stub value\r\n
1250 s> Date: $HTTP_DATE$\r\n
1250 s> Date: $HTTP_DATE$\r\n
1251 s> Content-Type: application/mercurial-exp-framing-0005\r\n
1251 s> Content-Type: application/mercurial-exp-framing-0005\r\n
1252 s> Transfer-Encoding: chunked\r\n
1252 s> Transfer-Encoding: chunked\r\n
1253 s> \r\n
1253 s> \r\n
1254 s> 13\r\n
1254 s> 13\r\n
1255 s> \x0b\x00\x00\x01\x00\x02\x011
1255 s> \x0b\x00\x00\x01\x00\x02\x011
1256 s> \xa1FstatusBok
1256 s> \xa1FstatusBok
1257 s> \r\n
1257 s> \r\n
1258 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
1258 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
1259 s> 63\r\n
1259 s> 63\r\n
1260 s> [\x00\x00\x01\x00\x02\x001
1260 s> [\x00\x00\x01\x00\x02\x001
1261 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1261 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1262 s> \r\n
1262 s> \r\n
1263 received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
1263 received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
1264 s> 8\r\n
1264 s> 8\r\n
1265 s> \x00\x00\x00\x01\x00\x02\x002
1265 s> \x00\x00\x00\x01\x00\x02\x002
1266 s> \r\n
1266 s> \r\n
1267 s> 0\r\n
1267 s> 0\r\n
1268 s> \r\n
1268 s> \r\n
1269 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
1269 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
1270 response: gen[
1270 response: gen[
1271 {
1271 {
1272 b'totalitems': 1
1272 b'totalitems': 1
1273 },
1273 },
1274 {
1274 {
1275 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1275 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1276 b'parents': [
1276 b'parents': [
1277 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1277 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1278 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1278 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1279 ]
1279 ]
1280 }
1280 }
1281 ]
1281 ]
1282
1282
1283 Cached entry should be available on server
1283 Cached entry should be available on server
1284
1284
1285 $ sendhttpraw << EOF
1285 $ sendhttpraw << EOF
1286 > httprequest GET api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0
1286 > httprequest GET api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0
1287 > user-agent: test
1287 > user-agent: test
1288 > EOF
1288 > EOF
1289 using raw connection to peer
1289 using raw connection to peer
1290 s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n
1290 s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n
1291 s> Accept-Encoding: identity\r\n
1291 s> Accept-Encoding: identity\r\n
1292 s> user-agent: test\r\n
1292 s> user-agent: test\r\n
1293 s> host: $LOCALIP:$HGPORT\r\n (glob)
1293 s> host: $LOCALIP:$HGPORT\r\n (glob)
1294 s> \r\n
1294 s> \r\n
1295 s> makefile('rb', None)
1295 s> makefile('rb', None)
1296 s> HTTP/1.1 200 OK\r\n
1296 s> HTTP/1.1 200 OK\r\n
1297 s> Server: testing stub value\r\n
1297 s> Server: testing stub value\r\n
1298 s> Date: $HTTP_DATE$\r\n
1298 s> Date: $HTTP_DATE$\r\n
1299 s> Content-Type: application/mercurial-cbor\r\n
1299 s> Content-Type: application/mercurial-cbor\r\n
1300 s> Content-Length: 91\r\n
1300 s> Content-Length: 91\r\n
1301 s> \r\n
1301 s> \r\n
1302 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1302 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1303 cbor> [
1303 cbor> [
1304 {
1304 {
1305 b'totalitems': 1
1305 b'totalitems': 1
1306 },
1306 },
1307 {
1307 {
1308 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1308 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1309 b'parents': [
1309 b'parents': [
1310 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1310 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1311 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1311 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1312 ]
1312 ]
1313 }
1313 }
1314 ]
1314 ]
1315
1315
1316 2nd request should result in content redirect response
1316 2nd request should result in content redirect response
1317
1317
1318 $ sendhttpv2peer << EOF
1318 $ sendhttpv2peer << EOF
1319 > command manifestdata
1319 > command manifestdata
1320 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1320 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1321 > tree eval:b''
1321 > tree eval:b''
1322 > fields eval:[b'parents']
1322 > fields eval:[b'parents']
1323 > EOF
1323 > EOF
1324 creating http peer for wire protocol version 2
1324 creating http peer for wire protocol version 2
1325 sending manifestdata command
1325 sending manifestdata command
1326 s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
1326 s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
1327 s> Accept-Encoding: identity\r\n
1327 s> Accept-Encoding: identity\r\n
1328 s> accept: application/mercurial-exp-framing-0005\r\n
1328 s> accept: application/mercurial-exp-framing-0005\r\n
1329 s> content-type: application/mercurial-exp-framing-0005\r\n
1329 s> content-type: application/mercurial-exp-framing-0005\r\n
1330 s> content-length: 128\r\n
1330 s> content-length: 128\r\n
1331 s> host: $LOCALIP:$HGPORT\r\n (glob)
1331 s> host: $LOCALIP:$HGPORT\r\n (glob)
1332 s> user-agent: Mercurial debugwireproto\r\n
1332 s> user-agent: Mercurial debugwireproto\r\n
1333 s> \r\n
1333 s> \r\n
1334 s> x\x00\x00\x01\x00\x01\x01\x11\xa3Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdataHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Elocal
1334 s> x\x00\x00\x01\x00\x01\x01\x11\xa3Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdataHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Elocal
1335 s> makefile('rb', None)
1335 s> makefile('rb', None)
1336 s> HTTP/1.1 200 OK\r\n
1336 s> HTTP/1.1 200 OK\r\n
1337 s> Server: testing stub value\r\n
1337 s> Server: testing stub value\r\n
1338 s> Date: $HTTP_DATE$\r\n
1338 s> Date: $HTTP_DATE$\r\n
1339 s> Content-Type: application/mercurial-exp-framing-0005\r\n
1339 s> Content-Type: application/mercurial-exp-framing-0005\r\n
1340 s> Transfer-Encoding: chunked\r\n
1340 s> Transfer-Encoding: chunked\r\n
1341 s> \r\n
1341 s> \r\n
1342 s> *\r\n (glob)
1342 s> *\r\n (glob)
1343 s> \x*\x00\x00\x01\x00\x02\x011 (glob)
1343 s> \x*\x00\x00\x01\x00\x02\x011 (glob)
1344 s> \xa2Hlocation\xa2ImediatypeX\x1aapplication/mercurial-cborCurl*http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0FstatusHredirect (glob)
1344 s> \xa2Hlocation\xa2ImediatypeX\x1aapplication/mercurial-cborCurl*http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0FstatusHredirect (glob)
1345 s> \r\n
1345 s> \r\n
1346 received frame(size=*; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) (glob)
1346 received frame(size=*; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) (glob)
1347 s> 8\r\n
1347 s> 8\r\n
1348 s> \x00\x00\x00\x01\x00\x02\x001
1348 s> \x00\x00\x00\x01\x00\x02\x001
1349 s> \r\n
1349 s> \r\n
1350 s> 8\r\n
1350 s> 8\r\n
1351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
1351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
1352 s> \x00\x00\x00\x01\x00\x02\x002
1352 s> \x00\x00\x00\x01\x00\x02\x002
1353 s> \r\n
1353 s> \r\n
1354 s> 0\r\n
1354 s> 0\r\n
1355 s> \r\n
1355 s> \r\n
1356 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
1356 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
1357 abort: redirect responses not yet supported
1357 (following redirect to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0) (glob)
1358 [255]
1358 s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n
1359 s> Accept-Encoding: identity\r\n
1360 s> accept: application/mercurial-cbor\r\n
1361 s> host: *:$HGPORT\r\n (glob)
1362 s> user-agent: Mercurial debugwireproto\r\n
1363 s> \r\n
1364 s> makefile('rb', None)
1365 s> HTTP/1.1 200 OK\r\n
1366 s> Server: testing stub value\r\n
1367 s> Date: $HTTP_DATE$\r\n
1368 s> Content-Type: application/mercurial-cbor\r\n
1369 s> Content-Length: 91\r\n
1370 s> \r\n
1371 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1372 response: gen[
1373 {
1374 b'totalitems': 1
1375 },
1376 {
1377 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1378 b'parents': [
1379 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1380 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1381 ]
1382 }
1383 ]
1359
1384
1360 $ cat error.log
1385 $ cat error.log
1361 $ killdaemons.py
1386 $ killdaemons.py
1362
1387
1363 $ cat .hg/blackbox.log
1388 $ cat .hg/blackbox.log
1364 *> cacher constructed for manifestdata (glob)
1389 *> cacher constructed for manifestdata (glob)
1365 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1390 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1366 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1391 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1367 *> cacher constructed for manifestdata (glob)
1392 *> cacher constructed for manifestdata (glob)
1368 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1393 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1369 *> sending content redirect for c045a581599d58608efd3d93d8129841f2af04a0 to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1394 *> sending content redirect for c045a581599d58608efd3d93d8129841f2af04a0 to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 (glob)
General Comments 0
You need to be logged in to leave comments. Login now