##// END OF EJS Templates
wireprotov2: send content encoded frames from server...
Gregory Szorc -
r40173:b5bf3dd6 default
parent child Browse files
Show More
@@ -1,1744 +1,1855 b''
1 # wireprotoframing.py - unified framing protocol for wire protocol
1 # wireprotoframing.py - unified framing protocol for wire protocol
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 # This file contains functionality to support the unified frame-based wire
8 # This file contains functionality to support the unified frame-based wire
9 # protocol. For details about the protocol, see
9 # protocol. For details about the protocol, see
10 # `hg help internals.wireprotocol`.
10 # `hg help internals.wireprotocol`.
11
11
12 from __future__ import absolute_import
12 from __future__ import absolute_import
13
13
14 import collections
14 import collections
15 import struct
15 import struct
16
16
17 from .i18n import _
17 from .i18n import _
18 from .thirdparty import (
18 from .thirdparty import (
19 attr,
19 attr,
20 )
20 )
21 from . import (
21 from . import (
22 encoding,
22 encoding,
23 error,
23 error,
24 pycompat,
24 pycompat,
25 util,
25 util,
26 wireprototypes,
26 wireprototypes,
27 )
27 )
28 from .utils import (
28 from .utils import (
29 cborutil,
29 cborutil,
30 stringutil,
30 stringutil,
31 )
31 )
32
32
33 FRAME_HEADER_SIZE = 8
33 FRAME_HEADER_SIZE = 8
34 DEFAULT_MAX_FRAME_SIZE = 32768
34 DEFAULT_MAX_FRAME_SIZE = 32768
35
35
36 STREAM_FLAG_BEGIN_STREAM = 0x01
36 STREAM_FLAG_BEGIN_STREAM = 0x01
37 STREAM_FLAG_END_STREAM = 0x02
37 STREAM_FLAG_END_STREAM = 0x02
38 STREAM_FLAG_ENCODING_APPLIED = 0x04
38 STREAM_FLAG_ENCODING_APPLIED = 0x04
39
39
40 STREAM_FLAGS = {
40 STREAM_FLAGS = {
41 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
41 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
42 b'stream-end': STREAM_FLAG_END_STREAM,
42 b'stream-end': STREAM_FLAG_END_STREAM,
43 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
43 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
44 }
44 }
45
45
46 FRAME_TYPE_COMMAND_REQUEST = 0x01
46 FRAME_TYPE_COMMAND_REQUEST = 0x01
47 FRAME_TYPE_COMMAND_DATA = 0x02
47 FRAME_TYPE_COMMAND_DATA = 0x02
48 FRAME_TYPE_COMMAND_RESPONSE = 0x03
48 FRAME_TYPE_COMMAND_RESPONSE = 0x03
49 FRAME_TYPE_ERROR_RESPONSE = 0x05
49 FRAME_TYPE_ERROR_RESPONSE = 0x05
50 FRAME_TYPE_TEXT_OUTPUT = 0x06
50 FRAME_TYPE_TEXT_OUTPUT = 0x06
51 FRAME_TYPE_PROGRESS = 0x07
51 FRAME_TYPE_PROGRESS = 0x07
52 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS = 0x08
52 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS = 0x08
53 FRAME_TYPE_STREAM_SETTINGS = 0x09
53 FRAME_TYPE_STREAM_SETTINGS = 0x09
54
54
55 FRAME_TYPES = {
55 FRAME_TYPES = {
56 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
56 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
57 b'command-data': FRAME_TYPE_COMMAND_DATA,
57 b'command-data': FRAME_TYPE_COMMAND_DATA,
58 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
58 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
59 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
59 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
60 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
60 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
61 b'progress': FRAME_TYPE_PROGRESS,
61 b'progress': FRAME_TYPE_PROGRESS,
62 b'sender-protocol-settings': FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
62 b'sender-protocol-settings': FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
63 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
63 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
64 }
64 }
65
65
66 FLAG_COMMAND_REQUEST_NEW = 0x01
66 FLAG_COMMAND_REQUEST_NEW = 0x01
67 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
67 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
68 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
68 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
69 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
69 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
70
70
71 FLAGS_COMMAND_REQUEST = {
71 FLAGS_COMMAND_REQUEST = {
72 b'new': FLAG_COMMAND_REQUEST_NEW,
72 b'new': FLAG_COMMAND_REQUEST_NEW,
73 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
73 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
74 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
74 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
75 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
75 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
76 }
76 }
77
77
78 FLAG_COMMAND_DATA_CONTINUATION = 0x01
78 FLAG_COMMAND_DATA_CONTINUATION = 0x01
79 FLAG_COMMAND_DATA_EOS = 0x02
79 FLAG_COMMAND_DATA_EOS = 0x02
80
80
81 FLAGS_COMMAND_DATA = {
81 FLAGS_COMMAND_DATA = {
82 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
82 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
83 b'eos': FLAG_COMMAND_DATA_EOS,
83 b'eos': FLAG_COMMAND_DATA_EOS,
84 }
84 }
85
85
86 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
86 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
87 FLAG_COMMAND_RESPONSE_EOS = 0x02
87 FLAG_COMMAND_RESPONSE_EOS = 0x02
88
88
89 FLAGS_COMMAND_RESPONSE = {
89 FLAGS_COMMAND_RESPONSE = {
90 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
90 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
91 b'eos': FLAG_COMMAND_RESPONSE_EOS,
91 b'eos': FLAG_COMMAND_RESPONSE_EOS,
92 }
92 }
93
93
94 FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION = 0x01
94 FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION = 0x01
95 FLAG_SENDER_PROTOCOL_SETTINGS_EOS = 0x02
95 FLAG_SENDER_PROTOCOL_SETTINGS_EOS = 0x02
96
96
97 FLAGS_SENDER_PROTOCOL_SETTINGS = {
97 FLAGS_SENDER_PROTOCOL_SETTINGS = {
98 b'continuation': FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION,
98 b'continuation': FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION,
99 b'eos': FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
99 b'eos': FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
100 }
100 }
101
101
102 FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION = 0x01
102 FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION = 0x01
103 FLAG_STREAM_ENCODING_SETTINGS_EOS = 0x02
103 FLAG_STREAM_ENCODING_SETTINGS_EOS = 0x02
104
104
105 FLAGS_STREAM_ENCODING_SETTINGS = {
105 FLAGS_STREAM_ENCODING_SETTINGS = {
106 b'continuation': FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION,
106 b'continuation': FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION,
107 b'eos': FLAG_STREAM_ENCODING_SETTINGS_EOS,
107 b'eos': FLAG_STREAM_ENCODING_SETTINGS_EOS,
108 }
108 }
109
109
110 # Maps frame types to their available flags.
110 # Maps frame types to their available flags.
111 FRAME_TYPE_FLAGS = {
111 FRAME_TYPE_FLAGS = {
112 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
112 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
113 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
113 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
114 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
114 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
115 FRAME_TYPE_ERROR_RESPONSE: {},
115 FRAME_TYPE_ERROR_RESPONSE: {},
116 FRAME_TYPE_TEXT_OUTPUT: {},
116 FRAME_TYPE_TEXT_OUTPUT: {},
117 FRAME_TYPE_PROGRESS: {},
117 FRAME_TYPE_PROGRESS: {},
118 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS: FLAGS_SENDER_PROTOCOL_SETTINGS,
118 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS: FLAGS_SENDER_PROTOCOL_SETTINGS,
119 FRAME_TYPE_STREAM_SETTINGS: FLAGS_STREAM_ENCODING_SETTINGS,
119 FRAME_TYPE_STREAM_SETTINGS: FLAGS_STREAM_ENCODING_SETTINGS,
120 }
120 }
121
121
122 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
122 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
123
123
124 def humanflags(mapping, value):
124 def humanflags(mapping, value):
125 """Convert a numeric flags value to a human value, using a mapping table."""
125 """Convert a numeric flags value to a human value, using a mapping table."""
126 namemap = {v: k for k, v in mapping.iteritems()}
126 namemap = {v: k for k, v in mapping.iteritems()}
127 flags = []
127 flags = []
128 val = 1
128 val = 1
129 while value >= val:
129 while value >= val:
130 if value & val:
130 if value & val:
131 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
131 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
132 val <<= 1
132 val <<= 1
133
133
134 return b'|'.join(flags)
134 return b'|'.join(flags)
135
135
136 @attr.s(slots=True)
136 @attr.s(slots=True)
137 class frameheader(object):
137 class frameheader(object):
138 """Represents the data in a frame header."""
138 """Represents the data in a frame header."""
139
139
140 length = attr.ib()
140 length = attr.ib()
141 requestid = attr.ib()
141 requestid = attr.ib()
142 streamid = attr.ib()
142 streamid = attr.ib()
143 streamflags = attr.ib()
143 streamflags = attr.ib()
144 typeid = attr.ib()
144 typeid = attr.ib()
145 flags = attr.ib()
145 flags = attr.ib()
146
146
147 @attr.s(slots=True, repr=False)
147 @attr.s(slots=True, repr=False)
148 class frame(object):
148 class frame(object):
149 """Represents a parsed frame."""
149 """Represents a parsed frame."""
150
150
151 requestid = attr.ib()
151 requestid = attr.ib()
152 streamid = attr.ib()
152 streamid = attr.ib()
153 streamflags = attr.ib()
153 streamflags = attr.ib()
154 typeid = attr.ib()
154 typeid = attr.ib()
155 flags = attr.ib()
155 flags = attr.ib()
156 payload = attr.ib()
156 payload = attr.ib()
157
157
158 @encoding.strmethod
158 @encoding.strmethod
159 def __repr__(self):
159 def __repr__(self):
160 typename = '<unknown 0x%02x>' % self.typeid
160 typename = '<unknown 0x%02x>' % self.typeid
161 for name, value in FRAME_TYPES.iteritems():
161 for name, value in FRAME_TYPES.iteritems():
162 if value == self.typeid:
162 if value == self.typeid:
163 typename = name
163 typename = name
164 break
164 break
165
165
166 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
166 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
167 'type=%s; flags=%s)' % (
167 'type=%s; flags=%s)' % (
168 len(self.payload), self.requestid, self.streamid,
168 len(self.payload), self.requestid, self.streamid,
169 humanflags(STREAM_FLAGS, self.streamflags), typename,
169 humanflags(STREAM_FLAGS, self.streamflags), typename,
170 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
170 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
171
171
172 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
172 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
173 """Assemble a frame into a byte array."""
173 """Assemble a frame into a byte array."""
174 # TODO assert size of payload.
174 # TODO assert size of payload.
175 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
175 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
176
176
177 # 24 bits length
177 # 24 bits length
178 # 16 bits request id
178 # 16 bits request id
179 # 8 bits stream id
179 # 8 bits stream id
180 # 8 bits stream flags
180 # 8 bits stream flags
181 # 4 bits type
181 # 4 bits type
182 # 4 bits flags
182 # 4 bits flags
183
183
184 l = struct.pack(r'<I', len(payload))
184 l = struct.pack(r'<I', len(payload))
185 frame[0:3] = l[0:3]
185 frame[0:3] = l[0:3]
186 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
186 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
187 frame[7] = (typeid << 4) | flags
187 frame[7] = (typeid << 4) | flags
188 frame[8:] = payload
188 frame[8:] = payload
189
189
190 return frame
190 return frame
191
191
192 def makeframefromhumanstring(s):
192 def makeframefromhumanstring(s):
193 """Create a frame from a human readable string
193 """Create a frame from a human readable string
194
194
195 Strings have the form:
195 Strings have the form:
196
196
197 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
197 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
198
198
199 This can be used by user-facing applications and tests for creating
199 This can be used by user-facing applications and tests for creating
200 frames easily without having to type out a bunch of constants.
200 frames easily without having to type out a bunch of constants.
201
201
202 Request ID and stream IDs are integers.
202 Request ID and stream IDs are integers.
203
203
204 Stream flags, frame type, and flags can be specified by integer or
204 Stream flags, frame type, and flags can be specified by integer or
205 named constant.
205 named constant.
206
206
207 Flags can be delimited by `|` to bitwise OR them together.
207 Flags can be delimited by `|` to bitwise OR them together.
208
208
209 If the payload begins with ``cbor:``, the following string will be
209 If the payload begins with ``cbor:``, the following string will be
210 evaluated as Python literal and the resulting object will be fed into
210 evaluated as Python literal and the resulting object will be fed into
211 a CBOR encoder. Otherwise, the payload is interpreted as a Python
211 a CBOR encoder. Otherwise, the payload is interpreted as a Python
212 byte string literal.
212 byte string literal.
213 """
213 """
214 fields = s.split(b' ', 5)
214 fields = s.split(b' ', 5)
215 requestid, streamid, streamflags, frametype, frameflags, payload = fields
215 requestid, streamid, streamflags, frametype, frameflags, payload = fields
216
216
217 requestid = int(requestid)
217 requestid = int(requestid)
218 streamid = int(streamid)
218 streamid = int(streamid)
219
219
220 finalstreamflags = 0
220 finalstreamflags = 0
221 for flag in streamflags.split(b'|'):
221 for flag in streamflags.split(b'|'):
222 if flag in STREAM_FLAGS:
222 if flag in STREAM_FLAGS:
223 finalstreamflags |= STREAM_FLAGS[flag]
223 finalstreamflags |= STREAM_FLAGS[flag]
224 else:
224 else:
225 finalstreamflags |= int(flag)
225 finalstreamflags |= int(flag)
226
226
227 if frametype in FRAME_TYPES:
227 if frametype in FRAME_TYPES:
228 frametype = FRAME_TYPES[frametype]
228 frametype = FRAME_TYPES[frametype]
229 else:
229 else:
230 frametype = int(frametype)
230 frametype = int(frametype)
231
231
232 finalflags = 0
232 finalflags = 0
233 validflags = FRAME_TYPE_FLAGS[frametype]
233 validflags = FRAME_TYPE_FLAGS[frametype]
234 for flag in frameflags.split(b'|'):
234 for flag in frameflags.split(b'|'):
235 if flag in validflags:
235 if flag in validflags:
236 finalflags |= validflags[flag]
236 finalflags |= validflags[flag]
237 else:
237 else:
238 finalflags |= int(flag)
238 finalflags |= int(flag)
239
239
240 if payload.startswith(b'cbor:'):
240 if payload.startswith(b'cbor:'):
241 payload = b''.join(cborutil.streamencode(
241 payload = b''.join(cborutil.streamencode(
242 stringutil.evalpythonliteral(payload[5:])))
242 stringutil.evalpythonliteral(payload[5:])))
243
243
244 else:
244 else:
245 payload = stringutil.unescapestr(payload)
245 payload = stringutil.unescapestr(payload)
246
246
247 return makeframe(requestid=requestid, streamid=streamid,
247 return makeframe(requestid=requestid, streamid=streamid,
248 streamflags=finalstreamflags, typeid=frametype,
248 streamflags=finalstreamflags, typeid=frametype,
249 flags=finalflags, payload=payload)
249 flags=finalflags, payload=payload)
250
250
251 def parseheader(data):
251 def parseheader(data):
252 """Parse a unified framing protocol frame header from a buffer.
252 """Parse a unified framing protocol frame header from a buffer.
253
253
254 The header is expected to be in the buffer at offset 0 and the
254 The header is expected to be in the buffer at offset 0 and the
255 buffer is expected to be large enough to hold a full header.
255 buffer is expected to be large enough to hold a full header.
256 """
256 """
257 # 24 bits payload length (little endian)
257 # 24 bits payload length (little endian)
258 # 16 bits request ID
258 # 16 bits request ID
259 # 8 bits stream ID
259 # 8 bits stream ID
260 # 8 bits stream flags
260 # 8 bits stream flags
261 # 4 bits frame type
261 # 4 bits frame type
262 # 4 bits frame flags
262 # 4 bits frame flags
263 # ... payload
263 # ... payload
264 framelength = data[0] + 256 * data[1] + 16384 * data[2]
264 framelength = data[0] + 256 * data[1] + 16384 * data[2]
265 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
265 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
266 typeflags = data[7]
266 typeflags = data[7]
267
267
268 frametype = (typeflags & 0xf0) >> 4
268 frametype = (typeflags & 0xf0) >> 4
269 frameflags = typeflags & 0x0f
269 frameflags = typeflags & 0x0f
270
270
271 return frameheader(framelength, requestid, streamid, streamflags,
271 return frameheader(framelength, requestid, streamid, streamflags,
272 frametype, frameflags)
272 frametype, frameflags)
273
273
274 def readframe(fh):
274 def readframe(fh):
275 """Read a unified framing protocol frame from a file object.
275 """Read a unified framing protocol frame from a file object.
276
276
277 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
277 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
278 None if no frame is available. May raise if a malformed frame is
278 None if no frame is available. May raise if a malformed frame is
279 seen.
279 seen.
280 """
280 """
281 header = bytearray(FRAME_HEADER_SIZE)
281 header = bytearray(FRAME_HEADER_SIZE)
282
282
283 readcount = fh.readinto(header)
283 readcount = fh.readinto(header)
284
284
285 if readcount == 0:
285 if readcount == 0:
286 return None
286 return None
287
287
288 if readcount != FRAME_HEADER_SIZE:
288 if readcount != FRAME_HEADER_SIZE:
289 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
289 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
290 (readcount, header))
290 (readcount, header))
291
291
292 h = parseheader(header)
292 h = parseheader(header)
293
293
294 payload = fh.read(h.length)
294 payload = fh.read(h.length)
295 if len(payload) != h.length:
295 if len(payload) != h.length:
296 raise error.Abort(_('frame length error: expected %d; got %d') %
296 raise error.Abort(_('frame length error: expected %d; got %d') %
297 (h.length, len(payload)))
297 (h.length, len(payload)))
298
298
299 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
299 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
300 payload)
300 payload)
301
301
302 def createcommandframes(stream, requestid, cmd, args, datafh=None,
302 def createcommandframes(stream, requestid, cmd, args, datafh=None,
303 maxframesize=DEFAULT_MAX_FRAME_SIZE,
303 maxframesize=DEFAULT_MAX_FRAME_SIZE,
304 redirect=None):
304 redirect=None):
305 """Create frames necessary to transmit a request to run a command.
305 """Create frames necessary to transmit a request to run a command.
306
306
307 This is a generator of bytearrays. Each item represents a frame
307 This is a generator of bytearrays. Each item represents a frame
308 ready to be sent over the wire to a peer.
308 ready to be sent over the wire to a peer.
309 """
309 """
310 data = {b'name': cmd}
310 data = {b'name': cmd}
311 if args:
311 if args:
312 data[b'args'] = args
312 data[b'args'] = args
313
313
314 if redirect:
314 if redirect:
315 data[b'redirect'] = redirect
315 data[b'redirect'] = redirect
316
316
317 data = b''.join(cborutil.streamencode(data))
317 data = b''.join(cborutil.streamencode(data))
318
318
319 offset = 0
319 offset = 0
320
320
321 while True:
321 while True:
322 flags = 0
322 flags = 0
323
323
324 # Must set new or continuation flag.
324 # Must set new or continuation flag.
325 if not offset:
325 if not offset:
326 flags |= FLAG_COMMAND_REQUEST_NEW
326 flags |= FLAG_COMMAND_REQUEST_NEW
327 else:
327 else:
328 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
328 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
329
329
330 # Data frames is set on all frames.
330 # Data frames is set on all frames.
331 if datafh:
331 if datafh:
332 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
332 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
333
333
334 payload = data[offset:offset + maxframesize]
334 payload = data[offset:offset + maxframesize]
335 offset += len(payload)
335 offset += len(payload)
336
336
337 if len(payload) == maxframesize and offset < len(data):
337 if len(payload) == maxframesize and offset < len(data):
338 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
338 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
339
339
340 yield stream.makeframe(requestid=requestid,
340 yield stream.makeframe(requestid=requestid,
341 typeid=FRAME_TYPE_COMMAND_REQUEST,
341 typeid=FRAME_TYPE_COMMAND_REQUEST,
342 flags=flags,
342 flags=flags,
343 payload=payload)
343 payload=payload)
344
344
345 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
345 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
346 break
346 break
347
347
348 if datafh:
348 if datafh:
349 while True:
349 while True:
350 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
350 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
351
351
352 done = False
352 done = False
353 if len(data) == DEFAULT_MAX_FRAME_SIZE:
353 if len(data) == DEFAULT_MAX_FRAME_SIZE:
354 flags = FLAG_COMMAND_DATA_CONTINUATION
354 flags = FLAG_COMMAND_DATA_CONTINUATION
355 else:
355 else:
356 flags = FLAG_COMMAND_DATA_EOS
356 flags = FLAG_COMMAND_DATA_EOS
357 assert datafh.read(1) == b''
357 assert datafh.read(1) == b''
358 done = True
358 done = True
359
359
360 yield stream.makeframe(requestid=requestid,
360 yield stream.makeframe(requestid=requestid,
361 typeid=FRAME_TYPE_COMMAND_DATA,
361 typeid=FRAME_TYPE_COMMAND_DATA,
362 flags=flags,
362 flags=flags,
363 payload=data)
363 payload=data)
364
364
365 if done:
365 if done:
366 break
366 break
367
367
368 def createcommandresponseokframe(stream, requestid):
368 def createcommandresponseokframe(stream, requestid):
369 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
369 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
370
370
371 if stream.streamsettingssent:
372 overall = stream.encode(overall)
373 encoded = True
374
375 if not overall:
376 return None
377 else:
378 encoded = False
379
371 return stream.makeframe(requestid=requestid,
380 return stream.makeframe(requestid=requestid,
372 typeid=FRAME_TYPE_COMMAND_RESPONSE,
381 typeid=FRAME_TYPE_COMMAND_RESPONSE,
373 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
382 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
374 payload=overall)
383 payload=overall,
384 encoded=encoded)
375
385
376 def createcommandresponseeosframe(stream, requestid):
386 def createcommandresponseeosframes(stream, requestid,
387 maxframesize=DEFAULT_MAX_FRAME_SIZE):
377 """Create an empty payload frame representing command end-of-stream."""
388 """Create an empty payload frame representing command end-of-stream."""
378 return stream.makeframe(requestid=requestid,
389 payload = stream.flush()
379 typeid=FRAME_TYPE_COMMAND_RESPONSE,
390
380 flags=FLAG_COMMAND_RESPONSE_EOS,
391 offset = 0
381 payload=b'')
392 while True:
393 chunk = payload[offset:offset + maxframesize]
394 offset += len(chunk)
395
396 done = offset == len(payload)
397
398 if done:
399 flags = FLAG_COMMAND_RESPONSE_EOS
400 else:
401 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
402
403 yield stream.makeframe(requestid=requestid,
404 typeid=FRAME_TYPE_COMMAND_RESPONSE,
405 flags=flags,
406 payload=chunk,
407 encoded=payload != b'')
408
409 if done:
410 break
382
411
383 def createalternatelocationresponseframe(stream, requestid, location):
412 def createalternatelocationresponseframe(stream, requestid, location):
384 data = {
413 data = {
385 b'status': b'redirect',
414 b'status': b'redirect',
386 b'location': {
415 b'location': {
387 b'url': location.url,
416 b'url': location.url,
388 b'mediatype': location.mediatype,
417 b'mediatype': location.mediatype,
389 }
418 }
390 }
419 }
391
420
392 for a in (r'size', r'fullhashes', r'fullhashseed', r'serverdercerts',
421 for a in (r'size', r'fullhashes', r'fullhashseed', r'serverdercerts',
393 r'servercadercerts'):
422 r'servercadercerts'):
394 value = getattr(location, a)
423 value = getattr(location, a)
395 if value is not None:
424 if value is not None:
396 data[b'location'][pycompat.bytestr(a)] = value
425 data[b'location'][pycompat.bytestr(a)] = value
397
426
427 payload = b''.join(cborutil.streamencode(data))
428
429 if stream.streamsettingssent:
430 payload = stream.encode(payload)
431 encoded = True
432 else:
433 encoded = False
434
398 return stream.makeframe(requestid=requestid,
435 return stream.makeframe(requestid=requestid,
399 typeid=FRAME_TYPE_COMMAND_RESPONSE,
436 typeid=FRAME_TYPE_COMMAND_RESPONSE,
400 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
437 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
401 payload=b''.join(cborutil.streamencode(data)))
438 payload=payload,
439 encoded=encoded)
402
440
403 def createcommanderrorresponse(stream, requestid, message, args=None):
441 def createcommanderrorresponse(stream, requestid, message, args=None):
404 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
442 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
405 # formatting works consistently?
443 # formatting works consistently?
406 m = {
444 m = {
407 b'status': b'error',
445 b'status': b'error',
408 b'error': {
446 b'error': {
409 b'message': message,
447 b'message': message,
410 }
448 }
411 }
449 }
412
450
413 if args:
451 if args:
414 m[b'error'][b'args'] = args
452 m[b'error'][b'args'] = args
415
453
416 overall = b''.join(cborutil.streamencode(m))
454 overall = b''.join(cborutil.streamencode(m))
417
455
418 yield stream.makeframe(requestid=requestid,
456 yield stream.makeframe(requestid=requestid,
419 typeid=FRAME_TYPE_COMMAND_RESPONSE,
457 typeid=FRAME_TYPE_COMMAND_RESPONSE,
420 flags=FLAG_COMMAND_RESPONSE_EOS,
458 flags=FLAG_COMMAND_RESPONSE_EOS,
421 payload=overall)
459 payload=overall)
422
460
423 def createerrorframe(stream, requestid, msg, errtype):
461 def createerrorframe(stream, requestid, msg, errtype):
424 # TODO properly handle frame size limits.
462 # TODO properly handle frame size limits.
425 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
463 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
426
464
427 payload = b''.join(cborutil.streamencode({
465 payload = b''.join(cborutil.streamencode({
428 b'type': errtype,
466 b'type': errtype,
429 b'message': [{b'msg': msg}],
467 b'message': [{b'msg': msg}],
430 }))
468 }))
431
469
432 yield stream.makeframe(requestid=requestid,
470 yield stream.makeframe(requestid=requestid,
433 typeid=FRAME_TYPE_ERROR_RESPONSE,
471 typeid=FRAME_TYPE_ERROR_RESPONSE,
434 flags=0,
472 flags=0,
435 payload=payload)
473 payload=payload)
436
474
437 def createtextoutputframe(stream, requestid, atoms,
475 def createtextoutputframe(stream, requestid, atoms,
438 maxframesize=DEFAULT_MAX_FRAME_SIZE):
476 maxframesize=DEFAULT_MAX_FRAME_SIZE):
439 """Create a text output frame to render text to people.
477 """Create a text output frame to render text to people.
440
478
441 ``atoms`` is a 3-tuple of (formatting string, args, labels).
479 ``atoms`` is a 3-tuple of (formatting string, args, labels).
442
480
443 The formatting string contains ``%s`` tokens to be replaced by the
481 The formatting string contains ``%s`` tokens to be replaced by the
444 corresponding indexed entry in ``args``. ``labels`` is an iterable of
482 corresponding indexed entry in ``args``. ``labels`` is an iterable of
445 formatters to be applied at rendering time. In terms of the ``ui``
483 formatters to be applied at rendering time. In terms of the ``ui``
446 class, each atom corresponds to a ``ui.write()``.
484 class, each atom corresponds to a ``ui.write()``.
447 """
485 """
448 atomdicts = []
486 atomdicts = []
449
487
450 for (formatting, args, labels) in atoms:
488 for (formatting, args, labels) in atoms:
451 # TODO look for localstr, other types here?
489 # TODO look for localstr, other types here?
452
490
453 if not isinstance(formatting, bytes):
491 if not isinstance(formatting, bytes):
454 raise ValueError('must use bytes formatting strings')
492 raise ValueError('must use bytes formatting strings')
455 for arg in args:
493 for arg in args:
456 if not isinstance(arg, bytes):
494 if not isinstance(arg, bytes):
457 raise ValueError('must use bytes for arguments')
495 raise ValueError('must use bytes for arguments')
458 for label in labels:
496 for label in labels:
459 if not isinstance(label, bytes):
497 if not isinstance(label, bytes):
460 raise ValueError('must use bytes for labels')
498 raise ValueError('must use bytes for labels')
461
499
462 # Formatting string must be ASCII.
500 # Formatting string must be ASCII.
463 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
501 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
464
502
465 # Arguments must be UTF-8.
503 # Arguments must be UTF-8.
466 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
504 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
467
505
468 # Labels must be ASCII.
506 # Labels must be ASCII.
469 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
507 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
470 for l in labels]
508 for l in labels]
471
509
472 atom = {b'msg': formatting}
510 atom = {b'msg': formatting}
473 if args:
511 if args:
474 atom[b'args'] = args
512 atom[b'args'] = args
475 if labels:
513 if labels:
476 atom[b'labels'] = labels
514 atom[b'labels'] = labels
477
515
478 atomdicts.append(atom)
516 atomdicts.append(atom)
479
517
480 payload = b''.join(cborutil.streamencode(atomdicts))
518 payload = b''.join(cborutil.streamencode(atomdicts))
481
519
482 if len(payload) > maxframesize:
520 if len(payload) > maxframesize:
483 raise ValueError('cannot encode data in a single frame')
521 raise ValueError('cannot encode data in a single frame')
484
522
485 yield stream.makeframe(requestid=requestid,
523 yield stream.makeframe(requestid=requestid,
486 typeid=FRAME_TYPE_TEXT_OUTPUT,
524 typeid=FRAME_TYPE_TEXT_OUTPUT,
487 flags=0,
525 flags=0,
488 payload=payload)
526 payload=payload)
489
527
490 class bufferingcommandresponseemitter(object):
528 class bufferingcommandresponseemitter(object):
491 """Helper object to emit command response frames intelligently.
529 """Helper object to emit command response frames intelligently.
492
530
493 Raw command response data is likely emitted in chunks much smaller
531 Raw command response data is likely emitted in chunks much smaller
494 than what can fit in a single frame. This class exists to buffer
532 than what can fit in a single frame. This class exists to buffer
495 chunks until enough data is available to fit in a single frame.
533 chunks until enough data is available to fit in a single frame.
496
534
497 TODO we'll need something like this when compression is supported.
535 TODO we'll need something like this when compression is supported.
498 So it might make sense to implement this functionality at the stream
536 So it might make sense to implement this functionality at the stream
499 level.
537 level.
500 """
538 """
501 def __init__(self, stream, requestid, maxframesize=DEFAULT_MAX_FRAME_SIZE):
539 def __init__(self, stream, requestid, maxframesize=DEFAULT_MAX_FRAME_SIZE):
502 self._stream = stream
540 self._stream = stream
503 self._requestid = requestid
541 self._requestid = requestid
504 self._maxsize = maxframesize
542 self._maxsize = maxframesize
505 self._chunks = []
543 self._chunks = []
506 self._chunkssize = 0
544 self._chunkssize = 0
507
545
508 def send(self, data):
546 def send(self, data):
509 """Send new data for emission.
547 """Send new data for emission.
510
548
511 Is a generator of new frames that were derived from the new input.
549 Is a generator of new frames that were derived from the new input.
512
550
513 If the special input ``None`` is received, flushes all buffered
551 If the special input ``None`` is received, flushes all buffered
514 data to frames.
552 data to frames.
515 """
553 """
516
554
517 if data is None:
555 if data is None:
518 for frame in self._flush():
556 for frame in self._flush():
519 yield frame
557 yield frame
520 return
558 return
521
559
560 data = self._stream.encode(data)
561
522 # There is a ton of potential to do more complicated things here.
562 # There is a ton of potential to do more complicated things here.
523 # Our immediate goal is to coalesce small chunks into big frames,
563 # Our immediate goal is to coalesce small chunks into big frames,
524 # not achieve the fewest number of frames possible. So we go with
564 # not achieve the fewest number of frames possible. So we go with
525 # a simple implementation:
565 # a simple implementation:
526 #
566 #
527 # * If a chunk is too large for a frame, we flush and emit frames
567 # * If a chunk is too large for a frame, we flush and emit frames
528 # for the new chunk.
568 # for the new chunk.
529 # * If a chunk can be buffered without total buffered size limits
569 # * If a chunk can be buffered without total buffered size limits
530 # being exceeded, we do that.
570 # being exceeded, we do that.
531 # * If a chunk causes us to go over our buffering limit, we flush
571 # * If a chunk causes us to go over our buffering limit, we flush
532 # and then buffer the new chunk.
572 # and then buffer the new chunk.
533
573
534 if not data:
574 if not data:
535 return
575 return
536
576
537 if len(data) > self._maxsize:
577 if len(data) > self._maxsize:
538 for frame in self._flush():
578 for frame in self._flush():
539 yield frame
579 yield frame
540
580
541 # Now emit frames for the big chunk.
581 # Now emit frames for the big chunk.
542 offset = 0
582 offset = 0
543 while True:
583 while True:
544 chunk = data[offset:offset + self._maxsize]
584 chunk = data[offset:offset + self._maxsize]
545 offset += len(chunk)
585 offset += len(chunk)
546
586
547 yield self._stream.makeframe(
587 yield self._stream.makeframe(
548 self._requestid,
588 self._requestid,
549 typeid=FRAME_TYPE_COMMAND_RESPONSE,
589 typeid=FRAME_TYPE_COMMAND_RESPONSE,
550 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
590 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
551 payload=chunk)
591 payload=chunk,
592 encoded=True)
552
593
553 if offset == len(data):
594 if offset == len(data):
554 return
595 return
555
596
556 # If we don't have enough to constitute a full frame, buffer and
597 # If we don't have enough to constitute a full frame, buffer and
557 # return.
598 # return.
558 if len(data) + self._chunkssize < self._maxsize:
599 if len(data) + self._chunkssize < self._maxsize:
559 self._chunks.append(data)
600 self._chunks.append(data)
560 self._chunkssize += len(data)
601 self._chunkssize += len(data)
561 return
602 return
562
603
563 # Else flush what we have and buffer the new chunk. We could do
604 # Else flush what we have and buffer the new chunk. We could do
564 # something more intelligent here, like break the chunk. Let's
605 # something more intelligent here, like break the chunk. Let's
565 # keep things simple for now.
606 # keep things simple for now.
566 for frame in self._flush():
607 for frame in self._flush():
567 yield frame
608 yield frame
568
609
569 self._chunks.append(data)
610 self._chunks.append(data)
570 self._chunkssize = len(data)
611 self._chunkssize = len(data)
571
612
572 def _flush(self):
613 def _flush(self):
573 payload = b''.join(self._chunks)
614 payload = b''.join(self._chunks)
574 assert len(payload) <= self._maxsize
615 assert len(payload) <= self._maxsize
575
616
576 self._chunks[:] = []
617 self._chunks[:] = []
577 self._chunkssize = 0
618 self._chunkssize = 0
578
619
579 if not payload:
620 if not payload:
580 return
621 return
581
622
582 yield self._stream.makeframe(
623 yield self._stream.makeframe(
583 self._requestid,
624 self._requestid,
584 typeid=FRAME_TYPE_COMMAND_RESPONSE,
625 typeid=FRAME_TYPE_COMMAND_RESPONSE,
585 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
626 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
586 payload=payload)
627 payload=payload,
628 encoded=True)
587
629
588 # TODO consider defining encoders/decoders using the util.compressionengine
630 # TODO consider defining encoders/decoders using the util.compressionengine
589 # mechanism.
631 # mechanism.
590
632
591 class identityencoder(object):
633 class identityencoder(object):
592 """Encoder for the "identity" stream encoding profile."""
634 """Encoder for the "identity" stream encoding profile."""
593 def __init__(self, ui):
635 def __init__(self, ui):
594 pass
636 pass
595
637
596 def encode(self, data):
638 def encode(self, data):
597 return data
639 return data
598
640
599 def flush(self):
641 def flush(self):
600 return b''
642 return b''
601
643
602 def finish(self):
644 def finish(self):
603 return b''
645 return b''
604
646
605 class identitydecoder(object):
647 class identitydecoder(object):
606 """Decoder for the "identity" stream encoding profile."""
648 """Decoder for the "identity" stream encoding profile."""
607
649
608 def __init__(self, ui, extraobjs):
650 def __init__(self, ui, extraobjs):
609 if extraobjs:
651 if extraobjs:
610 raise error.Abort(_('identity decoder received unexpected '
652 raise error.Abort(_('identity decoder received unexpected '
611 'additional values'))
653 'additional values'))
612
654
613 def decode(self, data):
655 def decode(self, data):
614 return data
656 return data
615
657
616 class zlibencoder(object):
658 class zlibencoder(object):
617 def __init__(self, ui):
659 def __init__(self, ui):
618 import zlib
660 import zlib
619 self._zlib = zlib
661 self._zlib = zlib
620 self._compressor = zlib.compressobj()
662 self._compressor = zlib.compressobj()
621
663
622 def encode(self, data):
664 def encode(self, data):
623 return self._compressor.compress(data)
665 return self._compressor.compress(data)
624
666
625 def flush(self):
667 def flush(self):
626 # Z_SYNC_FLUSH doesn't reset compression context, which is
668 # Z_SYNC_FLUSH doesn't reset compression context, which is
627 # what we want.
669 # what we want.
628 return self._compressor.flush(self._zlib.Z_SYNC_FLUSH)
670 return self._compressor.flush(self._zlib.Z_SYNC_FLUSH)
629
671
630 def finish(self):
672 def finish(self):
631 res = self._compressor.flush(self._zlib.Z_FINISH)
673 res = self._compressor.flush(self._zlib.Z_FINISH)
632 self._compressor = None
674 self._compressor = None
633 return res
675 return res
634
676
635 class zlibdecoder(object):
677 class zlibdecoder(object):
636 def __init__(self, ui, extraobjs):
678 def __init__(self, ui, extraobjs):
637 import zlib
679 import zlib
638
680
639 if extraobjs:
681 if extraobjs:
640 raise error.Abort(_('zlib decoder received unexpected '
682 raise error.Abort(_('zlib decoder received unexpected '
641 'additional values'))
683 'additional values'))
642
684
643 self._decompressor = zlib.decompressobj()
685 self._decompressor = zlib.decompressobj()
644
686
645 def decode(self, data):
687 def decode(self, data):
646 # Python 2's zlib module doesn't use the buffer protocol and can't
688 # Python 2's zlib module doesn't use the buffer protocol and can't
647 # handle all bytes-like types.
689 # handle all bytes-like types.
648 if not pycompat.ispy3 and isinstance(data, bytearray):
690 if not pycompat.ispy3 and isinstance(data, bytearray):
649 data = bytes(data)
691 data = bytes(data)
650
692
651 return self._decompressor.decompress(data)
693 return self._decompressor.decompress(data)
652
694
653 class zstdbaseencoder(object):
695 class zstdbaseencoder(object):
654 def __init__(self, level):
696 def __init__(self, level):
655 from . import zstd
697 from . import zstd
656
698
657 self._zstd = zstd
699 self._zstd = zstd
658 cctx = zstd.ZstdCompressor(level=level)
700 cctx = zstd.ZstdCompressor(level=level)
659 self._compressor = cctx.compressobj()
701 self._compressor = cctx.compressobj()
660
702
661 def encode(self, data):
703 def encode(self, data):
662 return self._compressor.compress(data)
704 return self._compressor.compress(data)
663
705
664 def flush(self):
706 def flush(self):
665 # COMPRESSOBJ_FLUSH_BLOCK flushes all data previously fed into the
707 # COMPRESSOBJ_FLUSH_BLOCK flushes all data previously fed into the
666 # compressor and allows a decompressor to access all encoded data
708 # compressor and allows a decompressor to access all encoded data
667 # up to this point.
709 # up to this point.
668 return self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_BLOCK)
710 return self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_BLOCK)
669
711
670 def finish(self):
712 def finish(self):
671 res = self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_FINISH)
713 res = self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_FINISH)
672 self._compressor = None
714 self._compressor = None
673 return res
715 return res
674
716
675 class zstd8mbencoder(zstdbaseencoder):
717 class zstd8mbencoder(zstdbaseencoder):
676 def __init__(self, ui):
718 def __init__(self, ui):
677 super(zstd8mbencoder, self).__init__(3)
719 super(zstd8mbencoder, self).__init__(3)
678
720
679 class zstdbasedecoder(object):
721 class zstdbasedecoder(object):
680 def __init__(self, maxwindowsize):
722 def __init__(self, maxwindowsize):
681 from . import zstd
723 from . import zstd
682 dctx = zstd.ZstdDecompressor(max_window_size=maxwindowsize)
724 dctx = zstd.ZstdDecompressor(max_window_size=maxwindowsize)
683 self._decompressor = dctx.decompressobj()
725 self._decompressor = dctx.decompressobj()
684
726
685 def decode(self, data):
727 def decode(self, data):
686 return self._decompressor.decompress(data)
728 return self._decompressor.decompress(data)
687
729
688 class zstd8mbdecoder(zstdbasedecoder):
730 class zstd8mbdecoder(zstdbasedecoder):
689 def __init__(self, ui, extraobjs):
731 def __init__(self, ui, extraobjs):
690 if extraobjs:
732 if extraobjs:
691 raise error.Abort(_('zstd8mb decoder received unexpected '
733 raise error.Abort(_('zstd8mb decoder received unexpected '
692 'additional values'))
734 'additional values'))
693
735
694 super(zstd8mbdecoder, self).__init__(maxwindowsize=8 * 1048576)
736 super(zstd8mbdecoder, self).__init__(maxwindowsize=8 * 1048576)
695
737
696 # We lazily populate this to avoid excessive module imports when importing
738 # We lazily populate this to avoid excessive module imports when importing
697 # this module.
739 # this module.
698 STREAM_ENCODERS = {}
740 STREAM_ENCODERS = {}
699 STREAM_ENCODERS_ORDER = []
741 STREAM_ENCODERS_ORDER = []
700
742
701 def populatestreamencoders():
743 def populatestreamencoders():
702 if STREAM_ENCODERS:
744 if STREAM_ENCODERS:
703 return
745 return
704
746
705 try:
747 try:
706 from . import zstd
748 from . import zstd
707 zstd.__version__
749 zstd.__version__
708 except ImportError:
750 except ImportError:
709 zstd = None
751 zstd = None
710
752
711 # zstandard is fastest and is preferred.
753 # zstandard is fastest and is preferred.
712 if zstd:
754 if zstd:
713 STREAM_ENCODERS[b'zstd-8mb'] = (zstd8mbencoder, zstd8mbdecoder)
755 STREAM_ENCODERS[b'zstd-8mb'] = (zstd8mbencoder, zstd8mbdecoder)
714 STREAM_ENCODERS_ORDER.append(b'zstd-8mb')
756 STREAM_ENCODERS_ORDER.append(b'zstd-8mb')
715
757
716 STREAM_ENCODERS[b'zlib'] = (zlibencoder, zlibdecoder)
758 STREAM_ENCODERS[b'zlib'] = (zlibencoder, zlibdecoder)
717 STREAM_ENCODERS_ORDER.append(b'zlib')
759 STREAM_ENCODERS_ORDER.append(b'zlib')
718
760
719 STREAM_ENCODERS[b'identity'] = (identityencoder, identitydecoder)
761 STREAM_ENCODERS[b'identity'] = (identityencoder, identitydecoder)
720 STREAM_ENCODERS_ORDER.append(b'identity')
762 STREAM_ENCODERS_ORDER.append(b'identity')
721
763
722 class stream(object):
764 class stream(object):
723 """Represents a logical unidirectional series of frames."""
765 """Represents a logical unidirectional series of frames."""
724
766
725 def __init__(self, streamid, active=False):
767 def __init__(self, streamid, active=False):
726 self.streamid = streamid
768 self.streamid = streamid
727 self._active = active
769 self._active = active
728
770
729 def makeframe(self, requestid, typeid, flags, payload):
771 def makeframe(self, requestid, typeid, flags, payload):
730 """Create a frame to be sent out over this stream.
772 """Create a frame to be sent out over this stream.
731
773
732 Only returns the frame instance. Does not actually send it.
774 Only returns the frame instance. Does not actually send it.
733 """
775 """
734 streamflags = 0
776 streamflags = 0
735 if not self._active:
777 if not self._active:
736 streamflags |= STREAM_FLAG_BEGIN_STREAM
778 streamflags |= STREAM_FLAG_BEGIN_STREAM
737 self._active = True
779 self._active = True
738
780
739 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
781 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
740 payload)
782 payload)
741
783
742 class inputstream(stream):
784 class inputstream(stream):
743 """Represents a stream used for receiving data."""
785 """Represents a stream used for receiving data."""
744
786
745 def __init__(self, streamid, active=False):
787 def __init__(self, streamid, active=False):
746 super(inputstream, self).__init__(streamid, active=active)
788 super(inputstream, self).__init__(streamid, active=active)
747 self._decoder = None
789 self._decoder = None
748
790
749 def setdecoder(self, ui, name, extraobjs):
791 def setdecoder(self, ui, name, extraobjs):
750 """Set the decoder for this stream.
792 """Set the decoder for this stream.
751
793
752 Receives the stream profile name and any additional CBOR objects
794 Receives the stream profile name and any additional CBOR objects
753 decoded from the stream encoding settings frame payloads.
795 decoded from the stream encoding settings frame payloads.
754 """
796 """
755 if name not in STREAM_ENCODERS:
797 if name not in STREAM_ENCODERS:
756 raise error.Abort(_('unknown stream decoder: %s') % name)
798 raise error.Abort(_('unknown stream decoder: %s') % name)
757
799
758 self._decoder = STREAM_ENCODERS[name][1](ui, extraobjs)
800 self._decoder = STREAM_ENCODERS[name][1](ui, extraobjs)
759
801
760 def decode(self, data):
802 def decode(self, data):
761 # Default is identity decoder. We don't bother instantiating one
803 # Default is identity decoder. We don't bother instantiating one
762 # because it is trivial.
804 # because it is trivial.
763 if not self._decoder:
805 if not self._decoder:
764 return data
806 return data
765
807
766 return self._decoder.decode(data)
808 return self._decoder.decode(data)
767
809
768 def flush(self):
810 def flush(self):
769 if not self._decoder:
811 if not self._decoder:
770 return b''
812 return b''
771
813
772 return self._decoder.flush()
814 return self._decoder.flush()
773
815
774 class outputstream(stream):
816 class outputstream(stream):
775 """Represents a stream used for sending data."""
817 """Represents a stream used for sending data."""
776
818
777 def __init__(self, streamid, active=False):
819 def __init__(self, streamid, active=False):
778 super(outputstream, self).__init__(streamid, active=active)
820 super(outputstream, self).__init__(streamid, active=active)
821 self.streamsettingssent = False
779 self._encoder = None
822 self._encoder = None
823 self._encodername = None
780
824
781 def setencoder(self, ui, name):
825 def setencoder(self, ui, name):
782 """Set the encoder for this stream.
826 """Set the encoder for this stream.
783
827
784 Receives the stream profile name.
828 Receives the stream profile name.
785 """
829 """
786 if name not in STREAM_ENCODERS:
830 if name not in STREAM_ENCODERS:
787 raise error.Abort(_('unknown stream encoder: %s') % name)
831 raise error.Abort(_('unknown stream encoder: %s') % name)
788
832
789 self._encoder = STREAM_ENCODERS[name][0](ui)
833 self._encoder = STREAM_ENCODERS[name][0](ui)
834 self._encodername = name
790
835
791 def encode(self, data):
836 def encode(self, data):
792 if not self._encoder:
837 if not self._encoder:
793 return data
838 return data
794
839
795 return self._encoder.encode(data)
840 return self._encoder.encode(data)
796
841
797 def flush(self):
842 def flush(self):
798 if not self._encoder:
843 if not self._encoder:
799 return b''
844 return b''
800
845
801 return self._encoder.flush()
846 return self._encoder.flush()
802
847
803 def finish(self):
848 def finish(self):
804 if not self._encoder:
849 if not self._encoder:
805 return b''
850 return b''
806
851
807 self._encoder.finish()
852 self._encoder.finish()
808
853
854 def makeframe(self, requestid, typeid, flags, payload,
855 encoded=False):
856 """Create a frame to be sent out over this stream.
857
858 Only returns the frame instance. Does not actually send it.
859 """
860 streamflags = 0
861 if not self._active:
862 streamflags |= STREAM_FLAG_BEGIN_STREAM
863 self._active = True
864
865 if encoded:
866 if not self.streamsettingssent:
867 raise error.ProgrammingError(
868 b'attempting to send encoded frame without sending stream '
869 b'settings')
870
871 streamflags |= STREAM_FLAG_ENCODING_APPLIED
872
873 if (typeid == FRAME_TYPE_STREAM_SETTINGS
874 and flags & FLAG_STREAM_ENCODING_SETTINGS_EOS):
875 self.streamsettingssent = True
876
877 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
878 payload)
879
880 def makestreamsettingsframe(self, requestid):
881 """Create a stream settings frame for this stream.
882
883 Returns frame data or None if no stream settings frame is needed or has
884 already been sent.
885 """
886 if not self._encoder or self.streamsettingssent:
887 return None
888
889 payload = b''.join(cborutil.streamencode(self._encodername))
890 return self.makeframe(requestid, FRAME_TYPE_STREAM_SETTINGS,
891 FLAG_STREAM_ENCODING_SETTINGS_EOS, payload)
892
809 def ensureserverstream(stream):
893 def ensureserverstream(stream):
810 if stream.streamid % 2:
894 if stream.streamid % 2:
811 raise error.ProgrammingError('server should only write to even '
895 raise error.ProgrammingError('server should only write to even '
812 'numbered streams; %d is not even' %
896 'numbered streams; %d is not even' %
813 stream.streamid)
897 stream.streamid)
814
898
815 DEFAULT_PROTOCOL_SETTINGS = {
899 DEFAULT_PROTOCOL_SETTINGS = {
816 'contentencodings': [b'identity'],
900 'contentencodings': [b'identity'],
817 }
901 }
818
902
819 class serverreactor(object):
903 class serverreactor(object):
820 """Holds state of a server handling frame-based protocol requests.
904 """Holds state of a server handling frame-based protocol requests.
821
905
822 This class is the "brain" of the unified frame-based protocol server
906 This class is the "brain" of the unified frame-based protocol server
823 component. While the protocol is stateless from the perspective of
907 component. While the protocol is stateless from the perspective of
824 requests/commands, something needs to track which frames have been
908 requests/commands, something needs to track which frames have been
825 received, what frames to expect, etc. This class is that thing.
909 received, what frames to expect, etc. This class is that thing.
826
910
827 Instances are modeled as a state machine of sorts. Instances are also
911 Instances are modeled as a state machine of sorts. Instances are also
828 reactionary to external events. The point of this class is to encapsulate
912 reactionary to external events. The point of this class is to encapsulate
829 the state of the connection and the exchange of frames, not to perform
913 the state of the connection and the exchange of frames, not to perform
830 work. Instead, callers tell this class when something occurs, like a
914 work. Instead, callers tell this class when something occurs, like a
831 frame arriving. If that activity is worthy of a follow-up action (say
915 frame arriving. If that activity is worthy of a follow-up action (say
832 *run a command*), the return value of that handler will say so.
916 *run a command*), the return value of that handler will say so.
833
917
834 I/O and CPU intensive operations are purposefully delegated outside of
918 I/O and CPU intensive operations are purposefully delegated outside of
835 this class.
919 this class.
836
920
837 Consumers are expected to tell instances when events occur. They do so by
921 Consumers are expected to tell instances when events occur. They do so by
838 calling the various ``on*`` methods. These methods return a 2-tuple
922 calling the various ``on*`` methods. These methods return a 2-tuple
839 describing any follow-up action(s) to take. The first element is the
923 describing any follow-up action(s) to take. The first element is the
840 name of an action to perform. The second is a data structure (usually
924 name of an action to perform. The second is a data structure (usually
841 a dict) specific to that action that contains more information. e.g.
925 a dict) specific to that action that contains more information. e.g.
842 if the server wants to send frames back to the client, the data structure
926 if the server wants to send frames back to the client, the data structure
843 will contain a reference to those frames.
927 will contain a reference to those frames.
844
928
845 Valid actions that consumers can be instructed to take are:
929 Valid actions that consumers can be instructed to take are:
846
930
847 sendframes
931 sendframes
848 Indicates that frames should be sent to the client. The ``framegen``
932 Indicates that frames should be sent to the client. The ``framegen``
849 key contains a generator of frames that should be sent. The server
933 key contains a generator of frames that should be sent. The server
850 assumes that all frames are sent to the client.
934 assumes that all frames are sent to the client.
851
935
852 error
936 error
853 Indicates that an error occurred. Consumer should probably abort.
937 Indicates that an error occurred. Consumer should probably abort.
854
938
855 runcommand
939 runcommand
856 Indicates that the consumer should run a wire protocol command. Details
940 Indicates that the consumer should run a wire protocol command. Details
857 of the command to run are given in the data structure.
941 of the command to run are given in the data structure.
858
942
859 wantframe
943 wantframe
860 Indicates that nothing of interest happened and the server is waiting on
944 Indicates that nothing of interest happened and the server is waiting on
861 more frames from the client before anything interesting can be done.
945 more frames from the client before anything interesting can be done.
862
946
863 noop
947 noop
864 Indicates no additional action is required.
948 Indicates no additional action is required.
865
949
866 Known Issues
950 Known Issues
867 ------------
951 ------------
868
952
869 There are no limits to the number of partially received commands or their
953 There are no limits to the number of partially received commands or their
870 size. A malicious client could stream command request data and exhaust the
954 size. A malicious client could stream command request data and exhaust the
871 server's memory.
955 server's memory.
872
956
873 Partially received commands are not acted upon when end of input is
957 Partially received commands are not acted upon when end of input is
874 reached. Should the server error if it receives a partial request?
958 reached. Should the server error if it receives a partial request?
875 Should the client send a message to abort a partially transmitted request
959 Should the client send a message to abort a partially transmitted request
876 to facilitate graceful shutdown?
960 to facilitate graceful shutdown?
877
961
878 Active requests that haven't been responded to aren't tracked. This means
962 Active requests that haven't been responded to aren't tracked. This means
879 that if we receive a command and instruct its dispatch, another command
963 that if we receive a command and instruct its dispatch, another command
880 with its request ID can come in over the wire and there will be a race
964 with its request ID can come in over the wire and there will be a race
881 between who responds to what.
965 between who responds to what.
882 """
966 """
883
967
884 def __init__(self, ui, deferoutput=False):
968 def __init__(self, ui, deferoutput=False):
885 """Construct a new server reactor.
969 """Construct a new server reactor.
886
970
887 ``deferoutput`` can be used to indicate that no output frames should be
971 ``deferoutput`` can be used to indicate that no output frames should be
888 instructed to be sent until input has been exhausted. In this mode,
972 instructed to be sent until input has been exhausted. In this mode,
889 events that would normally generate output frames (such as a command
973 events that would normally generate output frames (such as a command
890 response being ready) will instead defer instructing the consumer to
974 response being ready) will instead defer instructing the consumer to
891 send those frames. This is useful for half-duplex transports where the
975 send those frames. This is useful for half-duplex transports where the
892 sender cannot receive until all data has been transmitted.
976 sender cannot receive until all data has been transmitted.
893 """
977 """
894 self._ui = ui
978 self._ui = ui
895 self._deferoutput = deferoutput
979 self._deferoutput = deferoutput
896 self._state = 'initial'
980 self._state = 'initial'
897 self._nextoutgoingstreamid = 2
981 self._nextoutgoingstreamid = 2
898 self._bufferedframegens = []
982 self._bufferedframegens = []
899 # stream id -> stream instance for all active streams from the client.
983 # stream id -> stream instance for all active streams from the client.
900 self._incomingstreams = {}
984 self._incomingstreams = {}
901 self._outgoingstreams = {}
985 self._outgoingstreams = {}
902 # request id -> dict of commands that are actively being received.
986 # request id -> dict of commands that are actively being received.
903 self._receivingcommands = {}
987 self._receivingcommands = {}
904 # Request IDs that have been received and are actively being processed.
988 # Request IDs that have been received and are actively being processed.
905 # Once all output for a request has been sent, it is removed from this
989 # Once all output for a request has been sent, it is removed from this
906 # set.
990 # set.
907 self._activecommands = set()
991 self._activecommands = set()
908
992
909 self._protocolsettingsdecoder = None
993 self._protocolsettingsdecoder = None
910
994
911 # Sender protocol settings are optional. Set implied default values.
995 # Sender protocol settings are optional. Set implied default values.
912 self._sendersettings = dict(DEFAULT_PROTOCOL_SETTINGS)
996 self._sendersettings = dict(DEFAULT_PROTOCOL_SETTINGS)
913
997
914 populatestreamencoders()
998 populatestreamencoders()
915
999
916 def onframerecv(self, frame):
1000 def onframerecv(self, frame):
917 """Process a frame that has been received off the wire.
1001 """Process a frame that has been received off the wire.
918
1002
919 Returns a dict with an ``action`` key that details what action,
1003 Returns a dict with an ``action`` key that details what action,
920 if any, the consumer should take next.
1004 if any, the consumer should take next.
921 """
1005 """
922 if not frame.streamid % 2:
1006 if not frame.streamid % 2:
923 self._state = 'errored'
1007 self._state = 'errored'
924 return self._makeerrorresult(
1008 return self._makeerrorresult(
925 _('received frame with even numbered stream ID: %d') %
1009 _('received frame with even numbered stream ID: %d') %
926 frame.streamid)
1010 frame.streamid)
927
1011
928 if frame.streamid not in self._incomingstreams:
1012 if frame.streamid not in self._incomingstreams:
929 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1013 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
930 self._state = 'errored'
1014 self._state = 'errored'
931 return self._makeerrorresult(
1015 return self._makeerrorresult(
932 _('received frame on unknown inactive stream without '
1016 _('received frame on unknown inactive stream without '
933 'beginning of stream flag set'))
1017 'beginning of stream flag set'))
934
1018
935 self._incomingstreams[frame.streamid] = inputstream(frame.streamid)
1019 self._incomingstreams[frame.streamid] = inputstream(frame.streamid)
936
1020
937 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1021 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
938 # TODO handle decoding frames
1022 # TODO handle decoding frames
939 self._state = 'errored'
1023 self._state = 'errored'
940 raise error.ProgrammingError('support for decoding stream payloads '
1024 raise error.ProgrammingError('support for decoding stream payloads '
941 'not yet implemented')
1025 'not yet implemented')
942
1026
943 if frame.streamflags & STREAM_FLAG_END_STREAM:
1027 if frame.streamflags & STREAM_FLAG_END_STREAM:
944 del self._incomingstreams[frame.streamid]
1028 del self._incomingstreams[frame.streamid]
945
1029
946 handlers = {
1030 handlers = {
947 'initial': self._onframeinitial,
1031 'initial': self._onframeinitial,
948 'protocol-settings-receiving': self._onframeprotocolsettings,
1032 'protocol-settings-receiving': self._onframeprotocolsettings,
949 'idle': self._onframeidle,
1033 'idle': self._onframeidle,
950 'command-receiving': self._onframecommandreceiving,
1034 'command-receiving': self._onframecommandreceiving,
951 'errored': self._onframeerrored,
1035 'errored': self._onframeerrored,
952 }
1036 }
953
1037
954 meth = handlers.get(self._state)
1038 meth = handlers.get(self._state)
955 if not meth:
1039 if not meth:
956 raise error.ProgrammingError('unhandled state: %s' % self._state)
1040 raise error.ProgrammingError('unhandled state: %s' % self._state)
957
1041
958 return meth(frame)
1042 return meth(frame)
959
1043
960 def oncommandresponsereadyobjects(self, stream, requestid, objs):
1044 def oncommandresponsereadyobjects(self, stream, requestid, objs):
961 """Signal that objects are ready to be sent to the client.
1045 """Signal that objects are ready to be sent to the client.
962
1046
963 ``objs`` is an iterable of objects (typically a generator) that will
1047 ``objs`` is an iterable of objects (typically a generator) that will
964 be encoded via CBOR and added to frames, which will be sent to the
1048 be encoded via CBOR and added to frames, which will be sent to the
965 client.
1049 client.
966 """
1050 """
967 ensureserverstream(stream)
1051 ensureserverstream(stream)
968
1052
969 # A more robust solution would be to check for objs.{next,__next__}.
1053 # A more robust solution would be to check for objs.{next,__next__}.
970 if isinstance(objs, list):
1054 if isinstance(objs, list):
971 objs = iter(objs)
1055 objs = iter(objs)
972
1056
973 # We need to take care over exception handling. Uncaught exceptions
1057 # We need to take care over exception handling. Uncaught exceptions
974 # when generating frames could lead to premature end of the frame
1058 # when generating frames could lead to premature end of the frame
975 # stream and the possibility of the server or client process getting
1059 # stream and the possibility of the server or client process getting
976 # in a bad state.
1060 # in a bad state.
977 #
1061 #
978 # Keep in mind that if ``objs`` is a generator, advancing it could
1062 # Keep in mind that if ``objs`` is a generator, advancing it could
979 # raise exceptions that originated in e.g. wire protocol command
1063 # raise exceptions that originated in e.g. wire protocol command
980 # functions. That is why we differentiate between exceptions raised
1064 # functions. That is why we differentiate between exceptions raised
981 # when iterating versus other exceptions that occur.
1065 # when iterating versus other exceptions that occur.
982 #
1066 #
983 # In all cases, when the function finishes, the request is fully
1067 # In all cases, when the function finishes, the request is fully
984 # handled and no new frames for it should be seen.
1068 # handled and no new frames for it should be seen.
985
1069
986 def sendframes():
1070 def sendframes():
987 emitted = False
1071 emitted = False
988 alternatelocationsent = False
1072 alternatelocationsent = False
989 emitter = bufferingcommandresponseemitter(stream, requestid)
1073 emitter = bufferingcommandresponseemitter(stream, requestid)
990 while True:
1074 while True:
991 try:
1075 try:
992 o = next(objs)
1076 o = next(objs)
993 except StopIteration:
1077 except StopIteration:
994 for frame in emitter.send(None):
1078 for frame in emitter.send(None):
995 yield frame
1079 yield frame
996
1080
997 if emitted:
1081 if emitted:
998 yield createcommandresponseeosframe(stream, requestid)
1082 for frame in createcommandresponseeosframes(
1083 stream, requestid):
1084 yield frame
999 break
1085 break
1000
1086
1001 except error.WireprotoCommandError as e:
1087 except error.WireprotoCommandError as e:
1002 for frame in createcommanderrorresponse(
1088 for frame in createcommanderrorresponse(
1003 stream, requestid, e.message, e.messageargs):
1089 stream, requestid, e.message, e.messageargs):
1004 yield frame
1090 yield frame
1005 break
1091 break
1006
1092
1007 except Exception as e:
1093 except Exception as e:
1008 for frame in createerrorframe(
1094 for frame in createerrorframe(
1009 stream, requestid, '%s' % stringutil.forcebytestr(e),
1095 stream, requestid, '%s' % stringutil.forcebytestr(e),
1010 errtype='server'):
1096 errtype='server'):
1011
1097
1012 yield frame
1098 yield frame
1013
1099
1014 break
1100 break
1015
1101
1016 try:
1102 try:
1017 # Alternate location responses can only be the first and
1103 # Alternate location responses can only be the first and
1018 # only object in the output stream.
1104 # only object in the output stream.
1019 if isinstance(o, wireprototypes.alternatelocationresponse):
1105 if isinstance(o, wireprototypes.alternatelocationresponse):
1020 if emitted:
1106 if emitted:
1021 raise error.ProgrammingError(
1107 raise error.ProgrammingError(
1022 'alternatelocationresponse seen after initial '
1108 'alternatelocationresponse seen after initial '
1023 'output object')
1109 'output object')
1024
1110
1111 frame = stream.makestreamsettingsframe(requestid)
1112 if frame:
1113 yield frame
1114
1025 yield createalternatelocationresponseframe(
1115 yield createalternatelocationresponseframe(
1026 stream, requestid, o)
1116 stream, requestid, o)
1027
1117
1028 alternatelocationsent = True
1118 alternatelocationsent = True
1029 emitted = True
1119 emitted = True
1030 continue
1120 continue
1031
1121
1032 if alternatelocationsent:
1122 if alternatelocationsent:
1033 raise error.ProgrammingError(
1123 raise error.ProgrammingError(
1034 'object follows alternatelocationresponse')
1124 'object follows alternatelocationresponse')
1035
1125
1036 if not emitted:
1126 if not emitted:
1037 yield createcommandresponseokframe(stream, requestid)
1127 # Frame is optional.
1128 frame = stream.makestreamsettingsframe(requestid)
1129 if frame:
1130 yield frame
1131
1132 # May be None if empty frame (due to encoding).
1133 frame = createcommandresponseokframe(stream, requestid)
1134 if frame:
1135 yield frame
1136
1038 emitted = True
1137 emitted = True
1039
1138
1040 # Objects emitted by command functions can be serializable
1139 # Objects emitted by command functions can be serializable
1041 # data structures or special types.
1140 # data structures or special types.
1042 # TODO consider extracting the content normalization to a
1141 # TODO consider extracting the content normalization to a
1043 # standalone function, as it may be useful for e.g. cachers.
1142 # standalone function, as it may be useful for e.g. cachers.
1044
1143
1045 # A pre-encoded object is sent directly to the emitter.
1144 # A pre-encoded object is sent directly to the emitter.
1046 if isinstance(o, wireprototypes.encodedresponse):
1145 if isinstance(o, wireprototypes.encodedresponse):
1047 for frame in emitter.send(o.data):
1146 for frame in emitter.send(o.data):
1048 yield frame
1147 yield frame
1049
1148
1050 # A regular object is CBOR encoded.
1149 # A regular object is CBOR encoded.
1051 else:
1150 else:
1052 for chunk in cborutil.streamencode(o):
1151 for chunk in cborutil.streamencode(o):
1053 for frame in emitter.send(chunk):
1152 for frame in emitter.send(chunk):
1054 yield frame
1153 yield frame
1055
1154
1056 except Exception as e:
1155 except Exception as e:
1057 for frame in createerrorframe(stream, requestid,
1156 for frame in createerrorframe(stream, requestid,
1058 '%s' % e,
1157 '%s' % e,
1059 errtype='server'):
1158 errtype='server'):
1060 yield frame
1159 yield frame
1061
1160
1062 break
1161 break
1063
1162
1064 self._activecommands.remove(requestid)
1163 self._activecommands.remove(requestid)
1065
1164
1066 return self._handlesendframes(sendframes())
1165 return self._handlesendframes(sendframes())
1067
1166
1068 def oninputeof(self):
1167 def oninputeof(self):
1069 """Signals that end of input has been received.
1168 """Signals that end of input has been received.
1070
1169
1071 No more frames will be received. All pending activity should be
1170 No more frames will be received. All pending activity should be
1072 completed.
1171 completed.
1073 """
1172 """
1074 # TODO should we do anything about in-flight commands?
1173 # TODO should we do anything about in-flight commands?
1075
1174
1076 if not self._deferoutput or not self._bufferedframegens:
1175 if not self._deferoutput or not self._bufferedframegens:
1077 return 'noop', {}
1176 return 'noop', {}
1078
1177
1079 # If we buffered all our responses, emit those.
1178 # If we buffered all our responses, emit those.
1080 def makegen():
1179 def makegen():
1081 for gen in self._bufferedframegens:
1180 for gen in self._bufferedframegens:
1082 for frame in gen:
1181 for frame in gen:
1083 yield frame
1182 yield frame
1084
1183
1085 return 'sendframes', {
1184 return 'sendframes', {
1086 'framegen': makegen(),
1185 'framegen': makegen(),
1087 }
1186 }
1088
1187
1089 def _handlesendframes(self, framegen):
1188 def _handlesendframes(self, framegen):
1090 if self._deferoutput:
1189 if self._deferoutput:
1091 self._bufferedframegens.append(framegen)
1190 self._bufferedframegens.append(framegen)
1092 return 'noop', {}
1191 return 'noop', {}
1093 else:
1192 else:
1094 return 'sendframes', {
1193 return 'sendframes', {
1095 'framegen': framegen,
1194 'framegen': framegen,
1096 }
1195 }
1097
1196
1098 def onservererror(self, stream, requestid, msg):
1197 def onservererror(self, stream, requestid, msg):
1099 ensureserverstream(stream)
1198 ensureserverstream(stream)
1100
1199
1101 def sendframes():
1200 def sendframes():
1102 for frame in createerrorframe(stream, requestid, msg,
1201 for frame in createerrorframe(stream, requestid, msg,
1103 errtype='server'):
1202 errtype='server'):
1104 yield frame
1203 yield frame
1105
1204
1106 self._activecommands.remove(requestid)
1205 self._activecommands.remove(requestid)
1107
1206
1108 return self._handlesendframes(sendframes())
1207 return self._handlesendframes(sendframes())
1109
1208
1110 def oncommanderror(self, stream, requestid, message, args=None):
1209 def oncommanderror(self, stream, requestid, message, args=None):
1111 """Called when a command encountered an error before sending output."""
1210 """Called when a command encountered an error before sending output."""
1112 ensureserverstream(stream)
1211 ensureserverstream(stream)
1113
1212
1114 def sendframes():
1213 def sendframes():
1115 for frame in createcommanderrorresponse(stream, requestid, message,
1214 for frame in createcommanderrorresponse(stream, requestid, message,
1116 args):
1215 args):
1117 yield frame
1216 yield frame
1118
1217
1119 self._activecommands.remove(requestid)
1218 self._activecommands.remove(requestid)
1120
1219
1121 return self._handlesendframes(sendframes())
1220 return self._handlesendframes(sendframes())
1122
1221
1123 def makeoutputstream(self):
1222 def makeoutputstream(self):
1124 """Create a stream to be used for sending data to the client."""
1223 """Create a stream to be used for sending data to the client.
1224
1225 If this is called before protocol settings frames are received, we
1226 don't know what stream encodings are supported by the client and
1227 we will default to identity.
1228 """
1125 streamid = self._nextoutgoingstreamid
1229 streamid = self._nextoutgoingstreamid
1126 self._nextoutgoingstreamid += 2
1230 self._nextoutgoingstreamid += 2
1127
1231
1128 s = outputstream(streamid)
1232 s = outputstream(streamid)
1129 self._outgoingstreams[streamid] = s
1233 self._outgoingstreams[streamid] = s
1130
1234
1235 # Always use the *server's* preferred encoder over the client's,
1236 # as servers have more to lose from sub-optimal encoders being used.
1237 for name in STREAM_ENCODERS_ORDER:
1238 if name in self._sendersettings['contentencodings']:
1239 s.setencoder(self._ui, name)
1240 break
1241
1131 return s
1242 return s
1132
1243
1133 def _makeerrorresult(self, msg):
1244 def _makeerrorresult(self, msg):
1134 return 'error', {
1245 return 'error', {
1135 'message': msg,
1246 'message': msg,
1136 }
1247 }
1137
1248
1138 def _makeruncommandresult(self, requestid):
1249 def _makeruncommandresult(self, requestid):
1139 entry = self._receivingcommands[requestid]
1250 entry = self._receivingcommands[requestid]
1140
1251
1141 if not entry['requestdone']:
1252 if not entry['requestdone']:
1142 self._state = 'errored'
1253 self._state = 'errored'
1143 raise error.ProgrammingError('should not be called without '
1254 raise error.ProgrammingError('should not be called without '
1144 'requestdone set')
1255 'requestdone set')
1145
1256
1146 del self._receivingcommands[requestid]
1257 del self._receivingcommands[requestid]
1147
1258
1148 if self._receivingcommands:
1259 if self._receivingcommands:
1149 self._state = 'command-receiving'
1260 self._state = 'command-receiving'
1150 else:
1261 else:
1151 self._state = 'idle'
1262 self._state = 'idle'
1152
1263
1153 # Decode the payloads as CBOR.
1264 # Decode the payloads as CBOR.
1154 entry['payload'].seek(0)
1265 entry['payload'].seek(0)
1155 request = cborutil.decodeall(entry['payload'].getvalue())[0]
1266 request = cborutil.decodeall(entry['payload'].getvalue())[0]
1156
1267
1157 if b'name' not in request:
1268 if b'name' not in request:
1158 self._state = 'errored'
1269 self._state = 'errored'
1159 return self._makeerrorresult(
1270 return self._makeerrorresult(
1160 _('command request missing "name" field'))
1271 _('command request missing "name" field'))
1161
1272
1162 if b'args' not in request:
1273 if b'args' not in request:
1163 request[b'args'] = {}
1274 request[b'args'] = {}
1164
1275
1165 assert requestid not in self._activecommands
1276 assert requestid not in self._activecommands
1166 self._activecommands.add(requestid)
1277 self._activecommands.add(requestid)
1167
1278
1168 return 'runcommand', {
1279 return 'runcommand', {
1169 'requestid': requestid,
1280 'requestid': requestid,
1170 'command': request[b'name'],
1281 'command': request[b'name'],
1171 'args': request[b'args'],
1282 'args': request[b'args'],
1172 'redirect': request.get(b'redirect'),
1283 'redirect': request.get(b'redirect'),
1173 'data': entry['data'].getvalue() if entry['data'] else None,
1284 'data': entry['data'].getvalue() if entry['data'] else None,
1174 }
1285 }
1175
1286
1176 def _makewantframeresult(self):
1287 def _makewantframeresult(self):
1177 return 'wantframe', {
1288 return 'wantframe', {
1178 'state': self._state,
1289 'state': self._state,
1179 }
1290 }
1180
1291
1181 def _validatecommandrequestframe(self, frame):
1292 def _validatecommandrequestframe(self, frame):
1182 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1293 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1183 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
1294 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
1184
1295
1185 if new and continuation:
1296 if new and continuation:
1186 self._state = 'errored'
1297 self._state = 'errored'
1187 return self._makeerrorresult(
1298 return self._makeerrorresult(
1188 _('received command request frame with both new and '
1299 _('received command request frame with both new and '
1189 'continuation flags set'))
1300 'continuation flags set'))
1190
1301
1191 if not new and not continuation:
1302 if not new and not continuation:
1192 self._state = 'errored'
1303 self._state = 'errored'
1193 return self._makeerrorresult(
1304 return self._makeerrorresult(
1194 _('received command request frame with neither new nor '
1305 _('received command request frame with neither new nor '
1195 'continuation flags set'))
1306 'continuation flags set'))
1196
1307
1197 def _onframeinitial(self, frame):
1308 def _onframeinitial(self, frame):
1198 # Called when we receive a frame when in the "initial" state.
1309 # Called when we receive a frame when in the "initial" state.
1199 if frame.typeid == FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1310 if frame.typeid == FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1200 self._state = 'protocol-settings-receiving'
1311 self._state = 'protocol-settings-receiving'
1201 self._protocolsettingsdecoder = cborutil.bufferingdecoder()
1312 self._protocolsettingsdecoder = cborutil.bufferingdecoder()
1202 return self._onframeprotocolsettings(frame)
1313 return self._onframeprotocolsettings(frame)
1203
1314
1204 elif frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1315 elif frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1205 self._state = 'idle'
1316 self._state = 'idle'
1206 return self._onframeidle(frame)
1317 return self._onframeidle(frame)
1207
1318
1208 else:
1319 else:
1209 self._state = 'errored'
1320 self._state = 'errored'
1210 return self._makeerrorresult(
1321 return self._makeerrorresult(
1211 _('expected sender protocol settings or command request '
1322 _('expected sender protocol settings or command request '
1212 'frame; got %d') % frame.typeid)
1323 'frame; got %d') % frame.typeid)
1213
1324
1214 def _onframeprotocolsettings(self, frame):
1325 def _onframeprotocolsettings(self, frame):
1215 assert self._state == 'protocol-settings-receiving'
1326 assert self._state == 'protocol-settings-receiving'
1216 assert self._protocolsettingsdecoder is not None
1327 assert self._protocolsettingsdecoder is not None
1217
1328
1218 if frame.typeid != FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1329 if frame.typeid != FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1219 self._state = 'errored'
1330 self._state = 'errored'
1220 return self._makeerrorresult(
1331 return self._makeerrorresult(
1221 _('expected sender protocol settings frame; got %d') %
1332 _('expected sender protocol settings frame; got %d') %
1222 frame.typeid)
1333 frame.typeid)
1223
1334
1224 more = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION
1335 more = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION
1225 eos = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_EOS
1336 eos = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_EOS
1226
1337
1227 if more and eos:
1338 if more and eos:
1228 self._state = 'errored'
1339 self._state = 'errored'
1229 return self._makeerrorresult(
1340 return self._makeerrorresult(
1230 _('sender protocol settings frame cannot have both '
1341 _('sender protocol settings frame cannot have both '
1231 'continuation and end of stream flags set'))
1342 'continuation and end of stream flags set'))
1232
1343
1233 if not more and not eos:
1344 if not more and not eos:
1234 self._state = 'errored'
1345 self._state = 'errored'
1235 return self._makeerrorresult(
1346 return self._makeerrorresult(
1236 _('sender protocol settings frame must have continuation or '
1347 _('sender protocol settings frame must have continuation or '
1237 'end of stream flag set'))
1348 'end of stream flag set'))
1238
1349
1239 # TODO establish limits for maximum amount of data that can be
1350 # TODO establish limits for maximum amount of data that can be
1240 # buffered.
1351 # buffered.
1241 try:
1352 try:
1242 self._protocolsettingsdecoder.decode(frame.payload)
1353 self._protocolsettingsdecoder.decode(frame.payload)
1243 except Exception as e:
1354 except Exception as e:
1244 self._state = 'errored'
1355 self._state = 'errored'
1245 return self._makeerrorresult(
1356 return self._makeerrorresult(
1246 _('error decoding CBOR from sender protocol settings frame: %s')
1357 _('error decoding CBOR from sender protocol settings frame: %s')
1247 % stringutil.forcebytestr(e))
1358 % stringutil.forcebytestr(e))
1248
1359
1249 if more:
1360 if more:
1250 return self._makewantframeresult()
1361 return self._makewantframeresult()
1251
1362
1252 assert eos
1363 assert eos
1253
1364
1254 decoded = self._protocolsettingsdecoder.getavailable()
1365 decoded = self._protocolsettingsdecoder.getavailable()
1255 self._protocolsettingsdecoder = None
1366 self._protocolsettingsdecoder = None
1256
1367
1257 if not decoded:
1368 if not decoded:
1258 self._state = 'errored'
1369 self._state = 'errored'
1259 return self._makeerrorresult(
1370 return self._makeerrorresult(
1260 _('sender protocol settings frame did not contain CBOR data'))
1371 _('sender protocol settings frame did not contain CBOR data'))
1261 elif len(decoded) > 1:
1372 elif len(decoded) > 1:
1262 self._state = 'errored'
1373 self._state = 'errored'
1263 return self._makeerrorresult(
1374 return self._makeerrorresult(
1264 _('sender protocol settings frame contained multiple CBOR '
1375 _('sender protocol settings frame contained multiple CBOR '
1265 'values'))
1376 'values'))
1266
1377
1267 d = decoded[0]
1378 d = decoded[0]
1268
1379
1269 if b'contentencodings' in d:
1380 if b'contentencodings' in d:
1270 self._sendersettings['contentencodings'] = d[b'contentencodings']
1381 self._sendersettings['contentencodings'] = d[b'contentencodings']
1271
1382
1272 self._state = 'idle'
1383 self._state = 'idle'
1273
1384
1274 return self._makewantframeresult()
1385 return self._makewantframeresult()
1275
1386
1276 def _onframeidle(self, frame):
1387 def _onframeidle(self, frame):
1277 # The only frame type that should be received in this state is a
1388 # The only frame type that should be received in this state is a
1278 # command request.
1389 # command request.
1279 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
1390 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
1280 self._state = 'errored'
1391 self._state = 'errored'
1281 return self._makeerrorresult(
1392 return self._makeerrorresult(
1282 _('expected command request frame; got %d') % frame.typeid)
1393 _('expected command request frame; got %d') % frame.typeid)
1283
1394
1284 res = self._validatecommandrequestframe(frame)
1395 res = self._validatecommandrequestframe(frame)
1285 if res:
1396 if res:
1286 return res
1397 return res
1287
1398
1288 if frame.requestid in self._receivingcommands:
1399 if frame.requestid in self._receivingcommands:
1289 self._state = 'errored'
1400 self._state = 'errored'
1290 return self._makeerrorresult(
1401 return self._makeerrorresult(
1291 _('request with ID %d already received') % frame.requestid)
1402 _('request with ID %d already received') % frame.requestid)
1292
1403
1293 if frame.requestid in self._activecommands:
1404 if frame.requestid in self._activecommands:
1294 self._state = 'errored'
1405 self._state = 'errored'
1295 return self._makeerrorresult(
1406 return self._makeerrorresult(
1296 _('request with ID %d is already active') % frame.requestid)
1407 _('request with ID %d is already active') % frame.requestid)
1297
1408
1298 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1409 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1299 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1410 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1300 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
1411 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
1301
1412
1302 if not new:
1413 if not new:
1303 self._state = 'errored'
1414 self._state = 'errored'
1304 return self._makeerrorresult(
1415 return self._makeerrorresult(
1305 _('received command request frame without new flag set'))
1416 _('received command request frame without new flag set'))
1306
1417
1307 payload = util.bytesio()
1418 payload = util.bytesio()
1308 payload.write(frame.payload)
1419 payload.write(frame.payload)
1309
1420
1310 self._receivingcommands[frame.requestid] = {
1421 self._receivingcommands[frame.requestid] = {
1311 'payload': payload,
1422 'payload': payload,
1312 'data': None,
1423 'data': None,
1313 'requestdone': not moreframes,
1424 'requestdone': not moreframes,
1314 'expectingdata': bool(expectingdata),
1425 'expectingdata': bool(expectingdata),
1315 }
1426 }
1316
1427
1317 # This is the final frame for this request. Dispatch it.
1428 # This is the final frame for this request. Dispatch it.
1318 if not moreframes and not expectingdata:
1429 if not moreframes and not expectingdata:
1319 return self._makeruncommandresult(frame.requestid)
1430 return self._makeruncommandresult(frame.requestid)
1320
1431
1321 assert moreframes or expectingdata
1432 assert moreframes or expectingdata
1322 self._state = 'command-receiving'
1433 self._state = 'command-receiving'
1323 return self._makewantframeresult()
1434 return self._makewantframeresult()
1324
1435
1325 def _onframecommandreceiving(self, frame):
1436 def _onframecommandreceiving(self, frame):
1326 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1437 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1327 # Process new command requests as such.
1438 # Process new command requests as such.
1328 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
1439 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
1329 return self._onframeidle(frame)
1440 return self._onframeidle(frame)
1330
1441
1331 res = self._validatecommandrequestframe(frame)
1442 res = self._validatecommandrequestframe(frame)
1332 if res:
1443 if res:
1333 return res
1444 return res
1334
1445
1335 # All other frames should be related to a command that is currently
1446 # All other frames should be related to a command that is currently
1336 # receiving but is not active.
1447 # receiving but is not active.
1337 if frame.requestid in self._activecommands:
1448 if frame.requestid in self._activecommands:
1338 self._state = 'errored'
1449 self._state = 'errored'
1339 return self._makeerrorresult(
1450 return self._makeerrorresult(
1340 _('received frame for request that is still active: %d') %
1451 _('received frame for request that is still active: %d') %
1341 frame.requestid)
1452 frame.requestid)
1342
1453
1343 if frame.requestid not in self._receivingcommands:
1454 if frame.requestid not in self._receivingcommands:
1344 self._state = 'errored'
1455 self._state = 'errored'
1345 return self._makeerrorresult(
1456 return self._makeerrorresult(
1346 _('received frame for request that is not receiving: %d') %
1457 _('received frame for request that is not receiving: %d') %
1347 frame.requestid)
1458 frame.requestid)
1348
1459
1349 entry = self._receivingcommands[frame.requestid]
1460 entry = self._receivingcommands[frame.requestid]
1350
1461
1351 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1462 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1352 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1463 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1353 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
1464 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
1354
1465
1355 if entry['requestdone']:
1466 if entry['requestdone']:
1356 self._state = 'errored'
1467 self._state = 'errored'
1357 return self._makeerrorresult(
1468 return self._makeerrorresult(
1358 _('received command request frame when request frames '
1469 _('received command request frame when request frames '
1359 'were supposedly done'))
1470 'were supposedly done'))
1360
1471
1361 if expectingdata != entry['expectingdata']:
1472 if expectingdata != entry['expectingdata']:
1362 self._state = 'errored'
1473 self._state = 'errored'
1363 return self._makeerrorresult(
1474 return self._makeerrorresult(
1364 _('mismatch between expect data flag and previous frame'))
1475 _('mismatch between expect data flag and previous frame'))
1365
1476
1366 entry['payload'].write(frame.payload)
1477 entry['payload'].write(frame.payload)
1367
1478
1368 if not moreframes:
1479 if not moreframes:
1369 entry['requestdone'] = True
1480 entry['requestdone'] = True
1370
1481
1371 if not moreframes and not expectingdata:
1482 if not moreframes and not expectingdata:
1372 return self._makeruncommandresult(frame.requestid)
1483 return self._makeruncommandresult(frame.requestid)
1373
1484
1374 return self._makewantframeresult()
1485 return self._makewantframeresult()
1375
1486
1376 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
1487 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
1377 if not entry['expectingdata']:
1488 if not entry['expectingdata']:
1378 self._state = 'errored'
1489 self._state = 'errored'
1379 return self._makeerrorresult(_(
1490 return self._makeerrorresult(_(
1380 'received command data frame for request that is not '
1491 'received command data frame for request that is not '
1381 'expecting data: %d') % frame.requestid)
1492 'expecting data: %d') % frame.requestid)
1382
1493
1383 if entry['data'] is None:
1494 if entry['data'] is None:
1384 entry['data'] = util.bytesio()
1495 entry['data'] = util.bytesio()
1385
1496
1386 return self._handlecommanddataframe(frame, entry)
1497 return self._handlecommanddataframe(frame, entry)
1387 else:
1498 else:
1388 self._state = 'errored'
1499 self._state = 'errored'
1389 return self._makeerrorresult(_(
1500 return self._makeerrorresult(_(
1390 'received unexpected frame type: %d') % frame.typeid)
1501 'received unexpected frame type: %d') % frame.typeid)
1391
1502
1392 def _handlecommanddataframe(self, frame, entry):
1503 def _handlecommanddataframe(self, frame, entry):
1393 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
1504 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
1394
1505
1395 # TODO support streaming data instead of buffering it.
1506 # TODO support streaming data instead of buffering it.
1396 entry['data'].write(frame.payload)
1507 entry['data'].write(frame.payload)
1397
1508
1398 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
1509 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
1399 return self._makewantframeresult()
1510 return self._makewantframeresult()
1400 elif frame.flags & FLAG_COMMAND_DATA_EOS:
1511 elif frame.flags & FLAG_COMMAND_DATA_EOS:
1401 entry['data'].seek(0)
1512 entry['data'].seek(0)
1402 return self._makeruncommandresult(frame.requestid)
1513 return self._makeruncommandresult(frame.requestid)
1403 else:
1514 else:
1404 self._state = 'errored'
1515 self._state = 'errored'
1405 return self._makeerrorresult(_('command data frame without '
1516 return self._makeerrorresult(_('command data frame without '
1406 'flags'))
1517 'flags'))
1407
1518
1408 def _onframeerrored(self, frame):
1519 def _onframeerrored(self, frame):
1409 return self._makeerrorresult(_('server already errored'))
1520 return self._makeerrorresult(_('server already errored'))
1410
1521
1411 class commandrequest(object):
1522 class commandrequest(object):
1412 """Represents a request to run a command."""
1523 """Represents a request to run a command."""
1413
1524
1414 def __init__(self, requestid, name, args, datafh=None, redirect=None):
1525 def __init__(self, requestid, name, args, datafh=None, redirect=None):
1415 self.requestid = requestid
1526 self.requestid = requestid
1416 self.name = name
1527 self.name = name
1417 self.args = args
1528 self.args = args
1418 self.datafh = datafh
1529 self.datafh = datafh
1419 self.redirect = redirect
1530 self.redirect = redirect
1420 self.state = 'pending'
1531 self.state = 'pending'
1421
1532
1422 class clientreactor(object):
1533 class clientreactor(object):
1423 """Holds state of a client issuing frame-based protocol requests.
1534 """Holds state of a client issuing frame-based protocol requests.
1424
1535
1425 This is like ``serverreactor`` but for client-side state.
1536 This is like ``serverreactor`` but for client-side state.
1426
1537
1427 Each instance is bound to the lifetime of a connection. For persistent
1538 Each instance is bound to the lifetime of a connection. For persistent
1428 connection transports using e.g. TCP sockets and speaking the raw
1539 connection transports using e.g. TCP sockets and speaking the raw
1429 framing protocol, there will be a single instance for the lifetime of
1540 framing protocol, there will be a single instance for the lifetime of
1430 the TCP socket. For transports where there are multiple discrete
1541 the TCP socket. For transports where there are multiple discrete
1431 interactions (say tunneled within in HTTP request), there will be a
1542 interactions (say tunneled within in HTTP request), there will be a
1432 separate instance for each distinct interaction.
1543 separate instance for each distinct interaction.
1433
1544
1434 Consumers are expected to tell instances when events occur by calling
1545 Consumers are expected to tell instances when events occur by calling
1435 various methods. These methods return a 2-tuple describing any follow-up
1546 various methods. These methods return a 2-tuple describing any follow-up
1436 action(s) to take. The first element is the name of an action to
1547 action(s) to take. The first element is the name of an action to
1437 perform. The second is a data structure (usually a dict) specific to
1548 perform. The second is a data structure (usually a dict) specific to
1438 that action that contains more information. e.g. if the reactor wants
1549 that action that contains more information. e.g. if the reactor wants
1439 to send frames to the server, the data structure will contain a reference
1550 to send frames to the server, the data structure will contain a reference
1440 to those frames.
1551 to those frames.
1441
1552
1442 Valid actions that consumers can be instructed to take are:
1553 Valid actions that consumers can be instructed to take are:
1443
1554
1444 noop
1555 noop
1445 Indicates no additional action is required.
1556 Indicates no additional action is required.
1446
1557
1447 sendframes
1558 sendframes
1448 Indicates that frames should be sent to the server. The ``framegen``
1559 Indicates that frames should be sent to the server. The ``framegen``
1449 key contains a generator of frames that should be sent. The reactor
1560 key contains a generator of frames that should be sent. The reactor
1450 assumes that all frames in this generator are sent to the server.
1561 assumes that all frames in this generator are sent to the server.
1451
1562
1452 error
1563 error
1453 Indicates that an error occurred. The ``message`` key contains an
1564 Indicates that an error occurred. The ``message`` key contains an
1454 error message describing the failure.
1565 error message describing the failure.
1455
1566
1456 responsedata
1567 responsedata
1457 Indicates a response to a previously-issued command was received.
1568 Indicates a response to a previously-issued command was received.
1458
1569
1459 The ``request`` key contains the ``commandrequest`` instance that
1570 The ``request`` key contains the ``commandrequest`` instance that
1460 represents the request this data is for.
1571 represents the request this data is for.
1461
1572
1462 The ``data`` key contains the decoded data from the server.
1573 The ``data`` key contains the decoded data from the server.
1463
1574
1464 ``expectmore`` and ``eos`` evaluate to True when more response data
1575 ``expectmore`` and ``eos`` evaluate to True when more response data
1465 is expected to follow or we're at the end of the response stream,
1576 is expected to follow or we're at the end of the response stream,
1466 respectively.
1577 respectively.
1467 """
1578 """
1468 def __init__(self, ui, hasmultiplesend=False, buffersends=True,
1579 def __init__(self, ui, hasmultiplesend=False, buffersends=True,
1469 clientcontentencoders=None):
1580 clientcontentencoders=None):
1470 """Create a new instance.
1581 """Create a new instance.
1471
1582
1472 ``hasmultiplesend`` indicates whether multiple sends are supported
1583 ``hasmultiplesend`` indicates whether multiple sends are supported
1473 by the transport. When True, it is possible to send commands immediately
1584 by the transport. When True, it is possible to send commands immediately
1474 instead of buffering until the caller signals an intent to finish a
1585 instead of buffering until the caller signals an intent to finish a
1475 send operation.
1586 send operation.
1476
1587
1477 ``buffercommands`` indicates whether sends should be buffered until the
1588 ``buffercommands`` indicates whether sends should be buffered until the
1478 last request has been issued.
1589 last request has been issued.
1479
1590
1480 ``clientcontentencoders`` is an iterable of content encoders the client
1591 ``clientcontentencoders`` is an iterable of content encoders the client
1481 will advertise to the server and that the server can use for encoding
1592 will advertise to the server and that the server can use for encoding
1482 data. If not defined, the client will not advertise content encoders
1593 data. If not defined, the client will not advertise content encoders
1483 to the server.
1594 to the server.
1484 """
1595 """
1485 self._ui = ui
1596 self._ui = ui
1486 self._hasmultiplesend = hasmultiplesend
1597 self._hasmultiplesend = hasmultiplesend
1487 self._buffersends = buffersends
1598 self._buffersends = buffersends
1488 self._clientcontentencoders = clientcontentencoders
1599 self._clientcontentencoders = clientcontentencoders
1489
1600
1490 self._canissuecommands = True
1601 self._canissuecommands = True
1491 self._cansend = True
1602 self._cansend = True
1492 self._protocolsettingssent = False
1603 self._protocolsettingssent = False
1493
1604
1494 self._nextrequestid = 1
1605 self._nextrequestid = 1
1495 # We only support a single outgoing stream for now.
1606 # We only support a single outgoing stream for now.
1496 self._outgoingstream = outputstream(1)
1607 self._outgoingstream = outputstream(1)
1497 self._pendingrequests = collections.deque()
1608 self._pendingrequests = collections.deque()
1498 self._activerequests = {}
1609 self._activerequests = {}
1499 self._incomingstreams = {}
1610 self._incomingstreams = {}
1500 self._streamsettingsdecoders = {}
1611 self._streamsettingsdecoders = {}
1501
1612
1502 populatestreamencoders()
1613 populatestreamencoders()
1503
1614
1504 def callcommand(self, name, args, datafh=None, redirect=None):
1615 def callcommand(self, name, args, datafh=None, redirect=None):
1505 """Request that a command be executed.
1616 """Request that a command be executed.
1506
1617
1507 Receives the command name, a dict of arguments to pass to the command,
1618 Receives the command name, a dict of arguments to pass to the command,
1508 and an optional file object containing the raw data for the command.
1619 and an optional file object containing the raw data for the command.
1509
1620
1510 Returns a 3-tuple of (request, action, action data).
1621 Returns a 3-tuple of (request, action, action data).
1511 """
1622 """
1512 if not self._canissuecommands:
1623 if not self._canissuecommands:
1513 raise error.ProgrammingError('cannot issue new commands')
1624 raise error.ProgrammingError('cannot issue new commands')
1514
1625
1515 requestid = self._nextrequestid
1626 requestid = self._nextrequestid
1516 self._nextrequestid += 2
1627 self._nextrequestid += 2
1517
1628
1518 request = commandrequest(requestid, name, args, datafh=datafh,
1629 request = commandrequest(requestid, name, args, datafh=datafh,
1519 redirect=redirect)
1630 redirect=redirect)
1520
1631
1521 if self._buffersends:
1632 if self._buffersends:
1522 self._pendingrequests.append(request)
1633 self._pendingrequests.append(request)
1523 return request, 'noop', {}
1634 return request, 'noop', {}
1524 else:
1635 else:
1525 if not self._cansend:
1636 if not self._cansend:
1526 raise error.ProgrammingError('sends cannot be performed on '
1637 raise error.ProgrammingError('sends cannot be performed on '
1527 'this instance')
1638 'this instance')
1528
1639
1529 if not self._hasmultiplesend:
1640 if not self._hasmultiplesend:
1530 self._cansend = False
1641 self._cansend = False
1531 self._canissuecommands = False
1642 self._canissuecommands = False
1532
1643
1533 return request, 'sendframes', {
1644 return request, 'sendframes', {
1534 'framegen': self._makecommandframes(request),
1645 'framegen': self._makecommandframes(request),
1535 }
1646 }
1536
1647
1537 def flushcommands(self):
1648 def flushcommands(self):
1538 """Request that all queued commands be sent.
1649 """Request that all queued commands be sent.
1539
1650
1540 If any commands are buffered, this will instruct the caller to send
1651 If any commands are buffered, this will instruct the caller to send
1541 them over the wire. If no commands are buffered it instructs the client
1652 them over the wire. If no commands are buffered it instructs the client
1542 to no-op.
1653 to no-op.
1543
1654
1544 If instances aren't configured for multiple sends, no new command
1655 If instances aren't configured for multiple sends, no new command
1545 requests are allowed after this is called.
1656 requests are allowed after this is called.
1546 """
1657 """
1547 if not self._pendingrequests:
1658 if not self._pendingrequests:
1548 return 'noop', {}
1659 return 'noop', {}
1549
1660
1550 if not self._cansend:
1661 if not self._cansend:
1551 raise error.ProgrammingError('sends cannot be performed on this '
1662 raise error.ProgrammingError('sends cannot be performed on this '
1552 'instance')
1663 'instance')
1553
1664
1554 # If the instance only allows sending once, mark that we have fired
1665 # If the instance only allows sending once, mark that we have fired
1555 # our one shot.
1666 # our one shot.
1556 if not self._hasmultiplesend:
1667 if not self._hasmultiplesend:
1557 self._canissuecommands = False
1668 self._canissuecommands = False
1558 self._cansend = False
1669 self._cansend = False
1559
1670
1560 def makeframes():
1671 def makeframes():
1561 while self._pendingrequests:
1672 while self._pendingrequests:
1562 request = self._pendingrequests.popleft()
1673 request = self._pendingrequests.popleft()
1563 for frame in self._makecommandframes(request):
1674 for frame in self._makecommandframes(request):
1564 yield frame
1675 yield frame
1565
1676
1566 return 'sendframes', {
1677 return 'sendframes', {
1567 'framegen': makeframes(),
1678 'framegen': makeframes(),
1568 }
1679 }
1569
1680
1570 def _makecommandframes(self, request):
1681 def _makecommandframes(self, request):
1571 """Emit frames to issue a command request.
1682 """Emit frames to issue a command request.
1572
1683
1573 As a side-effect, update request accounting to reflect its changed
1684 As a side-effect, update request accounting to reflect its changed
1574 state.
1685 state.
1575 """
1686 """
1576 self._activerequests[request.requestid] = request
1687 self._activerequests[request.requestid] = request
1577 request.state = 'sending'
1688 request.state = 'sending'
1578
1689
1579 if not self._protocolsettingssent and self._clientcontentencoders:
1690 if not self._protocolsettingssent and self._clientcontentencoders:
1580 self._protocolsettingssent = True
1691 self._protocolsettingssent = True
1581
1692
1582 payload = b''.join(cborutil.streamencode({
1693 payload = b''.join(cborutil.streamencode({
1583 b'contentencodings': self._clientcontentencoders,
1694 b'contentencodings': self._clientcontentencoders,
1584 }))
1695 }))
1585
1696
1586 yield self._outgoingstream.makeframe(
1697 yield self._outgoingstream.makeframe(
1587 requestid=request.requestid,
1698 requestid=request.requestid,
1588 typeid=FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
1699 typeid=FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
1589 flags=FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
1700 flags=FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
1590 payload=payload)
1701 payload=payload)
1591
1702
1592 res = createcommandframes(self._outgoingstream,
1703 res = createcommandframes(self._outgoingstream,
1593 request.requestid,
1704 request.requestid,
1594 request.name,
1705 request.name,
1595 request.args,
1706 request.args,
1596 datafh=request.datafh,
1707 datafh=request.datafh,
1597 redirect=request.redirect)
1708 redirect=request.redirect)
1598
1709
1599 for frame in res:
1710 for frame in res:
1600 yield frame
1711 yield frame
1601
1712
1602 request.state = 'sent'
1713 request.state = 'sent'
1603
1714
1604 def onframerecv(self, frame):
1715 def onframerecv(self, frame):
1605 """Process a frame that has been received off the wire.
1716 """Process a frame that has been received off the wire.
1606
1717
1607 Returns a 2-tuple of (action, meta) describing further action the
1718 Returns a 2-tuple of (action, meta) describing further action the
1608 caller needs to take as a result of receiving this frame.
1719 caller needs to take as a result of receiving this frame.
1609 """
1720 """
1610 if frame.streamid % 2:
1721 if frame.streamid % 2:
1611 return 'error', {
1722 return 'error', {
1612 'message': (
1723 'message': (
1613 _('received frame with odd numbered stream ID: %d') %
1724 _('received frame with odd numbered stream ID: %d') %
1614 frame.streamid),
1725 frame.streamid),
1615 }
1726 }
1616
1727
1617 if frame.streamid not in self._incomingstreams:
1728 if frame.streamid not in self._incomingstreams:
1618 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1729 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1619 return 'error', {
1730 return 'error', {
1620 'message': _('received frame on unknown stream '
1731 'message': _('received frame on unknown stream '
1621 'without beginning of stream flag set'),
1732 'without beginning of stream flag set'),
1622 }
1733 }
1623
1734
1624 self._incomingstreams[frame.streamid] = inputstream(
1735 self._incomingstreams[frame.streamid] = inputstream(
1625 frame.streamid)
1736 frame.streamid)
1626
1737
1627 stream = self._incomingstreams[frame.streamid]
1738 stream = self._incomingstreams[frame.streamid]
1628
1739
1629 # If the payload is encoded, ask the stream to decode it. We
1740 # If the payload is encoded, ask the stream to decode it. We
1630 # merely substitute the decoded result into the frame payload as
1741 # merely substitute the decoded result into the frame payload as
1631 # if it had been transferred all along.
1742 # if it had been transferred all along.
1632 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1743 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1633 frame.payload = stream.decode(frame.payload)
1744 frame.payload = stream.decode(frame.payload)
1634
1745
1635 if frame.streamflags & STREAM_FLAG_END_STREAM:
1746 if frame.streamflags & STREAM_FLAG_END_STREAM:
1636 del self._incomingstreams[frame.streamid]
1747 del self._incomingstreams[frame.streamid]
1637
1748
1638 if frame.typeid == FRAME_TYPE_STREAM_SETTINGS:
1749 if frame.typeid == FRAME_TYPE_STREAM_SETTINGS:
1639 return self._onstreamsettingsframe(frame)
1750 return self._onstreamsettingsframe(frame)
1640
1751
1641 if frame.requestid not in self._activerequests:
1752 if frame.requestid not in self._activerequests:
1642 return 'error', {
1753 return 'error', {
1643 'message': (_('received frame for inactive request ID: %d') %
1754 'message': (_('received frame for inactive request ID: %d') %
1644 frame.requestid),
1755 frame.requestid),
1645 }
1756 }
1646
1757
1647 request = self._activerequests[frame.requestid]
1758 request = self._activerequests[frame.requestid]
1648 request.state = 'receiving'
1759 request.state = 'receiving'
1649
1760
1650 handlers = {
1761 handlers = {
1651 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1762 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1652 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1763 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1653 }
1764 }
1654
1765
1655 meth = handlers.get(frame.typeid)
1766 meth = handlers.get(frame.typeid)
1656 if not meth:
1767 if not meth:
1657 raise error.ProgrammingError('unhandled frame type: %d' %
1768 raise error.ProgrammingError('unhandled frame type: %d' %
1658 frame.typeid)
1769 frame.typeid)
1659
1770
1660 return meth(request, frame)
1771 return meth(request, frame)
1661
1772
1662 def _onstreamsettingsframe(self, frame):
1773 def _onstreamsettingsframe(self, frame):
1663 assert frame.typeid == FRAME_TYPE_STREAM_SETTINGS
1774 assert frame.typeid == FRAME_TYPE_STREAM_SETTINGS
1664
1775
1665 more = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION
1776 more = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION
1666 eos = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_EOS
1777 eos = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_EOS
1667
1778
1668 if more and eos:
1779 if more and eos:
1669 return 'error', {
1780 return 'error', {
1670 'message': (_('stream encoding settings frame cannot have both '
1781 'message': (_('stream encoding settings frame cannot have both '
1671 'continuation and end of stream flags set')),
1782 'continuation and end of stream flags set')),
1672 }
1783 }
1673
1784
1674 if not more and not eos:
1785 if not more and not eos:
1675 return 'error', {
1786 return 'error', {
1676 'message': _('stream encoding settings frame must have '
1787 'message': _('stream encoding settings frame must have '
1677 'continuation or end of stream flag set'),
1788 'continuation or end of stream flag set'),
1678 }
1789 }
1679
1790
1680 if frame.streamid not in self._streamsettingsdecoders:
1791 if frame.streamid not in self._streamsettingsdecoders:
1681 decoder = cborutil.bufferingdecoder()
1792 decoder = cborutil.bufferingdecoder()
1682 self._streamsettingsdecoders[frame.streamid] = decoder
1793 self._streamsettingsdecoders[frame.streamid] = decoder
1683
1794
1684 decoder = self._streamsettingsdecoders[frame.streamid]
1795 decoder = self._streamsettingsdecoders[frame.streamid]
1685
1796
1686 try:
1797 try:
1687 decoder.decode(frame.payload)
1798 decoder.decode(frame.payload)
1688 except Exception as e:
1799 except Exception as e:
1689 return 'error', {
1800 return 'error', {
1690 'message': (_('error decoding CBOR from stream encoding '
1801 'message': (_('error decoding CBOR from stream encoding '
1691 'settings frame: %s') %
1802 'settings frame: %s') %
1692 stringutil.forcebytestr(e)),
1803 stringutil.forcebytestr(e)),
1693 }
1804 }
1694
1805
1695 if more:
1806 if more:
1696 return 'noop', {}
1807 return 'noop', {}
1697
1808
1698 assert eos
1809 assert eos
1699
1810
1700 decoded = decoder.getavailable()
1811 decoded = decoder.getavailable()
1701 del self._streamsettingsdecoders[frame.streamid]
1812 del self._streamsettingsdecoders[frame.streamid]
1702
1813
1703 if not decoded:
1814 if not decoded:
1704 return 'error', {
1815 return 'error', {
1705 'message': _('stream encoding settings frame did not contain '
1816 'message': _('stream encoding settings frame did not contain '
1706 'CBOR data'),
1817 'CBOR data'),
1707 }
1818 }
1708
1819
1709 try:
1820 try:
1710 self._incomingstreams[frame.streamid].setdecoder(self._ui,
1821 self._incomingstreams[frame.streamid].setdecoder(self._ui,
1711 decoded[0],
1822 decoded[0],
1712 decoded[1:])
1823 decoded[1:])
1713 except Exception as e:
1824 except Exception as e:
1714 return 'error', {
1825 return 'error', {
1715 'message': (_('error setting stream decoder: %s') %
1826 'message': (_('error setting stream decoder: %s') %
1716 stringutil.forcebytestr(e)),
1827 stringutil.forcebytestr(e)),
1717 }
1828 }
1718
1829
1719 return 'noop', {}
1830 return 'noop', {}
1720
1831
1721 def _oncommandresponseframe(self, request, frame):
1832 def _oncommandresponseframe(self, request, frame):
1722 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1833 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1723 request.state = 'received'
1834 request.state = 'received'
1724 del self._activerequests[request.requestid]
1835 del self._activerequests[request.requestid]
1725
1836
1726 return 'responsedata', {
1837 return 'responsedata', {
1727 'request': request,
1838 'request': request,
1728 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1839 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1729 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1840 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1730 'data': frame.payload,
1841 'data': frame.payload,
1731 }
1842 }
1732
1843
1733 def _onerrorresponseframe(self, request, frame):
1844 def _onerrorresponseframe(self, request, frame):
1734 request.state = 'errored'
1845 request.state = 'errored'
1735 del self._activerequests[request.requestid]
1846 del self._activerequests[request.requestid]
1736
1847
1737 # The payload should be a CBOR map.
1848 # The payload should be a CBOR map.
1738 m = cborutil.decodeall(frame.payload)[0]
1849 m = cborutil.decodeall(frame.payload)[0]
1739
1850
1740 return 'error', {
1851 return 'error', {
1741 'request': request,
1852 'request': request,
1742 'type': m['type'],
1853 'type': m['type'],
1743 'message': m['message'],
1854 'message': m['message'],
1744 }
1855 }
@@ -1,1187 +1,1192 b''
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import hashlib
10 import hashlib
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 hex,
14 hex,
15 nullid,
15 nullid,
16 )
16 )
17 from . import (
17 from . import (
18 discovery,
18 discovery,
19 encoding,
19 encoding,
20 error,
20 error,
21 narrowspec,
21 narrowspec,
22 pycompat,
22 pycompat,
23 wireprotoframing,
23 wireprotoframing,
24 wireprototypes,
24 wireprototypes,
25 )
25 )
26 from .utils import (
26 from .utils import (
27 cborutil,
27 cborutil,
28 interfaceutil,
28 interfaceutil,
29 stringutil,
29 stringutil,
30 )
30 )
31
31
32 FRAMINGTYPE = b'application/mercurial-exp-framing-0006'
32 FRAMINGTYPE = b'application/mercurial-exp-framing-0006'
33
33
34 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
34 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
35
35
36 COMMANDS = wireprototypes.commanddict()
36 COMMANDS = wireprototypes.commanddict()
37
37
38 # Value inserted into cache key computation function. Change the value to
38 # Value inserted into cache key computation function. Change the value to
39 # force new cache keys for every command request. This should be done when
39 # force new cache keys for every command request. This should be done when
40 # there is a change to how caching works, etc.
40 # there is a change to how caching works, etc.
41 GLOBAL_CACHE_VERSION = 1
41 GLOBAL_CACHE_VERSION = 1
42
42
43 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
43 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
44 from .hgweb import common as hgwebcommon
44 from .hgweb import common as hgwebcommon
45
45
46 # URL space looks like: <permissions>/<command>, where <permission> can
46 # URL space looks like: <permissions>/<command>, where <permission> can
47 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
47 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
48
48
49 # Root URL does nothing meaningful... yet.
49 # Root URL does nothing meaningful... yet.
50 if not urlparts:
50 if not urlparts:
51 res.status = b'200 OK'
51 res.status = b'200 OK'
52 res.headers[b'Content-Type'] = b'text/plain'
52 res.headers[b'Content-Type'] = b'text/plain'
53 res.setbodybytes(_('HTTP version 2 API handler'))
53 res.setbodybytes(_('HTTP version 2 API handler'))
54 return
54 return
55
55
56 if len(urlparts) == 1:
56 if len(urlparts) == 1:
57 res.status = b'404 Not Found'
57 res.status = b'404 Not Found'
58 res.headers[b'Content-Type'] = b'text/plain'
58 res.headers[b'Content-Type'] = b'text/plain'
59 res.setbodybytes(_('do not know how to process %s\n') %
59 res.setbodybytes(_('do not know how to process %s\n') %
60 req.dispatchpath)
60 req.dispatchpath)
61 return
61 return
62
62
63 permission, command = urlparts[0:2]
63 permission, command = urlparts[0:2]
64
64
65 if permission not in (b'ro', b'rw'):
65 if permission not in (b'ro', b'rw'):
66 res.status = b'404 Not Found'
66 res.status = b'404 Not Found'
67 res.headers[b'Content-Type'] = b'text/plain'
67 res.headers[b'Content-Type'] = b'text/plain'
68 res.setbodybytes(_('unknown permission: %s') % permission)
68 res.setbodybytes(_('unknown permission: %s') % permission)
69 return
69 return
70
70
71 if req.method != 'POST':
71 if req.method != 'POST':
72 res.status = b'405 Method Not Allowed'
72 res.status = b'405 Method Not Allowed'
73 res.headers[b'Allow'] = b'POST'
73 res.headers[b'Allow'] = b'POST'
74 res.setbodybytes(_('commands require POST requests'))
74 res.setbodybytes(_('commands require POST requests'))
75 return
75 return
76
76
77 # At some point we'll want to use our own API instead of recycling the
77 # At some point we'll want to use our own API instead of recycling the
78 # behavior of version 1 of the wire protocol...
78 # behavior of version 1 of the wire protocol...
79 # TODO return reasonable responses - not responses that overload the
79 # TODO return reasonable responses - not responses that overload the
80 # HTTP status line message for error reporting.
80 # HTTP status line message for error reporting.
81 try:
81 try:
82 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
82 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
83 except hgwebcommon.ErrorResponse as e:
83 except hgwebcommon.ErrorResponse as e:
84 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
84 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
85 for k, v in e.headers:
85 for k, v in e.headers:
86 res.headers[k] = v
86 res.headers[k] = v
87 res.setbodybytes('permission denied')
87 res.setbodybytes('permission denied')
88 return
88 return
89
89
90 # We have a special endpoint to reflect the request back at the client.
90 # We have a special endpoint to reflect the request back at the client.
91 if command == b'debugreflect':
91 if command == b'debugreflect':
92 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
92 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
93 return
93 return
94
94
95 # Extra commands that we handle that aren't really wire protocol
95 # Extra commands that we handle that aren't really wire protocol
96 # commands. Think extra hard before making this hackery available to
96 # commands. Think extra hard before making this hackery available to
97 # extension.
97 # extension.
98 extracommands = {'multirequest'}
98 extracommands = {'multirequest'}
99
99
100 if command not in COMMANDS and command not in extracommands:
100 if command not in COMMANDS and command not in extracommands:
101 res.status = b'404 Not Found'
101 res.status = b'404 Not Found'
102 res.headers[b'Content-Type'] = b'text/plain'
102 res.headers[b'Content-Type'] = b'text/plain'
103 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
103 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
104 return
104 return
105
105
106 repo = rctx.repo
106 repo = rctx.repo
107 ui = repo.ui
107 ui = repo.ui
108
108
109 proto = httpv2protocolhandler(req, ui)
109 proto = httpv2protocolhandler(req, ui)
110
110
111 if (not COMMANDS.commandavailable(command, proto)
111 if (not COMMANDS.commandavailable(command, proto)
112 and command not in extracommands):
112 and command not in extracommands):
113 res.status = b'404 Not Found'
113 res.status = b'404 Not Found'
114 res.headers[b'Content-Type'] = b'text/plain'
114 res.headers[b'Content-Type'] = b'text/plain'
115 res.setbodybytes(_('invalid wire protocol command: %s') % command)
115 res.setbodybytes(_('invalid wire protocol command: %s') % command)
116 return
116 return
117
117
118 # TODO consider cases where proxies may add additional Accept headers.
118 # TODO consider cases where proxies may add additional Accept headers.
119 if req.headers.get(b'Accept') != FRAMINGTYPE:
119 if req.headers.get(b'Accept') != FRAMINGTYPE:
120 res.status = b'406 Not Acceptable'
120 res.status = b'406 Not Acceptable'
121 res.headers[b'Content-Type'] = b'text/plain'
121 res.headers[b'Content-Type'] = b'text/plain'
122 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
122 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
123 % FRAMINGTYPE)
123 % FRAMINGTYPE)
124 return
124 return
125
125
126 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
126 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
127 res.status = b'415 Unsupported Media Type'
127 res.status = b'415 Unsupported Media Type'
128 # TODO we should send a response with appropriate media type,
128 # TODO we should send a response with appropriate media type,
129 # since client does Accept it.
129 # since client does Accept it.
130 res.headers[b'Content-Type'] = b'text/plain'
130 res.headers[b'Content-Type'] = b'text/plain'
131 res.setbodybytes(_('client MUST send Content-Type header with '
131 res.setbodybytes(_('client MUST send Content-Type header with '
132 'value: %s\n') % FRAMINGTYPE)
132 'value: %s\n') % FRAMINGTYPE)
133 return
133 return
134
134
135 _processhttpv2request(ui, repo, req, res, permission, command, proto)
135 _processhttpv2request(ui, repo, req, res, permission, command, proto)
136
136
137 def _processhttpv2reflectrequest(ui, repo, req, res):
137 def _processhttpv2reflectrequest(ui, repo, req, res):
138 """Reads unified frame protocol request and dumps out state to client.
138 """Reads unified frame protocol request and dumps out state to client.
139
139
140 This special endpoint can be used to help debug the wire protocol.
140 This special endpoint can be used to help debug the wire protocol.
141
141
142 Instead of routing the request through the normal dispatch mechanism,
142 Instead of routing the request through the normal dispatch mechanism,
143 we instead read all frames, decode them, and feed them into our state
143 we instead read all frames, decode them, and feed them into our state
144 tracker. We then dump the log of all that activity back out to the
144 tracker. We then dump the log of all that activity back out to the
145 client.
145 client.
146 """
146 """
147 import json
147 import json
148
148
149 # Reflection APIs have a history of being abused, accidentally disclosing
149 # Reflection APIs have a history of being abused, accidentally disclosing
150 # sensitive data, etc. So we have a config knob.
150 # sensitive data, etc. So we have a config knob.
151 if not ui.configbool('experimental', 'web.api.debugreflect'):
151 if not ui.configbool('experimental', 'web.api.debugreflect'):
152 res.status = b'404 Not Found'
152 res.status = b'404 Not Found'
153 res.headers[b'Content-Type'] = b'text/plain'
153 res.headers[b'Content-Type'] = b'text/plain'
154 res.setbodybytes(_('debugreflect service not available'))
154 res.setbodybytes(_('debugreflect service not available'))
155 return
155 return
156
156
157 # We assume we have a unified framing protocol request body.
157 # We assume we have a unified framing protocol request body.
158
158
159 reactor = wireprotoframing.serverreactor(ui)
159 reactor = wireprotoframing.serverreactor(ui)
160 states = []
160 states = []
161
161
162 while True:
162 while True:
163 frame = wireprotoframing.readframe(req.bodyfh)
163 frame = wireprotoframing.readframe(req.bodyfh)
164
164
165 if not frame:
165 if not frame:
166 states.append(b'received: <no frame>')
166 states.append(b'received: <no frame>')
167 break
167 break
168
168
169 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
169 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
170 frame.requestid,
170 frame.requestid,
171 frame.payload))
171 frame.payload))
172
172
173 action, meta = reactor.onframerecv(frame)
173 action, meta = reactor.onframerecv(frame)
174 states.append(json.dumps((action, meta), sort_keys=True,
174 states.append(json.dumps((action, meta), sort_keys=True,
175 separators=(', ', ': ')))
175 separators=(', ', ': ')))
176
176
177 action, meta = reactor.oninputeof()
177 action, meta = reactor.oninputeof()
178 meta['action'] = action
178 meta['action'] = action
179 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
179 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
180
180
181 res.status = b'200 OK'
181 res.status = b'200 OK'
182 res.headers[b'Content-Type'] = b'text/plain'
182 res.headers[b'Content-Type'] = b'text/plain'
183 res.setbodybytes(b'\n'.join(states))
183 res.setbodybytes(b'\n'.join(states))
184
184
185 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
185 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
186 """Post-validation handler for HTTPv2 requests.
186 """Post-validation handler for HTTPv2 requests.
187
187
188 Called when the HTTP request contains unified frame-based protocol
188 Called when the HTTP request contains unified frame-based protocol
189 frames for evaluation.
189 frames for evaluation.
190 """
190 """
191 # TODO Some HTTP clients are full duplex and can receive data before
191 # TODO Some HTTP clients are full duplex and can receive data before
192 # the entire request is transmitted. Figure out a way to indicate support
192 # the entire request is transmitted. Figure out a way to indicate support
193 # for that so we can opt into full duplex mode.
193 # for that so we can opt into full duplex mode.
194 reactor = wireprotoframing.serverreactor(ui, deferoutput=True)
194 reactor = wireprotoframing.serverreactor(ui, deferoutput=True)
195 seencommand = False
195 seencommand = False
196
196
197 outstream = reactor.makeoutputstream()
197 outstream = None
198
198
199 while True:
199 while True:
200 frame = wireprotoframing.readframe(req.bodyfh)
200 frame = wireprotoframing.readframe(req.bodyfh)
201 if not frame:
201 if not frame:
202 break
202 break
203
203
204 action, meta = reactor.onframerecv(frame)
204 action, meta = reactor.onframerecv(frame)
205
205
206 if action == 'wantframe':
206 if action == 'wantframe':
207 # Need more data before we can do anything.
207 # Need more data before we can do anything.
208 continue
208 continue
209 elif action == 'runcommand':
209 elif action == 'runcommand':
210 # Defer creating output stream because we need to wait for
211 # protocol settings frames so proper encoding can be applied.
212 if not outstream:
213 outstream = reactor.makeoutputstream()
214
210 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
215 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
211 reqcommand, reactor, outstream,
216 reqcommand, reactor, outstream,
212 meta, issubsequent=seencommand)
217 meta, issubsequent=seencommand)
213
218
214 if sentoutput:
219 if sentoutput:
215 return
220 return
216
221
217 seencommand = True
222 seencommand = True
218
223
219 elif action == 'error':
224 elif action == 'error':
220 # TODO define proper error mechanism.
225 # TODO define proper error mechanism.
221 res.status = b'200 OK'
226 res.status = b'200 OK'
222 res.headers[b'Content-Type'] = b'text/plain'
227 res.headers[b'Content-Type'] = b'text/plain'
223 res.setbodybytes(meta['message'] + b'\n')
228 res.setbodybytes(meta['message'] + b'\n')
224 return
229 return
225 else:
230 else:
226 raise error.ProgrammingError(
231 raise error.ProgrammingError(
227 'unhandled action from frame processor: %s' % action)
232 'unhandled action from frame processor: %s' % action)
228
233
229 action, meta = reactor.oninputeof()
234 action, meta = reactor.oninputeof()
230 if action == 'sendframes':
235 if action == 'sendframes':
231 # We assume we haven't started sending the response yet. If we're
236 # We assume we haven't started sending the response yet. If we're
232 # wrong, the response type will raise an exception.
237 # wrong, the response type will raise an exception.
233 res.status = b'200 OK'
238 res.status = b'200 OK'
234 res.headers[b'Content-Type'] = FRAMINGTYPE
239 res.headers[b'Content-Type'] = FRAMINGTYPE
235 res.setbodygen(meta['framegen'])
240 res.setbodygen(meta['framegen'])
236 elif action == 'noop':
241 elif action == 'noop':
237 pass
242 pass
238 else:
243 else:
239 raise error.ProgrammingError('unhandled action from frame processor: %s'
244 raise error.ProgrammingError('unhandled action from frame processor: %s'
240 % action)
245 % action)
241
246
242 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
247 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
243 outstream, command, issubsequent):
248 outstream, command, issubsequent):
244 """Dispatch a wire protocol command made from HTTPv2 requests.
249 """Dispatch a wire protocol command made from HTTPv2 requests.
245
250
246 The authenticated permission (``authedperm``) along with the original
251 The authenticated permission (``authedperm``) along with the original
247 command from the URL (``reqcommand``) are passed in.
252 command from the URL (``reqcommand``) are passed in.
248 """
253 """
249 # We already validated that the session has permissions to perform the
254 # We already validated that the session has permissions to perform the
250 # actions in ``authedperm``. In the unified frame protocol, the canonical
255 # actions in ``authedperm``. In the unified frame protocol, the canonical
251 # command to run is expressed in a frame. However, the URL also requested
256 # command to run is expressed in a frame. However, the URL also requested
252 # to run a specific command. We need to be careful that the command we
257 # to run a specific command. We need to be careful that the command we
253 # run doesn't have permissions requirements greater than what was granted
258 # run doesn't have permissions requirements greater than what was granted
254 # by ``authedperm``.
259 # by ``authedperm``.
255 #
260 #
256 # Our rule for this is we only allow one command per HTTP request and
261 # Our rule for this is we only allow one command per HTTP request and
257 # that command must match the command in the URL. However, we make
262 # that command must match the command in the URL. However, we make
258 # an exception for the ``multirequest`` URL. This URL is allowed to
263 # an exception for the ``multirequest`` URL. This URL is allowed to
259 # execute multiple commands. We double check permissions of each command
264 # execute multiple commands. We double check permissions of each command
260 # as it is invoked to ensure there is no privilege escalation.
265 # as it is invoked to ensure there is no privilege escalation.
261 # TODO consider allowing multiple commands to regular command URLs
266 # TODO consider allowing multiple commands to regular command URLs
262 # iff each command is the same.
267 # iff each command is the same.
263
268
264 proto = httpv2protocolhandler(req, ui, args=command['args'])
269 proto = httpv2protocolhandler(req, ui, args=command['args'])
265
270
266 if reqcommand == b'multirequest':
271 if reqcommand == b'multirequest':
267 if not COMMANDS.commandavailable(command['command'], proto):
272 if not COMMANDS.commandavailable(command['command'], proto):
268 # TODO proper error mechanism
273 # TODO proper error mechanism
269 res.status = b'200 OK'
274 res.status = b'200 OK'
270 res.headers[b'Content-Type'] = b'text/plain'
275 res.headers[b'Content-Type'] = b'text/plain'
271 res.setbodybytes(_('wire protocol command not available: %s') %
276 res.setbodybytes(_('wire protocol command not available: %s') %
272 command['command'])
277 command['command'])
273 return True
278 return True
274
279
275 # TODO don't use assert here, since it may be elided by -O.
280 # TODO don't use assert here, since it may be elided by -O.
276 assert authedperm in (b'ro', b'rw')
281 assert authedperm in (b'ro', b'rw')
277 wirecommand = COMMANDS[command['command']]
282 wirecommand = COMMANDS[command['command']]
278 assert wirecommand.permission in ('push', 'pull')
283 assert wirecommand.permission in ('push', 'pull')
279
284
280 if authedperm == b'ro' and wirecommand.permission != 'pull':
285 if authedperm == b'ro' and wirecommand.permission != 'pull':
281 # TODO proper error mechanism
286 # TODO proper error mechanism
282 res.status = b'403 Forbidden'
287 res.status = b'403 Forbidden'
283 res.headers[b'Content-Type'] = b'text/plain'
288 res.headers[b'Content-Type'] = b'text/plain'
284 res.setbodybytes(_('insufficient permissions to execute '
289 res.setbodybytes(_('insufficient permissions to execute '
285 'command: %s') % command['command'])
290 'command: %s') % command['command'])
286 return True
291 return True
287
292
288 # TODO should we also call checkperm() here? Maybe not if we're going
293 # TODO should we also call checkperm() here? Maybe not if we're going
289 # to overhaul that API. The granted scope from the URL check should
294 # to overhaul that API. The granted scope from the URL check should
290 # be good enough.
295 # be good enough.
291
296
292 else:
297 else:
293 # Don't allow multiple commands outside of ``multirequest`` URL.
298 # Don't allow multiple commands outside of ``multirequest`` URL.
294 if issubsequent:
299 if issubsequent:
295 # TODO proper error mechanism
300 # TODO proper error mechanism
296 res.status = b'200 OK'
301 res.status = b'200 OK'
297 res.headers[b'Content-Type'] = b'text/plain'
302 res.headers[b'Content-Type'] = b'text/plain'
298 res.setbodybytes(_('multiple commands cannot be issued to this '
303 res.setbodybytes(_('multiple commands cannot be issued to this '
299 'URL'))
304 'URL'))
300 return True
305 return True
301
306
302 if reqcommand != command['command']:
307 if reqcommand != command['command']:
303 # TODO define proper error mechanism
308 # TODO define proper error mechanism
304 res.status = b'200 OK'
309 res.status = b'200 OK'
305 res.headers[b'Content-Type'] = b'text/plain'
310 res.headers[b'Content-Type'] = b'text/plain'
306 res.setbodybytes(_('command in frame must match command in URL'))
311 res.setbodybytes(_('command in frame must match command in URL'))
307 return True
312 return True
308
313
309 res.status = b'200 OK'
314 res.status = b'200 OK'
310 res.headers[b'Content-Type'] = FRAMINGTYPE
315 res.headers[b'Content-Type'] = FRAMINGTYPE
311
316
312 try:
317 try:
313 objs = dispatch(repo, proto, command['command'], command['redirect'])
318 objs = dispatch(repo, proto, command['command'], command['redirect'])
314
319
315 action, meta = reactor.oncommandresponsereadyobjects(
320 action, meta = reactor.oncommandresponsereadyobjects(
316 outstream, command['requestid'], objs)
321 outstream, command['requestid'], objs)
317
322
318 except error.WireprotoCommandError as e:
323 except error.WireprotoCommandError as e:
319 action, meta = reactor.oncommanderror(
324 action, meta = reactor.oncommanderror(
320 outstream, command['requestid'], e.message, e.messageargs)
325 outstream, command['requestid'], e.message, e.messageargs)
321
326
322 except Exception as e:
327 except Exception as e:
323 action, meta = reactor.onservererror(
328 action, meta = reactor.onservererror(
324 outstream, command['requestid'],
329 outstream, command['requestid'],
325 _('exception when invoking command: %s') %
330 _('exception when invoking command: %s') %
326 stringutil.forcebytestr(e))
331 stringutil.forcebytestr(e))
327
332
328 if action == 'sendframes':
333 if action == 'sendframes':
329 res.setbodygen(meta['framegen'])
334 res.setbodygen(meta['framegen'])
330 return True
335 return True
331 elif action == 'noop':
336 elif action == 'noop':
332 return False
337 return False
333 else:
338 else:
334 raise error.ProgrammingError('unhandled event from reactor: %s' %
339 raise error.ProgrammingError('unhandled event from reactor: %s' %
335 action)
340 action)
336
341
337 def getdispatchrepo(repo, proto, command):
342 def getdispatchrepo(repo, proto, command):
338 return repo.filtered('served')
343 return repo.filtered('served')
339
344
340 def dispatch(repo, proto, command, redirect):
345 def dispatch(repo, proto, command, redirect):
341 """Run a wire protocol command.
346 """Run a wire protocol command.
342
347
343 Returns an iterable of objects that will be sent to the client.
348 Returns an iterable of objects that will be sent to the client.
344 """
349 """
345 repo = getdispatchrepo(repo, proto, command)
350 repo = getdispatchrepo(repo, proto, command)
346
351
347 entry = COMMANDS[command]
352 entry = COMMANDS[command]
348 func = entry.func
353 func = entry.func
349 spec = entry.args
354 spec = entry.args
350
355
351 args = proto.getargs(spec)
356 args = proto.getargs(spec)
352
357
353 # There is some duplicate boilerplate code here for calling the command and
358 # There is some duplicate boilerplate code here for calling the command and
354 # emitting objects. It is either that or a lot of indented code that looks
359 # emitting objects. It is either that or a lot of indented code that looks
355 # like a pyramid (since there are a lot of code paths that result in not
360 # like a pyramid (since there are a lot of code paths that result in not
356 # using the cacher).
361 # using the cacher).
357 callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args))
362 callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args))
358
363
359 # Request is not cacheable. Don't bother instantiating a cacher.
364 # Request is not cacheable. Don't bother instantiating a cacher.
360 if not entry.cachekeyfn:
365 if not entry.cachekeyfn:
361 for o in callcommand():
366 for o in callcommand():
362 yield o
367 yield o
363 return
368 return
364
369
365 if redirect:
370 if redirect:
366 redirecttargets = redirect[b'targets']
371 redirecttargets = redirect[b'targets']
367 redirecthashes = redirect[b'hashes']
372 redirecthashes = redirect[b'hashes']
368 else:
373 else:
369 redirecttargets = []
374 redirecttargets = []
370 redirecthashes = []
375 redirecthashes = []
371
376
372 cacher = makeresponsecacher(repo, proto, command, args,
377 cacher = makeresponsecacher(repo, proto, command, args,
373 cborutil.streamencode,
378 cborutil.streamencode,
374 redirecttargets=redirecttargets,
379 redirecttargets=redirecttargets,
375 redirecthashes=redirecthashes)
380 redirecthashes=redirecthashes)
376
381
377 # But we have no cacher. Do default handling.
382 # But we have no cacher. Do default handling.
378 if not cacher:
383 if not cacher:
379 for o in callcommand():
384 for o in callcommand():
380 yield o
385 yield o
381 return
386 return
382
387
383 with cacher:
388 with cacher:
384 cachekey = entry.cachekeyfn(repo, proto, cacher, **args)
389 cachekey = entry.cachekeyfn(repo, proto, cacher, **args)
385
390
386 # No cache key or the cacher doesn't like it. Do default handling.
391 # No cache key or the cacher doesn't like it. Do default handling.
387 if cachekey is None or not cacher.setcachekey(cachekey):
392 if cachekey is None or not cacher.setcachekey(cachekey):
388 for o in callcommand():
393 for o in callcommand():
389 yield o
394 yield o
390 return
395 return
391
396
392 # Serve it from the cache, if possible.
397 # Serve it from the cache, if possible.
393 cached = cacher.lookup()
398 cached = cacher.lookup()
394
399
395 if cached:
400 if cached:
396 for o in cached['objs']:
401 for o in cached['objs']:
397 yield o
402 yield o
398 return
403 return
399
404
400 # Else call the command and feed its output into the cacher, allowing
405 # Else call the command and feed its output into the cacher, allowing
401 # the cacher to buffer/mutate objects as it desires.
406 # the cacher to buffer/mutate objects as it desires.
402 for o in callcommand():
407 for o in callcommand():
403 for o in cacher.onobject(o):
408 for o in cacher.onobject(o):
404 yield o
409 yield o
405
410
406 for o in cacher.onfinished():
411 for o in cacher.onfinished():
407 yield o
412 yield o
408
413
409 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
414 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
410 class httpv2protocolhandler(object):
415 class httpv2protocolhandler(object):
411 def __init__(self, req, ui, args=None):
416 def __init__(self, req, ui, args=None):
412 self._req = req
417 self._req = req
413 self._ui = ui
418 self._ui = ui
414 self._args = args
419 self._args = args
415
420
416 @property
421 @property
417 def name(self):
422 def name(self):
418 return HTTP_WIREPROTO_V2
423 return HTTP_WIREPROTO_V2
419
424
420 def getargs(self, args):
425 def getargs(self, args):
421 # First look for args that were passed but aren't registered on this
426 # First look for args that were passed but aren't registered on this
422 # command.
427 # command.
423 extra = set(self._args) - set(args)
428 extra = set(self._args) - set(args)
424 if extra:
429 if extra:
425 raise error.WireprotoCommandError(
430 raise error.WireprotoCommandError(
426 'unsupported argument to command: %s' %
431 'unsupported argument to command: %s' %
427 ', '.join(sorted(extra)))
432 ', '.join(sorted(extra)))
428
433
429 # And look for required arguments that are missing.
434 # And look for required arguments that are missing.
430 missing = {a for a in args if args[a]['required']} - set(self._args)
435 missing = {a for a in args if args[a]['required']} - set(self._args)
431
436
432 if missing:
437 if missing:
433 raise error.WireprotoCommandError(
438 raise error.WireprotoCommandError(
434 'missing required arguments: %s' % ', '.join(sorted(missing)))
439 'missing required arguments: %s' % ', '.join(sorted(missing)))
435
440
436 # Now derive the arguments to pass to the command, taking into
441 # Now derive the arguments to pass to the command, taking into
437 # account the arguments specified by the client.
442 # account the arguments specified by the client.
438 data = {}
443 data = {}
439 for k, meta in sorted(args.items()):
444 for k, meta in sorted(args.items()):
440 # This argument wasn't passed by the client.
445 # This argument wasn't passed by the client.
441 if k not in self._args:
446 if k not in self._args:
442 data[k] = meta['default']()
447 data[k] = meta['default']()
443 continue
448 continue
444
449
445 v = self._args[k]
450 v = self._args[k]
446
451
447 # Sets may be expressed as lists. Silently normalize.
452 # Sets may be expressed as lists. Silently normalize.
448 if meta['type'] == 'set' and isinstance(v, list):
453 if meta['type'] == 'set' and isinstance(v, list):
449 v = set(v)
454 v = set(v)
450
455
451 # TODO consider more/stronger type validation.
456 # TODO consider more/stronger type validation.
452
457
453 data[k] = v
458 data[k] = v
454
459
455 return data
460 return data
456
461
457 def getprotocaps(self):
462 def getprotocaps(self):
458 # Protocol capabilities are currently not implemented for HTTP V2.
463 # Protocol capabilities are currently not implemented for HTTP V2.
459 return set()
464 return set()
460
465
461 def getpayload(self):
466 def getpayload(self):
462 raise NotImplementedError
467 raise NotImplementedError
463
468
464 @contextlib.contextmanager
469 @contextlib.contextmanager
465 def mayberedirectstdio(self):
470 def mayberedirectstdio(self):
466 raise NotImplementedError
471 raise NotImplementedError
467
472
468 def client(self):
473 def client(self):
469 raise NotImplementedError
474 raise NotImplementedError
470
475
471 def addcapabilities(self, repo, caps):
476 def addcapabilities(self, repo, caps):
472 return caps
477 return caps
473
478
474 def checkperm(self, perm):
479 def checkperm(self, perm):
475 raise NotImplementedError
480 raise NotImplementedError
476
481
477 def httpv2apidescriptor(req, repo):
482 def httpv2apidescriptor(req, repo):
478 proto = httpv2protocolhandler(req, repo.ui)
483 proto = httpv2protocolhandler(req, repo.ui)
479
484
480 return _capabilitiesv2(repo, proto)
485 return _capabilitiesv2(repo, proto)
481
486
482 def _capabilitiesv2(repo, proto):
487 def _capabilitiesv2(repo, proto):
483 """Obtain the set of capabilities for version 2 transports.
488 """Obtain the set of capabilities for version 2 transports.
484
489
485 These capabilities are distinct from the capabilities for version 1
490 These capabilities are distinct from the capabilities for version 1
486 transports.
491 transports.
487 """
492 """
488 caps = {
493 caps = {
489 'commands': {},
494 'commands': {},
490 'framingmediatypes': [FRAMINGTYPE],
495 'framingmediatypes': [FRAMINGTYPE],
491 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
496 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
492 }
497 }
493
498
494 for command, entry in COMMANDS.items():
499 for command, entry in COMMANDS.items():
495 args = {}
500 args = {}
496
501
497 for arg, meta in entry.args.items():
502 for arg, meta in entry.args.items():
498 args[arg] = {
503 args[arg] = {
499 # TODO should this be a normalized type using CBOR's
504 # TODO should this be a normalized type using CBOR's
500 # terminology?
505 # terminology?
501 b'type': meta['type'],
506 b'type': meta['type'],
502 b'required': meta['required'],
507 b'required': meta['required'],
503 }
508 }
504
509
505 if not meta['required']:
510 if not meta['required']:
506 args[arg][b'default'] = meta['default']()
511 args[arg][b'default'] = meta['default']()
507
512
508 if meta['validvalues']:
513 if meta['validvalues']:
509 args[arg][b'validvalues'] = meta['validvalues']
514 args[arg][b'validvalues'] = meta['validvalues']
510
515
511 caps['commands'][command] = {
516 caps['commands'][command] = {
512 'args': args,
517 'args': args,
513 'permissions': [entry.permission],
518 'permissions': [entry.permission],
514 }
519 }
515
520
516 caps['rawrepoformats'] = sorted(repo.requirements &
521 caps['rawrepoformats'] = sorted(repo.requirements &
517 repo.supportedformats)
522 repo.supportedformats)
518
523
519 targets = getadvertisedredirecttargets(repo, proto)
524 targets = getadvertisedredirecttargets(repo, proto)
520 if targets:
525 if targets:
521 caps[b'redirect'] = {
526 caps[b'redirect'] = {
522 b'targets': [],
527 b'targets': [],
523 b'hashes': [b'sha256', b'sha1'],
528 b'hashes': [b'sha256', b'sha1'],
524 }
529 }
525
530
526 for target in targets:
531 for target in targets:
527 entry = {
532 entry = {
528 b'name': target['name'],
533 b'name': target['name'],
529 b'protocol': target['protocol'],
534 b'protocol': target['protocol'],
530 b'uris': target['uris'],
535 b'uris': target['uris'],
531 }
536 }
532
537
533 for key in ('snirequired', 'tlsversions'):
538 for key in ('snirequired', 'tlsversions'):
534 if key in target:
539 if key in target:
535 entry[key] = target[key]
540 entry[key] = target[key]
536
541
537 caps[b'redirect'][b'targets'].append(entry)
542 caps[b'redirect'][b'targets'].append(entry)
538
543
539 return proto.addcapabilities(repo, caps)
544 return proto.addcapabilities(repo, caps)
540
545
541 def getadvertisedredirecttargets(repo, proto):
546 def getadvertisedredirecttargets(repo, proto):
542 """Obtain a list of content redirect targets.
547 """Obtain a list of content redirect targets.
543
548
544 Returns a list containing potential redirect targets that will be
549 Returns a list containing potential redirect targets that will be
545 advertised in capabilities data. Each dict MUST have the following
550 advertised in capabilities data. Each dict MUST have the following
546 keys:
551 keys:
547
552
548 name
553 name
549 The name of this redirect target. This is the identifier clients use
554 The name of this redirect target. This is the identifier clients use
550 to refer to a target. It is transferred as part of every command
555 to refer to a target. It is transferred as part of every command
551 request.
556 request.
552
557
553 protocol
558 protocol
554 Network protocol used by this target. Typically this is the string
559 Network protocol used by this target. Typically this is the string
555 in front of the ``://`` in a URL. e.g. ``https``.
560 in front of the ``://`` in a URL. e.g. ``https``.
556
561
557 uris
562 uris
558 List of representative URIs for this target. Clients can use the
563 List of representative URIs for this target. Clients can use the
559 URIs to test parsing for compatibility or for ordering preference
564 URIs to test parsing for compatibility or for ordering preference
560 for which target to use.
565 for which target to use.
561
566
562 The following optional keys are recognized:
567 The following optional keys are recognized:
563
568
564 snirequired
569 snirequired
565 Bool indicating if Server Name Indication (SNI) is required to
570 Bool indicating if Server Name Indication (SNI) is required to
566 connect to this target.
571 connect to this target.
567
572
568 tlsversions
573 tlsversions
569 List of bytes indicating which TLS versions are supported by this
574 List of bytes indicating which TLS versions are supported by this
570 target.
575 target.
571
576
572 By default, clients reflect the target order advertised by servers
577 By default, clients reflect the target order advertised by servers
573 and servers will use the first client-advertised target when picking
578 and servers will use the first client-advertised target when picking
574 a redirect target. So targets should be advertised in the order the
579 a redirect target. So targets should be advertised in the order the
575 server prefers they be used.
580 server prefers they be used.
576 """
581 """
577 return []
582 return []
578
583
579 def wireprotocommand(name, args=None, permission='push', cachekeyfn=None):
584 def wireprotocommand(name, args=None, permission='push', cachekeyfn=None):
580 """Decorator to declare a wire protocol command.
585 """Decorator to declare a wire protocol command.
581
586
582 ``name`` is the name of the wire protocol command being provided.
587 ``name`` is the name of the wire protocol command being provided.
583
588
584 ``args`` is a dict defining arguments accepted by the command. Keys are
589 ``args`` is a dict defining arguments accepted by the command. Keys are
585 the argument name. Values are dicts with the following keys:
590 the argument name. Values are dicts with the following keys:
586
591
587 ``type``
592 ``type``
588 The argument data type. Must be one of the following string
593 The argument data type. Must be one of the following string
589 literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
594 literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
590 or ``bool``.
595 or ``bool``.
591
596
592 ``default``
597 ``default``
593 A callable returning the default value for this argument. If not
598 A callable returning the default value for this argument. If not
594 specified, ``None`` will be the default value.
599 specified, ``None`` will be the default value.
595
600
596 ``example``
601 ``example``
597 An example value for this argument.
602 An example value for this argument.
598
603
599 ``validvalues``
604 ``validvalues``
600 Set of recognized values for this argument.
605 Set of recognized values for this argument.
601
606
602 ``permission`` defines the permission type needed to run this command.
607 ``permission`` defines the permission type needed to run this command.
603 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
608 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
604 respectively. Default is to assume command requires ``push`` permissions
609 respectively. Default is to assume command requires ``push`` permissions
605 because otherwise commands not declaring their permissions could modify
610 because otherwise commands not declaring their permissions could modify
606 a repository that is supposed to be read-only.
611 a repository that is supposed to be read-only.
607
612
608 ``cachekeyfn`` defines an optional callable that can derive the
613 ``cachekeyfn`` defines an optional callable that can derive the
609 cache key for this request.
614 cache key for this request.
610
615
611 Wire protocol commands are generators of objects to be serialized and
616 Wire protocol commands are generators of objects to be serialized and
612 sent to the client.
617 sent to the client.
613
618
614 If a command raises an uncaught exception, this will be translated into
619 If a command raises an uncaught exception, this will be translated into
615 a command error.
620 a command error.
616
621
617 All commands can opt in to being cacheable by defining a function
622 All commands can opt in to being cacheable by defining a function
618 (``cachekeyfn``) that is called to derive a cache key. This function
623 (``cachekeyfn``) that is called to derive a cache key. This function
619 receives the same arguments as the command itself plus a ``cacher``
624 receives the same arguments as the command itself plus a ``cacher``
620 argument containing the active cacher for the request and returns a bytes
625 argument containing the active cacher for the request and returns a bytes
621 containing the key in a cache the response to this command may be cached
626 containing the key in a cache the response to this command may be cached
622 under.
627 under.
623 """
628 """
624 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
629 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
625 if v['version'] == 2}
630 if v['version'] == 2}
626
631
627 if permission not in ('push', 'pull'):
632 if permission not in ('push', 'pull'):
628 raise error.ProgrammingError('invalid wire protocol permission; '
633 raise error.ProgrammingError('invalid wire protocol permission; '
629 'got %s; expected "push" or "pull"' %
634 'got %s; expected "push" or "pull"' %
630 permission)
635 permission)
631
636
632 if args is None:
637 if args is None:
633 args = {}
638 args = {}
634
639
635 if not isinstance(args, dict):
640 if not isinstance(args, dict):
636 raise error.ProgrammingError('arguments for version 2 commands '
641 raise error.ProgrammingError('arguments for version 2 commands '
637 'must be declared as dicts')
642 'must be declared as dicts')
638
643
639 for arg, meta in args.items():
644 for arg, meta in args.items():
640 if arg == '*':
645 if arg == '*':
641 raise error.ProgrammingError('* argument name not allowed on '
646 raise error.ProgrammingError('* argument name not allowed on '
642 'version 2 commands')
647 'version 2 commands')
643
648
644 if not isinstance(meta, dict):
649 if not isinstance(meta, dict):
645 raise error.ProgrammingError('arguments for version 2 commands '
650 raise error.ProgrammingError('arguments for version 2 commands '
646 'must declare metadata as a dict')
651 'must declare metadata as a dict')
647
652
648 if 'type' not in meta:
653 if 'type' not in meta:
649 raise error.ProgrammingError('%s argument for command %s does not '
654 raise error.ProgrammingError('%s argument for command %s does not '
650 'declare type field' % (arg, name))
655 'declare type field' % (arg, name))
651
656
652 if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'):
657 if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'):
653 raise error.ProgrammingError('%s argument for command %s has '
658 raise error.ProgrammingError('%s argument for command %s has '
654 'illegal type: %s' % (arg, name,
659 'illegal type: %s' % (arg, name,
655 meta['type']))
660 meta['type']))
656
661
657 if 'example' not in meta:
662 if 'example' not in meta:
658 raise error.ProgrammingError('%s argument for command %s does not '
663 raise error.ProgrammingError('%s argument for command %s does not '
659 'declare example field' % (arg, name))
664 'declare example field' % (arg, name))
660
665
661 meta['required'] = 'default' not in meta
666 meta['required'] = 'default' not in meta
662
667
663 meta.setdefault('default', lambda: None)
668 meta.setdefault('default', lambda: None)
664 meta.setdefault('validvalues', None)
669 meta.setdefault('validvalues', None)
665
670
666 def register(func):
671 def register(func):
667 if name in COMMANDS:
672 if name in COMMANDS:
668 raise error.ProgrammingError('%s command already registered '
673 raise error.ProgrammingError('%s command already registered '
669 'for version 2' % name)
674 'for version 2' % name)
670
675
671 COMMANDS[name] = wireprototypes.commandentry(
676 COMMANDS[name] = wireprototypes.commandentry(
672 func, args=args, transports=transports, permission=permission,
677 func, args=args, transports=transports, permission=permission,
673 cachekeyfn=cachekeyfn)
678 cachekeyfn=cachekeyfn)
674
679
675 return func
680 return func
676
681
677 return register
682 return register
678
683
679 def makecommandcachekeyfn(command, localversion=None, allargs=False):
684 def makecommandcachekeyfn(command, localversion=None, allargs=False):
680 """Construct a cache key derivation function with common features.
685 """Construct a cache key derivation function with common features.
681
686
682 By default, the cache key is a hash of:
687 By default, the cache key is a hash of:
683
688
684 * The command name.
689 * The command name.
685 * A global cache version number.
690 * A global cache version number.
686 * A local cache version number (passed via ``localversion``).
691 * A local cache version number (passed via ``localversion``).
687 * All the arguments passed to the command.
692 * All the arguments passed to the command.
688 * The media type used.
693 * The media type used.
689 * Wire protocol version string.
694 * Wire protocol version string.
690 * The repository path.
695 * The repository path.
691 """
696 """
692 if not allargs:
697 if not allargs:
693 raise error.ProgrammingError('only allargs=True is currently supported')
698 raise error.ProgrammingError('only allargs=True is currently supported')
694
699
695 if localversion is None:
700 if localversion is None:
696 raise error.ProgrammingError('must set localversion argument value')
701 raise error.ProgrammingError('must set localversion argument value')
697
702
698 def cachekeyfn(repo, proto, cacher, **args):
703 def cachekeyfn(repo, proto, cacher, **args):
699 spec = COMMANDS[command]
704 spec = COMMANDS[command]
700
705
701 # Commands that mutate the repo can not be cached.
706 # Commands that mutate the repo can not be cached.
702 if spec.permission == 'push':
707 if spec.permission == 'push':
703 return None
708 return None
704
709
705 # TODO config option to disable caching.
710 # TODO config option to disable caching.
706
711
707 # Our key derivation strategy is to construct a data structure
712 # Our key derivation strategy is to construct a data structure
708 # holding everything that could influence cacheability and to hash
713 # holding everything that could influence cacheability and to hash
709 # the CBOR representation of that. Using CBOR seems like it might
714 # the CBOR representation of that. Using CBOR seems like it might
710 # be overkill. However, simpler hashing mechanisms are prone to
715 # be overkill. However, simpler hashing mechanisms are prone to
711 # duplicate input issues. e.g. if you just concatenate two values,
716 # duplicate input issues. e.g. if you just concatenate two values,
712 # "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides
717 # "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides
713 # "padding" between values and prevents these problems.
718 # "padding" between values and prevents these problems.
714
719
715 # Seed the hash with various data.
720 # Seed the hash with various data.
716 state = {
721 state = {
717 # To invalidate all cache keys.
722 # To invalidate all cache keys.
718 b'globalversion': GLOBAL_CACHE_VERSION,
723 b'globalversion': GLOBAL_CACHE_VERSION,
719 # More granular cache key invalidation.
724 # More granular cache key invalidation.
720 b'localversion': localversion,
725 b'localversion': localversion,
721 # Cache keys are segmented by command.
726 # Cache keys are segmented by command.
722 b'command': pycompat.sysbytes(command),
727 b'command': pycompat.sysbytes(command),
723 # Throw in the media type and API version strings so changes
728 # Throw in the media type and API version strings so changes
724 # to exchange semantics invalid cache.
729 # to exchange semantics invalid cache.
725 b'mediatype': FRAMINGTYPE,
730 b'mediatype': FRAMINGTYPE,
726 b'version': HTTP_WIREPROTO_V2,
731 b'version': HTTP_WIREPROTO_V2,
727 # So same requests for different repos don't share cache keys.
732 # So same requests for different repos don't share cache keys.
728 b'repo': repo.root,
733 b'repo': repo.root,
729 }
734 }
730
735
731 # The arguments passed to us will have already been normalized.
736 # The arguments passed to us will have already been normalized.
732 # Default values will be set, etc. This is important because it
737 # Default values will be set, etc. This is important because it
733 # means that it doesn't matter if clients send an explicit argument
738 # means that it doesn't matter if clients send an explicit argument
734 # or rely on the default value: it will all normalize to the same
739 # or rely on the default value: it will all normalize to the same
735 # set of arguments on the server and therefore the same cache key.
740 # set of arguments on the server and therefore the same cache key.
736 #
741 #
737 # Arguments by their very nature must support being encoded to CBOR.
742 # Arguments by their very nature must support being encoded to CBOR.
738 # And the CBOR encoder is deterministic. So we hash the arguments
743 # And the CBOR encoder is deterministic. So we hash the arguments
739 # by feeding the CBOR of their representation into the hasher.
744 # by feeding the CBOR of their representation into the hasher.
740 if allargs:
745 if allargs:
741 state[b'args'] = pycompat.byteskwargs(args)
746 state[b'args'] = pycompat.byteskwargs(args)
742
747
743 cacher.adjustcachekeystate(state)
748 cacher.adjustcachekeystate(state)
744
749
745 hasher = hashlib.sha1()
750 hasher = hashlib.sha1()
746 for chunk in cborutil.streamencode(state):
751 for chunk in cborutil.streamencode(state):
747 hasher.update(chunk)
752 hasher.update(chunk)
748
753
749 return pycompat.sysbytes(hasher.hexdigest())
754 return pycompat.sysbytes(hasher.hexdigest())
750
755
751 return cachekeyfn
756 return cachekeyfn
752
757
753 def makeresponsecacher(repo, proto, command, args, objencoderfn,
758 def makeresponsecacher(repo, proto, command, args, objencoderfn,
754 redirecttargets, redirecthashes):
759 redirecttargets, redirecthashes):
755 """Construct a cacher for a cacheable command.
760 """Construct a cacher for a cacheable command.
756
761
757 Returns an ``iwireprotocolcommandcacher`` instance.
762 Returns an ``iwireprotocolcommandcacher`` instance.
758
763
759 Extensions can monkeypatch this function to provide custom caching
764 Extensions can monkeypatch this function to provide custom caching
760 backends.
765 backends.
761 """
766 """
762 return None
767 return None
763
768
764 @wireprotocommand('branchmap', permission='pull')
769 @wireprotocommand('branchmap', permission='pull')
765 def branchmapv2(repo, proto):
770 def branchmapv2(repo, proto):
766 yield {encoding.fromlocal(k): v
771 yield {encoding.fromlocal(k): v
767 for k, v in repo.branchmap().iteritems()}
772 for k, v in repo.branchmap().iteritems()}
768
773
769 @wireprotocommand('capabilities', permission='pull')
774 @wireprotocommand('capabilities', permission='pull')
770 def capabilitiesv2(repo, proto):
775 def capabilitiesv2(repo, proto):
771 yield _capabilitiesv2(repo, proto)
776 yield _capabilitiesv2(repo, proto)
772
777
773 @wireprotocommand(
778 @wireprotocommand(
774 'changesetdata',
779 'changesetdata',
775 args={
780 args={
776 'noderange': {
781 'noderange': {
777 'type': 'list',
782 'type': 'list',
778 'default': lambda: None,
783 'default': lambda: None,
779 'example': [[b'0123456...'], [b'abcdef...']],
784 'example': [[b'0123456...'], [b'abcdef...']],
780 },
785 },
781 'nodes': {
786 'nodes': {
782 'type': 'list',
787 'type': 'list',
783 'default': lambda: None,
788 'default': lambda: None,
784 'example': [b'0123456...'],
789 'example': [b'0123456...'],
785 },
790 },
786 'nodesdepth': {
791 'nodesdepth': {
787 'type': 'int',
792 'type': 'int',
788 'default': lambda: None,
793 'default': lambda: None,
789 'example': 10,
794 'example': 10,
790 },
795 },
791 'fields': {
796 'fields': {
792 'type': 'set',
797 'type': 'set',
793 'default': set,
798 'default': set,
794 'example': {b'parents', b'revision'},
799 'example': {b'parents', b'revision'},
795 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
800 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
796 },
801 },
797 },
802 },
798 permission='pull')
803 permission='pull')
799 def changesetdata(repo, proto, noderange, nodes, nodesdepth, fields):
804 def changesetdata(repo, proto, noderange, nodes, nodesdepth, fields):
800 # TODO look for unknown fields and abort when they can't be serviced.
805 # TODO look for unknown fields and abort when they can't be serviced.
801 # This could probably be validated by dispatcher using validvalues.
806 # This could probably be validated by dispatcher using validvalues.
802
807
803 if noderange is None and nodes is None:
808 if noderange is None and nodes is None:
804 raise error.WireprotoCommandError(
809 raise error.WireprotoCommandError(
805 'noderange or nodes must be defined')
810 'noderange or nodes must be defined')
806
811
807 if nodesdepth is not None and nodes is None:
812 if nodesdepth is not None and nodes is None:
808 raise error.WireprotoCommandError(
813 raise error.WireprotoCommandError(
809 'nodesdepth requires the nodes argument')
814 'nodesdepth requires the nodes argument')
810
815
811 if noderange is not None:
816 if noderange is not None:
812 if len(noderange) != 2:
817 if len(noderange) != 2:
813 raise error.WireprotoCommandError(
818 raise error.WireprotoCommandError(
814 'noderange must consist of 2 elements')
819 'noderange must consist of 2 elements')
815
820
816 if not noderange[1]:
821 if not noderange[1]:
817 raise error.WireprotoCommandError(
822 raise error.WireprotoCommandError(
818 'heads in noderange request cannot be empty')
823 'heads in noderange request cannot be empty')
819
824
820 cl = repo.changelog
825 cl = repo.changelog
821 hasnode = cl.hasnode
826 hasnode = cl.hasnode
822
827
823 seen = set()
828 seen = set()
824 outgoing = []
829 outgoing = []
825
830
826 if nodes is not None:
831 if nodes is not None:
827 outgoing = [n for n in nodes if hasnode(n)]
832 outgoing = [n for n in nodes if hasnode(n)]
828
833
829 if nodesdepth:
834 if nodesdepth:
830 outgoing = [cl.node(r) for r in
835 outgoing = [cl.node(r) for r in
831 repo.revs(b'ancestors(%ln, %d)', outgoing,
836 repo.revs(b'ancestors(%ln, %d)', outgoing,
832 nodesdepth - 1)]
837 nodesdepth - 1)]
833
838
834 seen |= set(outgoing)
839 seen |= set(outgoing)
835
840
836 if noderange is not None:
841 if noderange is not None:
837 if noderange[0]:
842 if noderange[0]:
838 common = [n for n in noderange[0] if hasnode(n)]
843 common = [n for n in noderange[0] if hasnode(n)]
839 else:
844 else:
840 common = [nullid]
845 common = [nullid]
841
846
842 for n in discovery.outgoing(repo, common, noderange[1]).missing:
847 for n in discovery.outgoing(repo, common, noderange[1]).missing:
843 if n not in seen:
848 if n not in seen:
844 outgoing.append(n)
849 outgoing.append(n)
845 # Don't need to add to seen here because this is the final
850 # Don't need to add to seen here because this is the final
846 # source of nodes and there should be no duplicates in this
851 # source of nodes and there should be no duplicates in this
847 # list.
852 # list.
848
853
849 seen.clear()
854 seen.clear()
850 publishing = repo.publishing()
855 publishing = repo.publishing()
851
856
852 if outgoing:
857 if outgoing:
853 repo.hook('preoutgoing', throw=True, source='serve')
858 repo.hook('preoutgoing', throw=True, source='serve')
854
859
855 yield {
860 yield {
856 b'totalitems': len(outgoing),
861 b'totalitems': len(outgoing),
857 }
862 }
858
863
859 # The phases of nodes already transferred to the client may have changed
864 # The phases of nodes already transferred to the client may have changed
860 # since the client last requested data. We send phase-only records
865 # since the client last requested data. We send phase-only records
861 # for these revisions, if requested.
866 # for these revisions, if requested.
862 if b'phase' in fields and noderange is not None:
867 if b'phase' in fields and noderange is not None:
863 # TODO skip nodes whose phase will be reflected by a node in the
868 # TODO skip nodes whose phase will be reflected by a node in the
864 # outgoing set. This is purely an optimization to reduce data
869 # outgoing set. This is purely an optimization to reduce data
865 # size.
870 # size.
866 for node in noderange[0]:
871 for node in noderange[0]:
867 yield {
872 yield {
868 b'node': node,
873 b'node': node,
869 b'phase': b'public' if publishing else repo[node].phasestr()
874 b'phase': b'public' if publishing else repo[node].phasestr()
870 }
875 }
871
876
872 nodebookmarks = {}
877 nodebookmarks = {}
873 for mark, node in repo._bookmarks.items():
878 for mark, node in repo._bookmarks.items():
874 nodebookmarks.setdefault(node, set()).add(mark)
879 nodebookmarks.setdefault(node, set()).add(mark)
875
880
876 # It is already topologically sorted by revision number.
881 # It is already topologically sorted by revision number.
877 for node in outgoing:
882 for node in outgoing:
878 d = {
883 d = {
879 b'node': node,
884 b'node': node,
880 }
885 }
881
886
882 if b'parents' in fields:
887 if b'parents' in fields:
883 d[b'parents'] = cl.parents(node)
888 d[b'parents'] = cl.parents(node)
884
889
885 if b'phase' in fields:
890 if b'phase' in fields:
886 if publishing:
891 if publishing:
887 d[b'phase'] = b'public'
892 d[b'phase'] = b'public'
888 else:
893 else:
889 ctx = repo[node]
894 ctx = repo[node]
890 d[b'phase'] = ctx.phasestr()
895 d[b'phase'] = ctx.phasestr()
891
896
892 if b'bookmarks' in fields and node in nodebookmarks:
897 if b'bookmarks' in fields and node in nodebookmarks:
893 d[b'bookmarks'] = sorted(nodebookmarks[node])
898 d[b'bookmarks'] = sorted(nodebookmarks[node])
894 del nodebookmarks[node]
899 del nodebookmarks[node]
895
900
896 followingmeta = []
901 followingmeta = []
897 followingdata = []
902 followingdata = []
898
903
899 if b'revision' in fields:
904 if b'revision' in fields:
900 revisiondata = cl.revision(node, raw=True)
905 revisiondata = cl.revision(node, raw=True)
901 followingmeta.append((b'revision', len(revisiondata)))
906 followingmeta.append((b'revision', len(revisiondata)))
902 followingdata.append(revisiondata)
907 followingdata.append(revisiondata)
903
908
904 # TODO make it possible for extensions to wrap a function or register
909 # TODO make it possible for extensions to wrap a function or register
905 # a handler to service custom fields.
910 # a handler to service custom fields.
906
911
907 if followingmeta:
912 if followingmeta:
908 d[b'fieldsfollowing'] = followingmeta
913 d[b'fieldsfollowing'] = followingmeta
909
914
910 yield d
915 yield d
911
916
912 for extra in followingdata:
917 for extra in followingdata:
913 yield extra
918 yield extra
914
919
915 # If requested, send bookmarks from nodes that didn't have revision
920 # If requested, send bookmarks from nodes that didn't have revision
916 # data sent so receiver is aware of any bookmark updates.
921 # data sent so receiver is aware of any bookmark updates.
917 if b'bookmarks' in fields:
922 if b'bookmarks' in fields:
918 for node, marks in sorted(nodebookmarks.iteritems()):
923 for node, marks in sorted(nodebookmarks.iteritems()):
919 yield {
924 yield {
920 b'node': node,
925 b'node': node,
921 b'bookmarks': sorted(marks),
926 b'bookmarks': sorted(marks),
922 }
927 }
923
928
924 class FileAccessError(Exception):
929 class FileAccessError(Exception):
925 """Represents an error accessing a specific file."""
930 """Represents an error accessing a specific file."""
926
931
927 def __init__(self, path, msg, args):
932 def __init__(self, path, msg, args):
928 self.path = path
933 self.path = path
929 self.msg = msg
934 self.msg = msg
930 self.args = args
935 self.args = args
931
936
932 def getfilestore(repo, proto, path):
937 def getfilestore(repo, proto, path):
933 """Obtain a file storage object for use with wire protocol.
938 """Obtain a file storage object for use with wire protocol.
934
939
935 Exists as a standalone function so extensions can monkeypatch to add
940 Exists as a standalone function so extensions can monkeypatch to add
936 access control.
941 access control.
937 """
942 """
938 # This seems to work even if the file doesn't exist. So catch
943 # This seems to work even if the file doesn't exist. So catch
939 # "empty" files and return an error.
944 # "empty" files and return an error.
940 fl = repo.file(path)
945 fl = repo.file(path)
941
946
942 if not len(fl):
947 if not len(fl):
943 raise FileAccessError(path, 'unknown file: %s', (path,))
948 raise FileAccessError(path, 'unknown file: %s', (path,))
944
949
945 return fl
950 return fl
946
951
947 @wireprotocommand(
952 @wireprotocommand(
948 'filedata',
953 'filedata',
949 args={
954 args={
950 'haveparents': {
955 'haveparents': {
951 'type': 'bool',
956 'type': 'bool',
952 'default': lambda: False,
957 'default': lambda: False,
953 'example': True,
958 'example': True,
954 },
959 },
955 'nodes': {
960 'nodes': {
956 'type': 'list',
961 'type': 'list',
957 'example': [b'0123456...'],
962 'example': [b'0123456...'],
958 },
963 },
959 'fields': {
964 'fields': {
960 'type': 'set',
965 'type': 'set',
961 'default': set,
966 'default': set,
962 'example': {b'parents', b'revision'},
967 'example': {b'parents', b'revision'},
963 'validvalues': {b'parents', b'revision'},
968 'validvalues': {b'parents', b'revision'},
964 },
969 },
965 'path': {
970 'path': {
966 'type': 'bytes',
971 'type': 'bytes',
967 'example': b'foo.txt',
972 'example': b'foo.txt',
968 }
973 }
969 },
974 },
970 permission='pull',
975 permission='pull',
971 # TODO censoring a file revision won't invalidate the cache.
976 # TODO censoring a file revision won't invalidate the cache.
972 # Figure out a way to take censoring into account when deriving
977 # Figure out a way to take censoring into account when deriving
973 # the cache key.
978 # the cache key.
974 cachekeyfn=makecommandcachekeyfn('filedata', 1, allargs=True))
979 cachekeyfn=makecommandcachekeyfn('filedata', 1, allargs=True))
975 def filedata(repo, proto, haveparents, nodes, fields, path):
980 def filedata(repo, proto, haveparents, nodes, fields, path):
976 try:
981 try:
977 # Extensions may wish to access the protocol handler.
982 # Extensions may wish to access the protocol handler.
978 store = getfilestore(repo, proto, path)
983 store = getfilestore(repo, proto, path)
979 except FileAccessError as e:
984 except FileAccessError as e:
980 raise error.WireprotoCommandError(e.msg, e.args)
985 raise error.WireprotoCommandError(e.msg, e.args)
981
986
982 # Validate requested nodes.
987 # Validate requested nodes.
983 for node in nodes:
988 for node in nodes:
984 try:
989 try:
985 store.rev(node)
990 store.rev(node)
986 except error.LookupError:
991 except error.LookupError:
987 raise error.WireprotoCommandError('unknown file node: %s',
992 raise error.WireprotoCommandError('unknown file node: %s',
988 (hex(node),))
993 (hex(node),))
989
994
990 revisions = store.emitrevisions(nodes,
995 revisions = store.emitrevisions(nodes,
991 revisiondata=b'revision' in fields,
996 revisiondata=b'revision' in fields,
992 assumehaveparentrevisions=haveparents)
997 assumehaveparentrevisions=haveparents)
993
998
994 yield {
999 yield {
995 b'totalitems': len(nodes),
1000 b'totalitems': len(nodes),
996 }
1001 }
997
1002
998 for revision in revisions:
1003 for revision in revisions:
999 d = {
1004 d = {
1000 b'node': revision.node,
1005 b'node': revision.node,
1001 }
1006 }
1002
1007
1003 if b'parents' in fields:
1008 if b'parents' in fields:
1004 d[b'parents'] = [revision.p1node, revision.p2node]
1009 d[b'parents'] = [revision.p1node, revision.p2node]
1005
1010
1006 followingmeta = []
1011 followingmeta = []
1007 followingdata = []
1012 followingdata = []
1008
1013
1009 if b'revision' in fields:
1014 if b'revision' in fields:
1010 if revision.revision is not None:
1015 if revision.revision is not None:
1011 followingmeta.append((b'revision', len(revision.revision)))
1016 followingmeta.append((b'revision', len(revision.revision)))
1012 followingdata.append(revision.revision)
1017 followingdata.append(revision.revision)
1013 else:
1018 else:
1014 d[b'deltabasenode'] = revision.basenode
1019 d[b'deltabasenode'] = revision.basenode
1015 followingmeta.append((b'delta', len(revision.delta)))
1020 followingmeta.append((b'delta', len(revision.delta)))
1016 followingdata.append(revision.delta)
1021 followingdata.append(revision.delta)
1017
1022
1018 if followingmeta:
1023 if followingmeta:
1019 d[b'fieldsfollowing'] = followingmeta
1024 d[b'fieldsfollowing'] = followingmeta
1020
1025
1021 yield d
1026 yield d
1022
1027
1023 for extra in followingdata:
1028 for extra in followingdata:
1024 yield extra
1029 yield extra
1025
1030
1026 @wireprotocommand(
1031 @wireprotocommand(
1027 'heads',
1032 'heads',
1028 args={
1033 args={
1029 'publiconly': {
1034 'publiconly': {
1030 'type': 'bool',
1035 'type': 'bool',
1031 'default': lambda: False,
1036 'default': lambda: False,
1032 'example': False,
1037 'example': False,
1033 },
1038 },
1034 },
1039 },
1035 permission='pull')
1040 permission='pull')
1036 def headsv2(repo, proto, publiconly):
1041 def headsv2(repo, proto, publiconly):
1037 if publiconly:
1042 if publiconly:
1038 repo = repo.filtered('immutable')
1043 repo = repo.filtered('immutable')
1039
1044
1040 yield repo.heads()
1045 yield repo.heads()
1041
1046
1042 @wireprotocommand(
1047 @wireprotocommand(
1043 'known',
1048 'known',
1044 args={
1049 args={
1045 'nodes': {
1050 'nodes': {
1046 'type': 'list',
1051 'type': 'list',
1047 'default': list,
1052 'default': list,
1048 'example': [b'deadbeef'],
1053 'example': [b'deadbeef'],
1049 },
1054 },
1050 },
1055 },
1051 permission='pull')
1056 permission='pull')
1052 def knownv2(repo, proto, nodes):
1057 def knownv2(repo, proto, nodes):
1053 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
1058 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
1054 yield result
1059 yield result
1055
1060
1056 @wireprotocommand(
1061 @wireprotocommand(
1057 'listkeys',
1062 'listkeys',
1058 args={
1063 args={
1059 'namespace': {
1064 'namespace': {
1060 'type': 'bytes',
1065 'type': 'bytes',
1061 'example': b'ns',
1066 'example': b'ns',
1062 },
1067 },
1063 },
1068 },
1064 permission='pull')
1069 permission='pull')
1065 def listkeysv2(repo, proto, namespace):
1070 def listkeysv2(repo, proto, namespace):
1066 keys = repo.listkeys(encoding.tolocal(namespace))
1071 keys = repo.listkeys(encoding.tolocal(namespace))
1067 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
1072 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
1068 for k, v in keys.iteritems()}
1073 for k, v in keys.iteritems()}
1069
1074
1070 yield keys
1075 yield keys
1071
1076
1072 @wireprotocommand(
1077 @wireprotocommand(
1073 'lookup',
1078 'lookup',
1074 args={
1079 args={
1075 'key': {
1080 'key': {
1076 'type': 'bytes',
1081 'type': 'bytes',
1077 'example': b'foo',
1082 'example': b'foo',
1078 },
1083 },
1079 },
1084 },
1080 permission='pull')
1085 permission='pull')
1081 def lookupv2(repo, proto, key):
1086 def lookupv2(repo, proto, key):
1082 key = encoding.tolocal(key)
1087 key = encoding.tolocal(key)
1083
1088
1084 # TODO handle exception.
1089 # TODO handle exception.
1085 node = repo.lookup(key)
1090 node = repo.lookup(key)
1086
1091
1087 yield node
1092 yield node
1088
1093
1089 @wireprotocommand(
1094 @wireprotocommand(
1090 'manifestdata',
1095 'manifestdata',
1091 args={
1096 args={
1092 'nodes': {
1097 'nodes': {
1093 'type': 'list',
1098 'type': 'list',
1094 'example': [b'0123456...'],
1099 'example': [b'0123456...'],
1095 },
1100 },
1096 'haveparents': {
1101 'haveparents': {
1097 'type': 'bool',
1102 'type': 'bool',
1098 'default': lambda: False,
1103 'default': lambda: False,
1099 'example': True,
1104 'example': True,
1100 },
1105 },
1101 'fields': {
1106 'fields': {
1102 'type': 'set',
1107 'type': 'set',
1103 'default': set,
1108 'default': set,
1104 'example': {b'parents', b'revision'},
1109 'example': {b'parents', b'revision'},
1105 'validvalues': {b'parents', b'revision'},
1110 'validvalues': {b'parents', b'revision'},
1106 },
1111 },
1107 'tree': {
1112 'tree': {
1108 'type': 'bytes',
1113 'type': 'bytes',
1109 'example': b'',
1114 'example': b'',
1110 },
1115 },
1111 },
1116 },
1112 permission='pull',
1117 permission='pull',
1113 cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True))
1118 cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True))
1114 def manifestdata(repo, proto, haveparents, nodes, fields, tree):
1119 def manifestdata(repo, proto, haveparents, nodes, fields, tree):
1115 store = repo.manifestlog.getstorage(tree)
1120 store = repo.manifestlog.getstorage(tree)
1116
1121
1117 # Validate the node is known and abort on unknown revisions.
1122 # Validate the node is known and abort on unknown revisions.
1118 for node in nodes:
1123 for node in nodes:
1119 try:
1124 try:
1120 store.rev(node)
1125 store.rev(node)
1121 except error.LookupError:
1126 except error.LookupError:
1122 raise error.WireprotoCommandError(
1127 raise error.WireprotoCommandError(
1123 'unknown node: %s', (node,))
1128 'unknown node: %s', (node,))
1124
1129
1125 revisions = store.emitrevisions(nodes,
1130 revisions = store.emitrevisions(nodes,
1126 revisiondata=b'revision' in fields,
1131 revisiondata=b'revision' in fields,
1127 assumehaveparentrevisions=haveparents)
1132 assumehaveparentrevisions=haveparents)
1128
1133
1129 yield {
1134 yield {
1130 b'totalitems': len(nodes),
1135 b'totalitems': len(nodes),
1131 }
1136 }
1132
1137
1133 for revision in revisions:
1138 for revision in revisions:
1134 d = {
1139 d = {
1135 b'node': revision.node,
1140 b'node': revision.node,
1136 }
1141 }
1137
1142
1138 if b'parents' in fields:
1143 if b'parents' in fields:
1139 d[b'parents'] = [revision.p1node, revision.p2node]
1144 d[b'parents'] = [revision.p1node, revision.p2node]
1140
1145
1141 followingmeta = []
1146 followingmeta = []
1142 followingdata = []
1147 followingdata = []
1143
1148
1144 if b'revision' in fields:
1149 if b'revision' in fields:
1145 if revision.revision is not None:
1150 if revision.revision is not None:
1146 followingmeta.append((b'revision', len(revision.revision)))
1151 followingmeta.append((b'revision', len(revision.revision)))
1147 followingdata.append(revision.revision)
1152 followingdata.append(revision.revision)
1148 else:
1153 else:
1149 d[b'deltabasenode'] = revision.basenode
1154 d[b'deltabasenode'] = revision.basenode
1150 followingmeta.append((b'delta', len(revision.delta)))
1155 followingmeta.append((b'delta', len(revision.delta)))
1151 followingdata.append(revision.delta)
1156 followingdata.append(revision.delta)
1152
1157
1153 if followingmeta:
1158 if followingmeta:
1154 d[b'fieldsfollowing'] = followingmeta
1159 d[b'fieldsfollowing'] = followingmeta
1155
1160
1156 yield d
1161 yield d
1157
1162
1158 for extra in followingdata:
1163 for extra in followingdata:
1159 yield extra
1164 yield extra
1160
1165
1161 @wireprotocommand(
1166 @wireprotocommand(
1162 'pushkey',
1167 'pushkey',
1163 args={
1168 args={
1164 'namespace': {
1169 'namespace': {
1165 'type': 'bytes',
1170 'type': 'bytes',
1166 'example': b'ns',
1171 'example': b'ns',
1167 },
1172 },
1168 'key': {
1173 'key': {
1169 'type': 'bytes',
1174 'type': 'bytes',
1170 'example': b'key',
1175 'example': b'key',
1171 },
1176 },
1172 'old': {
1177 'old': {
1173 'type': 'bytes',
1178 'type': 'bytes',
1174 'example': b'old',
1179 'example': b'old',
1175 },
1180 },
1176 'new': {
1181 'new': {
1177 'type': 'bytes',
1182 'type': 'bytes',
1178 'example': 'new',
1183 'example': 'new',
1179 },
1184 },
1180 },
1185 },
1181 permission='push')
1186 permission='push')
1182 def pushkeyv2(repo, proto, namespace, key, old, new):
1187 def pushkeyv2(repo, proto, namespace, key, old, new):
1183 # TODO handle ui output redirection
1188 # TODO handle ui output redirection
1184 yield repo.pushkey(encoding.tolocal(namespace),
1189 yield repo.pushkey(encoding.tolocal(namespace),
1185 encoding.tolocal(key),
1190 encoding.tolocal(key),
1186 encoding.tolocal(old),
1191 encoding.tolocal(old),
1187 encoding.tolocal(new))
1192 encoding.tolocal(new))
@@ -1,711 +1,732 b''
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4 $ enabledummycommands
4 $ enabledummycommands
5
5
6 $ hg init server
6 $ hg init server
7 $ cat > server/.hg/hgrc << EOF
7 $ cat > server/.hg/hgrc << EOF
8 > [experimental]
8 > [experimental]
9 > web.apiserver = true
9 > web.apiserver = true
10 > EOF
10 > EOF
11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
12 $ cat hg.pid > $DAEMON_PIDS
12 $ cat hg.pid > $DAEMON_PIDS
13
13
14 HTTP v2 protocol not enabled by default
14 HTTP v2 protocol not enabled by default
15
15
16 $ sendhttpraw << EOF
16 $ sendhttpraw << EOF
17 > httprequest GET api/$HTTPV2
17 > httprequest GET api/$HTTPV2
18 > user-agent: test
18 > user-agent: test
19 > EOF
19 > EOF
20 using raw connection to peer
20 using raw connection to peer
21 s> GET /api/exp-http-v2-0002 HTTP/1.1\r\n
21 s> GET /api/exp-http-v2-0002 HTTP/1.1\r\n
22 s> Accept-Encoding: identity\r\n
22 s> Accept-Encoding: identity\r\n
23 s> user-agent: test\r\n
23 s> user-agent: test\r\n
24 s> host: $LOCALIP:$HGPORT\r\n (glob)
24 s> host: $LOCALIP:$HGPORT\r\n (glob)
25 s> \r\n
25 s> \r\n
26 s> makefile('rb', None)
26 s> makefile('rb', None)
27 s> HTTP/1.1 404 Not Found\r\n
27 s> HTTP/1.1 404 Not Found\r\n
28 s> Server: testing stub value\r\n
28 s> Server: testing stub value\r\n
29 s> Date: $HTTP_DATE$\r\n
29 s> Date: $HTTP_DATE$\r\n
30 s> Content-Type: text/plain\r\n
30 s> Content-Type: text/plain\r\n
31 s> Content-Length: 33\r\n
31 s> Content-Length: 33\r\n
32 s> \r\n
32 s> \r\n
33 s> API exp-http-v2-0002 not enabled\n
33 s> API exp-http-v2-0002 not enabled\n
34
34
35 Restart server with support for HTTP v2 API
35 Restart server with support for HTTP v2 API
36
36
37 $ killdaemons.py
37 $ killdaemons.py
38 $ enablehttpv2 server
38 $ enablehttpv2 server
39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
40 $ cat hg.pid > $DAEMON_PIDS
40 $ cat hg.pid > $DAEMON_PIDS
41
41
42 Request to unknown command yields 404
42 Request to unknown command yields 404
43
43
44 $ sendhttpraw << EOF
44 $ sendhttpraw << EOF
45 > httprequest POST api/$HTTPV2/ro/badcommand
45 > httprequest POST api/$HTTPV2/ro/badcommand
46 > user-agent: test
46 > user-agent: test
47 > EOF
47 > EOF
48 using raw connection to peer
48 using raw connection to peer
49 s> POST /api/exp-http-v2-0002/ro/badcommand HTTP/1.1\r\n
49 s> POST /api/exp-http-v2-0002/ro/badcommand HTTP/1.1\r\n
50 s> Accept-Encoding: identity\r\n
50 s> Accept-Encoding: identity\r\n
51 s> user-agent: test\r\n
51 s> user-agent: test\r\n
52 s> host: $LOCALIP:$HGPORT\r\n (glob)
52 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 s> \r\n
53 s> \r\n
54 s> makefile('rb', None)
54 s> makefile('rb', None)
55 s> HTTP/1.1 404 Not Found\r\n
55 s> HTTP/1.1 404 Not Found\r\n
56 s> Server: testing stub value\r\n
56 s> Server: testing stub value\r\n
57 s> Date: $HTTP_DATE$\r\n
57 s> Date: $HTTP_DATE$\r\n
58 s> Content-Type: text/plain\r\n
58 s> Content-Type: text/plain\r\n
59 s> Content-Length: 42\r\n
59 s> Content-Length: 42\r\n
60 s> \r\n
60 s> \r\n
61 s> unknown wire protocol command: badcommand\n
61 s> unknown wire protocol command: badcommand\n
62
62
63 GET to read-only command yields a 405
63 GET to read-only command yields a 405
64
64
65 $ sendhttpraw << EOF
65 $ sendhttpraw << EOF
66 > httprequest GET api/$HTTPV2/ro/customreadonly
66 > httprequest GET api/$HTTPV2/ro/customreadonly
67 > user-agent: test
67 > user-agent: test
68 > EOF
68 > EOF
69 using raw connection to peer
69 using raw connection to peer
70 s> GET /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
70 s> GET /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
71 s> Accept-Encoding: identity\r\n
71 s> Accept-Encoding: identity\r\n
72 s> user-agent: test\r\n
72 s> user-agent: test\r\n
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
74 s> \r\n
74 s> \r\n
75 s> makefile('rb', None)
75 s> makefile('rb', None)
76 s> HTTP/1.1 405 Method Not Allowed\r\n
76 s> HTTP/1.1 405 Method Not Allowed\r\n
77 s> Server: testing stub value\r\n
77 s> Server: testing stub value\r\n
78 s> Date: $HTTP_DATE$\r\n
78 s> Date: $HTTP_DATE$\r\n
79 s> Allow: POST\r\n
79 s> Allow: POST\r\n
80 s> Content-Length: 30\r\n
80 s> Content-Length: 30\r\n
81 s> \r\n
81 s> \r\n
82 s> commands require POST requests
82 s> commands require POST requests
83
83
84 Missing Accept header results in 406
84 Missing Accept header results in 406
85
85
86 $ sendhttpraw << EOF
86 $ sendhttpraw << EOF
87 > httprequest POST api/$HTTPV2/ro/customreadonly
87 > httprequest POST api/$HTTPV2/ro/customreadonly
88 > user-agent: test
88 > user-agent: test
89 > EOF
89 > EOF
90 using raw connection to peer
90 using raw connection to peer
91 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
91 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
92 s> Accept-Encoding: identity\r\n
92 s> Accept-Encoding: identity\r\n
93 s> user-agent: test\r\n
93 s> user-agent: test\r\n
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 s> \r\n
95 s> \r\n
96 s> makefile('rb', None)
96 s> makefile('rb', None)
97 s> HTTP/1.1 406 Not Acceptable\r\n
97 s> HTTP/1.1 406 Not Acceptable\r\n
98 s> Server: testing stub value\r\n
98 s> Server: testing stub value\r\n
99 s> Date: $HTTP_DATE$\r\n
99 s> Date: $HTTP_DATE$\r\n
100 s> Content-Type: text/plain\r\n
100 s> Content-Type: text/plain\r\n
101 s> Content-Length: 85\r\n
101 s> Content-Length: 85\r\n
102 s> \r\n
102 s> \r\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
104
104
105 Bad Accept header results in 406
105 Bad Accept header results in 406
106
106
107 $ sendhttpraw << EOF
107 $ sendhttpraw << EOF
108 > httprequest POST api/$HTTPV2/ro/customreadonly
108 > httprequest POST api/$HTTPV2/ro/customreadonly
109 > accept: invalid
109 > accept: invalid
110 > user-agent: test
110 > user-agent: test
111 > EOF
111 > EOF
112 using raw connection to peer
112 using raw connection to peer
113 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
113 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
114 s> Accept-Encoding: identity\r\n
114 s> Accept-Encoding: identity\r\n
115 s> accept: invalid\r\n
115 s> accept: invalid\r\n
116 s> user-agent: test\r\n
116 s> user-agent: test\r\n
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
118 s> \r\n
118 s> \r\n
119 s> makefile('rb', None)
119 s> makefile('rb', None)
120 s> HTTP/1.1 406 Not Acceptable\r\n
120 s> HTTP/1.1 406 Not Acceptable\r\n
121 s> Server: testing stub value\r\n
121 s> Server: testing stub value\r\n
122 s> Date: $HTTP_DATE$\r\n
122 s> Date: $HTTP_DATE$\r\n
123 s> Content-Type: text/plain\r\n
123 s> Content-Type: text/plain\r\n
124 s> Content-Length: 85\r\n
124 s> Content-Length: 85\r\n
125 s> \r\n
125 s> \r\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
127
127
128 Bad Content-Type header results in 415
128 Bad Content-Type header results in 415
129
129
130 $ sendhttpraw << EOF
130 $ sendhttpraw << EOF
131 > httprequest POST api/$HTTPV2/ro/customreadonly
131 > httprequest POST api/$HTTPV2/ro/customreadonly
132 > accept: $MEDIATYPE
132 > accept: $MEDIATYPE
133 > user-agent: test
133 > user-agent: test
134 > content-type: badmedia
134 > content-type: badmedia
135 > EOF
135 > EOF
136 using raw connection to peer
136 using raw connection to peer
137 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
137 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
138 s> Accept-Encoding: identity\r\n
138 s> Accept-Encoding: identity\r\n
139 s> accept: application/mercurial-exp-framing-0006\r\n
139 s> accept: application/mercurial-exp-framing-0006\r\n
140 s> content-type: badmedia\r\n
140 s> content-type: badmedia\r\n
141 s> user-agent: test\r\n
141 s> user-agent: test\r\n
142 s> host: $LOCALIP:$HGPORT\r\n (glob)
142 s> host: $LOCALIP:$HGPORT\r\n (glob)
143 s> \r\n
143 s> \r\n
144 s> makefile('rb', None)
144 s> makefile('rb', None)
145 s> HTTP/1.1 415 Unsupported Media Type\r\n
145 s> HTTP/1.1 415 Unsupported Media Type\r\n
146 s> Server: testing stub value\r\n
146 s> Server: testing stub value\r\n
147 s> Date: $HTTP_DATE$\r\n
147 s> Date: $HTTP_DATE$\r\n
148 s> Content-Type: text/plain\r\n
148 s> Content-Type: text/plain\r\n
149 s> Content-Length: 88\r\n
149 s> Content-Length: 88\r\n
150 s> \r\n
150 s> \r\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0006\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0006\n
152
152
153 Request to read-only command works out of the box
153 Request to read-only command works out of the box
154
154
155 $ sendhttpraw << EOF
155 $ sendhttpraw << EOF
156 > httprequest POST api/$HTTPV2/ro/customreadonly
156 > httprequest POST api/$HTTPV2/ro/customreadonly
157 > accept: $MEDIATYPE
157 > accept: $MEDIATYPE
158 > content-type: $MEDIATYPE
158 > content-type: $MEDIATYPE
159 > user-agent: test
159 > user-agent: test
160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
161 > EOF
161 > EOF
162 using raw connection to peer
162 using raw connection to peer
163 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
163 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
164 s> Accept-Encoding: identity\r\n
164 s> Accept-Encoding: identity\r\n
165 s> *\r\n (glob)
165 s> *\r\n (glob)
166 s> content-type: application/mercurial-exp-framing-0006\r\n
166 s> content-type: application/mercurial-exp-framing-0006\r\n
167 s> user-agent: test\r\n
167 s> user-agent: test\r\n
168 s> content-length: 29\r\n
168 s> content-length: 29\r\n
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 s> \r\n
170 s> \r\n
171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
172 s> makefile('rb', None)
172 s> makefile('rb', None)
173 s> HTTP/1.1 200 OK\r\n
173 s> HTTP/1.1 200 OK\r\n
174 s> Server: testing stub value\r\n
174 s> Server: testing stub value\r\n
175 s> Date: $HTTP_DATE$\r\n
175 s> Date: $HTTP_DATE$\r\n
176 s> Content-Type: application/mercurial-exp-framing-0006\r\n
176 s> Content-Type: application/mercurial-exp-framing-0006\r\n
177 s> Transfer-Encoding: chunked\r\n
177 s> Transfer-Encoding: chunked\r\n
178 s> \r\n
178 s> \r\n
179 s> 11\r\n
180 s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity
181 s> \r\n
179 s> 13\r\n
182 s> 13\r\n
180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
183 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
181 s> \r\n
184 s> \r\n
182 s> 27\r\n
185 s> 27\r\n
183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
186 s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response
184 s> \r\n
187 s> \r\n
185 s> 8\r\n
188 s> 8\r\n
186 s> \x00\x00\x00\x01\x00\x02\x002
189 s> \x00\x00\x00\x01\x00\x02\x002
187 s> \r\n
190 s> \r\n
188 s> 0\r\n
191 s> 0\r\n
189 s> \r\n
192 s> \r\n
190
193
191 $ sendhttpv2peerverbose << EOF
194 $ sendhttpv2peerverbose << EOF
192 > command customreadonly
195 > command customreadonly
193 > EOF
196 > EOF
194 creating http peer for wire protocol version 2
197 creating http peer for wire protocol version 2
195 sending customreadonly command
198 sending customreadonly command
196 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
199 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
197 s> Accept-Encoding: identity\r\n
200 s> Accept-Encoding: identity\r\n
198 s> accept: application/mercurial-exp-framing-0006\r\n
201 s> accept: application/mercurial-exp-framing-0006\r\n
199 s> content-type: application/mercurial-exp-framing-0006\r\n
202 s> content-type: application/mercurial-exp-framing-0006\r\n
200 s> content-length: 65\r\n
203 s> content-length: 65\r\n
201 s> host: $LOCALIP:$HGPORT\r\n (glob)
204 s> host: $LOCALIP:$HGPORT\r\n (glob)
202 s> user-agent: Mercurial debugwireproto\r\n
205 s> user-agent: Mercurial debugwireproto\r\n
203 s> \r\n
206 s> \r\n
204 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x15\x00\x00\x01\x00\x01\x00\x11\xa1DnameNcustomreadonly
207 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x15\x00\x00\x01\x00\x01\x00\x11\xa1DnameNcustomreadonly
205 s> makefile('rb', None)
208 s> makefile('rb', None)
206 s> HTTP/1.1 200 OK\r\n
209 s> HTTP/1.1 200 OK\r\n
207 s> Server: testing stub value\r\n
210 s> Server: testing stub value\r\n
208 s> Date: $HTTP_DATE$\r\n
211 s> Date: $HTTP_DATE$\r\n
209 s> Content-Type: application/mercurial-exp-framing-0006\r\n
212 s> Content-Type: application/mercurial-exp-framing-0006\r\n
210 s> Transfer-Encoding: chunked\r\n
213 s> Transfer-Encoding: chunked\r\n
211 s> \r\n
214 s> \r\n
215 s> 11\r\n
216 s> \t\x00\x00\x01\x00\x02\x01\x92
217 s> Hidentity
218 s> \r\n
219 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
212 s> 13\r\n
220 s> 13\r\n
213 s> \x0b\x00\x00\x01\x00\x02\x011
221 s> \x0b\x00\x00\x01\x00\x02\x041
214 s> \xa1FstatusBok
222 s> \xa1FstatusBok
215 s> \r\n
223 s> \r\n
216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
224 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
217 s> 27\r\n
225 s> 27\r\n
218 s> \x1f\x00\x00\x01\x00\x02\x001
226 s> \x1f\x00\x00\x01\x00\x02\x041
219 s> X\x1dcustomreadonly bytes response
227 s> X\x1dcustomreadonly bytes response
220 s> \r\n
228 s> \r\n
221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
229 received frame(size=31; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
222 s> 8\r\n
230 s> 8\r\n
223 s> \x00\x00\x00\x01\x00\x02\x002
231 s> \x00\x00\x00\x01\x00\x02\x002
224 s> \r\n
232 s> \r\n
225 s> 0\r\n
233 s> 0\r\n
226 s> \r\n
234 s> \r\n
227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
235 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
228 response: gen[
236 response: gen[
229 b'customreadonly bytes response'
237 b'customreadonly bytes response'
230 ]
238 ]
231 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
239 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
232
240
233 Request to read-write command fails because server is read-only by default
241 Request to read-write command fails because server is read-only by default
234
242
235 GET to read-write request yields 405
243 GET to read-write request yields 405
236
244
237 $ sendhttpraw << EOF
245 $ sendhttpraw << EOF
238 > httprequest GET api/$HTTPV2/rw/customreadonly
246 > httprequest GET api/$HTTPV2/rw/customreadonly
239 > user-agent: test
247 > user-agent: test
240 > EOF
248 > EOF
241 using raw connection to peer
249 using raw connection to peer
242 s> GET /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
250 s> GET /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
243 s> Accept-Encoding: identity\r\n
251 s> Accept-Encoding: identity\r\n
244 s> user-agent: test\r\n
252 s> user-agent: test\r\n
245 s> host: $LOCALIP:$HGPORT\r\n (glob)
253 s> host: $LOCALIP:$HGPORT\r\n (glob)
246 s> \r\n
254 s> \r\n
247 s> makefile('rb', None)
255 s> makefile('rb', None)
248 s> HTTP/1.1 405 Method Not Allowed\r\n
256 s> HTTP/1.1 405 Method Not Allowed\r\n
249 s> Server: testing stub value\r\n
257 s> Server: testing stub value\r\n
250 s> Date: $HTTP_DATE$\r\n
258 s> Date: $HTTP_DATE$\r\n
251 s> Allow: POST\r\n
259 s> Allow: POST\r\n
252 s> Content-Length: 30\r\n
260 s> Content-Length: 30\r\n
253 s> \r\n
261 s> \r\n
254 s> commands require POST requests
262 s> commands require POST requests
255
263
256 Even for unknown commands
264 Even for unknown commands
257
265
258 $ sendhttpraw << EOF
266 $ sendhttpraw << EOF
259 > httprequest GET api/$HTTPV2/rw/badcommand
267 > httprequest GET api/$HTTPV2/rw/badcommand
260 > user-agent: test
268 > user-agent: test
261 > EOF
269 > EOF
262 using raw connection to peer
270 using raw connection to peer
263 s> GET /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
271 s> GET /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
264 s> Accept-Encoding: identity\r\n
272 s> Accept-Encoding: identity\r\n
265 s> user-agent: test\r\n
273 s> user-agent: test\r\n
266 s> host: $LOCALIP:$HGPORT\r\n (glob)
274 s> host: $LOCALIP:$HGPORT\r\n (glob)
267 s> \r\n
275 s> \r\n
268 s> makefile('rb', None)
276 s> makefile('rb', None)
269 s> HTTP/1.1 405 Method Not Allowed\r\n
277 s> HTTP/1.1 405 Method Not Allowed\r\n
270 s> Server: testing stub value\r\n
278 s> Server: testing stub value\r\n
271 s> Date: $HTTP_DATE$\r\n
279 s> Date: $HTTP_DATE$\r\n
272 s> Allow: POST\r\n
280 s> Allow: POST\r\n
273 s> Content-Length: 30\r\n
281 s> Content-Length: 30\r\n
274 s> \r\n
282 s> \r\n
275 s> commands require POST requests
283 s> commands require POST requests
276
284
277 SSL required by default
285 SSL required by default
278
286
279 $ sendhttpraw << EOF
287 $ sendhttpraw << EOF
280 > httprequest POST api/$HTTPV2/rw/customreadonly
288 > httprequest POST api/$HTTPV2/rw/customreadonly
281 > user-agent: test
289 > user-agent: test
282 > EOF
290 > EOF
283 using raw connection to peer
291 using raw connection to peer
284 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
292 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
285 s> Accept-Encoding: identity\r\n
293 s> Accept-Encoding: identity\r\n
286 s> user-agent: test\r\n
294 s> user-agent: test\r\n
287 s> host: $LOCALIP:$HGPORT\r\n (glob)
295 s> host: $LOCALIP:$HGPORT\r\n (glob)
288 s> \r\n
296 s> \r\n
289 s> makefile('rb', None)
297 s> makefile('rb', None)
290 s> HTTP/1.1 403 ssl required\r\n
298 s> HTTP/1.1 403 ssl required\r\n
291 s> Server: testing stub value\r\n
299 s> Server: testing stub value\r\n
292 s> Date: $HTTP_DATE$\r\n
300 s> Date: $HTTP_DATE$\r\n
293 s> Content-Length: 17\r\n
301 s> Content-Length: 17\r\n
294 s> \r\n
302 s> \r\n
295 s> permission denied
303 s> permission denied
296
304
297 Restart server to allow non-ssl read-write operations
305 Restart server to allow non-ssl read-write operations
298
306
299 $ killdaemons.py
307 $ killdaemons.py
300 $ cat > server/.hg/hgrc << EOF
308 $ cat > server/.hg/hgrc << EOF
301 > [experimental]
309 > [experimental]
302 > web.apiserver = true
310 > web.apiserver = true
303 > web.api.http-v2 = true
311 > web.api.http-v2 = true
304 > [web]
312 > [web]
305 > push_ssl = false
313 > push_ssl = false
306 > allow-push = *
314 > allow-push = *
307 > EOF
315 > EOF
308
316
309 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
317 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
310 $ cat hg.pid > $DAEMON_PIDS
318 $ cat hg.pid > $DAEMON_PIDS
311
319
312 Authorized request for valid read-write command works
320 Authorized request for valid read-write command works
313
321
314 $ sendhttpraw << EOF
322 $ sendhttpraw << EOF
315 > httprequest POST api/$HTTPV2/rw/customreadonly
323 > httprequest POST api/$HTTPV2/rw/customreadonly
316 > user-agent: test
324 > user-agent: test
317 > accept: $MEDIATYPE
325 > accept: $MEDIATYPE
318 > content-type: $MEDIATYPE
326 > content-type: $MEDIATYPE
319 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
327 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
320 > EOF
328 > EOF
321 using raw connection to peer
329 using raw connection to peer
322 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
330 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
323 s> Accept-Encoding: identity\r\n
331 s> Accept-Encoding: identity\r\n
324 s> accept: application/mercurial-exp-framing-0006\r\n
332 s> accept: application/mercurial-exp-framing-0006\r\n
325 s> content-type: application/mercurial-exp-framing-0006\r\n
333 s> content-type: application/mercurial-exp-framing-0006\r\n
326 s> user-agent: test\r\n
334 s> user-agent: test\r\n
327 s> content-length: 29\r\n
335 s> content-length: 29\r\n
328 s> host: $LOCALIP:$HGPORT\r\n (glob)
336 s> host: $LOCALIP:$HGPORT\r\n (glob)
329 s> \r\n
337 s> \r\n
330 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
338 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
331 s> makefile('rb', None)
339 s> makefile('rb', None)
332 s> HTTP/1.1 200 OK\r\n
340 s> HTTP/1.1 200 OK\r\n
333 s> Server: testing stub value\r\n
341 s> Server: testing stub value\r\n
334 s> Date: $HTTP_DATE$\r\n
342 s> Date: $HTTP_DATE$\r\n
335 s> Content-Type: application/mercurial-exp-framing-0006\r\n
343 s> Content-Type: application/mercurial-exp-framing-0006\r\n
336 s> Transfer-Encoding: chunked\r\n
344 s> Transfer-Encoding: chunked\r\n
337 s> \r\n
345 s> \r\n
346 s> 11\r\n
347 s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity
348 s> \r\n
338 s> 13\r\n
349 s> 13\r\n
339 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
350 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
340 s> \r\n
351 s> \r\n
341 s> 27\r\n
352 s> 27\r\n
342 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
353 s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response
343 s> \r\n
354 s> \r\n
344 s> 8\r\n
355 s> 8\r\n
345 s> \x00\x00\x00\x01\x00\x02\x002
356 s> \x00\x00\x00\x01\x00\x02\x002
346 s> \r\n
357 s> \r\n
347 s> 0\r\n
358 s> 0\r\n
348 s> \r\n
359 s> \r\n
349
360
350 Authorized request for unknown command is rejected
361 Authorized request for unknown command is rejected
351
362
352 $ sendhttpraw << EOF
363 $ sendhttpraw << EOF
353 > httprequest POST api/$HTTPV2/rw/badcommand
364 > httprequest POST api/$HTTPV2/rw/badcommand
354 > user-agent: test
365 > user-agent: test
355 > accept: $MEDIATYPE
366 > accept: $MEDIATYPE
356 > EOF
367 > EOF
357 using raw connection to peer
368 using raw connection to peer
358 s> POST /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
369 s> POST /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
359 s> Accept-Encoding: identity\r\n
370 s> Accept-Encoding: identity\r\n
360 s> accept: application/mercurial-exp-framing-0006\r\n
371 s> accept: application/mercurial-exp-framing-0006\r\n
361 s> user-agent: test\r\n
372 s> user-agent: test\r\n
362 s> host: $LOCALIP:$HGPORT\r\n (glob)
373 s> host: $LOCALIP:$HGPORT\r\n (glob)
363 s> \r\n
374 s> \r\n
364 s> makefile('rb', None)
375 s> makefile('rb', None)
365 s> HTTP/1.1 404 Not Found\r\n
376 s> HTTP/1.1 404 Not Found\r\n
366 s> Server: testing stub value\r\n
377 s> Server: testing stub value\r\n
367 s> Date: $HTTP_DATE$\r\n
378 s> Date: $HTTP_DATE$\r\n
368 s> Content-Type: text/plain\r\n
379 s> Content-Type: text/plain\r\n
369 s> Content-Length: 42\r\n
380 s> Content-Length: 42\r\n
370 s> \r\n
381 s> \r\n
371 s> unknown wire protocol command: badcommand\n
382 s> unknown wire protocol command: badcommand\n
372
383
373 debugreflect isn't enabled by default
384 debugreflect isn't enabled by default
374
385
375 $ sendhttpraw << EOF
386 $ sendhttpraw << EOF
376 > httprequest POST api/$HTTPV2/ro/debugreflect
387 > httprequest POST api/$HTTPV2/ro/debugreflect
377 > user-agent: test
388 > user-agent: test
378 > EOF
389 > EOF
379 using raw connection to peer
390 using raw connection to peer
380 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
391 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
381 s> Accept-Encoding: identity\r\n
392 s> Accept-Encoding: identity\r\n
382 s> user-agent: test\r\n
393 s> user-agent: test\r\n
383 s> host: $LOCALIP:$HGPORT\r\n (glob)
394 s> host: $LOCALIP:$HGPORT\r\n (glob)
384 s> \r\n
395 s> \r\n
385 s> makefile('rb', None)
396 s> makefile('rb', None)
386 s> HTTP/1.1 404 Not Found\r\n
397 s> HTTP/1.1 404 Not Found\r\n
387 s> Server: testing stub value\r\n
398 s> Server: testing stub value\r\n
388 s> Date: $HTTP_DATE$\r\n
399 s> Date: $HTTP_DATE$\r\n
389 s> Content-Type: text/plain\r\n
400 s> Content-Type: text/plain\r\n
390 s> Content-Length: 34\r\n
401 s> Content-Length: 34\r\n
391 s> \r\n
402 s> \r\n
392 s> debugreflect service not available
403 s> debugreflect service not available
393
404
394 Restart server to get debugreflect endpoint
405 Restart server to get debugreflect endpoint
395
406
396 $ killdaemons.py
407 $ killdaemons.py
397 $ cat > server/.hg/hgrc << EOF
408 $ cat > server/.hg/hgrc << EOF
398 > [experimental]
409 > [experimental]
399 > web.apiserver = true
410 > web.apiserver = true
400 > web.api.debugreflect = true
411 > web.api.debugreflect = true
401 > web.api.http-v2 = true
412 > web.api.http-v2 = true
402 > [web]
413 > [web]
403 > push_ssl = false
414 > push_ssl = false
404 > allow-push = *
415 > allow-push = *
405 > EOF
416 > EOF
406
417
407 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
418 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
408 $ cat hg.pid > $DAEMON_PIDS
419 $ cat hg.pid > $DAEMON_PIDS
409
420
410 Command frames can be reflected via debugreflect
421 Command frames can be reflected via debugreflect
411
422
412 $ sendhttpraw << EOF
423 $ sendhttpraw << EOF
413 > httprequest POST api/$HTTPV2/ro/debugreflect
424 > httprequest POST api/$HTTPV2/ro/debugreflect
414 > accept: $MEDIATYPE
425 > accept: $MEDIATYPE
415 > content-type: $MEDIATYPE
426 > content-type: $MEDIATYPE
416 > user-agent: test
427 > user-agent: test
417 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
428 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
418 > EOF
429 > EOF
419 using raw connection to peer
430 using raw connection to peer
420 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
431 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
421 s> Accept-Encoding: identity\r\n
432 s> Accept-Encoding: identity\r\n
422 s> accept: application/mercurial-exp-framing-0006\r\n
433 s> accept: application/mercurial-exp-framing-0006\r\n
423 s> content-type: application/mercurial-exp-framing-0006\r\n
434 s> content-type: application/mercurial-exp-framing-0006\r\n
424 s> user-agent: test\r\n
435 s> user-agent: test\r\n
425 s> content-length: 47\r\n
436 s> content-length: 47\r\n
426 s> host: $LOCALIP:$HGPORT\r\n (glob)
437 s> host: $LOCALIP:$HGPORT\r\n (glob)
427 s> \r\n
438 s> \r\n
428 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
439 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
429 s> makefile('rb', None)
440 s> makefile('rb', None)
430 s> HTTP/1.1 200 OK\r\n
441 s> HTTP/1.1 200 OK\r\n
431 s> Server: testing stub value\r\n
442 s> Server: testing stub value\r\n
432 s> Date: $HTTP_DATE$\r\n
443 s> Date: $HTTP_DATE$\r\n
433 s> Content-Type: text/plain\r\n
444 s> Content-Type: text/plain\r\n
434 s> Content-Length: 223\r\n
445 s> Content-Length: 223\r\n
435 s> \r\n
446 s> \r\n
436 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
447 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
437 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "redirect": null, "requestid": 1}]\n
448 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "redirect": null, "requestid": 1}]\n
438 s> received: <no frame>\n
449 s> received: <no frame>\n
439 s> {"action": "noop"}
450 s> {"action": "noop"}
440
451
441 Multiple requests to regular command URL are not allowed
452 Multiple requests to regular command URL are not allowed
442
453
443 $ sendhttpraw << EOF
454 $ sendhttpraw << EOF
444 > httprequest POST api/$HTTPV2/ro/customreadonly
455 > httprequest POST api/$HTTPV2/ro/customreadonly
445 > accept: $MEDIATYPE
456 > accept: $MEDIATYPE
446 > content-type: $MEDIATYPE
457 > content-type: $MEDIATYPE
447 > user-agent: test
458 > user-agent: test
448 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
459 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
449 > EOF
460 > EOF
450 using raw connection to peer
461 using raw connection to peer
451 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
462 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
452 s> Accept-Encoding: identity\r\n
463 s> Accept-Encoding: identity\r\n
453 s> accept: application/mercurial-exp-framing-0006\r\n
464 s> accept: application/mercurial-exp-framing-0006\r\n
454 s> content-type: application/mercurial-exp-framing-0006\r\n
465 s> content-type: application/mercurial-exp-framing-0006\r\n
455 s> user-agent: test\r\n
466 s> user-agent: test\r\n
456 s> content-length: 29\r\n
467 s> content-length: 29\r\n
457 s> host: $LOCALIP:$HGPORT\r\n (glob)
468 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 s> \r\n
469 s> \r\n
459 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
470 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
460 s> makefile('rb', None)
471 s> makefile('rb', None)
461 s> HTTP/1.1 200 OK\r\n
472 s> HTTP/1.1 200 OK\r\n
462 s> Server: testing stub value\r\n
473 s> Server: testing stub value\r\n
463 s> Date: $HTTP_DATE$\r\n
474 s> Date: $HTTP_DATE$\r\n
464 s> Content-Type: application/mercurial-exp-framing-0006\r\n
475 s> Content-Type: application/mercurial-exp-framing-0006\r\n
465 s> Transfer-Encoding: chunked\r\n
476 s> Transfer-Encoding: chunked\r\n
466 s> \r\n
477 s> \r\n
478 s> 11\r\n
479 s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity
480 s> \r\n
467 s> 13\r\n
481 s> 13\r\n
468 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
482 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
469 s> \r\n
483 s> \r\n
470 s> 27\r\n
484 s> 27\r\n
471 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
485 s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response
472 s> \r\n
486 s> \r\n
473 s> 8\r\n
487 s> 8\r\n
474 s> \x00\x00\x00\x01\x00\x02\x002
488 s> \x00\x00\x00\x01\x00\x02\x002
475 s> \r\n
489 s> \r\n
476 s> 0\r\n
490 s> 0\r\n
477 s> \r\n
491 s> \r\n
478
492
479 Multiple requests to "multirequest" URL are allowed
493 Multiple requests to "multirequest" URL are allowed
480
494
481 $ sendhttpraw << EOF
495 $ sendhttpraw << EOF
482 > httprequest POST api/$HTTPV2/ro/multirequest
496 > httprequest POST api/$HTTPV2/ro/multirequest
483 > accept: $MEDIATYPE
497 > accept: $MEDIATYPE
484 > content-type: $MEDIATYPE
498 > content-type: $MEDIATYPE
485 > user-agent: test
499 > user-agent: test
486 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
500 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
487 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
501 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
488 > EOF
502 > EOF
489 using raw connection to peer
503 using raw connection to peer
490 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
504 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
491 s> Accept-Encoding: identity\r\n
505 s> Accept-Encoding: identity\r\n
492 s> *\r\n (glob)
506 s> *\r\n (glob)
493 s> *\r\n (glob)
507 s> *\r\n (glob)
494 s> user-agent: test\r\n
508 s> user-agent: test\r\n
495 s> content-length: 58\r\n
509 s> content-length: 58\r\n
496 s> host: $LOCALIP:$HGPORT\r\n (glob)
510 s> host: $LOCALIP:$HGPORT\r\n (glob)
497 s> \r\n
511 s> \r\n
498 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
512 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
499 s> makefile('rb', None)
513 s> makefile('rb', None)
500 s> HTTP/1.1 200 OK\r\n
514 s> HTTP/1.1 200 OK\r\n
501 s> Server: testing stub value\r\n
515 s> Server: testing stub value\r\n
502 s> Date: $HTTP_DATE$\r\n
516 s> Date: $HTTP_DATE$\r\n
503 s> Content-Type: application/mercurial-exp-framing-0006\r\n
517 s> Content-Type: application/mercurial-exp-framing-0006\r\n
504 s> Transfer-Encoding: chunked\r\n
518 s> Transfer-Encoding: chunked\r\n
505 s> \r\n
519 s> \r\n
520 s> 11\r\n
521 s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity
522 s> \r\n
506 s> 13\r\n
523 s> 13\r\n
507 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
524 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
508 s> \r\n
525 s> \r\n
509 s> 27\r\n
526 s> 27\r\n
510 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
527 s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response
511 s> \r\n
528 s> \r\n
512 s> 8\r\n
529 s> 8\r\n
513 s> \x00\x00\x00\x01\x00\x02\x002
530 s> \x00\x00\x00\x01\x00\x02\x002
514 s> \r\n
531 s> \r\n
515 s> 13\r\n
532 s> 13\r\n
516 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
533 s> \x0b\x00\x00\x03\x00\x02\x041\xa1FstatusBok
517 s> \r\n
534 s> \r\n
518 s> 27\r\n
535 s> 27\r\n
519 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
536 s> \x1f\x00\x00\x03\x00\x02\x041X\x1dcustomreadonly bytes response
520 s> \r\n
537 s> \r\n
521 s> 8\r\n
538 s> 8\r\n
522 s> \x00\x00\x00\x03\x00\x02\x002
539 s> \x00\x00\x00\x03\x00\x02\x002
523 s> \r\n
540 s> \r\n
524 s> 0\r\n
541 s> 0\r\n
525 s> \r\n
542 s> \r\n
526
543
527 Interleaved requests to "multirequest" are processed
544 Interleaved requests to "multirequest" are processed
528
545
529 $ sendhttpraw << EOF
546 $ sendhttpraw << EOF
530 > httprequest POST api/$HTTPV2/ro/multirequest
547 > httprequest POST api/$HTTPV2/ro/multirequest
531 > accept: $MEDIATYPE
548 > accept: $MEDIATYPE
532 > content-type: $MEDIATYPE
549 > content-type: $MEDIATYPE
533 > user-agent: test
550 > user-agent: test
534 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
551 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
535 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
552 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
536 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
553 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
537 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
554 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
538 > EOF
555 > EOF
539 using raw connection to peer
556 using raw connection to peer
540 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
557 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
541 s> Accept-Encoding: identity\r\n
558 s> Accept-Encoding: identity\r\n
542 s> accept: application/mercurial-exp-framing-0006\r\n
559 s> accept: application/mercurial-exp-framing-0006\r\n
543 s> content-type: application/mercurial-exp-framing-0006\r\n
560 s> content-type: application/mercurial-exp-framing-0006\r\n
544 s> user-agent: test\r\n
561 s> user-agent: test\r\n
545 s> content-length: 115\r\n
562 s> content-length: 115\r\n
546 s> host: $LOCALIP:$HGPORT\r\n (glob)
563 s> host: $LOCALIP:$HGPORT\r\n (glob)
547 s> \r\n
564 s> \r\n
548 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
565 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
549 s> makefile('rb', None)
566 s> makefile('rb', None)
550 s> HTTP/1.1 200 OK\r\n
567 s> HTTP/1.1 200 OK\r\n
551 s> Server: testing stub value\r\n
568 s> Server: testing stub value\r\n
552 s> Date: $HTTP_DATE$\r\n
569 s> Date: $HTTP_DATE$\r\n
553 s> Content-Type: application/mercurial-exp-framing-0006\r\n
570 s> Content-Type: application/mercurial-exp-framing-0006\r\n
554 s> Transfer-Encoding: chunked\r\n
571 s> Transfer-Encoding: chunked\r\n
555 s> \r\n
572 s> \r\n
573 s> 11\r\n
574 s> \t\x00\x00\x03\x00\x02\x01\x92Hidentity
575 s> \r\n
556 s> 13\r\n
576 s> 13\r\n
557 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
577 s> \x0b\x00\x00\x03\x00\x02\x041\xa1FstatusBok
558 s> \r\n
578 s> \r\n
559 s> 28\r\n
579 s> 28\r\n
560 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
580 s> \x00\x00\x03\x00\x02\x041\xa3Ibookmarks@Jnamespaces@Fphases@
561 s> \r\n
581 s> \r\n
562 s> 8\r\n
582 s> 8\r\n
563 s> \x00\x00\x00\x03\x00\x02\x002
583 s> \x00\x00\x00\x03\x00\x02\x002
564 s> \r\n
584 s> \r\n
565 s> 13\r\n
585 s> 13\r\n
566 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
586 s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok
567 s> \r\n
587 s> \r\n
568 s> 9\r\n
588 s> 9\r\n
569 s> \x01\x00\x00\x01\x00\x02\x001\xa0
589 s> \x01\x00\x00\x01\x00\x02\x041\xa0
570 s> \r\n
590 s> \r\n
571 s> 8\r\n
591 s> 8\r\n
572 s> \x00\x00\x00\x01\x00\x02\x002
592 s> \x00\x00\x00\x01\x00\x02\x002
573 s> \r\n
593 s> \r\n
574 s> 0\r\n
594 s> 0\r\n
575 s> \r\n
595 s> \r\n
576
596
577 Restart server to disable read-write access
597 Restart server to disable read-write access
578
598
579 $ killdaemons.py
599 $ killdaemons.py
580 $ cat > server/.hg/hgrc << EOF
600 $ cat > server/.hg/hgrc << EOF
581 > [experimental]
601 > [experimental]
582 > web.apiserver = true
602 > web.apiserver = true
583 > web.api.debugreflect = true
603 > web.api.debugreflect = true
584 > web.api.http-v2 = true
604 > web.api.http-v2 = true
585 > [web]
605 > [web]
586 > push_ssl = false
606 > push_ssl = false
587 > EOF
607 > EOF
588
608
589 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
609 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
590 $ cat hg.pid > $DAEMON_PIDS
610 $ cat hg.pid > $DAEMON_PIDS
591
611
592 Attempting to run a read-write command via multirequest on read-only URL is not allowed
612 Attempting to run a read-write command via multirequest on read-only URL is not allowed
593
613
594 $ sendhttpraw << EOF
614 $ sendhttpraw << EOF
595 > httprequest POST api/$HTTPV2/ro/multirequest
615 > httprequest POST api/$HTTPV2/ro/multirequest
596 > accept: $MEDIATYPE
616 > accept: $MEDIATYPE
597 > content-type: $MEDIATYPE
617 > content-type: $MEDIATYPE
598 > user-agent: test
618 > user-agent: test
599 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
619 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
600 > EOF
620 > EOF
601 using raw connection to peer
621 using raw connection to peer
602 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
622 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
603 s> Accept-Encoding: identity\r\n
623 s> Accept-Encoding: identity\r\n
604 s> accept: application/mercurial-exp-framing-0006\r\n
624 s> accept: application/mercurial-exp-framing-0006\r\n
605 s> content-type: application/mercurial-exp-framing-0006\r\n
625 s> content-type: application/mercurial-exp-framing-0006\r\n
606 s> user-agent: test\r\n
626 s> user-agent: test\r\n
607 s> content-length: 22\r\n
627 s> content-length: 22\r\n
608 s> host: $LOCALIP:$HGPORT\r\n (glob)
628 s> host: $LOCALIP:$HGPORT\r\n (glob)
609 s> \r\n
629 s> \r\n
610 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
630 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
611 s> makefile('rb', None)
631 s> makefile('rb', None)
612 s> HTTP/1.1 403 Forbidden\r\n
632 s> HTTP/1.1 403 Forbidden\r\n
613 s> Server: testing stub value\r\n
633 s> Server: testing stub value\r\n
614 s> Date: $HTTP_DATE$\r\n
634 s> Date: $HTTP_DATE$\r\n
615 s> Content-Type: text/plain\r\n
635 s> Content-Type: text/plain\r\n
616 s> Content-Length: 52\r\n
636 s> Content-Length: 52\r\n
617 s> \r\n
637 s> \r\n
618 s> insufficient permissions to execute command: pushkey
638 s> insufficient permissions to execute command: pushkey
619
639
620 Defining an invalid content encoding results in warning
640 Defining an invalid content encoding results in warning
621
641
622 $ hg --config experimental.httppeer.v2-encoder-order=identity,badencoder --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
642 $ hg --config experimental.httppeer.v2-encoder-order=identity,badencoder --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
623 > command heads
643 > command heads
624 > EOF
644 > EOF
625 creating http peer for wire protocol version 2
645 creating http peer for wire protocol version 2
626 sending heads command
646 sending heads command
627 wire protocol version 2 encoder referenced in config (badencoder) is not known; ignoring
647 wire protocol version 2 encoder referenced in config (badencoder) is not known; ignoring
628 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
648 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
629 s> Accept-Encoding: identity\r\n
649 s> Accept-Encoding: identity\r\n
630 s> accept: application/mercurial-exp-framing-0006\r\n
650 s> accept: application/mercurial-exp-framing-0006\r\n
631 s> content-type: application/mercurial-exp-framing-0006\r\n
651 s> content-type: application/mercurial-exp-framing-0006\r\n
632 s> content-length: 56\r\n
652 s> content-length: 56\r\n
633 s> host: $LOCALIP:$HGPORT\r\n (glob)
653 s> host: $LOCALIP:$HGPORT\r\n (glob)
634 s> user-agent: Mercurial debugwireproto\r\n
654 s> user-agent: Mercurial debugwireproto\r\n
635 s> \r\n
655 s> \r\n
636 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
656 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
637 s> makefile('rb', None)
657 s> makefile('rb', None)
638 s> HTTP/1.1 200 OK\r\n
658 s> HTTP/1.1 200 OK\r\n
639 s> Server: testing stub value\r\n
659 s> Server: testing stub value\r\n
640 s> Date: $HTTP_DATE$\r\n
660 s> Date: $HTTP_DATE$\r\n
641 s> Content-Type: application/mercurial-exp-framing-0006\r\n
661 s> Content-Type: application/mercurial-exp-framing-0006\r\n
642 s> Transfer-Encoding: chunked\r\n
662 s> Transfer-Encoding: chunked\r\n
643 s> \r\n
663 s> \r\n
664 s> 11\r\n
665 s> \t\x00\x00\x01\x00\x02\x01\x92
666 s> Hidentity
667 s> \r\n
668 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
644 s> 13\r\n
669 s> 13\r\n
645 s> \x0b\x00\x00\x01\x00\x02\x011
670 s> \x0b\x00\x00\x01\x00\x02\x041
646 s> \xa1FstatusBok
671 s> \xa1FstatusBok
647 s> \r\n
672 s> \r\n
648 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
673 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
649 s> 1e\r\n
674 s> 1e\r\n
650 s> \x16\x00\x00\x01\x00\x02\x001
675 s> \x16\x00\x00\x01\x00\x02\x041
651 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
676 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
652 s> \r\n
677 s> \r\n
653 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
678 received frame(size=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
654 s> 8\r\n
679 s> 8\r\n
655 s> \x00\x00\x00\x01\x00\x02\x002
680 s> \x00\x00\x00\x01\x00\x02\x002
656 s> \r\n
681 s> \r\n
657 s> 0\r\n
682 s> 0\r\n
658 s> \r\n
683 s> \r\n
659 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
684 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
660 response: [
685 response: [
661 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
686 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
662 ]
687 ]
663 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
688 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
664
689
665 #if zstd
690 #if zstd
666
691
667 $ hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
692 $ hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
668 > command heads
693 > command heads
669 > EOF
694 > EOF
670 creating http peer for wire protocol version 2
695 creating http peer for wire protocol version 2
671 sending heads command
696 sending heads command
672 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
697 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
673 s> Accept-Encoding: identity\r\n
698 s> Accept-Encoding: identity\r\n
674 s> accept: application/mercurial-exp-framing-0006\r\n
699 s> accept: application/mercurial-exp-framing-0006\r\n
675 s> content-type: application/mercurial-exp-framing-0006\r\n
700 s> content-type: application/mercurial-exp-framing-0006\r\n
676 s> content-length: 70\r\n
701 s> content-length: 70\r\n
677 s> host: $LOCALIP:$HGPORT\r\n (glob)
702 s> host: $LOCALIP:$HGPORT\r\n (glob)
678 s> user-agent: Mercurial debugwireproto\r\n
703 s> user-agent: Mercurial debugwireproto\r\n
679 s> \r\n
704 s> \r\n
680 s> *\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x83Hzstd-8mbDzlibHidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
705 s> *\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x83Hzstd-8mbDzlibHidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
681 s> makefile('rb', None)
706 s> makefile('rb', None)
682 s> HTTP/1.1 200 OK\r\n
707 s> HTTP/1.1 200 OK\r\n
683 s> Server: testing stub value\r\n
708 s> Server: testing stub value\r\n
684 s> Date: $HTTP_DATE$\r\n
709 s> Date: $HTTP_DATE$\r\n
685 s> Content-Type: application/mercurial-exp-framing-0006\r\n
710 s> Content-Type: application/mercurial-exp-framing-0006\r\n
686 s> Transfer-Encoding: chunked\r\n
711 s> Transfer-Encoding: chunked\r\n
687 s> \r\n
712 s> \r\n
688 s> 13\r\n
713 s> 11\r\n
689 s> \x0b\x00\x00\x01\x00\x02\x011
714 s> \t\x00\x00\x01\x00\x02\x01\x92
690 s> \xa1FstatusBok
715 s> Hzstd-8mb
691 s> \r\n
716 s> \r\n
692 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
717 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
693 s> 1e\r\n
718 s> 25\r\n
694 s> \x16\x00\x00\x01\x00\x02\x001
719 s> \x1d\x00\x00\x01\x00\x02\x042
695 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
720 s> (\xb5/\xfd\x00P\xa4\x00\x00p\xa1FstatusBok\x81T\x00\x01\x00\tP\x02
696 s> \r\n
721 s> \r\n
697 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
722 received frame(size=29; request=1; stream=2; streamflags=encoded; type=command-response; flags=eos)
698 s> 8\r\n
699 s> \x00\x00\x00\x01\x00\x02\x002
700 s> \r\n
701 s> 0\r\n
723 s> 0\r\n
702 s> \r\n
724 s> \r\n
703 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
704 response: [
725 response: [
705 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
726 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
706 ]
727 ]
707 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
728 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
708
729
709 #endif
730 #endif
710
731
711 $ cat error.log
732 $ cat error.log
@@ -1,754 +1,759 b''
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4
4
5 $ cat >> $HGRCPATH << EOF
5 $ cat >> $HGRCPATH << EOF
6 > [web]
6 > [web]
7 > push_ssl = false
7 > push_ssl = false
8 > allow_push = *
8 > allow_push = *
9 > EOF
9 > EOF
10
10
11 $ hg init server
11 $ hg init server
12 $ cd server
12 $ cd server
13 $ touch a
13 $ touch a
14 $ hg -q commit -A -m initial
14 $ hg -q commit -A -m initial
15 $ cd ..
15 $ cd ..
16
16
17 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
17 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
18 $ cat hg.pid >> $DAEMON_PIDS
18 $ cat hg.pid >> $DAEMON_PIDS
19
19
20 compression formats are advertised in compression capability
20 compression formats are advertised in compression capability
21
21
22 #if zstd
22 #if zstd
23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
24 #else
24 #else
25 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
25 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
26 #endif
26 #endif
27
27
28 $ killdaemons.py
28 $ killdaemons.py
29
29
30 server.compressionengines can replace engines list wholesale
30 server.compressionengines can replace engines list wholesale
31
31
32 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
32 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
33 $ cat hg.pid > $DAEMON_PIDS
33 $ cat hg.pid > $DAEMON_PIDS
34 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
34 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
35
35
36 $ killdaemons.py
36 $ killdaemons.py
37
37
38 Order of engines can also change
38 Order of engines can also change
39
39
40 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
40 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
41 $ cat hg.pid > $DAEMON_PIDS
41 $ cat hg.pid > $DAEMON_PIDS
42 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
42 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
43
43
44 $ killdaemons.py
44 $ killdaemons.py
45
45
46 Start a default server again
46 Start a default server again
47
47
48 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
48 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
49 $ cat hg.pid > $DAEMON_PIDS
49 $ cat hg.pid > $DAEMON_PIDS
50
50
51 Server should send application/mercurial-0.1 to clients if no Accept is used
51 Server should send application/mercurial-0.1 to clients if no Accept is used
52
52
53 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
53 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
54 200 Script output follows
54 200 Script output follows
55 content-type: application/mercurial-0.1
55 content-type: application/mercurial-0.1
56 date: $HTTP_DATE$
56 date: $HTTP_DATE$
57 server: testing stub value
57 server: testing stub value
58 transfer-encoding: chunked
58 transfer-encoding: chunked
59
59
60 Server should send application/mercurial-0.1 when client says it wants it
60 Server should send application/mercurial-0.1 when client says it wants it
61
61
62 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
62 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
63 200 Script output follows
63 200 Script output follows
64 content-type: application/mercurial-0.1
64 content-type: application/mercurial-0.1
65 date: $HTTP_DATE$
65 date: $HTTP_DATE$
66 server: testing stub value
66 server: testing stub value
67 transfer-encoding: chunked
67 transfer-encoding: chunked
68
68
69 Server should send application/mercurial-0.2 when client says it wants it
69 Server should send application/mercurial-0.2 when client says it wants it
70
70
71 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
71 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
72 200 Script output follows
72 200 Script output follows
73 content-type: application/mercurial-0.2
73 content-type: application/mercurial-0.2
74 date: $HTTP_DATE$
74 date: $HTTP_DATE$
75 server: testing stub value
75 server: testing stub value
76 transfer-encoding: chunked
76 transfer-encoding: chunked
77
77
78 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
78 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
79 200 Script output follows
79 200 Script output follows
80 content-type: application/mercurial-0.2
80 content-type: application/mercurial-0.2
81 date: $HTTP_DATE$
81 date: $HTTP_DATE$
82 server: testing stub value
82 server: testing stub value
83 transfer-encoding: chunked
83 transfer-encoding: chunked
84
84
85 Requesting a compression format that server doesn't support results will fall back to 0.1
85 Requesting a compression format that server doesn't support results will fall back to 0.1
86
86
87 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
87 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
88 200 Script output follows
88 200 Script output follows
89 content-type: application/mercurial-0.1
89 content-type: application/mercurial-0.1
90 date: $HTTP_DATE$
90 date: $HTTP_DATE$
91 server: testing stub value
91 server: testing stub value
92 transfer-encoding: chunked
92 transfer-encoding: chunked
93
93
94 #if zstd
94 #if zstd
95 zstd is used if available
95 zstd is used if available
96
96
97 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
97 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
98 $ f --size --hexdump --bytes 36 --sha1 resp
98 $ f --size --hexdump --bytes 36 --sha1 resp
99 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
99 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
100 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
100 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
101 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
101 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
102 0020: 28 b5 2f fd |(./.|
102 0020: 28 b5 2f fd |(./.|
103
103
104 #endif
104 #endif
105
105
106 application/mercurial-0.2 is not yet used on non-streaming responses
106 application/mercurial-0.2 is not yet used on non-streaming responses
107
107
108 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
108 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
109 200 Script output follows
109 200 Script output follows
110 content-length: 41
110 content-length: 41
111 content-type: application/mercurial-0.1
111 content-type: application/mercurial-0.1
112 date: $HTTP_DATE$
112 date: $HTTP_DATE$
113 server: testing stub value
113 server: testing stub value
114
114
115 e93700bd72895c5addab234c56d4024b487a362f
115 e93700bd72895c5addab234c56d4024b487a362f
116
116
117 Now test protocol preference usage
117 Now test protocol preference usage
118
118
119 $ killdaemons.py
119 $ killdaemons.py
120 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
120 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
121 $ cat hg.pid > $DAEMON_PIDS
121 $ cat hg.pid > $DAEMON_PIDS
122
122
123 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
123 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
124
124
125 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
125 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
126 200 Script output follows
126 200 Script output follows
127 content-type: application/mercurial-0.1
127 content-type: application/mercurial-0.1
128
128
129 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
129 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
130 $ f --size --hexdump --bytes 28 --sha1 resp
130 $ f --size --hexdump --bytes 28 --sha1 resp
131 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
131 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
132 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
132 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
133 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
133 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
134
134
135 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
135 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
136
136
137 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
137 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
138 $ f --size --hexdump --bytes 28 --sha1 resp
138 $ f --size --hexdump --bytes 28 --sha1 resp
139 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
139 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
140 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
140 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
141 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
141 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
142
142
143 0.2 with no compression will get "none" because that is server's preference
143 0.2 with no compression will get "none" because that is server's preference
144 (spec says ZL and UN are implicitly supported)
144 (spec says ZL and UN are implicitly supported)
145
145
146 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
146 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
147 $ f --size --hexdump --bytes 32 --sha1 resp
147 $ f --size --hexdump --bytes 32 --sha1 resp
148 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
148 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
149 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
149 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
150 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
150 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
151
151
152 Client receives server preference even if local order doesn't match
152 Client receives server preference even if local order doesn't match
153
153
154 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
154 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
155 $ f --size --hexdump --bytes 32 --sha1 resp
155 $ f --size --hexdump --bytes 32 --sha1 resp
156 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
156 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
157 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
157 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
158 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
158 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
159
159
160 Client receives only supported format even if not server preferred format
160 Client receives only supported format even if not server preferred format
161
161
162 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
162 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
163 $ f --size --hexdump --bytes 33 --sha1 resp
163 $ f --size --hexdump --bytes 33 --sha1 resp
164 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
164 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
165 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
165 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
166 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
166 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
167 0020: 78 |x|
167 0020: 78 |x|
168
168
169 $ killdaemons.py
169 $ killdaemons.py
170 $ cd ..
170 $ cd ..
171
171
172 Test listkeys for listing namespaces
172 Test listkeys for listing namespaces
173
173
174 $ hg init empty
174 $ hg init empty
175 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
175 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
176 $ cat hg.pid > $DAEMON_PIDS
176 $ cat hg.pid > $DAEMON_PIDS
177
177
178 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
178 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
179 > command listkeys
179 > command listkeys
180 > namespace namespaces
180 > namespace namespaces
181 > EOF
181 > EOF
182 s> GET /?cmd=capabilities HTTP/1.1\r\n
182 s> GET /?cmd=capabilities HTTP/1.1\r\n
183 s> Accept-Encoding: identity\r\n
183 s> Accept-Encoding: identity\r\n
184 s> accept: application/mercurial-0.1\r\n
184 s> accept: application/mercurial-0.1\r\n
185 s> host: $LOCALIP:$HGPORT\r\n (glob)
185 s> host: $LOCALIP:$HGPORT\r\n (glob)
186 s> user-agent: Mercurial debugwireproto\r\n
186 s> user-agent: Mercurial debugwireproto\r\n
187 s> \r\n
187 s> \r\n
188 s> makefile('rb', None)
188 s> makefile('rb', None)
189 s> HTTP/1.1 200 Script output follows\r\n
189 s> HTTP/1.1 200 Script output follows\r\n
190 s> Server: testing stub value\r\n
190 s> Server: testing stub value\r\n
191 s> Date: $HTTP_DATE$\r\n
191 s> Date: $HTTP_DATE$\r\n
192 s> Content-Type: application/mercurial-0.1\r\n
192 s> Content-Type: application/mercurial-0.1\r\n
193 s> Content-Length: *\r\n (glob)
193 s> Content-Length: *\r\n (glob)
194 s> \r\n
194 s> \r\n
195 s> batch 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
195 s> batch 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
196 sending listkeys command
196 sending listkeys command
197 s> GET /?cmd=listkeys HTTP/1.1\r\n
197 s> GET /?cmd=listkeys HTTP/1.1\r\n
198 s> Accept-Encoding: identity\r\n
198 s> Accept-Encoding: identity\r\n
199 s> vary: X-HgArg-1,X-HgProto-1\r\n
199 s> vary: X-HgArg-1,X-HgProto-1\r\n
200 s> x-hgarg-1: namespace=namespaces\r\n
200 s> x-hgarg-1: namespace=namespaces\r\n
201 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
201 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
202 s> accept: application/mercurial-0.1\r\n
202 s> accept: application/mercurial-0.1\r\n
203 s> host: $LOCALIP:$HGPORT\r\n (glob)
203 s> host: $LOCALIP:$HGPORT\r\n (glob)
204 s> user-agent: Mercurial debugwireproto\r\n
204 s> user-agent: Mercurial debugwireproto\r\n
205 s> \r\n
205 s> \r\n
206 s> makefile('rb', None)
206 s> makefile('rb', None)
207 s> HTTP/1.1 200 Script output follows\r\n
207 s> HTTP/1.1 200 Script output follows\r\n
208 s> Server: testing stub value\r\n
208 s> Server: testing stub value\r\n
209 s> Date: $HTTP_DATE$\r\n
209 s> Date: $HTTP_DATE$\r\n
210 s> Content-Type: application/mercurial-0.1\r\n
210 s> Content-Type: application/mercurial-0.1\r\n
211 s> Content-Length: 30\r\n
211 s> Content-Length: 30\r\n
212 s> \r\n
212 s> \r\n
213 s> bookmarks\t\n
213 s> bookmarks\t\n
214 s> namespaces\t\n
214 s> namespaces\t\n
215 s> phases\t
215 s> phases\t
216 response: {
216 response: {
217 b'bookmarks': b'',
217 b'bookmarks': b'',
218 b'namespaces': b'',
218 b'namespaces': b'',
219 b'phases': b''
219 b'phases': b''
220 }
220 }
221 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
221 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
222
222
223 Same thing, but with "httprequest" command
223 Same thing, but with "httprequest" command
224
224
225 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
225 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
226 > httprequest GET ?cmd=listkeys
226 > httprequest GET ?cmd=listkeys
227 > user-agent: test
227 > user-agent: test
228 > x-hgarg-1: namespace=namespaces
228 > x-hgarg-1: namespace=namespaces
229 > EOF
229 > EOF
230 using raw connection to peer
230 using raw connection to peer
231 s> GET /?cmd=listkeys HTTP/1.1\r\n
231 s> GET /?cmd=listkeys HTTP/1.1\r\n
232 s> Accept-Encoding: identity\r\n
232 s> Accept-Encoding: identity\r\n
233 s> user-agent: test\r\n
233 s> user-agent: test\r\n
234 s> x-hgarg-1: namespace=namespaces\r\n
234 s> x-hgarg-1: namespace=namespaces\r\n
235 s> host: $LOCALIP:$HGPORT\r\n (glob)
235 s> host: $LOCALIP:$HGPORT\r\n (glob)
236 s> \r\n
236 s> \r\n
237 s> makefile('rb', None)
237 s> makefile('rb', None)
238 s> HTTP/1.1 200 Script output follows\r\n
238 s> HTTP/1.1 200 Script output follows\r\n
239 s> Server: testing stub value\r\n
239 s> Server: testing stub value\r\n
240 s> Date: $HTTP_DATE$\r\n
240 s> Date: $HTTP_DATE$\r\n
241 s> Content-Type: application/mercurial-0.1\r\n
241 s> Content-Type: application/mercurial-0.1\r\n
242 s> Content-Length: 30\r\n
242 s> Content-Length: 30\r\n
243 s> \r\n
243 s> \r\n
244 s> bookmarks\t\n
244 s> bookmarks\t\n
245 s> namespaces\t\n
245 s> namespaces\t\n
246 s> phases\t
246 s> phases\t
247
247
248 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
248 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
249
249
250 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
250 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
251 > command heads
251 > command heads
252 > EOF
252 > EOF
253 s> GET /?cmd=capabilities HTTP/1.1\r\n
253 s> GET /?cmd=capabilities HTTP/1.1\r\n
254 s> Accept-Encoding: identity\r\n
254 s> Accept-Encoding: identity\r\n
255 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
255 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
256 s> x-hgproto-1: cbor\r\n
256 s> x-hgproto-1: cbor\r\n
257 s> x-hgupgrade-1: exp-http-v2-0002\r\n
257 s> x-hgupgrade-1: exp-http-v2-0002\r\n
258 s> accept: application/mercurial-0.1\r\n
258 s> accept: application/mercurial-0.1\r\n
259 s> host: $LOCALIP:$HGPORT\r\n (glob)
259 s> host: $LOCALIP:$HGPORT\r\n (glob)
260 s> user-agent: Mercurial debugwireproto\r\n
260 s> user-agent: Mercurial debugwireproto\r\n
261 s> \r\n
261 s> \r\n
262 s> makefile('rb', None)
262 s> makefile('rb', None)
263 s> HTTP/1.1 200 Script output follows\r\n
263 s> HTTP/1.1 200 Script output follows\r\n
264 s> Server: testing stub value\r\n
264 s> Server: testing stub value\r\n
265 s> Date: $HTTP_DATE$\r\n
265 s> Date: $HTTP_DATE$\r\n
266 s> Content-Type: application/mercurial-0.1\r\n
266 s> Content-Type: application/mercurial-0.1\r\n
267 s> Content-Length: *\r\n (glob)
267 s> Content-Length: *\r\n (glob)
268 s> \r\n
268 s> \r\n
269 s> batch 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
269 s> batch 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
270 sending heads command
270 sending heads command
271 s> GET /?cmd=heads HTTP/1.1\r\n
271 s> GET /?cmd=heads HTTP/1.1\r\n
272 s> Accept-Encoding: identity\r\n
272 s> Accept-Encoding: identity\r\n
273 s> vary: X-HgProto-1\r\n
273 s> vary: X-HgProto-1\r\n
274 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
274 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
275 s> accept: application/mercurial-0.1\r\n
275 s> accept: application/mercurial-0.1\r\n
276 s> host: $LOCALIP:$HGPORT\r\n (glob)
276 s> host: $LOCALIP:$HGPORT\r\n (glob)
277 s> user-agent: Mercurial debugwireproto\r\n
277 s> user-agent: Mercurial debugwireproto\r\n
278 s> \r\n
278 s> \r\n
279 s> makefile('rb', None)
279 s> makefile('rb', None)
280 s> HTTP/1.1 200 Script output follows\r\n
280 s> HTTP/1.1 200 Script output follows\r\n
281 s> Server: testing stub value\r\n
281 s> Server: testing stub value\r\n
282 s> Date: $HTTP_DATE$\r\n
282 s> Date: $HTTP_DATE$\r\n
283 s> Content-Type: application/mercurial-0.1\r\n
283 s> Content-Type: application/mercurial-0.1\r\n
284 s> Content-Length: 41\r\n
284 s> Content-Length: 41\r\n
285 s> \r\n
285 s> \r\n
286 s> 0000000000000000000000000000000000000000\n
286 s> 0000000000000000000000000000000000000000\n
287 response: [
287 response: [
288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
289 ]
289 ]
290 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
290 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
291
291
292 $ killdaemons.py
292 $ killdaemons.py
293 $ enablehttpv2 empty
293 $ enablehttpv2 empty
294 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
294 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
295 $ cat hg.pid > $DAEMON_PIDS
295 $ cat hg.pid > $DAEMON_PIDS
296
296
297 Client with HTTPv2 enabled automatically upgrades if the server supports it
297 Client with HTTPv2 enabled automatically upgrades if the server supports it
298
298
299 $ hg --config experimental.httppeer.advertise-v2=true --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
299 $ hg --config experimental.httppeer.advertise-v2=true --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
300 > command heads
300 > command heads
301 > EOF
301 > EOF
302 s> GET /?cmd=capabilities HTTP/1.1\r\n
302 s> GET /?cmd=capabilities HTTP/1.1\r\n
303 s> Accept-Encoding: identity\r\n
303 s> Accept-Encoding: identity\r\n
304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
305 s> x-hgproto-1: cbor\r\n
305 s> x-hgproto-1: cbor\r\n
306 s> x-hgupgrade-1: exp-http-v2-0002\r\n
306 s> x-hgupgrade-1: exp-http-v2-0002\r\n
307 s> accept: application/mercurial-0.1\r\n
307 s> accept: application/mercurial-0.1\r\n
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
309 s> user-agent: Mercurial debugwireproto\r\n
309 s> user-agent: Mercurial debugwireproto\r\n
310 s> \r\n
310 s> \r\n
311 s> makefile('rb', None)
311 s> makefile('rb', None)
312 s> HTTP/1.1 200 OK\r\n
312 s> HTTP/1.1 200 OK\r\n
313 s> Server: testing stub value\r\n
313 s> Server: testing stub value\r\n
314 s> Date: $HTTP_DATE$\r\n
314 s> Date: $HTTP_DATE$\r\n
315 s> Content-Type: application/mercurial-cbor\r\n
315 s> Content-Type: application/mercurial-cbor\r\n
316 s> Content-Length: *\r\n (glob)
316 s> Content-Length: *\r\n (glob)
317 s> \r\n
317 s> \r\n
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
319 sending heads command
319 sending heads command
320 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
320 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
321 s> Accept-Encoding: identity\r\n
321 s> Accept-Encoding: identity\r\n
322 s> accept: application/mercurial-exp-framing-0006\r\n
322 s> accept: application/mercurial-exp-framing-0006\r\n
323 s> content-type: application/mercurial-exp-framing-0006\r\n
323 s> content-type: application/mercurial-exp-framing-0006\r\n
324 s> content-length: 56\r\n
324 s> content-length: 56\r\n
325 s> host: $LOCALIP:$HGPORT\r\n (glob)
325 s> host: $LOCALIP:$HGPORT\r\n (glob)
326 s> user-agent: Mercurial debugwireproto\r\n
326 s> user-agent: Mercurial debugwireproto\r\n
327 s> \r\n
327 s> \r\n
328 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
328 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
329 s> makefile('rb', None)
329 s> makefile('rb', None)
330 s> HTTP/1.1 200 OK\r\n
330 s> HTTP/1.1 200 OK\r\n
331 s> Server: testing stub value\r\n
331 s> Server: testing stub value\r\n
332 s> Date: $HTTP_DATE$\r\n
332 s> Date: $HTTP_DATE$\r\n
333 s> Content-Type: application/mercurial-exp-framing-0006\r\n
333 s> Content-Type: application/mercurial-exp-framing-0006\r\n
334 s> Transfer-Encoding: chunked\r\n
334 s> Transfer-Encoding: chunked\r\n
335 s> \r\n
335 s> \r\n
336 s> 11\r\n
337 s> \t\x00\x00\x01\x00\x02\x01\x92
338 s> Hidentity
339 s> \r\n
340 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
336 s> 13\r\n
341 s> 13\r\n
337 s> \x0b\x00\x00\x01\x00\x02\x011
342 s> \x0b\x00\x00\x01\x00\x02\x041
338 s> \xa1FstatusBok
343 s> \xa1FstatusBok
339 s> \r\n
344 s> \r\n
340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
345 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
341 s> 1e\r\n
346 s> 1e\r\n
342 s> \x16\x00\x00\x01\x00\x02\x001
347 s> \x16\x00\x00\x01\x00\x02\x041
343 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
348 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
344 s> \r\n
349 s> \r\n
345 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
350 received frame(size=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
346 s> 8\r\n
351 s> 8\r\n
347 s> \x00\x00\x00\x01\x00\x02\x002
352 s> \x00\x00\x00\x01\x00\x02\x002
348 s> \r\n
353 s> \r\n
349 s> 0\r\n
354 s> 0\r\n
350 s> \r\n
355 s> \r\n
351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
356 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
352 response: [
357 response: [
353 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
358 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
354 ]
359 ]
355 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
360 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
356
361
357 $ killdaemons.py
362 $ killdaemons.py
358
363
359 HTTP client follows HTTP redirect on handshake to new repo
364 HTTP client follows HTTP redirect on handshake to new repo
360
365
361 $ cd $TESTTMP
366 $ cd $TESTTMP
362
367
363 $ hg init redirector
368 $ hg init redirector
364 $ hg init redirected
369 $ hg init redirected
365 $ cd redirected
370 $ cd redirected
366 $ touch foo
371 $ touch foo
367 $ hg -q commit -A -m initial
372 $ hg -q commit -A -m initial
368 $ cd ..
373 $ cd ..
369
374
370 $ cat > paths.conf << EOF
375 $ cat > paths.conf << EOF
371 > [paths]
376 > [paths]
372 > / = $TESTTMP/*
377 > / = $TESTTMP/*
373 > EOF
378 > EOF
374
379
375 $ cat > redirectext.py << EOF
380 $ cat > redirectext.py << EOF
376 > from mercurial import extensions, wireprotoserver
381 > from mercurial import extensions, wireprotoserver
377 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
382 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
378 > path = req.advertisedurl[len(req.advertisedbaseurl):]
383 > path = req.advertisedurl[len(req.advertisedbaseurl):]
379 > if not path.startswith(b'/redirector'):
384 > if not path.startswith(b'/redirector'):
380 > return orig(repo, req, res, proto, cmd)
385 > return orig(repo, req, res, proto, cmd)
381 > relpath = path[len(b'/redirector'):]
386 > relpath = path[len(b'/redirector'):]
382 > res.status = b'301 Redirect'
387 > res.status = b'301 Redirect'
383 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
388 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
384 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
389 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
385 > newurl = newurl[0:newurl.index(b'?')]
390 > newurl = newurl[0:newurl.index(b'?')]
386 > res.headers[b'Location'] = newurl
391 > res.headers[b'Location'] = newurl
387 > res.headers[b'Content-Type'] = b'text/plain'
392 > res.headers[b'Content-Type'] = b'text/plain'
388 > res.setbodybytes(b'redirected')
393 > res.setbodybytes(b'redirected')
389 > return True
394 > return True
390 >
395 >
391 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
396 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
392 > EOF
397 > EOF
393
398
394 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
399 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
395 > --config server.compressionengines=zlib \
400 > --config server.compressionengines=zlib \
396 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
401 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
397 $ cat hg.pid > $DAEMON_PIDS
402 $ cat hg.pid > $DAEMON_PIDS
398
403
399 Verify our HTTP 301 is served properly
404 Verify our HTTP 301 is served properly
400
405
401 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
406 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
402 > httprequest GET /redirector?cmd=capabilities
407 > httprequest GET /redirector?cmd=capabilities
403 > user-agent: test
408 > user-agent: test
404 > EOF
409 > EOF
405 using raw connection to peer
410 using raw connection to peer
406 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
411 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
407 s> Accept-Encoding: identity\r\n
412 s> Accept-Encoding: identity\r\n
408 s> user-agent: test\r\n
413 s> user-agent: test\r\n
409 s> host: $LOCALIP:$HGPORT\r\n (glob)
414 s> host: $LOCALIP:$HGPORT\r\n (glob)
410 s> \r\n
415 s> \r\n
411 s> makefile('rb', None)
416 s> makefile('rb', None)
412 s> HTTP/1.1 301 Redirect\r\n
417 s> HTTP/1.1 301 Redirect\r\n
413 s> Server: testing stub value\r\n
418 s> Server: testing stub value\r\n
414 s> Date: $HTTP_DATE$\r\n
419 s> Date: $HTTP_DATE$\r\n
415 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
420 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
416 s> Content-Type: text/plain\r\n
421 s> Content-Type: text/plain\r\n
417 s> Content-Length: 10\r\n
422 s> Content-Length: 10\r\n
418 s> \r\n
423 s> \r\n
419 s> redirected
424 s> redirected
420 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
425 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
421 s> Accept-Encoding: identity\r\n
426 s> Accept-Encoding: identity\r\n
422 s> user-agent: test\r\n
427 s> user-agent: test\r\n
423 s> host: $LOCALIP:$HGPORT\r\n (glob)
428 s> host: $LOCALIP:$HGPORT\r\n (glob)
424 s> \r\n
429 s> \r\n
425 s> makefile('rb', None)
430 s> makefile('rb', None)
426 s> HTTP/1.1 200 Script output follows\r\n
431 s> HTTP/1.1 200 Script output follows\r\n
427 s> Server: testing stub value\r\n
432 s> Server: testing stub value\r\n
428 s> Date: $HTTP_DATE$\r\n
433 s> Date: $HTTP_DATE$\r\n
429 s> Content-Type: application/mercurial-0.1\r\n
434 s> Content-Type: application/mercurial-0.1\r\n
430 s> Content-Length: 467\r\n
435 s> Content-Length: 467\r\n
431 s> \r\n
436 s> \r\n
432 s> batch 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
437 s> batch 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
433
438
434 Test with the HTTP peer
439 Test with the HTTP peer
435
440
436 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
441 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
437 > command heads
442 > command heads
438 > EOF
443 > EOF
439 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
444 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
440 s> Accept-Encoding: identity\r\n
445 s> Accept-Encoding: identity\r\n
441 s> accept: application/mercurial-0.1\r\n
446 s> accept: application/mercurial-0.1\r\n
442 s> host: $LOCALIP:$HGPORT\r\n (glob)
447 s> host: $LOCALIP:$HGPORT\r\n (glob)
443 s> user-agent: Mercurial debugwireproto\r\n
448 s> user-agent: Mercurial debugwireproto\r\n
444 s> \r\n
449 s> \r\n
445 s> makefile('rb', None)
450 s> makefile('rb', None)
446 s> HTTP/1.1 301 Redirect\r\n
451 s> HTTP/1.1 301 Redirect\r\n
447 s> Server: testing stub value\r\n
452 s> Server: testing stub value\r\n
448 s> Date: $HTTP_DATE$\r\n
453 s> Date: $HTTP_DATE$\r\n
449 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
454 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
450 s> Content-Type: text/plain\r\n
455 s> Content-Type: text/plain\r\n
451 s> Content-Length: 10\r\n
456 s> Content-Length: 10\r\n
452 s> \r\n
457 s> \r\n
453 s> redirected
458 s> redirected
454 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
459 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
455 s> Accept-Encoding: identity\r\n
460 s> Accept-Encoding: identity\r\n
456 s> accept: application/mercurial-0.1\r\n
461 s> accept: application/mercurial-0.1\r\n
457 s> host: $LOCALIP:$HGPORT\r\n (glob)
462 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 s> user-agent: Mercurial debugwireproto\r\n
463 s> user-agent: Mercurial debugwireproto\r\n
459 s> \r\n
464 s> \r\n
460 s> makefile('rb', None)
465 s> makefile('rb', None)
461 s> HTTP/1.1 200 Script output follows\r\n
466 s> HTTP/1.1 200 Script output follows\r\n
462 s> Server: testing stub value\r\n
467 s> Server: testing stub value\r\n
463 s> Date: $HTTP_DATE$\r\n
468 s> Date: $HTTP_DATE$\r\n
464 s> Content-Type: application/mercurial-0.1\r\n
469 s> Content-Type: application/mercurial-0.1\r\n
465 s> Content-Length: 467\r\n
470 s> Content-Length: 467\r\n
466 s> \r\n
471 s> \r\n
467 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
472 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
468 s> batch 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
473 s> batch 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
469 sending heads command
474 sending heads command
470 s> GET /redirected?cmd=heads HTTP/1.1\r\n
475 s> GET /redirected?cmd=heads HTTP/1.1\r\n
471 s> Accept-Encoding: identity\r\n
476 s> Accept-Encoding: identity\r\n
472 s> vary: X-HgProto-1\r\n
477 s> vary: X-HgProto-1\r\n
473 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
478 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
474 s> accept: application/mercurial-0.1\r\n
479 s> accept: application/mercurial-0.1\r\n
475 s> host: $LOCALIP:$HGPORT\r\n (glob)
480 s> host: $LOCALIP:$HGPORT\r\n (glob)
476 s> user-agent: Mercurial debugwireproto\r\n
481 s> user-agent: Mercurial debugwireproto\r\n
477 s> \r\n
482 s> \r\n
478 s> makefile('rb', None)
483 s> makefile('rb', None)
479 s> HTTP/1.1 200 Script output follows\r\n
484 s> HTTP/1.1 200 Script output follows\r\n
480 s> Server: testing stub value\r\n
485 s> Server: testing stub value\r\n
481 s> Date: $HTTP_DATE$\r\n
486 s> Date: $HTTP_DATE$\r\n
482 s> Content-Type: application/mercurial-0.1\r\n
487 s> Content-Type: application/mercurial-0.1\r\n
483 s> Content-Length: 41\r\n
488 s> Content-Length: 41\r\n
484 s> \r\n
489 s> \r\n
485 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
490 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
486 response: [
491 response: [
487 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
492 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
488 ]
493 ]
489 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
494 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
490
495
491 $ killdaemons.py
496 $ killdaemons.py
492
497
493 Now test a variation where we strip the query string from the redirect URL.
498 Now test a variation where we strip the query string from the redirect URL.
494 (SCM Manager apparently did this and clients would recover from it)
499 (SCM Manager apparently did this and clients would recover from it)
495
500
496 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
501 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
497 > --config server.compressionengines=zlib \
502 > --config server.compressionengines=zlib \
498 > --config testing.redirectqs=false \
503 > --config testing.redirectqs=false \
499 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
504 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
500 $ cat hg.pid > $DAEMON_PIDS
505 $ cat hg.pid > $DAEMON_PIDS
501
506
502 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
507 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
503 > httprequest GET /redirector?cmd=capabilities
508 > httprequest GET /redirector?cmd=capabilities
504 > user-agent: test
509 > user-agent: test
505 > EOF
510 > EOF
506 using raw connection to peer
511 using raw connection to peer
507 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
512 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
508 s> Accept-Encoding: identity\r\n
513 s> Accept-Encoding: identity\r\n
509 s> user-agent: test\r\n
514 s> user-agent: test\r\n
510 s> host: $LOCALIP:$HGPORT\r\n (glob)
515 s> host: $LOCALIP:$HGPORT\r\n (glob)
511 s> \r\n
516 s> \r\n
512 s> makefile('rb', None)
517 s> makefile('rb', None)
513 s> HTTP/1.1 301 Redirect\r\n
518 s> HTTP/1.1 301 Redirect\r\n
514 s> Server: testing stub value\r\n
519 s> Server: testing stub value\r\n
515 s> Date: $HTTP_DATE$\r\n
520 s> Date: $HTTP_DATE$\r\n
516 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
521 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
517 s> Content-Type: text/plain\r\n
522 s> Content-Type: text/plain\r\n
518 s> Content-Length: 10\r\n
523 s> Content-Length: 10\r\n
519 s> \r\n
524 s> \r\n
520 s> redirected
525 s> redirected
521 s> GET /redirected HTTP/1.1\r\n
526 s> GET /redirected HTTP/1.1\r\n
522 s> Accept-Encoding: identity\r\n
527 s> Accept-Encoding: identity\r\n
523 s> user-agent: test\r\n
528 s> user-agent: test\r\n
524 s> host: $LOCALIP:$HGPORT\r\n (glob)
529 s> host: $LOCALIP:$HGPORT\r\n (glob)
525 s> \r\n
530 s> \r\n
526 s> makefile('rb', None)
531 s> makefile('rb', None)
527 s> HTTP/1.1 200 Script output follows\r\n
532 s> HTTP/1.1 200 Script output follows\r\n
528 s> Server: testing stub value\r\n
533 s> Server: testing stub value\r\n
529 s> Date: $HTTP_DATE$\r\n
534 s> Date: $HTTP_DATE$\r\n
530 s> ETag: W/"*"\r\n (glob)
535 s> ETag: W/"*"\r\n (glob)
531 s> Content-Type: text/html; charset=ascii\r\n
536 s> Content-Type: text/html; charset=ascii\r\n
532 s> Transfer-Encoding: chunked\r\n
537 s> Transfer-Encoding: chunked\r\n
533 s> \r\n
538 s> \r\n
534 s> 414\r\n
539 s> 414\r\n
535 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
540 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
536 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
541 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
537 s> <head>\n
542 s> <head>\n
538 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
543 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
539 s> <meta name="robots" content="index, nofollow" />\n
544 s> <meta name="robots" content="index, nofollow" />\n
540 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
545 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
541 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
546 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
542 s> \n
547 s> \n
543 s> <title>redirected: log</title>\n
548 s> <title>redirected: log</title>\n
544 s> <link rel="alternate" type="application/atom+xml"\n
549 s> <link rel="alternate" type="application/atom+xml"\n
545 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
550 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
546 s> <link rel="alternate" type="application/rss+xml"\n
551 s> <link rel="alternate" type="application/rss+xml"\n
547 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
552 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
548 s> </head>\n
553 s> </head>\n
549 s> <body>\n
554 s> <body>\n
550 s> \n
555 s> \n
551 s> <div class="container">\n
556 s> <div class="container">\n
552 s> <div class="menu">\n
557 s> <div class="menu">\n
553 s> <div class="logo">\n
558 s> <div class="logo">\n
554 s> <a href="https://mercurial-scm.org/">\n
559 s> <a href="https://mercurial-scm.org/">\n
555 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
560 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
556 s> </div>\n
561 s> </div>\n
557 s> <ul>\n
562 s> <ul>\n
558 s> <li class="active">log</li>\n
563 s> <li class="active">log</li>\n
559 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
564 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
560 s> <li><a href="/redirected/tags">tags</a></li>\n
565 s> <li><a href="/redirected/tags">tags</a></li>\n
561 s> <li><a href="
566 s> <li><a href="
562 s> \r\n
567 s> \r\n
563 s> 810\r\n
568 s> 810\r\n
564 s> /redirected/bookmarks">bookmarks</a></li>\n
569 s> /redirected/bookmarks">bookmarks</a></li>\n
565 s> <li><a href="/redirected/branches">branches</a></li>\n
570 s> <li><a href="/redirected/branches">branches</a></li>\n
566 s> </ul>\n
571 s> </ul>\n
567 s> <ul>\n
572 s> <ul>\n
568 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
573 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
569 s> <li><a href="/redirected/file/tip">browse</a></li>\n
574 s> <li><a href="/redirected/file/tip">browse</a></li>\n
570 s> </ul>\n
575 s> </ul>\n
571 s> <ul>\n
576 s> <ul>\n
572 s> \n
577 s> \n
573 s> </ul>\n
578 s> </ul>\n
574 s> <ul>\n
579 s> <ul>\n
575 s> <li><a href="/redirected/help">help</a></li>\n
580 s> <li><a href="/redirected/help">help</a></li>\n
576 s> </ul>\n
581 s> </ul>\n
577 s> <div class="atom-logo">\n
582 s> <div class="atom-logo">\n
578 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
583 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
579 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
584 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
580 s> </a>\n
585 s> </a>\n
581 s> </div>\n
586 s> </div>\n
582 s> </div>\n
587 s> </div>\n
583 s> \n
588 s> \n
584 s> <div class="main">\n
589 s> <div class="main">\n
585 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
590 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
586 s> <h3>log</h3>\n
591 s> <h3>log</h3>\n
587 s> \n
592 s> \n
588 s> \n
593 s> \n
589 s> <form class="search" action="/redirected/log">\n
594 s> <form class="search" action="/redirected/log">\n
590 s> \n
595 s> \n
591 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
596 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
592 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
597 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
593 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
598 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
594 s> </form>\n
599 s> </form>\n
595 s> \n
600 s> \n
596 s> <div class="navigate">\n
601 s> <div class="navigate">\n
597 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
602 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
598 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
603 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
599 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
604 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
600 s> </div>\n
605 s> </div>\n
601 s> \n
606 s> \n
602 s> <table class="bigtable">\n
607 s> <table class="bigtable">\n
603 s> <thead>\n
608 s> <thead>\n
604 s> <tr>\n
609 s> <tr>\n
605 s> <th class="age">age</th>\n
610 s> <th class="age">age</th>\n
606 s> <th class="author">author</th>\n
611 s> <th class="author">author</th>\n
607 s> <th class="description">description</th>\n
612 s> <th class="description">description</th>\n
608 s> </tr>\n
613 s> </tr>\n
609 s> </thead>\n
614 s> </thead>\n
610 s> <tbody class="stripes2">\n
615 s> <tbody class="stripes2">\n
611 s> <tr>\n
616 s> <tr>\n
612 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
617 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
613 s> <td class="author">test</td>\n
618 s> <td class="author">test</td>\n
614 s> <td class="description">\n
619 s> <td class="description">\n
615 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
620 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
616 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
621 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
617 s> </td>\n
622 s> </td>\n
618 s> </tr>\n
623 s> </tr>\n
619 s> \n
624 s> \n
620 s> </tbody>\n
625 s> </tbody>\n
621 s> </table>\n
626 s> </table>\n
622 s> \n
627 s> \n
623 s> <div class="navigate">\n
628 s> <div class="navigate">\n
624 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
629 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
625 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
630 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
626 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
631 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
627 s> </div>\n
632 s> </div>\n
628 s> \n
633 s> \n
629 s> <script type="text/javascript">\n
634 s> <script type="text/javascript">\n
630 s> ajaxScrollInit(\n
635 s> ajaxScrollInit(\n
631 s> \'/redirected/shortlog/%next%\',\n
636 s> \'/redirected/shortlog/%next%\',\n
632 s> \'\', <!-- NEXTHASH\n
637 s> \'\', <!-- NEXTHASH\n
633 s> function (htmlText) {
638 s> function (htmlText) {
634 s> \r\n
639 s> \r\n
635 s> 14a\r\n
640 s> 14a\r\n
636 s> \n
641 s> \n
637 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
642 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
638 s> return m ? m[1] : null;\n
643 s> return m ? m[1] : null;\n
639 s> },\n
644 s> },\n
640 s> \'.bigtable > tbody\',\n
645 s> \'.bigtable > tbody\',\n
641 s> \'<tr class="%class%">\\\n
646 s> \'<tr class="%class%">\\\n
642 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
647 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
643 s> </tr>\'\n
648 s> </tr>\'\n
644 s> );\n
649 s> );\n
645 s> </script>\n
650 s> </script>\n
646 s> \n
651 s> \n
647 s> </div>\n
652 s> </div>\n
648 s> </div>\n
653 s> </div>\n
649 s> \n
654 s> \n
650 s> \n
655 s> \n
651 s> \n
656 s> \n
652 s> </body>\n
657 s> </body>\n
653 s> </html>\n
658 s> </html>\n
654 s> \n
659 s> \n
655 s> \r\n
660 s> \r\n
656 s> 0\r\n
661 s> 0\r\n
657 s> \r\n
662 s> \r\n
658
663
659 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
664 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
660 > command heads
665 > command heads
661 > EOF
666 > EOF
662 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
667 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
663 s> Accept-Encoding: identity\r\n
668 s> Accept-Encoding: identity\r\n
664 s> accept: application/mercurial-0.1\r\n
669 s> accept: application/mercurial-0.1\r\n
665 s> host: $LOCALIP:$HGPORT\r\n (glob)
670 s> host: $LOCALIP:$HGPORT\r\n (glob)
666 s> user-agent: Mercurial debugwireproto\r\n
671 s> user-agent: Mercurial debugwireproto\r\n
667 s> \r\n
672 s> \r\n
668 s> makefile('rb', None)
673 s> makefile('rb', None)
669 s> HTTP/1.1 301 Redirect\r\n
674 s> HTTP/1.1 301 Redirect\r\n
670 s> Server: testing stub value\r\n
675 s> Server: testing stub value\r\n
671 s> Date: $HTTP_DATE$\r\n
676 s> Date: $HTTP_DATE$\r\n
672 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
677 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
673 s> Content-Type: text/plain\r\n
678 s> Content-Type: text/plain\r\n
674 s> Content-Length: 10\r\n
679 s> Content-Length: 10\r\n
675 s> \r\n
680 s> \r\n
676 s> redirected
681 s> redirected
677 s> GET /redirected HTTP/1.1\r\n
682 s> GET /redirected HTTP/1.1\r\n
678 s> Accept-Encoding: identity\r\n
683 s> Accept-Encoding: identity\r\n
679 s> accept: application/mercurial-0.1\r\n
684 s> accept: application/mercurial-0.1\r\n
680 s> host: $LOCALIP:$HGPORT\r\n (glob)
685 s> host: $LOCALIP:$HGPORT\r\n (glob)
681 s> user-agent: Mercurial debugwireproto\r\n
686 s> user-agent: Mercurial debugwireproto\r\n
682 s> \r\n
687 s> \r\n
683 s> makefile('rb', None)
688 s> makefile('rb', None)
684 s> HTTP/1.1 200 Script output follows\r\n
689 s> HTTP/1.1 200 Script output follows\r\n
685 s> Server: testing stub value\r\n
690 s> Server: testing stub value\r\n
686 s> Date: $HTTP_DATE$\r\n
691 s> Date: $HTTP_DATE$\r\n
687 s> ETag: W/"*"\r\n (glob)
692 s> ETag: W/"*"\r\n (glob)
688 s> Content-Type: text/html; charset=ascii\r\n
693 s> Content-Type: text/html; charset=ascii\r\n
689 s> Transfer-Encoding: chunked\r\n
694 s> Transfer-Encoding: chunked\r\n
690 s> \r\n
695 s> \r\n
691 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
696 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
692 s> 414\r\n
697 s> 414\r\n
693 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
698 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
694 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
699 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
695 s> <head>\n
700 s> <head>\n
696 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
701 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
697 s> <meta name="robots" content="index, nofollow" />\n
702 s> <meta name="robots" content="index, nofollow" />\n
698 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
703 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
699 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
704 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
700 s> \n
705 s> \n
701 s> <title>redirected: log</title>\n
706 s> <title>redirected: log</title>\n
702 s> <link rel="alternate" type="application/atom+xml"\n
707 s> <link rel="alternate" type="application/atom+xml"\n
703 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
708 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
704 s> <link rel="alternate" type="application/rss+xml"\n
709 s> <link rel="alternate" type="application/rss+xml"\n
705 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
710 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
706 s> </head>\n
711 s> </head>\n
707 s> <body>\n
712 s> <body>\n
708 s> \n
713 s> \n
709 s> <div class="container">\n
714 s> <div class="container">\n
710 s> <div class="menu">\n
715 s> <div class="menu">\n
711 s> <div class="logo">\n
716 s> <div class="logo">\n
712 s> <a href="https://mercurial-scm.org/">\n
717 s> <a href="https://mercurial-scm.org/">\n
713 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
718 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
714 s> </div>\n
719 s> </div>\n
715 s> <ul>\n
720 s> <ul>\n
716 s> <li class="active">log</li>\n
721 s> <li class="active">log</li>\n
717 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
722 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
718 s> <li><a href="/redirected/tags">tags</a
723 s> <li><a href="/redirected/tags">tags</a
719 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
724 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
720 s> Accept-Encoding: identity\r\n
725 s> Accept-Encoding: identity\r\n
721 s> accept: application/mercurial-0.1\r\n
726 s> accept: application/mercurial-0.1\r\n
722 s> host: $LOCALIP:$HGPORT\r\n (glob)
727 s> host: $LOCALIP:$HGPORT\r\n (glob)
723 s> user-agent: Mercurial debugwireproto\r\n
728 s> user-agent: Mercurial debugwireproto\r\n
724 s> \r\n
729 s> \r\n
725 s> makefile('rb', None)
730 s> makefile('rb', None)
726 s> HTTP/1.1 200 Script output follows\r\n
731 s> HTTP/1.1 200 Script output follows\r\n
727 s> Server: testing stub value\r\n
732 s> Server: testing stub value\r\n
728 s> Date: $HTTP_DATE$\r\n
733 s> Date: $HTTP_DATE$\r\n
729 s> Content-Type: application/mercurial-0.1\r\n
734 s> Content-Type: application/mercurial-0.1\r\n
730 s> Content-Length: 467\r\n
735 s> Content-Length: 467\r\n
731 s> \r\n
736 s> \r\n
732 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
737 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
733 s> batch 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
738 s> batch 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
734 sending heads command
739 sending heads command
735 s> GET /redirected?cmd=heads HTTP/1.1\r\n
740 s> GET /redirected?cmd=heads HTTP/1.1\r\n
736 s> Accept-Encoding: identity\r\n
741 s> Accept-Encoding: identity\r\n
737 s> vary: X-HgProto-1\r\n
742 s> vary: X-HgProto-1\r\n
738 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
743 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
739 s> accept: application/mercurial-0.1\r\n
744 s> accept: application/mercurial-0.1\r\n
740 s> host: $LOCALIP:$HGPORT\r\n (glob)
745 s> host: $LOCALIP:$HGPORT\r\n (glob)
741 s> user-agent: Mercurial debugwireproto\r\n
746 s> user-agent: Mercurial debugwireproto\r\n
742 s> \r\n
747 s> \r\n
743 s> makefile('rb', None)
748 s> makefile('rb', None)
744 s> HTTP/1.1 200 Script output follows\r\n
749 s> HTTP/1.1 200 Script output follows\r\n
745 s> Server: testing stub value\r\n
750 s> Server: testing stub value\r\n
746 s> Date: $HTTP_DATE$\r\n
751 s> Date: $HTTP_DATE$\r\n
747 s> Content-Type: application/mercurial-0.1\r\n
752 s> Content-Type: application/mercurial-0.1\r\n
748 s> Content-Length: 41\r\n
753 s> Content-Length: 41\r\n
749 s> \r\n
754 s> \r\n
750 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
755 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
751 response: [
756 response: [
752 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
757 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
753 ]
758 ]
754 (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
759 (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
@@ -1,663 +1,668 b''
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4
4
5 $ hg init server
5 $ hg init server
6
6
7 zstd isn't present in plain builds. Make tests easier by removing
7 zstd isn't present in plain builds. Make tests easier by removing
8 zstd from the equation.
8 zstd from the equation.
9
9
10 $ cat >> server/.hg/hgrc << EOF
10 $ cat >> server/.hg/hgrc << EOF
11 > [server]
11 > [server]
12 > compressionengines = zlib
12 > compressionengines = zlib
13 > EOF
13 > EOF
14
14
15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
16 $ cat hg.pid > $DAEMON_PIDS
16 $ cat hg.pid > $DAEMON_PIDS
17
17
18 A normal capabilities request is serviced for version 1
18 A normal capabilities request is serviced for version 1
19
19
20 $ sendhttpraw << EOF
20 $ sendhttpraw << EOF
21 > httprequest GET ?cmd=capabilities
21 > httprequest GET ?cmd=capabilities
22 > user-agent: test
22 > user-agent: test
23 > EOF
23 > EOF
24 using raw connection to peer
24 using raw connection to peer
25 s> GET /?cmd=capabilities HTTP/1.1\r\n
25 s> GET /?cmd=capabilities HTTP/1.1\r\n
26 s> Accept-Encoding: identity\r\n
26 s> Accept-Encoding: identity\r\n
27 s> user-agent: test\r\n
27 s> user-agent: test\r\n
28 s> host: $LOCALIP:$HGPORT\r\n (glob)
28 s> host: $LOCALIP:$HGPORT\r\n (glob)
29 s> \r\n
29 s> \r\n
30 s> makefile('rb', None)
30 s> makefile('rb', None)
31 s> HTTP/1.1 200 Script output follows\r\n
31 s> HTTP/1.1 200 Script output follows\r\n
32 s> Server: testing stub value\r\n
32 s> Server: testing stub value\r\n
33 s> Date: $HTTP_DATE$\r\n
33 s> Date: $HTTP_DATE$\r\n
34 s> Content-Type: application/mercurial-0.1\r\n
34 s> Content-Type: application/mercurial-0.1\r\n
35 s> Content-Length: *\r\n (glob)
35 s> Content-Length: *\r\n (glob)
36 s> \r\n
36 s> \r\n
37 s> batch 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
37 s> batch 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
38
38
39 A proper request without the API server enabled returns the legacy response
39 A proper request without the API server enabled returns the legacy response
40
40
41 $ sendhttpraw << EOF
41 $ sendhttpraw << EOF
42 > httprequest GET ?cmd=capabilities
42 > httprequest GET ?cmd=capabilities
43 > user-agent: test
43 > user-agent: test
44 > x-hgupgrade-1: foo
44 > x-hgupgrade-1: foo
45 > x-hgproto-1: cbor
45 > x-hgproto-1: cbor
46 > EOF
46 > EOF
47 using raw connection to peer
47 using raw connection to peer
48 s> GET /?cmd=capabilities HTTP/1.1\r\n
48 s> GET /?cmd=capabilities HTTP/1.1\r\n
49 s> Accept-Encoding: identity\r\n
49 s> Accept-Encoding: identity\r\n
50 s> user-agent: test\r\n
50 s> user-agent: test\r\n
51 s> x-hgproto-1: cbor\r\n
51 s> x-hgproto-1: cbor\r\n
52 s> x-hgupgrade-1: foo\r\n
52 s> x-hgupgrade-1: foo\r\n
53 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 s> host: $LOCALIP:$HGPORT\r\n (glob)
54 s> \r\n
54 s> \r\n
55 s> makefile('rb', None)
55 s> makefile('rb', None)
56 s> HTTP/1.1 200 Script output follows\r\n
56 s> HTTP/1.1 200 Script output follows\r\n
57 s> Server: testing stub value\r\n
57 s> Server: testing stub value\r\n
58 s> Date: $HTTP_DATE$\r\n
58 s> Date: $HTTP_DATE$\r\n
59 s> Content-Type: application/mercurial-0.1\r\n
59 s> Content-Type: application/mercurial-0.1\r\n
60 s> Content-Length: *\r\n (glob)
60 s> Content-Length: *\r\n (glob)
61 s> \r\n
61 s> \r\n
62 s> batch 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
62 s> batch 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
63
63
64 Restart with just API server enabled. This enables serving the new format.
64 Restart with just API server enabled. This enables serving the new format.
65
65
66 $ killdaemons.py
66 $ killdaemons.py
67 $ cat error.log
67 $ cat error.log
68
68
69 $ cat >> server/.hg/hgrc << EOF
69 $ cat >> server/.hg/hgrc << EOF
70 > [experimental]
70 > [experimental]
71 > web.apiserver = true
71 > web.apiserver = true
72 > EOF
72 > EOF
73
73
74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
75 $ cat hg.pid > $DAEMON_PIDS
75 $ cat hg.pid > $DAEMON_PIDS
76
76
77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
78
78
79 $ sendhttpraw << EOF
79 $ sendhttpraw << EOF
80 > httprequest GET ?cmd=capabilities
80 > httprequest GET ?cmd=capabilities
81 > user-agent: test
81 > user-agent: test
82 > x-hgupgrade-1: foo bar
82 > x-hgupgrade-1: foo bar
83 > EOF
83 > EOF
84 using raw connection to peer
84 using raw connection to peer
85 s> GET /?cmd=capabilities HTTP/1.1\r\n
85 s> GET /?cmd=capabilities HTTP/1.1\r\n
86 s> Accept-Encoding: identity\r\n
86 s> Accept-Encoding: identity\r\n
87 s> user-agent: test\r\n
87 s> user-agent: test\r\n
88 s> x-hgupgrade-1: foo bar\r\n
88 s> x-hgupgrade-1: foo bar\r\n
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
90 s> \r\n
90 s> \r\n
91 s> makefile('rb', None)
91 s> makefile('rb', None)
92 s> HTTP/1.1 200 Script output follows\r\n
92 s> HTTP/1.1 200 Script output follows\r\n
93 s> Server: testing stub value\r\n
93 s> Server: testing stub value\r\n
94 s> Date: $HTTP_DATE$\r\n
94 s> Date: $HTTP_DATE$\r\n
95 s> Content-Type: application/mercurial-0.1\r\n
95 s> Content-Type: application/mercurial-0.1\r\n
96 s> Content-Length: *\r\n (glob)
96 s> Content-Length: *\r\n (glob)
97 s> \r\n
97 s> \r\n
98 s> batch 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
98 s> batch 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
99
99
100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
101
101
102 $ sendhttpraw << EOF
102 $ sendhttpraw << EOF
103 > httprequest GET ?cmd=capabilities
103 > httprequest GET ?cmd=capabilities
104 > user-agent: test
104 > user-agent: test
105 > x-hgupgrade-1: foo bar
105 > x-hgupgrade-1: foo bar
106 > x-hgproto-1: some value
106 > x-hgproto-1: some value
107 > EOF
107 > EOF
108 using raw connection to peer
108 using raw connection to peer
109 s> GET /?cmd=capabilities HTTP/1.1\r\n
109 s> GET /?cmd=capabilities HTTP/1.1\r\n
110 s> Accept-Encoding: identity\r\n
110 s> Accept-Encoding: identity\r\n
111 s> user-agent: test\r\n
111 s> user-agent: test\r\n
112 s> x-hgproto-1: some value\r\n
112 s> x-hgproto-1: some value\r\n
113 s> x-hgupgrade-1: foo bar\r\n
113 s> x-hgupgrade-1: foo bar\r\n
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 s> \r\n
115 s> \r\n
116 s> makefile('rb', None)
116 s> makefile('rb', None)
117 s> HTTP/1.1 200 Script output follows\r\n
117 s> HTTP/1.1 200 Script output follows\r\n
118 s> Server: testing stub value\r\n
118 s> Server: testing stub value\r\n
119 s> Date: $HTTP_DATE$\r\n
119 s> Date: $HTTP_DATE$\r\n
120 s> Content-Type: application/mercurial-0.1\r\n
120 s> Content-Type: application/mercurial-0.1\r\n
121 s> Content-Length: *\r\n (glob)
121 s> Content-Length: *\r\n (glob)
122 s> \r\n
122 s> \r\n
123 s> batch 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
123 s> batch 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
124
124
125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
126
126
127 $ sendhttpraw << EOF
127 $ sendhttpraw << EOF
128 > httprequest GET ?cmd=capabilities
128 > httprequest GET ?cmd=capabilities
129 > user-agent: test
129 > user-agent: test
130 > x-hgupgrade-1: foo bar
130 > x-hgupgrade-1: foo bar
131 > x-hgproto-1: cbor
131 > x-hgproto-1: cbor
132 > EOF
132 > EOF
133 using raw connection to peer
133 using raw connection to peer
134 s> GET /?cmd=capabilities HTTP/1.1\r\n
134 s> GET /?cmd=capabilities HTTP/1.1\r\n
135 s> Accept-Encoding: identity\r\n
135 s> Accept-Encoding: identity\r\n
136 s> user-agent: test\r\n
136 s> user-agent: test\r\n
137 s> x-hgproto-1: cbor\r\n
137 s> x-hgproto-1: cbor\r\n
138 s> x-hgupgrade-1: foo bar\r\n
138 s> x-hgupgrade-1: foo bar\r\n
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 s> \r\n
140 s> \r\n
141 s> makefile('rb', None)
141 s> makefile('rb', None)
142 s> HTTP/1.1 200 OK\r\n
142 s> HTTP/1.1 200 OK\r\n
143 s> Server: testing stub value\r\n
143 s> Server: testing stub value\r\n
144 s> Date: $HTTP_DATE$\r\n
144 s> Date: $HTTP_DATE$\r\n
145 s> Content-Type: application/mercurial-cbor\r\n
145 s> Content-Type: application/mercurial-cbor\r\n
146 s> Content-Length: *\r\n (glob)
146 s> Content-Length: *\r\n (glob)
147 s> \r\n
147 s> \r\n
148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch 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
148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch 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
149 cbor> [
149 cbor> [
150 {
150 {
151 b'apibase': b'api/',
151 b'apibase': b'api/',
152 b'apis': {},
152 b'apis': {},
153 b'v1capabilities': b'batch 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'
153 b'v1capabilities': b'batch 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'
154 }
154 }
155 ]
155 ]
156
156
157 Restart server to enable HTTPv2
157 Restart server to enable HTTPv2
158
158
159 $ killdaemons.py
159 $ killdaemons.py
160 $ enablehttpv2 server
160 $ enablehttpv2 server
161 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
161 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
162 $ cat hg.pid > $DAEMON_PIDS
162 $ cat hg.pid > $DAEMON_PIDS
163
163
164 Only requested API services are returned
164 Only requested API services are returned
165
165
166 $ sendhttpraw << EOF
166 $ sendhttpraw << EOF
167 > httprequest GET ?cmd=capabilities
167 > httprequest GET ?cmd=capabilities
168 > user-agent: test
168 > user-agent: test
169 > x-hgupgrade-1: foo bar
169 > x-hgupgrade-1: foo bar
170 > x-hgproto-1: cbor
170 > x-hgproto-1: cbor
171 > EOF
171 > EOF
172 using raw connection to peer
172 using raw connection to peer
173 s> GET /?cmd=capabilities HTTP/1.1\r\n
173 s> GET /?cmd=capabilities HTTP/1.1\r\n
174 s> Accept-Encoding: identity\r\n
174 s> Accept-Encoding: identity\r\n
175 s> user-agent: test\r\n
175 s> user-agent: test\r\n
176 s> x-hgproto-1: cbor\r\n
176 s> x-hgproto-1: cbor\r\n
177 s> x-hgupgrade-1: foo bar\r\n
177 s> x-hgupgrade-1: foo bar\r\n
178 s> host: $LOCALIP:$HGPORT\r\n (glob)
178 s> host: $LOCALIP:$HGPORT\r\n (glob)
179 s> \r\n
179 s> \r\n
180 s> makefile('rb', None)
180 s> makefile('rb', None)
181 s> HTTP/1.1 200 OK\r\n
181 s> HTTP/1.1 200 OK\r\n
182 s> Server: testing stub value\r\n
182 s> Server: testing stub value\r\n
183 s> Date: $HTTP_DATE$\r\n
183 s> Date: $HTTP_DATE$\r\n
184 s> Content-Type: application/mercurial-cbor\r\n
184 s> Content-Type: application/mercurial-cbor\r\n
185 s> Content-Length: *\r\n (glob)
185 s> Content-Length: *\r\n (glob)
186 s> \r\n
186 s> \r\n
187 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch 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
187 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch 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
188 cbor> [
188 cbor> [
189 {
189 {
190 b'apibase': b'api/',
190 b'apibase': b'api/',
191 b'apis': {},
191 b'apis': {},
192 b'v1capabilities': b'batch 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'
192 b'v1capabilities': b'batch 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'
193 }
193 }
194 ]
194 ]
195
195
196 Request for HTTPv2 service returns information about it
196 Request for HTTPv2 service returns information about it
197
197
198 $ sendhttpraw << EOF
198 $ sendhttpraw << EOF
199 > httprequest GET ?cmd=capabilities
199 > httprequest GET ?cmd=capabilities
200 > user-agent: test
200 > user-agent: test
201 > x-hgupgrade-1: exp-http-v2-0002 foo bar
201 > x-hgupgrade-1: exp-http-v2-0002 foo bar
202 > x-hgproto-1: cbor
202 > x-hgproto-1: cbor
203 > EOF
203 > EOF
204 using raw connection to peer
204 using raw connection to peer
205 s> GET /?cmd=capabilities HTTP/1.1\r\n
205 s> GET /?cmd=capabilities HTTP/1.1\r\n
206 s> Accept-Encoding: identity\r\n
206 s> Accept-Encoding: identity\r\n
207 s> user-agent: test\r\n
207 s> user-agent: test\r\n
208 s> x-hgproto-1: cbor\r\n
208 s> x-hgproto-1: cbor\r\n
209 s> x-hgupgrade-1: exp-http-v2-0002 foo bar\r\n
209 s> x-hgupgrade-1: exp-http-v2-0002 foo bar\r\n
210 s> host: $LOCALIP:$HGPORT\r\n (glob)
210 s> host: $LOCALIP:$HGPORT\r\n (glob)
211 s> \r\n
211 s> \r\n
212 s> makefile('rb', None)
212 s> makefile('rb', None)
213 s> HTTP/1.1 200 OK\r\n
213 s> HTTP/1.1 200 OK\r\n
214 s> Server: testing stub value\r\n
214 s> Server: testing stub value\r\n
215 s> Date: $HTTP_DATE$\r\n
215 s> Date: $HTTP_DATE$\r\n
216 s> Content-Type: application/mercurial-cbor\r\n
216 s> Content-Type: application/mercurial-cbor\r\n
217 s> Content-Length: *\r\n (glob)
217 s> Content-Length: *\r\n (glob)
218 s> \r\n
218 s> \r\n
219 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
219 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
220 cbor> [
220 cbor> [
221 {
221 {
222 b'apibase': b'api/',
222 b'apibase': b'api/',
223 b'apis': {
223 b'apis': {
224 b'exp-http-v2-0002': {
224 b'exp-http-v2-0002': {
225 b'commands': {
225 b'commands': {
226 b'branchmap': {
226 b'branchmap': {
227 b'args': {},
227 b'args': {},
228 b'permissions': [
228 b'permissions': [
229 b'pull'
229 b'pull'
230 ]
230 ]
231 },
231 },
232 b'capabilities': {
232 b'capabilities': {
233 b'args': {},
233 b'args': {},
234 b'permissions': [
234 b'permissions': [
235 b'pull'
235 b'pull'
236 ]
236 ]
237 },
237 },
238 b'changesetdata': {
238 b'changesetdata': {
239 b'args': {
239 b'args': {
240 b'fields': {
240 b'fields': {
241 b'default': set([]),
241 b'default': set([]),
242 b'required': False,
242 b'required': False,
243 b'type': b'set',
243 b'type': b'set',
244 b'validvalues': set([
244 b'validvalues': set([
245 b'bookmarks',
245 b'bookmarks',
246 b'parents',
246 b'parents',
247 b'phase',
247 b'phase',
248 b'revision'
248 b'revision'
249 ])
249 ])
250 },
250 },
251 b'noderange': {
251 b'noderange': {
252 b'default': None,
252 b'default': None,
253 b'required': False,
253 b'required': False,
254 b'type': b'list'
254 b'type': b'list'
255 },
255 },
256 b'nodes': {
256 b'nodes': {
257 b'default': None,
257 b'default': None,
258 b'required': False,
258 b'required': False,
259 b'type': b'list'
259 b'type': b'list'
260 },
260 },
261 b'nodesdepth': {
261 b'nodesdepth': {
262 b'default': None,
262 b'default': None,
263 b'required': False,
263 b'required': False,
264 b'type': b'int'
264 b'type': b'int'
265 }
265 }
266 },
266 },
267 b'permissions': [
267 b'permissions': [
268 b'pull'
268 b'pull'
269 ]
269 ]
270 },
270 },
271 b'filedata': {
271 b'filedata': {
272 b'args': {
272 b'args': {
273 b'fields': {
273 b'fields': {
274 b'default': set([]),
274 b'default': set([]),
275 b'required': False,
275 b'required': False,
276 b'type': b'set',
276 b'type': b'set',
277 b'validvalues': set([
277 b'validvalues': set([
278 b'parents',
278 b'parents',
279 b'revision'
279 b'revision'
280 ])
280 ])
281 },
281 },
282 b'haveparents': {
282 b'haveparents': {
283 b'default': False,
283 b'default': False,
284 b'required': False,
284 b'required': False,
285 b'type': b'bool'
285 b'type': b'bool'
286 },
286 },
287 b'nodes': {
287 b'nodes': {
288 b'required': True,
288 b'required': True,
289 b'type': b'list'
289 b'type': b'list'
290 },
290 },
291 b'path': {
291 b'path': {
292 b'required': True,
292 b'required': True,
293 b'type': b'bytes'
293 b'type': b'bytes'
294 }
294 }
295 },
295 },
296 b'permissions': [
296 b'permissions': [
297 b'pull'
297 b'pull'
298 ]
298 ]
299 },
299 },
300 b'heads': {
300 b'heads': {
301 b'args': {
301 b'args': {
302 b'publiconly': {
302 b'publiconly': {
303 b'default': False,
303 b'default': False,
304 b'required': False,
304 b'required': False,
305 b'type': b'bool'
305 b'type': b'bool'
306 }
306 }
307 },
307 },
308 b'permissions': [
308 b'permissions': [
309 b'pull'
309 b'pull'
310 ]
310 ]
311 },
311 },
312 b'known': {
312 b'known': {
313 b'args': {
313 b'args': {
314 b'nodes': {
314 b'nodes': {
315 b'default': [],
315 b'default': [],
316 b'required': False,
316 b'required': False,
317 b'type': b'list'
317 b'type': b'list'
318 }
318 }
319 },
319 },
320 b'permissions': [
320 b'permissions': [
321 b'pull'
321 b'pull'
322 ]
322 ]
323 },
323 },
324 b'listkeys': {
324 b'listkeys': {
325 b'args': {
325 b'args': {
326 b'namespace': {
326 b'namespace': {
327 b'required': True,
327 b'required': True,
328 b'type': b'bytes'
328 b'type': b'bytes'
329 }
329 }
330 },
330 },
331 b'permissions': [
331 b'permissions': [
332 b'pull'
332 b'pull'
333 ]
333 ]
334 },
334 },
335 b'lookup': {
335 b'lookup': {
336 b'args': {
336 b'args': {
337 b'key': {
337 b'key': {
338 b'required': True,
338 b'required': True,
339 b'type': b'bytes'
339 b'type': b'bytes'
340 }
340 }
341 },
341 },
342 b'permissions': [
342 b'permissions': [
343 b'pull'
343 b'pull'
344 ]
344 ]
345 },
345 },
346 b'manifestdata': {
346 b'manifestdata': {
347 b'args': {
347 b'args': {
348 b'fields': {
348 b'fields': {
349 b'default': set([]),
349 b'default': set([]),
350 b'required': False,
350 b'required': False,
351 b'type': b'set',
351 b'type': b'set',
352 b'validvalues': set([
352 b'validvalues': set([
353 b'parents',
353 b'parents',
354 b'revision'
354 b'revision'
355 ])
355 ])
356 },
356 },
357 b'haveparents': {
357 b'haveparents': {
358 b'default': False,
358 b'default': False,
359 b'required': False,
359 b'required': False,
360 b'type': b'bool'
360 b'type': b'bool'
361 },
361 },
362 b'nodes': {
362 b'nodes': {
363 b'required': True,
363 b'required': True,
364 b'type': b'list'
364 b'type': b'list'
365 },
365 },
366 b'tree': {
366 b'tree': {
367 b'required': True,
367 b'required': True,
368 b'type': b'bytes'
368 b'type': b'bytes'
369 }
369 }
370 },
370 },
371 b'permissions': [
371 b'permissions': [
372 b'pull'
372 b'pull'
373 ]
373 ]
374 },
374 },
375 b'pushkey': {
375 b'pushkey': {
376 b'args': {
376 b'args': {
377 b'key': {
377 b'key': {
378 b'required': True,
378 b'required': True,
379 b'type': b'bytes'
379 b'type': b'bytes'
380 },
380 },
381 b'namespace': {
381 b'namespace': {
382 b'required': True,
382 b'required': True,
383 b'type': b'bytes'
383 b'type': b'bytes'
384 },
384 },
385 b'new': {
385 b'new': {
386 b'required': True,
386 b'required': True,
387 b'type': b'bytes'
387 b'type': b'bytes'
388 },
388 },
389 b'old': {
389 b'old': {
390 b'required': True,
390 b'required': True,
391 b'type': b'bytes'
391 b'type': b'bytes'
392 }
392 }
393 },
393 },
394 b'permissions': [
394 b'permissions': [
395 b'push'
395 b'push'
396 ]
396 ]
397 }
397 }
398 },
398 },
399 b'framingmediatypes': [
399 b'framingmediatypes': [
400 b'application/mercurial-exp-framing-0006'
400 b'application/mercurial-exp-framing-0006'
401 ],
401 ],
402 b'pathfilterprefixes': set([
402 b'pathfilterprefixes': set([
403 b'path:',
403 b'path:',
404 b'rootfilesin:'
404 b'rootfilesin:'
405 ]),
405 ]),
406 b'rawrepoformats': [
406 b'rawrepoformats': [
407 b'generaldelta',
407 b'generaldelta',
408 b'revlogv1'
408 b'revlogv1'
409 ]
409 ]
410 }
410 }
411 },
411 },
412 b'v1capabilities': b'batch 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'
412 b'v1capabilities': b'batch 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'
413 }
413 }
414 ]
414 ]
415
415
416 capabilities command returns expected info
416 capabilities command returns expected info
417
417
418 $ sendhttpv2peerhandshake << EOF
418 $ sendhttpv2peerhandshake << EOF
419 > command capabilities
419 > command capabilities
420 > EOF
420 > EOF
421 creating http peer for wire protocol version 2
421 creating http peer for wire protocol version 2
422 s> GET /?cmd=capabilities HTTP/1.1\r\n
422 s> GET /?cmd=capabilities HTTP/1.1\r\n
423 s> Accept-Encoding: identity\r\n
423 s> Accept-Encoding: identity\r\n
424 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
424 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
425 s> x-hgproto-1: cbor\r\n
425 s> x-hgproto-1: cbor\r\n
426 s> x-hgupgrade-1: exp-http-v2-0002\r\n
426 s> x-hgupgrade-1: exp-http-v2-0002\r\n
427 s> accept: application/mercurial-0.1\r\n
427 s> accept: application/mercurial-0.1\r\n
428 s> host: $LOCALIP:$HGPORT\r\n (glob)
428 s> host: $LOCALIP:$HGPORT\r\n (glob)
429 s> user-agent: Mercurial debugwireproto\r\n
429 s> user-agent: Mercurial debugwireproto\r\n
430 s> \r\n
430 s> \r\n
431 s> makefile('rb', None)
431 s> makefile('rb', None)
432 s> HTTP/1.1 200 OK\r\n
432 s> HTTP/1.1 200 OK\r\n
433 s> Server: testing stub value\r\n
433 s> Server: testing stub value\r\n
434 s> Date: $HTTP_DATE$\r\n
434 s> Date: $HTTP_DATE$\r\n
435 s> Content-Type: application/mercurial-cbor\r\n
435 s> Content-Type: application/mercurial-cbor\r\n
436 s> Content-Length: *\r\n (glob)
436 s> Content-Length: *\r\n (glob)
437 s> \r\n
437 s> \r\n
438 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
438 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch 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
439 sending capabilities command
439 sending capabilities command
440 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
440 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
441 s> Accept-Encoding: identity\r\n
441 s> Accept-Encoding: identity\r\n
442 s> accept: application/mercurial-exp-framing-0006\r\n
442 s> accept: application/mercurial-exp-framing-0006\r\n
443 s> content-type: application/mercurial-exp-framing-0006\r\n
443 s> content-type: application/mercurial-exp-framing-0006\r\n
444 s> content-length: 63\r\n
444 s> content-length: 63\r\n
445 s> host: $LOCALIP:$HGPORT\r\n (glob)
445 s> host: $LOCALIP:$HGPORT\r\n (glob)
446 s> user-agent: Mercurial debugwireproto\r\n
446 s> user-agent: Mercurial debugwireproto\r\n
447 s> \r\n
447 s> \r\n
448 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x13\x00\x00\x01\x00\x01\x00\x11\xa1DnameLcapabilities
448 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x13\x00\x00\x01\x00\x01\x00\x11\xa1DnameLcapabilities
449 s> makefile('rb', None)
449 s> makefile('rb', None)
450 s> HTTP/1.1 200 OK\r\n
450 s> HTTP/1.1 200 OK\r\n
451 s> Server: testing stub value\r\n
451 s> Server: testing stub value\r\n
452 s> Date: $HTTP_DATE$\r\n
452 s> Date: $HTTP_DATE$\r\n
453 s> Content-Type: application/mercurial-exp-framing-0006\r\n
453 s> Content-Type: application/mercurial-exp-framing-0006\r\n
454 s> Transfer-Encoding: chunked\r\n
454 s> Transfer-Encoding: chunked\r\n
455 s> \r\n
455 s> \r\n
456 s> 11\r\n
457 s> \t\x00\x00\x01\x00\x02\x01\x92
458 s> Hidentity
459 s> \r\n
460 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
456 s> 13\r\n
461 s> 13\r\n
457 s> \x0b\x00\x00\x01\x00\x02\x011
462 s> \x0b\x00\x00\x01\x00\x02\x041
458 s> \xa1FstatusBok
463 s> \xa1FstatusBok
459 s> \r\n
464 s> \r\n
460 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
465 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
461 s> 508\r\n
466 s> 508\r\n
462 s> \x00\x05\x00\x01\x00\x02\x001
467 s> \x00\x05\x00\x01\x00\x02\x041
463 s> \xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
468 s> \xa4Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
464 s> \r\n
469 s> \r\n
465 received frame(size=1280; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
470 received frame(size=1280; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
466 s> 8\r\n
471 s> 8\r\n
467 s> \x00\x00\x00\x01\x00\x02\x002
472 s> \x00\x00\x00\x01\x00\x02\x002
468 s> \r\n
473 s> \r\n
469 s> 0\r\n
474 s> 0\r\n
470 s> \r\n
475 s> \r\n
471 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
476 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
472 response: gen[
477 response: gen[
473 {
478 {
474 b'commands': {
479 b'commands': {
475 b'branchmap': {
480 b'branchmap': {
476 b'args': {},
481 b'args': {},
477 b'permissions': [
482 b'permissions': [
478 b'pull'
483 b'pull'
479 ]
484 ]
480 },
485 },
481 b'capabilities': {
486 b'capabilities': {
482 b'args': {},
487 b'args': {},
483 b'permissions': [
488 b'permissions': [
484 b'pull'
489 b'pull'
485 ]
490 ]
486 },
491 },
487 b'changesetdata': {
492 b'changesetdata': {
488 b'args': {
493 b'args': {
489 b'fields': {
494 b'fields': {
490 b'default': set([]),
495 b'default': set([]),
491 b'required': False,
496 b'required': False,
492 b'type': b'set',
497 b'type': b'set',
493 b'validvalues': set([
498 b'validvalues': set([
494 b'bookmarks',
499 b'bookmarks',
495 b'parents',
500 b'parents',
496 b'phase',
501 b'phase',
497 b'revision'
502 b'revision'
498 ])
503 ])
499 },
504 },
500 b'noderange': {
505 b'noderange': {
501 b'default': None,
506 b'default': None,
502 b'required': False,
507 b'required': False,
503 b'type': b'list'
508 b'type': b'list'
504 },
509 },
505 b'nodes': {
510 b'nodes': {
506 b'default': None,
511 b'default': None,
507 b'required': False,
512 b'required': False,
508 b'type': b'list'
513 b'type': b'list'
509 },
514 },
510 b'nodesdepth': {
515 b'nodesdepth': {
511 b'default': None,
516 b'default': None,
512 b'required': False,
517 b'required': False,
513 b'type': b'int'
518 b'type': b'int'
514 }
519 }
515 },
520 },
516 b'permissions': [
521 b'permissions': [
517 b'pull'
522 b'pull'
518 ]
523 ]
519 },
524 },
520 b'filedata': {
525 b'filedata': {
521 b'args': {
526 b'args': {
522 b'fields': {
527 b'fields': {
523 b'default': set([]),
528 b'default': set([]),
524 b'required': False,
529 b'required': False,
525 b'type': b'set',
530 b'type': b'set',
526 b'validvalues': set([
531 b'validvalues': set([
527 b'parents',
532 b'parents',
528 b'revision'
533 b'revision'
529 ])
534 ])
530 },
535 },
531 b'haveparents': {
536 b'haveparents': {
532 b'default': False,
537 b'default': False,
533 b'required': False,
538 b'required': False,
534 b'type': b'bool'
539 b'type': b'bool'
535 },
540 },
536 b'nodes': {
541 b'nodes': {
537 b'required': True,
542 b'required': True,
538 b'type': b'list'
543 b'type': b'list'
539 },
544 },
540 b'path': {
545 b'path': {
541 b'required': True,
546 b'required': True,
542 b'type': b'bytes'
547 b'type': b'bytes'
543 }
548 }
544 },
549 },
545 b'permissions': [
550 b'permissions': [
546 b'pull'
551 b'pull'
547 ]
552 ]
548 },
553 },
549 b'heads': {
554 b'heads': {
550 b'args': {
555 b'args': {
551 b'publiconly': {
556 b'publiconly': {
552 b'default': False,
557 b'default': False,
553 b'required': False,
558 b'required': False,
554 b'type': b'bool'
559 b'type': b'bool'
555 }
560 }
556 },
561 },
557 b'permissions': [
562 b'permissions': [
558 b'pull'
563 b'pull'
559 ]
564 ]
560 },
565 },
561 b'known': {
566 b'known': {
562 b'args': {
567 b'args': {
563 b'nodes': {
568 b'nodes': {
564 b'default': [],
569 b'default': [],
565 b'required': False,
570 b'required': False,
566 b'type': b'list'
571 b'type': b'list'
567 }
572 }
568 },
573 },
569 b'permissions': [
574 b'permissions': [
570 b'pull'
575 b'pull'
571 ]
576 ]
572 },
577 },
573 b'listkeys': {
578 b'listkeys': {
574 b'args': {
579 b'args': {
575 b'namespace': {
580 b'namespace': {
576 b'required': True,
581 b'required': True,
577 b'type': b'bytes'
582 b'type': b'bytes'
578 }
583 }
579 },
584 },
580 b'permissions': [
585 b'permissions': [
581 b'pull'
586 b'pull'
582 ]
587 ]
583 },
588 },
584 b'lookup': {
589 b'lookup': {
585 b'args': {
590 b'args': {
586 b'key': {
591 b'key': {
587 b'required': True,
592 b'required': True,
588 b'type': b'bytes'
593 b'type': b'bytes'
589 }
594 }
590 },
595 },
591 b'permissions': [
596 b'permissions': [
592 b'pull'
597 b'pull'
593 ]
598 ]
594 },
599 },
595 b'manifestdata': {
600 b'manifestdata': {
596 b'args': {
601 b'args': {
597 b'fields': {
602 b'fields': {
598 b'default': set([]),
603 b'default': set([]),
599 b'required': False,
604 b'required': False,
600 b'type': b'set',
605 b'type': b'set',
601 b'validvalues': set([
606 b'validvalues': set([
602 b'parents',
607 b'parents',
603 b'revision'
608 b'revision'
604 ])
609 ])
605 },
610 },
606 b'haveparents': {
611 b'haveparents': {
607 b'default': False,
612 b'default': False,
608 b'required': False,
613 b'required': False,
609 b'type': b'bool'
614 b'type': b'bool'
610 },
615 },
611 b'nodes': {
616 b'nodes': {
612 b'required': True,
617 b'required': True,
613 b'type': b'list'
618 b'type': b'list'
614 },
619 },
615 b'tree': {
620 b'tree': {
616 b'required': True,
621 b'required': True,
617 b'type': b'bytes'
622 b'type': b'bytes'
618 }
623 }
619 },
624 },
620 b'permissions': [
625 b'permissions': [
621 b'pull'
626 b'pull'
622 ]
627 ]
623 },
628 },
624 b'pushkey': {
629 b'pushkey': {
625 b'args': {
630 b'args': {
626 b'key': {
631 b'key': {
627 b'required': True,
632 b'required': True,
628 b'type': b'bytes'
633 b'type': b'bytes'
629 },
634 },
630 b'namespace': {
635 b'namespace': {
631 b'required': True,
636 b'required': True,
632 b'type': b'bytes'
637 b'type': b'bytes'
633 },
638 },
634 b'new': {
639 b'new': {
635 b'required': True,
640 b'required': True,
636 b'type': b'bytes'
641 b'type': b'bytes'
637 },
642 },
638 b'old': {
643 b'old': {
639 b'required': True,
644 b'required': True,
640 b'type': b'bytes'
645 b'type': b'bytes'
641 }
646 }
642 },
647 },
643 b'permissions': [
648 b'permissions': [
644 b'push'
649 b'push'
645 ]
650 ]
646 }
651 }
647 },
652 },
648 b'framingmediatypes': [
653 b'framingmediatypes': [
649 b'application/mercurial-exp-framing-0006'
654 b'application/mercurial-exp-framing-0006'
650 ],
655 ],
651 b'pathfilterprefixes': set([
656 b'pathfilterprefixes': set([
652 b'path:',
657 b'path:',
653 b'rootfilesin:'
658 b'rootfilesin:'
654 ]),
659 ]),
655 b'rawrepoformats': [
660 b'rawrepoformats': [
656 b'generaldelta',
661 b'generaldelta',
657 b'revlogv1'
662 b'revlogv1'
658 ]
663 ]
659 }
664 }
660 ]
665 ]
661 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
666 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
662
667
663 $ cat error.log
668 $ cat error.log
@@ -1,1290 +1,1310 b''
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 > [server]
14 > [server]
15 > compressionengines = zlib
15 > compressionengines = zlib
16 > [extensions]
16 > [extensions]
17 > simplecache = $TESTDIR/wireprotosimplecache.py
17 > simplecache = $TESTDIR/wireprotosimplecache.py
18 > [simplecache]
18 > [simplecache]
19 > cacheapi = true
19 > cacheapi = true
20 > EOF
20 > EOF
21
21
22 $ echo a0 > a
22 $ echo a0 > a
23 $ echo b0 > b
23 $ echo b0 > b
24 $ hg -q commit -A -m 'commit 0'
24 $ hg -q commit -A -m 'commit 0'
25 $ echo a1 > a
25 $ echo a1 > a
26 $ hg commit -m 'commit 1'
26 $ hg commit -m 'commit 1'
27
27
28 $ hg --debug debugindex -m
28 $ hg --debug debugindex -m
29 rev linkrev nodeid p1 p2
29 rev linkrev nodeid p1 p2
30 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
30 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
31 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
31 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
32
32
33 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
33 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
34 $ cat hg.pid > $DAEMON_PIDS
34 $ cat hg.pid > $DAEMON_PIDS
35
35
36 $ cat > redirects.py << EOF
36 $ cat > redirects.py << EOF
37 > [
37 > [
38 > {
38 > {
39 > b'name': b'target-a',
39 > b'name': b'target-a',
40 > b'protocol': b'http',
40 > b'protocol': b'http',
41 > b'snirequired': False,
41 > b'snirequired': False,
42 > b'tlsversions': [b'1.2', b'1.3'],
42 > b'tlsversions': [b'1.2', b'1.3'],
43 > b'uris': [b'http://example.com/'],
43 > b'uris': [b'http://example.com/'],
44 > },
44 > },
45 > ]
45 > ]
46 > EOF
46 > EOF
47
47
48 Redirect targets advertised when configured
48 Redirect targets advertised when configured
49
49
50 $ sendhttpv2peerhandshake << EOF
50 $ sendhttpv2peerhandshake << EOF
51 > command capabilities
51 > command capabilities
52 > EOF
52 > EOF
53 creating http peer for wire protocol version 2
53 creating http peer for wire protocol version 2
54 s> GET /?cmd=capabilities HTTP/1.1\r\n
54 s> GET /?cmd=capabilities HTTP/1.1\r\n
55 s> Accept-Encoding: identity\r\n
55 s> Accept-Encoding: identity\r\n
56 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
56 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
57 s> x-hgproto-1: cbor\r\n
57 s> x-hgproto-1: cbor\r\n
58 s> x-hgupgrade-1: exp-http-v2-0002\r\n
58 s> x-hgupgrade-1: exp-http-v2-0002\r\n
59 s> accept: application/mercurial-0.1\r\n
59 s> accept: application/mercurial-0.1\r\n
60 s> host: $LOCALIP:$HGPORT\r\n (glob)
60 s> host: $LOCALIP:$HGPORT\r\n (glob)
61 s> user-agent: Mercurial debugwireproto\r\n
61 s> user-agent: Mercurial debugwireproto\r\n
62 s> \r\n
62 s> \r\n
63 s> makefile('rb', None)
63 s> makefile('rb', None)
64 s> HTTP/1.1 200 OK\r\n
64 s> HTTP/1.1 200 OK\r\n
65 s> Server: testing stub value\r\n
65 s> Server: testing stub value\r\n
66 s> Date: $HTTP_DATE$\r\n
66 s> Date: $HTTP_DATE$\r\n
67 s> Content-Type: application/mercurial-cbor\r\n
67 s> Content-Type: application/mercurial-cbor\r\n
68 s> Content-Length: 1930\r\n
68 s> Content-Length: 1930\r\n
69 s> \r\n
69 s> \r\n
70 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd3batch 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
70 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd3batch 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
71 (remote redirect target target-a is compatible)
71 (remote redirect target target-a is compatible)
72 sending capabilities command
72 sending capabilities command
73 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
73 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
74 s> Accept-Encoding: identity\r\n
74 s> Accept-Encoding: identity\r\n
75 s> accept: application/mercurial-exp-framing-0006\r\n
75 s> accept: application/mercurial-exp-framing-0006\r\n
76 s> content-type: application/mercurial-exp-framing-0006\r\n
76 s> content-type: application/mercurial-exp-framing-0006\r\n
77 s> content-length: 111\r\n
77 s> content-length: 111\r\n
78 s> host: $LOCALIP:$HGPORT\r\n (glob)
78 s> host: $LOCALIP:$HGPORT\r\n (glob)
79 s> user-agent: Mercurial debugwireproto\r\n
79 s> user-agent: Mercurial debugwireproto\r\n
80 s> \r\n
80 s> \r\n
81 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
81 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
82 s> makefile('rb', None)
82 s> makefile('rb', None)
83 s> HTTP/1.1 200 OK\r\n
83 s> HTTP/1.1 200 OK\r\n
84 s> Server: testing stub value\r\n
84 s> Server: testing stub value\r\n
85 s> Date: $HTTP_DATE$\r\n
85 s> Date: $HTTP_DATE$\r\n
86 s> Content-Type: application/mercurial-exp-framing-0006\r\n
86 s> Content-Type: application/mercurial-exp-framing-0006\r\n
87 s> Transfer-Encoding: chunked\r\n
87 s> Transfer-Encoding: chunked\r\n
88 s> \r\n
88 s> \r\n
89 s> 11\r\n
90 s> \t\x00\x00\x01\x00\x02\x01\x92
91 s> Hidentity
92 s> \r\n
93 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
89 s> 13\r\n
94 s> 13\r\n
90 s> \x0b\x00\x00\x01\x00\x02\x011
95 s> \x0b\x00\x00\x01\x00\x02\x041
91 s> \xa1FstatusBok
96 s> \xa1FstatusBok
92 s> \r\n
97 s> \r\n
93 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
98 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
94 s> 588\r\n
99 s> 588\r\n
95 s> \x80\x05\x00\x01\x00\x02\x001
100 s> \x80\x05\x00\x01\x00\x02\x041
96 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
101 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
97 s> \r\n
102 s> \r\n
98 received frame(size=1408; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
103 received frame(size=1408; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
99 s> 8\r\n
104 s> 8\r\n
100 s> \x00\x00\x00\x01\x00\x02\x002
105 s> \x00\x00\x00\x01\x00\x02\x002
101 s> \r\n
106 s> \r\n
102 s> 0\r\n
107 s> 0\r\n
103 s> \r\n
108 s> \r\n
104 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
109 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
105 response: gen[
110 response: gen[
106 {
111 {
107 b'commands': {
112 b'commands': {
108 b'branchmap': {
113 b'branchmap': {
109 b'args': {},
114 b'args': {},
110 b'permissions': [
115 b'permissions': [
111 b'pull'
116 b'pull'
112 ]
117 ]
113 },
118 },
114 b'capabilities': {
119 b'capabilities': {
115 b'args': {},
120 b'args': {},
116 b'permissions': [
121 b'permissions': [
117 b'pull'
122 b'pull'
118 ]
123 ]
119 },
124 },
120 b'changesetdata': {
125 b'changesetdata': {
121 b'args': {
126 b'args': {
122 b'fields': {
127 b'fields': {
123 b'default': set([]),
128 b'default': set([]),
124 b'required': False,
129 b'required': False,
125 b'type': b'set',
130 b'type': b'set',
126 b'validvalues': set([
131 b'validvalues': set([
127 b'bookmarks',
132 b'bookmarks',
128 b'parents',
133 b'parents',
129 b'phase',
134 b'phase',
130 b'revision'
135 b'revision'
131 ])
136 ])
132 },
137 },
133 b'noderange': {
138 b'noderange': {
134 b'default': None,
139 b'default': None,
135 b'required': False,
140 b'required': False,
136 b'type': b'list'
141 b'type': b'list'
137 },
142 },
138 b'nodes': {
143 b'nodes': {
139 b'default': None,
144 b'default': None,
140 b'required': False,
145 b'required': False,
141 b'type': b'list'
146 b'type': b'list'
142 },
147 },
143 b'nodesdepth': {
148 b'nodesdepth': {
144 b'default': None,
149 b'default': None,
145 b'required': False,
150 b'required': False,
146 b'type': b'int'
151 b'type': b'int'
147 }
152 }
148 },
153 },
149 b'permissions': [
154 b'permissions': [
150 b'pull'
155 b'pull'
151 ]
156 ]
152 },
157 },
153 b'filedata': {
158 b'filedata': {
154 b'args': {
159 b'args': {
155 b'fields': {
160 b'fields': {
156 b'default': set([]),
161 b'default': set([]),
157 b'required': False,
162 b'required': False,
158 b'type': b'set',
163 b'type': b'set',
159 b'validvalues': set([
164 b'validvalues': set([
160 b'parents',
165 b'parents',
161 b'revision'
166 b'revision'
162 ])
167 ])
163 },
168 },
164 b'haveparents': {
169 b'haveparents': {
165 b'default': False,
170 b'default': False,
166 b'required': False,
171 b'required': False,
167 b'type': b'bool'
172 b'type': b'bool'
168 },
173 },
169 b'nodes': {
174 b'nodes': {
170 b'required': True,
175 b'required': True,
171 b'type': b'list'
176 b'type': b'list'
172 },
177 },
173 b'path': {
178 b'path': {
174 b'required': True,
179 b'required': True,
175 b'type': b'bytes'
180 b'type': b'bytes'
176 }
181 }
177 },
182 },
178 b'permissions': [
183 b'permissions': [
179 b'pull'
184 b'pull'
180 ]
185 ]
181 },
186 },
182 b'heads': {
187 b'heads': {
183 b'args': {
188 b'args': {
184 b'publiconly': {
189 b'publiconly': {
185 b'default': False,
190 b'default': False,
186 b'required': False,
191 b'required': False,
187 b'type': b'bool'
192 b'type': b'bool'
188 }
193 }
189 },
194 },
190 b'permissions': [
195 b'permissions': [
191 b'pull'
196 b'pull'
192 ]
197 ]
193 },
198 },
194 b'known': {
199 b'known': {
195 b'args': {
200 b'args': {
196 b'nodes': {
201 b'nodes': {
197 b'default': [],
202 b'default': [],
198 b'required': False,
203 b'required': False,
199 b'type': b'list'
204 b'type': b'list'
200 }
205 }
201 },
206 },
202 b'permissions': [
207 b'permissions': [
203 b'pull'
208 b'pull'
204 ]
209 ]
205 },
210 },
206 b'listkeys': {
211 b'listkeys': {
207 b'args': {
212 b'args': {
208 b'namespace': {
213 b'namespace': {
209 b'required': True,
214 b'required': True,
210 b'type': b'bytes'
215 b'type': b'bytes'
211 }
216 }
212 },
217 },
213 b'permissions': [
218 b'permissions': [
214 b'pull'
219 b'pull'
215 ]
220 ]
216 },
221 },
217 b'lookup': {
222 b'lookup': {
218 b'args': {
223 b'args': {
219 b'key': {
224 b'key': {
220 b'required': True,
225 b'required': True,
221 b'type': b'bytes'
226 b'type': b'bytes'
222 }
227 }
223 },
228 },
224 b'permissions': [
229 b'permissions': [
225 b'pull'
230 b'pull'
226 ]
231 ]
227 },
232 },
228 b'manifestdata': {
233 b'manifestdata': {
229 b'args': {
234 b'args': {
230 b'fields': {
235 b'fields': {
231 b'default': set([]),
236 b'default': set([]),
232 b'required': False,
237 b'required': False,
233 b'type': b'set',
238 b'type': b'set',
234 b'validvalues': set([
239 b'validvalues': set([
235 b'parents',
240 b'parents',
236 b'revision'
241 b'revision'
237 ])
242 ])
238 },
243 },
239 b'haveparents': {
244 b'haveparents': {
240 b'default': False,
245 b'default': False,
241 b'required': False,
246 b'required': False,
242 b'type': b'bool'
247 b'type': b'bool'
243 },
248 },
244 b'nodes': {
249 b'nodes': {
245 b'required': True,
250 b'required': True,
246 b'type': b'list'
251 b'type': b'list'
247 },
252 },
248 b'tree': {
253 b'tree': {
249 b'required': True,
254 b'required': True,
250 b'type': b'bytes'
255 b'type': b'bytes'
251 }
256 }
252 },
257 },
253 b'permissions': [
258 b'permissions': [
254 b'pull'
259 b'pull'
255 ]
260 ]
256 },
261 },
257 b'pushkey': {
262 b'pushkey': {
258 b'args': {
263 b'args': {
259 b'key': {
264 b'key': {
260 b'required': True,
265 b'required': True,
261 b'type': b'bytes'
266 b'type': b'bytes'
262 },
267 },
263 b'namespace': {
268 b'namespace': {
264 b'required': True,
269 b'required': True,
265 b'type': b'bytes'
270 b'type': b'bytes'
266 },
271 },
267 b'new': {
272 b'new': {
268 b'required': True,
273 b'required': True,
269 b'type': b'bytes'
274 b'type': b'bytes'
270 },
275 },
271 b'old': {
276 b'old': {
272 b'required': True,
277 b'required': True,
273 b'type': b'bytes'
278 b'type': b'bytes'
274 }
279 }
275 },
280 },
276 b'permissions': [
281 b'permissions': [
277 b'push'
282 b'push'
278 ]
283 ]
279 }
284 }
280 },
285 },
281 b'framingmediatypes': [
286 b'framingmediatypes': [
282 b'application/mercurial-exp-framing-0006'
287 b'application/mercurial-exp-framing-0006'
283 ],
288 ],
284 b'pathfilterprefixes': set([
289 b'pathfilterprefixes': set([
285 b'path:',
290 b'path:',
286 b'rootfilesin:'
291 b'rootfilesin:'
287 ]),
292 ]),
288 b'rawrepoformats': [
293 b'rawrepoformats': [
289 b'generaldelta',
294 b'generaldelta',
290 b'revlogv1'
295 b'revlogv1'
291 ],
296 ],
292 b'redirect': {
297 b'redirect': {
293 b'hashes': [
298 b'hashes': [
294 b'sha256',
299 b'sha256',
295 b'sha1'
300 b'sha1'
296 ],
301 ],
297 b'targets': [
302 b'targets': [
298 {
303 {
299 b'name': b'target-a',
304 b'name': b'target-a',
300 b'protocol': b'http',
305 b'protocol': b'http',
301 b'snirequired': False,
306 b'snirequired': False,
302 b'tlsversions': [
307 b'tlsversions': [
303 b'1.2',
308 b'1.2',
304 b'1.3'
309 b'1.3'
305 ],
310 ],
306 b'uris': [
311 b'uris': [
307 b'http://example.com/'
312 b'http://example.com/'
308 ]
313 ]
309 }
314 }
310 ]
315 ]
311 }
316 }
312 }
317 }
313 ]
318 ]
314 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
319 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
315
320
316 Unknown protocol is filtered from compatible targets
321 Unknown protocol is filtered from compatible targets
317
322
318 $ cat > redirects.py << EOF
323 $ cat > redirects.py << EOF
319 > [
324 > [
320 > {
325 > {
321 > b'name': b'target-a',
326 > b'name': b'target-a',
322 > b'protocol': b'http',
327 > b'protocol': b'http',
323 > b'uris': [b'http://example.com/'],
328 > b'uris': [b'http://example.com/'],
324 > },
329 > },
325 > {
330 > {
326 > b'name': b'target-b',
331 > b'name': b'target-b',
327 > b'protocol': b'unknown',
332 > b'protocol': b'unknown',
328 > b'uris': [b'unknown://example.com/'],
333 > b'uris': [b'unknown://example.com/'],
329 > },
334 > },
330 > ]
335 > ]
331 > EOF
336 > EOF
332
337
333 $ sendhttpv2peerhandshake << EOF
338 $ sendhttpv2peerhandshake << EOF
334 > command capabilities
339 > command capabilities
335 > EOF
340 > EOF
336 creating http peer for wire protocol version 2
341 creating http peer for wire protocol version 2
337 s> GET /?cmd=capabilities HTTP/1.1\r\n
342 s> GET /?cmd=capabilities HTTP/1.1\r\n
338 s> Accept-Encoding: identity\r\n
343 s> Accept-Encoding: identity\r\n
339 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
344 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
340 s> x-hgproto-1: cbor\r\n
345 s> x-hgproto-1: cbor\r\n
341 s> x-hgupgrade-1: exp-http-v2-0002\r\n
346 s> x-hgupgrade-1: exp-http-v2-0002\r\n
342 s> accept: application/mercurial-0.1\r\n
347 s> accept: application/mercurial-0.1\r\n
343 s> host: $LOCALIP:$HGPORT\r\n (glob)
348 s> host: $LOCALIP:$HGPORT\r\n (glob)
344 s> user-agent: Mercurial debugwireproto\r\n
349 s> user-agent: Mercurial debugwireproto\r\n
345 s> \r\n
350 s> \r\n
346 s> makefile('rb', None)
351 s> makefile('rb', None)
347 s> HTTP/1.1 200 OK\r\n
352 s> HTTP/1.1 200 OK\r\n
348 s> Server: testing stub value\r\n
353 s> Server: testing stub value\r\n
349 s> Date: $HTTP_DATE$\r\n
354 s> Date: $HTTP_DATE$\r\n
350 s> Content-Type: application/mercurial-cbor\r\n
355 s> Content-Type: application/mercurial-cbor\r\n
351 s> Content-Length: 1957\r\n
356 s> Content-Length: 1957\r\n
352 s> \r\n
357 s> \r\n
353 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd3batch 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\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd3batch 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
354 (remote redirect target target-a is compatible)
359 (remote redirect target target-a is compatible)
355 (remote redirect target target-b uses unsupported protocol: unknown)
360 (remote redirect target target-b uses unsupported protocol: unknown)
356 sending capabilities command
361 sending capabilities command
357 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
358 s> Accept-Encoding: identity\r\n
363 s> Accept-Encoding: identity\r\n
359 s> accept: application/mercurial-exp-framing-0006\r\n
364 s> accept: application/mercurial-exp-framing-0006\r\n
360 s> content-type: application/mercurial-exp-framing-0006\r\n
365 s> content-type: application/mercurial-exp-framing-0006\r\n
361 s> content-length: 111\r\n
366 s> content-length: 111\r\n
362 s> host: $LOCALIP:$HGPORT\r\n (glob)
367 s> host: $LOCALIP:$HGPORT\r\n (glob)
363 s> user-agent: Mercurial debugwireproto\r\n
368 s> user-agent: Mercurial debugwireproto\r\n
364 s> \r\n
369 s> \r\n
365 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
370 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
366 s> makefile('rb', None)
371 s> makefile('rb', None)
367 s> HTTP/1.1 200 OK\r\n
372 s> HTTP/1.1 200 OK\r\n
368 s> Server: testing stub value\r\n
373 s> Server: testing stub value\r\n
369 s> Date: $HTTP_DATE$\r\n
374 s> Date: $HTTP_DATE$\r\n
370 s> Content-Type: application/mercurial-exp-framing-0006\r\n
375 s> Content-Type: application/mercurial-exp-framing-0006\r\n
371 s> Transfer-Encoding: chunked\r\n
376 s> Transfer-Encoding: chunked\r\n
372 s> \r\n
377 s> \r\n
378 s> 11\r\n
379 s> \t\x00\x00\x01\x00\x02\x01\x92
380 s> Hidentity
381 s> \r\n
382 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
373 s> 13\r\n
383 s> 13\r\n
374 s> \x0b\x00\x00\x01\x00\x02\x011
384 s> \x0b\x00\x00\x01\x00\x02\x041
375 s> \xa1FstatusBok
385 s> \xa1FstatusBok
376 s> \r\n
386 s> \r\n
377 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
387 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
378 s> 5a3\r\n
388 s> 5a3\r\n
379 s> \x9b\x05\x00\x01\x00\x02\x001
389 s> \x9b\x05\x00\x01\x00\x02\x041
380 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
390 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
381 s> \r\n
391 s> \r\n
382 received frame(size=1435; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
392 received frame(size=1435; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
383 s> 8\r\n
393 s> 8\r\n
384 s> \x00\x00\x00\x01\x00\x02\x002
394 s> \x00\x00\x00\x01\x00\x02\x002
385 s> \r\n
395 s> \r\n
386 s> 0\r\n
396 s> 0\r\n
387 s> \r\n
397 s> \r\n
388 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
398 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
389 response: gen[
399 response: gen[
390 {
400 {
391 b'commands': {
401 b'commands': {
392 b'branchmap': {
402 b'branchmap': {
393 b'args': {},
403 b'args': {},
394 b'permissions': [
404 b'permissions': [
395 b'pull'
405 b'pull'
396 ]
406 ]
397 },
407 },
398 b'capabilities': {
408 b'capabilities': {
399 b'args': {},
409 b'args': {},
400 b'permissions': [
410 b'permissions': [
401 b'pull'
411 b'pull'
402 ]
412 ]
403 },
413 },
404 b'changesetdata': {
414 b'changesetdata': {
405 b'args': {
415 b'args': {
406 b'fields': {
416 b'fields': {
407 b'default': set([]),
417 b'default': set([]),
408 b'required': False,
418 b'required': False,
409 b'type': b'set',
419 b'type': b'set',
410 b'validvalues': set([
420 b'validvalues': set([
411 b'bookmarks',
421 b'bookmarks',
412 b'parents',
422 b'parents',
413 b'phase',
423 b'phase',
414 b'revision'
424 b'revision'
415 ])
425 ])
416 },
426 },
417 b'noderange': {
427 b'noderange': {
418 b'default': None,
428 b'default': None,
419 b'required': False,
429 b'required': False,
420 b'type': b'list'
430 b'type': b'list'
421 },
431 },
422 b'nodes': {
432 b'nodes': {
423 b'default': None,
433 b'default': None,
424 b'required': False,
434 b'required': False,
425 b'type': b'list'
435 b'type': b'list'
426 },
436 },
427 b'nodesdepth': {
437 b'nodesdepth': {
428 b'default': None,
438 b'default': None,
429 b'required': False,
439 b'required': False,
430 b'type': b'int'
440 b'type': b'int'
431 }
441 }
432 },
442 },
433 b'permissions': [
443 b'permissions': [
434 b'pull'
444 b'pull'
435 ]
445 ]
436 },
446 },
437 b'filedata': {
447 b'filedata': {
438 b'args': {
448 b'args': {
439 b'fields': {
449 b'fields': {
440 b'default': set([]),
450 b'default': set([]),
441 b'required': False,
451 b'required': False,
442 b'type': b'set',
452 b'type': b'set',
443 b'validvalues': set([
453 b'validvalues': set([
444 b'parents',
454 b'parents',
445 b'revision'
455 b'revision'
446 ])
456 ])
447 },
457 },
448 b'haveparents': {
458 b'haveparents': {
449 b'default': False,
459 b'default': False,
450 b'required': False,
460 b'required': False,
451 b'type': b'bool'
461 b'type': b'bool'
452 },
462 },
453 b'nodes': {
463 b'nodes': {
454 b'required': True,
464 b'required': True,
455 b'type': b'list'
465 b'type': b'list'
456 },
466 },
457 b'path': {
467 b'path': {
458 b'required': True,
468 b'required': True,
459 b'type': b'bytes'
469 b'type': b'bytes'
460 }
470 }
461 },
471 },
462 b'permissions': [
472 b'permissions': [
463 b'pull'
473 b'pull'
464 ]
474 ]
465 },
475 },
466 b'heads': {
476 b'heads': {
467 b'args': {
477 b'args': {
468 b'publiconly': {
478 b'publiconly': {
469 b'default': False,
479 b'default': False,
470 b'required': False,
480 b'required': False,
471 b'type': b'bool'
481 b'type': b'bool'
472 }
482 }
473 },
483 },
474 b'permissions': [
484 b'permissions': [
475 b'pull'
485 b'pull'
476 ]
486 ]
477 },
487 },
478 b'known': {
488 b'known': {
479 b'args': {
489 b'args': {
480 b'nodes': {
490 b'nodes': {
481 b'default': [],
491 b'default': [],
482 b'required': False,
492 b'required': False,
483 b'type': b'list'
493 b'type': b'list'
484 }
494 }
485 },
495 },
486 b'permissions': [
496 b'permissions': [
487 b'pull'
497 b'pull'
488 ]
498 ]
489 },
499 },
490 b'listkeys': {
500 b'listkeys': {
491 b'args': {
501 b'args': {
492 b'namespace': {
502 b'namespace': {
493 b'required': True,
503 b'required': True,
494 b'type': b'bytes'
504 b'type': b'bytes'
495 }
505 }
496 },
506 },
497 b'permissions': [
507 b'permissions': [
498 b'pull'
508 b'pull'
499 ]
509 ]
500 },
510 },
501 b'lookup': {
511 b'lookup': {
502 b'args': {
512 b'args': {
503 b'key': {
513 b'key': {
504 b'required': True,
514 b'required': True,
505 b'type': b'bytes'
515 b'type': b'bytes'
506 }
516 }
507 },
517 },
508 b'permissions': [
518 b'permissions': [
509 b'pull'
519 b'pull'
510 ]
520 ]
511 },
521 },
512 b'manifestdata': {
522 b'manifestdata': {
513 b'args': {
523 b'args': {
514 b'fields': {
524 b'fields': {
515 b'default': set([]),
525 b'default': set([]),
516 b'required': False,
526 b'required': False,
517 b'type': b'set',
527 b'type': b'set',
518 b'validvalues': set([
528 b'validvalues': set([
519 b'parents',
529 b'parents',
520 b'revision'
530 b'revision'
521 ])
531 ])
522 },
532 },
523 b'haveparents': {
533 b'haveparents': {
524 b'default': False,
534 b'default': False,
525 b'required': False,
535 b'required': False,
526 b'type': b'bool'
536 b'type': b'bool'
527 },
537 },
528 b'nodes': {
538 b'nodes': {
529 b'required': True,
539 b'required': True,
530 b'type': b'list'
540 b'type': b'list'
531 },
541 },
532 b'tree': {
542 b'tree': {
533 b'required': True,
543 b'required': True,
534 b'type': b'bytes'
544 b'type': b'bytes'
535 }
545 }
536 },
546 },
537 b'permissions': [
547 b'permissions': [
538 b'pull'
548 b'pull'
539 ]
549 ]
540 },
550 },
541 b'pushkey': {
551 b'pushkey': {
542 b'args': {
552 b'args': {
543 b'key': {
553 b'key': {
544 b'required': True,
554 b'required': True,
545 b'type': b'bytes'
555 b'type': b'bytes'
546 },
556 },
547 b'namespace': {
557 b'namespace': {
548 b'required': True,
558 b'required': True,
549 b'type': b'bytes'
559 b'type': b'bytes'
550 },
560 },
551 b'new': {
561 b'new': {
552 b'required': True,
562 b'required': True,
553 b'type': b'bytes'
563 b'type': b'bytes'
554 },
564 },
555 b'old': {
565 b'old': {
556 b'required': True,
566 b'required': True,
557 b'type': b'bytes'
567 b'type': b'bytes'
558 }
568 }
559 },
569 },
560 b'permissions': [
570 b'permissions': [
561 b'push'
571 b'push'
562 ]
572 ]
563 }
573 }
564 },
574 },
565 b'framingmediatypes': [
575 b'framingmediatypes': [
566 b'application/mercurial-exp-framing-0006'
576 b'application/mercurial-exp-framing-0006'
567 ],
577 ],
568 b'pathfilterprefixes': set([
578 b'pathfilterprefixes': set([
569 b'path:',
579 b'path:',
570 b'rootfilesin:'
580 b'rootfilesin:'
571 ]),
581 ]),
572 b'rawrepoformats': [
582 b'rawrepoformats': [
573 b'generaldelta',
583 b'generaldelta',
574 b'revlogv1'
584 b'revlogv1'
575 ],
585 ],
576 b'redirect': {
586 b'redirect': {
577 b'hashes': [
587 b'hashes': [
578 b'sha256',
588 b'sha256',
579 b'sha1'
589 b'sha1'
580 ],
590 ],
581 b'targets': [
591 b'targets': [
582 {
592 {
583 b'name': b'target-a',
593 b'name': b'target-a',
584 b'protocol': b'http',
594 b'protocol': b'http',
585 b'uris': [
595 b'uris': [
586 b'http://example.com/'
596 b'http://example.com/'
587 ]
597 ]
588 },
598 },
589 {
599 {
590 b'name': b'target-b',
600 b'name': b'target-b',
591 b'protocol': b'unknown',
601 b'protocol': b'unknown',
592 b'uris': [
602 b'uris': [
593 b'unknown://example.com/'
603 b'unknown://example.com/'
594 ]
604 ]
595 }
605 }
596 ]
606 ]
597 }
607 }
598 }
608 }
599 ]
609 ]
600 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
610 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
601
611
602 Missing SNI support filters targets that require SNI
612 Missing SNI support filters targets that require SNI
603
613
604 $ cat > nosni.py << EOF
614 $ cat > nosni.py << EOF
605 > from mercurial import sslutil
615 > from mercurial import sslutil
606 > sslutil.hassni = False
616 > sslutil.hassni = False
607 > EOF
617 > EOF
608 $ cat >> $HGRCPATH << EOF
618 $ cat >> $HGRCPATH << EOF
609 > [extensions]
619 > [extensions]
610 > nosni=`pwd`/nosni.py
620 > nosni=`pwd`/nosni.py
611 > EOF
621 > EOF
612
622
613 $ cat > redirects.py << EOF
623 $ cat > redirects.py << EOF
614 > [
624 > [
615 > {
625 > {
616 > b'name': b'target-bad-tls',
626 > b'name': b'target-bad-tls',
617 > b'protocol': b'https',
627 > b'protocol': b'https',
618 > b'uris': [b'https://example.com/'],
628 > b'uris': [b'https://example.com/'],
619 > b'snirequired': True,
629 > b'snirequired': True,
620 > },
630 > },
621 > ]
631 > ]
622 > EOF
632 > EOF
623
633
624 $ sendhttpv2peerhandshake << EOF
634 $ sendhttpv2peerhandshake << EOF
625 > command capabilities
635 > command capabilities
626 > EOF
636 > EOF
627 creating http peer for wire protocol version 2
637 creating http peer for wire protocol version 2
628 s> GET /?cmd=capabilities HTTP/1.1\r\n
638 s> GET /?cmd=capabilities HTTP/1.1\r\n
629 s> Accept-Encoding: identity\r\n
639 s> Accept-Encoding: identity\r\n
630 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
640 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
631 s> x-hgproto-1: cbor\r\n
641 s> x-hgproto-1: cbor\r\n
632 s> x-hgupgrade-1: exp-http-v2-0002\r\n
642 s> x-hgupgrade-1: exp-http-v2-0002\r\n
633 s> accept: application/mercurial-0.1\r\n
643 s> accept: application/mercurial-0.1\r\n
634 s> host: $LOCALIP:$HGPORT\r\n (glob)
644 s> host: $LOCALIP:$HGPORT\r\n (glob)
635 s> user-agent: Mercurial debugwireproto\r\n
645 s> user-agent: Mercurial debugwireproto\r\n
636 s> \r\n
646 s> \r\n
637 s> makefile('rb', None)
647 s> makefile('rb', None)
638 s> HTTP/1.1 200 OK\r\n
648 s> HTTP/1.1 200 OK\r\n
639 s> Server: testing stub value\r\n
649 s> Server: testing stub value\r\n
640 s> Date: $HTTP_DATE$\r\n
650 s> Date: $HTTP_DATE$\r\n
641 s> Content-Type: application/mercurial-cbor\r\n
651 s> Content-Type: application/mercurial-cbor\r\n
642 s> Content-Length: 1917\r\n
652 s> Content-Length: 1917\r\n
643 s> \r\n
653 s> \r\n
644 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch 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
654 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch 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
645 (redirect target target-bad-tls requires SNI, which is unsupported)
655 (redirect target target-bad-tls requires SNI, which is unsupported)
646 sending capabilities command
656 sending capabilities command
647 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
657 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
648 s> Accept-Encoding: identity\r\n
658 s> Accept-Encoding: identity\r\n
649 s> accept: application/mercurial-exp-framing-0006\r\n
659 s> accept: application/mercurial-exp-framing-0006\r\n
650 s> content-type: application/mercurial-exp-framing-0006\r\n
660 s> content-type: application/mercurial-exp-framing-0006\r\n
651 s> content-length: 102\r\n
661 s> content-length: 102\r\n
652 s> host: $LOCALIP:$HGPORT\r\n (glob)
662 s> host: $LOCALIP:$HGPORT\r\n (glob)
653 s> user-agent: Mercurial debugwireproto\r\n
663 s> user-agent: Mercurial debugwireproto\r\n
654 s> \r\n
664 s> \r\n
655 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
665 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
656 s> makefile('rb', None)
666 s> makefile('rb', None)
657 s> HTTP/1.1 200 OK\r\n
667 s> HTTP/1.1 200 OK\r\n
658 s> Server: testing stub value\r\n
668 s> Server: testing stub value\r\n
659 s> Date: $HTTP_DATE$\r\n
669 s> Date: $HTTP_DATE$\r\n
660 s> Content-Type: application/mercurial-exp-framing-0006\r\n
670 s> Content-Type: application/mercurial-exp-framing-0006\r\n
661 s> Transfer-Encoding: chunked\r\n
671 s> Transfer-Encoding: chunked\r\n
662 s> \r\n
672 s> \r\n
673 s> 11\r\n
674 s> \t\x00\x00\x01\x00\x02\x01\x92
675 s> Hidentity
676 s> \r\n
677 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
663 s> 13\r\n
678 s> 13\r\n
664 s> \x0b\x00\x00\x01\x00\x02\x011
679 s> \x0b\x00\x00\x01\x00\x02\x041
665 s> \xa1FstatusBok
680 s> \xa1FstatusBok
666 s> \r\n
681 s> \r\n
667 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
682 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
668 s> 57b\r\n
683 s> 57b\r\n
669 s> s\x05\x00\x01\x00\x02\x001
684 s> s\x05\x00\x01\x00\x02\x041
670 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
685 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
671 s> \r\n
686 s> \r\n
672 received frame(size=1395; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
687 received frame(size=1395; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
673 s> 8\r\n
688 s> 8\r\n
674 s> \x00\x00\x00\x01\x00\x02\x002
689 s> \x00\x00\x00\x01\x00\x02\x002
675 s> \r\n
690 s> \r\n
676 s> 0\r\n
691 s> 0\r\n
677 s> \r\n
692 s> \r\n
678 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
693 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
679 response: gen[
694 response: gen[
680 {
695 {
681 b'commands': {
696 b'commands': {
682 b'branchmap': {
697 b'branchmap': {
683 b'args': {},
698 b'args': {},
684 b'permissions': [
699 b'permissions': [
685 b'pull'
700 b'pull'
686 ]
701 ]
687 },
702 },
688 b'capabilities': {
703 b'capabilities': {
689 b'args': {},
704 b'args': {},
690 b'permissions': [
705 b'permissions': [
691 b'pull'
706 b'pull'
692 ]
707 ]
693 },
708 },
694 b'changesetdata': {
709 b'changesetdata': {
695 b'args': {
710 b'args': {
696 b'fields': {
711 b'fields': {
697 b'default': set([]),
712 b'default': set([]),
698 b'required': False,
713 b'required': False,
699 b'type': b'set',
714 b'type': b'set',
700 b'validvalues': set([
715 b'validvalues': set([
701 b'bookmarks',
716 b'bookmarks',
702 b'parents',
717 b'parents',
703 b'phase',
718 b'phase',
704 b'revision'
719 b'revision'
705 ])
720 ])
706 },
721 },
707 b'noderange': {
722 b'noderange': {
708 b'default': None,
723 b'default': None,
709 b'required': False,
724 b'required': False,
710 b'type': b'list'
725 b'type': b'list'
711 },
726 },
712 b'nodes': {
727 b'nodes': {
713 b'default': None,
728 b'default': None,
714 b'required': False,
729 b'required': False,
715 b'type': b'list'
730 b'type': b'list'
716 },
731 },
717 b'nodesdepth': {
732 b'nodesdepth': {
718 b'default': None,
733 b'default': None,
719 b'required': False,
734 b'required': False,
720 b'type': b'int'
735 b'type': b'int'
721 }
736 }
722 },
737 },
723 b'permissions': [
738 b'permissions': [
724 b'pull'
739 b'pull'
725 ]
740 ]
726 },
741 },
727 b'filedata': {
742 b'filedata': {
728 b'args': {
743 b'args': {
729 b'fields': {
744 b'fields': {
730 b'default': set([]),
745 b'default': set([]),
731 b'required': False,
746 b'required': False,
732 b'type': b'set',
747 b'type': b'set',
733 b'validvalues': set([
748 b'validvalues': set([
734 b'parents',
749 b'parents',
735 b'revision'
750 b'revision'
736 ])
751 ])
737 },
752 },
738 b'haveparents': {
753 b'haveparents': {
739 b'default': False,
754 b'default': False,
740 b'required': False,
755 b'required': False,
741 b'type': b'bool'
756 b'type': b'bool'
742 },
757 },
743 b'nodes': {
758 b'nodes': {
744 b'required': True,
759 b'required': True,
745 b'type': b'list'
760 b'type': b'list'
746 },
761 },
747 b'path': {
762 b'path': {
748 b'required': True,
763 b'required': True,
749 b'type': b'bytes'
764 b'type': b'bytes'
750 }
765 }
751 },
766 },
752 b'permissions': [
767 b'permissions': [
753 b'pull'
768 b'pull'
754 ]
769 ]
755 },
770 },
756 b'heads': {
771 b'heads': {
757 b'args': {
772 b'args': {
758 b'publiconly': {
773 b'publiconly': {
759 b'default': False,
774 b'default': False,
760 b'required': False,
775 b'required': False,
761 b'type': b'bool'
776 b'type': b'bool'
762 }
777 }
763 },
778 },
764 b'permissions': [
779 b'permissions': [
765 b'pull'
780 b'pull'
766 ]
781 ]
767 },
782 },
768 b'known': {
783 b'known': {
769 b'args': {
784 b'args': {
770 b'nodes': {
785 b'nodes': {
771 b'default': [],
786 b'default': [],
772 b'required': False,
787 b'required': False,
773 b'type': b'list'
788 b'type': b'list'
774 }
789 }
775 },
790 },
776 b'permissions': [
791 b'permissions': [
777 b'pull'
792 b'pull'
778 ]
793 ]
779 },
794 },
780 b'listkeys': {
795 b'listkeys': {
781 b'args': {
796 b'args': {
782 b'namespace': {
797 b'namespace': {
783 b'required': True,
798 b'required': True,
784 b'type': b'bytes'
799 b'type': b'bytes'
785 }
800 }
786 },
801 },
787 b'permissions': [
802 b'permissions': [
788 b'pull'
803 b'pull'
789 ]
804 ]
790 },
805 },
791 b'lookup': {
806 b'lookup': {
792 b'args': {
807 b'args': {
793 b'key': {
808 b'key': {
794 b'required': True,
809 b'required': True,
795 b'type': b'bytes'
810 b'type': b'bytes'
796 }
811 }
797 },
812 },
798 b'permissions': [
813 b'permissions': [
799 b'pull'
814 b'pull'
800 ]
815 ]
801 },
816 },
802 b'manifestdata': {
817 b'manifestdata': {
803 b'args': {
818 b'args': {
804 b'fields': {
819 b'fields': {
805 b'default': set([]),
820 b'default': set([]),
806 b'required': False,
821 b'required': False,
807 b'type': b'set',
822 b'type': b'set',
808 b'validvalues': set([
823 b'validvalues': set([
809 b'parents',
824 b'parents',
810 b'revision'
825 b'revision'
811 ])
826 ])
812 },
827 },
813 b'haveparents': {
828 b'haveparents': {
814 b'default': False,
829 b'default': False,
815 b'required': False,
830 b'required': False,
816 b'type': b'bool'
831 b'type': b'bool'
817 },
832 },
818 b'nodes': {
833 b'nodes': {
819 b'required': True,
834 b'required': True,
820 b'type': b'list'
835 b'type': b'list'
821 },
836 },
822 b'tree': {
837 b'tree': {
823 b'required': True,
838 b'required': True,
824 b'type': b'bytes'
839 b'type': b'bytes'
825 }
840 }
826 },
841 },
827 b'permissions': [
842 b'permissions': [
828 b'pull'
843 b'pull'
829 ]
844 ]
830 },
845 },
831 b'pushkey': {
846 b'pushkey': {
832 b'args': {
847 b'args': {
833 b'key': {
848 b'key': {
834 b'required': True,
849 b'required': True,
835 b'type': b'bytes'
850 b'type': b'bytes'
836 },
851 },
837 b'namespace': {
852 b'namespace': {
838 b'required': True,
853 b'required': True,
839 b'type': b'bytes'
854 b'type': b'bytes'
840 },
855 },
841 b'new': {
856 b'new': {
842 b'required': True,
857 b'required': True,
843 b'type': b'bytes'
858 b'type': b'bytes'
844 },
859 },
845 b'old': {
860 b'old': {
846 b'required': True,
861 b'required': True,
847 b'type': b'bytes'
862 b'type': b'bytes'
848 }
863 }
849 },
864 },
850 b'permissions': [
865 b'permissions': [
851 b'push'
866 b'push'
852 ]
867 ]
853 }
868 }
854 },
869 },
855 b'framingmediatypes': [
870 b'framingmediatypes': [
856 b'application/mercurial-exp-framing-0006'
871 b'application/mercurial-exp-framing-0006'
857 ],
872 ],
858 b'pathfilterprefixes': set([
873 b'pathfilterprefixes': set([
859 b'path:',
874 b'path:',
860 b'rootfilesin:'
875 b'rootfilesin:'
861 ]),
876 ]),
862 b'rawrepoformats': [
877 b'rawrepoformats': [
863 b'generaldelta',
878 b'generaldelta',
864 b'revlogv1'
879 b'revlogv1'
865 ],
880 ],
866 b'redirect': {
881 b'redirect': {
867 b'hashes': [
882 b'hashes': [
868 b'sha256',
883 b'sha256',
869 b'sha1'
884 b'sha1'
870 ],
885 ],
871 b'targets': [
886 b'targets': [
872 {
887 {
873 b'name': b'target-bad-tls',
888 b'name': b'target-bad-tls',
874 b'protocol': b'https',
889 b'protocol': b'https',
875 b'snirequired': True,
890 b'snirequired': True,
876 b'uris': [
891 b'uris': [
877 b'https://example.com/'
892 b'https://example.com/'
878 ]
893 ]
879 }
894 }
880 ]
895 ]
881 }
896 }
882 }
897 }
883 ]
898 ]
884 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
899 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
885
900
886 $ cat >> $HGRCPATH << EOF
901 $ cat >> $HGRCPATH << EOF
887 > [extensions]
902 > [extensions]
888 > nosni=!
903 > nosni=!
889 > EOF
904 > EOF
890
905
891 Unknown tls value is filtered from compatible targets
906 Unknown tls value is filtered from compatible targets
892
907
893 $ cat > redirects.py << EOF
908 $ cat > redirects.py << EOF
894 > [
909 > [
895 > {
910 > {
896 > b'name': b'target-bad-tls',
911 > b'name': b'target-bad-tls',
897 > b'protocol': b'https',
912 > b'protocol': b'https',
898 > b'uris': [b'https://example.com/'],
913 > b'uris': [b'https://example.com/'],
899 > b'tlsversions': [b'42', b'39'],
914 > b'tlsversions': [b'42', b'39'],
900 > },
915 > },
901 > ]
916 > ]
902 > EOF
917 > EOF
903
918
904 $ sendhttpv2peerhandshake << EOF
919 $ sendhttpv2peerhandshake << EOF
905 > command capabilities
920 > command capabilities
906 > EOF
921 > EOF
907 creating http peer for wire protocol version 2
922 creating http peer for wire protocol version 2
908 s> GET /?cmd=capabilities HTTP/1.1\r\n
923 s> GET /?cmd=capabilities HTTP/1.1\r\n
909 s> Accept-Encoding: identity\r\n
924 s> Accept-Encoding: identity\r\n
910 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
925 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
911 s> x-hgproto-1: cbor\r\n
926 s> x-hgproto-1: cbor\r\n
912 s> x-hgupgrade-1: exp-http-v2-0002\r\n
927 s> x-hgupgrade-1: exp-http-v2-0002\r\n
913 s> accept: application/mercurial-0.1\r\n
928 s> accept: application/mercurial-0.1\r\n
914 s> host: $LOCALIP:$HGPORT\r\n (glob)
929 s> host: $LOCALIP:$HGPORT\r\n (glob)
915 s> user-agent: Mercurial debugwireproto\r\n
930 s> user-agent: Mercurial debugwireproto\r\n
916 s> \r\n
931 s> \r\n
917 s> makefile('rb', None)
932 s> makefile('rb', None)
918 s> HTTP/1.1 200 OK\r\n
933 s> HTTP/1.1 200 OK\r\n
919 s> Server: testing stub value\r\n
934 s> Server: testing stub value\r\n
920 s> Date: $HTTP_DATE$\r\n
935 s> Date: $HTTP_DATE$\r\n
921 s> Content-Type: application/mercurial-cbor\r\n
936 s> Content-Type: application/mercurial-cbor\r\n
922 s> Content-Length: 1923\r\n
937 s> Content-Length: 1923\r\n
923 s> \r\n
938 s> \r\n
924 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch 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
939 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch 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
925 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
940 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
926 sending capabilities command
941 sending capabilities command
927 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
942 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
928 s> Accept-Encoding: identity\r\n
943 s> Accept-Encoding: identity\r\n
929 s> accept: application/mercurial-exp-framing-0006\r\n
944 s> accept: application/mercurial-exp-framing-0006\r\n
930 s> content-type: application/mercurial-exp-framing-0006\r\n
945 s> content-type: application/mercurial-exp-framing-0006\r\n
931 s> content-length: 102\r\n
946 s> content-length: 102\r\n
932 s> host: $LOCALIP:$HGPORT\r\n (glob)
947 s> host: $LOCALIP:$HGPORT\r\n (glob)
933 s> user-agent: Mercurial debugwireproto\r\n
948 s> user-agent: Mercurial debugwireproto\r\n
934 s> \r\n
949 s> \r\n
935 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
950 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
936 s> makefile('rb', None)
951 s> makefile('rb', None)
937 s> HTTP/1.1 200 OK\r\n
952 s> HTTP/1.1 200 OK\r\n
938 s> Server: testing stub value\r\n
953 s> Server: testing stub value\r\n
939 s> Date: $HTTP_DATE$\r\n
954 s> Date: $HTTP_DATE$\r\n
940 s> Content-Type: application/mercurial-exp-framing-0006\r\n
955 s> Content-Type: application/mercurial-exp-framing-0006\r\n
941 s> Transfer-Encoding: chunked\r\n
956 s> Transfer-Encoding: chunked\r\n
942 s> \r\n
957 s> \r\n
958 s> 11\r\n
959 s> \t\x00\x00\x01\x00\x02\x01\x92
960 s> Hidentity
961 s> \r\n
962 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
943 s> 13\r\n
963 s> 13\r\n
944 s> \x0b\x00\x00\x01\x00\x02\x011
964 s> \x0b\x00\x00\x01\x00\x02\x041
945 s> \xa1FstatusBok
965 s> \xa1FstatusBok
946 s> \r\n
966 s> \r\n
947 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
967 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
948 s> 581\r\n
968 s> 581\r\n
949 s> y\x05\x00\x01\x00\x02\x001
969 s> y\x05\x00\x01\x00\x02\x041
950 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
970 s> \xa5Hcommands\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\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
951 s> \r\n
971 s> \r\n
952 received frame(size=1401; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
972 received frame(size=1401; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
953 s> 8\r\n
973 s> 8\r\n
954 s> \x00\x00\x00\x01\x00\x02\x002
974 s> \x00\x00\x00\x01\x00\x02\x002
955 s> \r\n
975 s> \r\n
956 s> 0\r\n
976 s> 0\r\n
957 s> \r\n
977 s> \r\n
958 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
978 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
959 response: gen[
979 response: gen[
960 {
980 {
961 b'commands': {
981 b'commands': {
962 b'branchmap': {
982 b'branchmap': {
963 b'args': {},
983 b'args': {},
964 b'permissions': [
984 b'permissions': [
965 b'pull'
985 b'pull'
966 ]
986 ]
967 },
987 },
968 b'capabilities': {
988 b'capabilities': {
969 b'args': {},
989 b'args': {},
970 b'permissions': [
990 b'permissions': [
971 b'pull'
991 b'pull'
972 ]
992 ]
973 },
993 },
974 b'changesetdata': {
994 b'changesetdata': {
975 b'args': {
995 b'args': {
976 b'fields': {
996 b'fields': {
977 b'default': set([]),
997 b'default': set([]),
978 b'required': False,
998 b'required': False,
979 b'type': b'set',
999 b'type': b'set',
980 b'validvalues': set([
1000 b'validvalues': set([
981 b'bookmarks',
1001 b'bookmarks',
982 b'parents',
1002 b'parents',
983 b'phase',
1003 b'phase',
984 b'revision'
1004 b'revision'
985 ])
1005 ])
986 },
1006 },
987 b'noderange': {
1007 b'noderange': {
988 b'default': None,
1008 b'default': None,
989 b'required': False,
1009 b'required': False,
990 b'type': b'list'
1010 b'type': b'list'
991 },
1011 },
992 b'nodes': {
1012 b'nodes': {
993 b'default': None,
1013 b'default': None,
994 b'required': False,
1014 b'required': False,
995 b'type': b'list'
1015 b'type': b'list'
996 },
1016 },
997 b'nodesdepth': {
1017 b'nodesdepth': {
998 b'default': None,
1018 b'default': None,
999 b'required': False,
1019 b'required': False,
1000 b'type': b'int'
1020 b'type': b'int'
1001 }
1021 }
1002 },
1022 },
1003 b'permissions': [
1023 b'permissions': [
1004 b'pull'
1024 b'pull'
1005 ]
1025 ]
1006 },
1026 },
1007 b'filedata': {
1027 b'filedata': {
1008 b'args': {
1028 b'args': {
1009 b'fields': {
1029 b'fields': {
1010 b'default': set([]),
1030 b'default': set([]),
1011 b'required': False,
1031 b'required': False,
1012 b'type': b'set',
1032 b'type': b'set',
1013 b'validvalues': set([
1033 b'validvalues': set([
1014 b'parents',
1034 b'parents',
1015 b'revision'
1035 b'revision'
1016 ])
1036 ])
1017 },
1037 },
1018 b'haveparents': {
1038 b'haveparents': {
1019 b'default': False,
1039 b'default': False,
1020 b'required': False,
1040 b'required': False,
1021 b'type': b'bool'
1041 b'type': b'bool'
1022 },
1042 },
1023 b'nodes': {
1043 b'nodes': {
1024 b'required': True,
1044 b'required': True,
1025 b'type': b'list'
1045 b'type': b'list'
1026 },
1046 },
1027 b'path': {
1047 b'path': {
1028 b'required': True,
1048 b'required': True,
1029 b'type': b'bytes'
1049 b'type': b'bytes'
1030 }
1050 }
1031 },
1051 },
1032 b'permissions': [
1052 b'permissions': [
1033 b'pull'
1053 b'pull'
1034 ]
1054 ]
1035 },
1055 },
1036 b'heads': {
1056 b'heads': {
1037 b'args': {
1057 b'args': {
1038 b'publiconly': {
1058 b'publiconly': {
1039 b'default': False,
1059 b'default': False,
1040 b'required': False,
1060 b'required': False,
1041 b'type': b'bool'
1061 b'type': b'bool'
1042 }
1062 }
1043 },
1063 },
1044 b'permissions': [
1064 b'permissions': [
1045 b'pull'
1065 b'pull'
1046 ]
1066 ]
1047 },
1067 },
1048 b'known': {
1068 b'known': {
1049 b'args': {
1069 b'args': {
1050 b'nodes': {
1070 b'nodes': {
1051 b'default': [],
1071 b'default': [],
1052 b'required': False,
1072 b'required': False,
1053 b'type': b'list'
1073 b'type': b'list'
1054 }
1074 }
1055 },
1075 },
1056 b'permissions': [
1076 b'permissions': [
1057 b'pull'
1077 b'pull'
1058 ]
1078 ]
1059 },
1079 },
1060 b'listkeys': {
1080 b'listkeys': {
1061 b'args': {
1081 b'args': {
1062 b'namespace': {
1082 b'namespace': {
1063 b'required': True,
1083 b'required': True,
1064 b'type': b'bytes'
1084 b'type': b'bytes'
1065 }
1085 }
1066 },
1086 },
1067 b'permissions': [
1087 b'permissions': [
1068 b'pull'
1088 b'pull'
1069 ]
1089 ]
1070 },
1090 },
1071 b'lookup': {
1091 b'lookup': {
1072 b'args': {
1092 b'args': {
1073 b'key': {
1093 b'key': {
1074 b'required': True,
1094 b'required': True,
1075 b'type': b'bytes'
1095 b'type': b'bytes'
1076 }
1096 }
1077 },
1097 },
1078 b'permissions': [
1098 b'permissions': [
1079 b'pull'
1099 b'pull'
1080 ]
1100 ]
1081 },
1101 },
1082 b'manifestdata': {
1102 b'manifestdata': {
1083 b'args': {
1103 b'args': {
1084 b'fields': {
1104 b'fields': {
1085 b'default': set([]),
1105 b'default': set([]),
1086 b'required': False,
1106 b'required': False,
1087 b'type': b'set',
1107 b'type': b'set',
1088 b'validvalues': set([
1108 b'validvalues': set([
1089 b'parents',
1109 b'parents',
1090 b'revision'
1110 b'revision'
1091 ])
1111 ])
1092 },
1112 },
1093 b'haveparents': {
1113 b'haveparents': {
1094 b'default': False,
1114 b'default': False,
1095 b'required': False,
1115 b'required': False,
1096 b'type': b'bool'
1116 b'type': b'bool'
1097 },
1117 },
1098 b'nodes': {
1118 b'nodes': {
1099 b'required': True,
1119 b'required': True,
1100 b'type': b'list'
1120 b'type': b'list'
1101 },
1121 },
1102 b'tree': {
1122 b'tree': {
1103 b'required': True,
1123 b'required': True,
1104 b'type': b'bytes'
1124 b'type': b'bytes'
1105 }
1125 }
1106 },
1126 },
1107 b'permissions': [
1127 b'permissions': [
1108 b'pull'
1128 b'pull'
1109 ]
1129 ]
1110 },
1130 },
1111 b'pushkey': {
1131 b'pushkey': {
1112 b'args': {
1132 b'args': {
1113 b'key': {
1133 b'key': {
1114 b'required': True,
1134 b'required': True,
1115 b'type': b'bytes'
1135 b'type': b'bytes'
1116 },
1136 },
1117 b'namespace': {
1137 b'namespace': {
1118 b'required': True,
1138 b'required': True,
1119 b'type': b'bytes'
1139 b'type': b'bytes'
1120 },
1140 },
1121 b'new': {
1141 b'new': {
1122 b'required': True,
1142 b'required': True,
1123 b'type': b'bytes'
1143 b'type': b'bytes'
1124 },
1144 },
1125 b'old': {
1145 b'old': {
1126 b'required': True,
1146 b'required': True,
1127 b'type': b'bytes'
1147 b'type': b'bytes'
1128 }
1148 }
1129 },
1149 },
1130 b'permissions': [
1150 b'permissions': [
1131 b'push'
1151 b'push'
1132 ]
1152 ]
1133 }
1153 }
1134 },
1154 },
1135 b'framingmediatypes': [
1155 b'framingmediatypes': [
1136 b'application/mercurial-exp-framing-0006'
1156 b'application/mercurial-exp-framing-0006'
1137 ],
1157 ],
1138 b'pathfilterprefixes': set([
1158 b'pathfilterprefixes': set([
1139 b'path:',
1159 b'path:',
1140 b'rootfilesin:'
1160 b'rootfilesin:'
1141 ]),
1161 ]),
1142 b'rawrepoformats': [
1162 b'rawrepoformats': [
1143 b'generaldelta',
1163 b'generaldelta',
1144 b'revlogv1'
1164 b'revlogv1'
1145 ],
1165 ],
1146 b'redirect': {
1166 b'redirect': {
1147 b'hashes': [
1167 b'hashes': [
1148 b'sha256',
1168 b'sha256',
1149 b'sha1'
1169 b'sha1'
1150 ],
1170 ],
1151 b'targets': [
1171 b'targets': [
1152 {
1172 {
1153 b'name': b'target-bad-tls',
1173 b'name': b'target-bad-tls',
1154 b'protocol': b'https',
1174 b'protocol': b'https',
1155 b'tlsversions': [
1175 b'tlsversions': [
1156 b'42',
1176 b'42',
1157 b'39'
1177 b'39'
1158 ],
1178 ],
1159 b'uris': [
1179 b'uris': [
1160 b'https://example.com/'
1180 b'https://example.com/'
1161 ]
1181 ]
1162 }
1182 }
1163 ]
1183 ]
1164 }
1184 }
1165 }
1185 }
1166 ]
1186 ]
1167 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
1187 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
1168
1188
1169 Set up the server to issue content redirects to its built-in API server.
1189 Set up the server to issue content redirects to its built-in API server.
1170
1190
1171 $ cat > redirects.py << EOF
1191 $ cat > redirects.py << EOF
1172 > [
1192 > [
1173 > {
1193 > {
1174 > b'name': b'local',
1194 > b'name': b'local',
1175 > b'protocol': b'http',
1195 > b'protocol': b'http',
1176 > b'uris': [b'http://example.com/'],
1196 > b'uris': [b'http://example.com/'],
1177 > },
1197 > },
1178 > ]
1198 > ]
1179 > EOF
1199 > EOF
1180
1200
1181 Request to eventual cache URL should return 404 (validating the cache server works)
1201 Request to eventual cache URL should return 404 (validating the cache server works)
1182
1202
1183 $ sendhttpraw << EOF
1203 $ sendhttpraw << EOF
1184 > httprequest GET api/simplecache/missingkey
1204 > httprequest GET api/simplecache/missingkey
1185 > user-agent: test
1205 > user-agent: test
1186 > EOF
1206 > EOF
1187 using raw connection to peer
1207 using raw connection to peer
1188 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1208 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1189 s> Accept-Encoding: identity\r\n
1209 s> Accept-Encoding: identity\r\n
1190 s> user-agent: test\r\n
1210 s> user-agent: test\r\n
1191 s> host: $LOCALIP:$HGPORT\r\n (glob)
1211 s> host: $LOCALIP:$HGPORT\r\n (glob)
1192 s> \r\n
1212 s> \r\n
1193 s> makefile('rb', None)
1213 s> makefile('rb', None)
1194 s> HTTP/1.1 404 Not Found\r\n
1214 s> HTTP/1.1 404 Not Found\r\n
1195 s> Server: testing stub value\r\n
1215 s> Server: testing stub value\r\n
1196 s> Date: $HTTP_DATE$\r\n
1216 s> Date: $HTTP_DATE$\r\n
1197 s> Content-Type: text/plain\r\n
1217 s> Content-Type: text/plain\r\n
1198 s> Content-Length: 22\r\n
1218 s> Content-Length: 22\r\n
1199 s> \r\n
1219 s> \r\n
1200 s> key not found in cache
1220 s> key not found in cache
1201
1221
1202 Send a cacheable request
1222 Send a cacheable request
1203
1223
1204 $ sendhttpv2peer << EOF
1224 $ sendhttpv2peer << EOF
1205 > command manifestdata
1225 > command manifestdata
1206 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1226 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1207 > tree eval:b''
1227 > tree eval:b''
1208 > fields eval:[b'parents']
1228 > fields eval:[b'parents']
1209 > EOF
1229 > EOF
1210 creating http peer for wire protocol version 2
1230 creating http peer for wire protocol version 2
1211 sending manifestdata command
1231 sending manifestdata command
1212 response: gen[
1232 response: gen[
1213 {
1233 {
1214 b'totalitems': 1
1234 b'totalitems': 1
1215 },
1235 },
1216 {
1236 {
1217 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1237 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1218 b'parents': [
1238 b'parents': [
1219 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1239 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1220 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1240 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1221 ]
1241 ]
1222 }
1242 }
1223 ]
1243 ]
1224
1244
1225 Cached entry should be available on server
1245 Cached entry should be available on server
1226
1246
1227 $ sendhttpraw << EOF
1247 $ sendhttpraw << EOF
1228 > httprequest GET api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca
1248 > httprequest GET api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca
1229 > user-agent: test
1249 > user-agent: test
1230 > EOF
1250 > EOF
1231 using raw connection to peer
1251 using raw connection to peer
1232 s> GET /api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca HTTP/1.1\r\n
1252 s> GET /api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca HTTP/1.1\r\n
1233 s> Accept-Encoding: identity\r\n
1253 s> Accept-Encoding: identity\r\n
1234 s> user-agent: test\r\n
1254 s> user-agent: test\r\n
1235 s> host: $LOCALIP:$HGPORT\r\n (glob)
1255 s> host: $LOCALIP:$HGPORT\r\n (glob)
1236 s> \r\n
1256 s> \r\n
1237 s> makefile('rb', None)
1257 s> makefile('rb', None)
1238 s> HTTP/1.1 200 OK\r\n
1258 s> HTTP/1.1 200 OK\r\n
1239 s> Server: testing stub value\r\n
1259 s> Server: testing stub value\r\n
1240 s> Date: $HTTP_DATE$\r\n
1260 s> Date: $HTTP_DATE$\r\n
1241 s> Content-Type: application/mercurial-cbor\r\n
1261 s> Content-Type: application/mercurial-cbor\r\n
1242 s> Content-Length: 91\r\n
1262 s> Content-Length: 91\r\n
1243 s> \r\n
1263 s> \r\n
1244 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
1264 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
1245 cbor> [
1265 cbor> [
1246 {
1266 {
1247 b'totalitems': 1
1267 b'totalitems': 1
1248 },
1268 },
1249 {
1269 {
1250 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1270 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1251 b'parents': [
1271 b'parents': [
1252 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1272 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1253 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1273 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1254 ]
1274 ]
1255 }
1275 }
1256 ]
1276 ]
1257
1277
1258 2nd request should result in content redirect response
1278 2nd request should result in content redirect response
1259
1279
1260 $ sendhttpv2peer << EOF
1280 $ sendhttpv2peer << EOF
1261 > command manifestdata
1281 > command manifestdata
1262 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1282 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1263 > tree eval:b''
1283 > tree eval:b''
1264 > fields eval:[b'parents']
1284 > fields eval:[b'parents']
1265 > EOF
1285 > EOF
1266 creating http peer for wire protocol version 2
1286 creating http peer for wire protocol version 2
1267 sending manifestdata command
1287 sending manifestdata command
1268 response: gen[
1288 response: gen[
1269 {
1289 {
1270 b'totalitems': 1
1290 b'totalitems': 1
1271 },
1291 },
1272 {
1292 {
1273 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1293 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1274 b'parents': [
1294 b'parents': [
1275 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1295 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1276 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1296 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1277 ]
1297 ]
1278 }
1298 }
1279 ]
1299 ]
1280
1300
1281 $ cat error.log
1301 $ cat error.log
1282 $ killdaemons.py
1302 $ killdaemons.py
1283
1303
1284 $ cat .hg/blackbox.log
1304 $ cat .hg/blackbox.log
1285 *> cacher constructed for manifestdata (glob)
1305 *> cacher constructed for manifestdata (glob)
1286 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1306 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1287 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1307 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1288 *> cacher constructed for manifestdata (glob)
1308 *> cacher constructed for manifestdata (glob)
1289 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1309 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1290 *> sending content redirect for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca to http://*:$HGPORT/api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1310 *> sending content redirect for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca to http://*:$HGPORT/api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
@@ -1,623 +1,644 b''
1 Tests for wire protocol version 2 exchange.
1 Tests for wire protocol version 2 exchange.
2 Tests in this file should be folded into existing tests once protocol
2 Tests in this file should be folded into existing tests once protocol
3 v2 has enough features that it can be enabled via #testcase in existing
3 v2 has enough features that it can be enabled via #testcase in existing
4 tests.
4 tests.
5
5
6 $ . $TESTDIR/wireprotohelpers.sh
6 $ . $TESTDIR/wireprotohelpers.sh
7 $ enablehttpv2client
7 $ enablehttpv2client
8
8
9 $ hg init server-simple
9 $ hg init server-simple
10 $ enablehttpv2 server-simple
10 $ enablehttpv2 server-simple
11 $ cd server-simple
11 $ cd server-simple
12 $ cat >> .hg/hgrc << EOF
12 $ cat >> .hg/hgrc << EOF
13 > [phases]
13 > [phases]
14 > publish = false
14 > publish = false
15 > EOF
15 > EOF
16 $ echo a0 > a
16 $ echo a0 > a
17 $ echo b0 > b
17 $ echo b0 > b
18 $ hg -q commit -A -m 'commit 0'
18 $ hg -q commit -A -m 'commit 0'
19
19
20 $ echo a1 > a
20 $ echo a1 > a
21 $ hg commit -m 'commit 1'
21 $ hg commit -m 'commit 1'
22 $ hg phase --public -r .
22 $ hg phase --public -r .
23 $ echo a2 > a
23 $ echo a2 > a
24 $ hg commit -m 'commit 2'
24 $ hg commit -m 'commit 2'
25
25
26 $ hg -q up -r 0
26 $ hg -q up -r 0
27 $ echo b1 > b
27 $ echo b1 > b
28 $ hg -q commit -m 'head 2 commit 1'
28 $ hg -q commit -m 'head 2 commit 1'
29 $ echo b2 > b
29 $ echo b2 > b
30 $ hg -q commit -m 'head 2 commit 2'
30 $ hg -q commit -m 'head 2 commit 2'
31
31
32 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
32 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
33 $ cat hg.pid > $DAEMON_PIDS
33 $ cat hg.pid > $DAEMON_PIDS
34
34
35 $ cd ..
35 $ cd ..
36
36
37 Test basic clone
37 Test basic clone
38
38
39 $ hg --debug clone -U http://localhost:$HGPORT client-simple
39 $ hg --debug clone -U http://localhost:$HGPORT client-simple
40 using http://localhost:$HGPORT/
40 using http://localhost:$HGPORT/
41 sending capabilities command
41 sending capabilities command
42 query 1; heads
42 query 1; heads
43 sending 2 commands
43 sending 2 commands
44 sending command heads: {}
44 sending command heads: {}
45 sending command known: {
45 sending command known: {
46 'nodes': []
46 'nodes': []
47 }
47 }
48 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
48 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
49 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
49 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
50 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
50 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
51 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
51 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
52 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
52 received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
53 received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
53 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
54 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
54 sending 1 commands
55 sending 1 commands
55 sending command changesetdata: {
56 sending command changesetdata: {
56 'fields': set([
57 'fields': set([
57 'bookmarks',
58 'bookmarks',
58 'parents',
59 'parents',
59 'phase',
60 'phase',
60 'revision'
61 'revision'
61 ]),
62 ]),
62 'noderange': [
63 'noderange': [
63 [],
64 [],
64 [
65 [
65 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
66 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
66 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
67 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
67 ]
68 ]
68 ]
69 ]
69 }
70 }
70 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
71 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
71 received frame(size=941; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
72 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
73 received frame(size=941; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
72 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
74 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
73 add changeset 3390ef850073
75 add changeset 3390ef850073
74 add changeset 4432d83626e8
76 add changeset 4432d83626e8
75 add changeset cd2534766bec
77 add changeset cd2534766bec
76 add changeset e96ae20f4188
78 add changeset e96ae20f4188
77 add changeset caa2a465451d
79 add changeset caa2a465451d
78 checking for updated bookmarks
80 checking for updated bookmarks
79 sending 1 commands
81 sending 1 commands
80 sending command manifestdata: {
82 sending command manifestdata: {
81 'fields': set([
83 'fields': set([
82 'parents',
84 'parents',
83 'revision'
85 'revision'
84 ]),
86 ]),
85 'haveparents': True,
87 'haveparents': True,
86 'nodes': [
88 'nodes': [
87 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
89 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
88 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
90 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
89 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
91 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
90 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
92 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
91 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
93 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
92 ],
94 ],
93 'tree': ''
95 'tree': ''
94 }
96 }
95 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
97 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
96 received frame(size=992; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
98 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
99 received frame(size=992; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
97 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
100 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
98 sending 2 commands
101 sending 2 commands
99 sending command filedata: {
102 sending command filedata: {
100 'fields': set([
103 'fields': set([
101 'parents',
104 'parents',
102 'revision'
105 'revision'
103 ]),
106 ]),
104 'haveparents': True,
107 'haveparents': True,
105 'nodes': [
108 'nodes': [
106 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
109 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
107 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
110 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
108 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
111 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
109 ],
112 ],
110 'path': 'a'
113 'path': 'a'
111 }
114 }
112 sending command filedata: {
115 sending command filedata: {
113 'fields': set([
116 'fields': set([
114 'parents',
117 'parents',
115 'revision'
118 'revision'
116 ]),
119 ]),
117 'haveparents': True,
120 'haveparents': True,
118 'nodes': [
121 'nodes': [
119 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
122 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
120 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
123 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
121 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
124 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
122 ],
125 ],
123 'path': 'b'
126 'path': 'b'
124 }
127 }
125 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
128 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
126 received frame(size=431; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
129 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
130 received frame(size=431; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
127 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
131 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
128 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
132 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
129 received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
133 received frame(size=431; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
130 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
134 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
131 updating the branch cache
135 updating the branch cache
132 new changesets 3390ef850073:caa2a465451d (3 drafts)
136 new changesets 3390ef850073:caa2a465451d (3 drafts)
133 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
137 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
134
138
135 All changesets should have been transferred
139 All changesets should have been transferred
136
140
137 $ hg -R client-simple debugindex -c
141 $ hg -R client-simple debugindex -c
138 rev linkrev nodeid p1 p2
142 rev linkrev nodeid p1 p2
139 0 0 3390ef850073 000000000000 000000000000
143 0 0 3390ef850073 000000000000 000000000000
140 1 1 4432d83626e8 3390ef850073 000000000000
144 1 1 4432d83626e8 3390ef850073 000000000000
141 2 2 cd2534766bec 4432d83626e8 000000000000
145 2 2 cd2534766bec 4432d83626e8 000000000000
142 3 3 e96ae20f4188 3390ef850073 000000000000
146 3 3 e96ae20f4188 3390ef850073 000000000000
143 4 4 caa2a465451d e96ae20f4188 000000000000
147 4 4 caa2a465451d e96ae20f4188 000000000000
144
148
145 $ hg -R client-simple log -G -T '{rev} {node} {phase}\n'
149 $ hg -R client-simple log -G -T '{rev} {node} {phase}\n'
146 o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft
150 o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft
147 |
151 |
148 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft
152 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft
149 |
153 |
150 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
154 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
151 | |
155 | |
152 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
156 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
153 |/
157 |/
154 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
158 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
155
159
156
160
157 All manifests should have been transferred
161 All manifests should have been transferred
158
162
159 $ hg -R client-simple debugindex -m
163 $ hg -R client-simple debugindex -m
160 rev linkrev nodeid p1 p2
164 rev linkrev nodeid p1 p2
161 0 0 992f4779029a 000000000000 000000000000
165 0 0 992f4779029a 000000000000 000000000000
162 1 1 a988fb43583e 992f4779029a 000000000000
166 1 1 a988fb43583e 992f4779029a 000000000000
163 2 2 ec804e488c20 a988fb43583e 000000000000
167 2 2 ec804e488c20 a988fb43583e 000000000000
164 3 3 045c7f3927da 992f4779029a 000000000000
168 3 3 045c7f3927da 992f4779029a 000000000000
165 4 4 379cb0c2e664 045c7f3927da 000000000000
169 4 4 379cb0c2e664 045c7f3927da 000000000000
166
170
167 Cloning only a specific revision works
171 Cloning only a specific revision works
168
172
169 $ hg --debug clone -U -r 4432d83626e8 http://localhost:$HGPORT client-singlehead
173 $ hg --debug clone -U -r 4432d83626e8 http://localhost:$HGPORT client-singlehead
170 using http://localhost:$HGPORT/
174 using http://localhost:$HGPORT/
171 sending capabilities command
175 sending capabilities command
172 sending 1 commands
176 sending 1 commands
173 sending command lookup: {
177 sending command lookup: {
174 'key': '4432d83626e8'
178 'key': '4432d83626e8'
175 }
179 }
176 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
180 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
177 received frame(size=21; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
181 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
182 received frame(size=21; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
178 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
183 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
179 query 1; heads
184 query 1; heads
180 sending 2 commands
185 sending 2 commands
181 sending command heads: {}
186 sending command heads: {}
182 sending command known: {
187 sending command known: {
183 'nodes': []
188 'nodes': []
184 }
189 }
185 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
190 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
186 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
191 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
192 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
187 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
193 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
188 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
194 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
189 received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
195 received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
190 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
196 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
191 sending 1 commands
197 sending 1 commands
192 sending command changesetdata: {
198 sending command changesetdata: {
193 'fields': set([
199 'fields': set([
194 'bookmarks',
200 'bookmarks',
195 'parents',
201 'parents',
196 'phase',
202 'phase',
197 'revision'
203 'revision'
198 ]),
204 ]),
199 'noderange': [
205 'noderange': [
200 [],
206 [],
201 [
207 [
202 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
208 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
203 ]
209 ]
204 ]
210 ]
205 }
211 }
206 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
212 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
207 received frame(size=381; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
213 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
214 received frame(size=381; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
208 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
215 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
209 add changeset 3390ef850073
216 add changeset 3390ef850073
210 add changeset 4432d83626e8
217 add changeset 4432d83626e8
211 checking for updated bookmarks
218 checking for updated bookmarks
212 sending 1 commands
219 sending 1 commands
213 sending command manifestdata: {
220 sending command manifestdata: {
214 'fields': set([
221 'fields': set([
215 'parents',
222 'parents',
216 'revision'
223 'revision'
217 ]),
224 ]),
218 'haveparents': True,
225 'haveparents': True,
219 'nodes': [
226 'nodes': [
220 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
227 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
221 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8'
228 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8'
222 ],
229 ],
223 'tree': ''
230 'tree': ''
224 }
231 }
225 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
232 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
226 received frame(size=404; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
233 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
234 received frame(size=404; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
235 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
228 sending 2 commands
236 sending 2 commands
229 sending command filedata: {
237 sending command filedata: {
230 'fields': set([
238 'fields': set([
231 'parents',
239 'parents',
232 'revision'
240 'revision'
233 ]),
241 ]),
234 'haveparents': True,
242 'haveparents': True,
235 'nodes': [
243 'nodes': [
236 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
244 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
237 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
245 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
238 ],
246 ],
239 'path': 'a'
247 'path': 'a'
240 }
248 }
241 sending command filedata: {
249 sending command filedata: {
242 'fields': set([
250 'fields': set([
243 'parents',
251 'parents',
244 'revision'
252 'revision'
245 ]),
253 ]),
246 'haveparents': True,
254 'haveparents': True,
247 'nodes': [
255 'nodes': [
248 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16'
256 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16'
249 ],
257 ],
250 'path': 'b'
258 'path': 'b'
251 }
259 }
252 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
260 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
253 received frame(size=277; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
261 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
262 received frame(size=277; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
254 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
263 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
255 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
264 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
256 received frame(size=123; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
265 received frame(size=123; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
257 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
266 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
258 updating the branch cache
267 updating the branch cache
259 new changesets 3390ef850073:4432d83626e8
268 new changesets 3390ef850073:4432d83626e8
260 (sent 6 HTTP requests and * bytes; received * bytes in responses) (glob)
269 (sent 6 HTTP requests and * bytes; received * bytes in responses) (glob)
261
270
262 $ cd client-singlehead
271 $ cd client-singlehead
263
272
264 $ hg log -G -T '{rev} {node} {phase}\n'
273 $ hg log -G -T '{rev} {node} {phase}\n'
265 o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
274 o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
266 |
275 |
267 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
276 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
268
277
269
278
270 $ hg debugindex -m
279 $ hg debugindex -m
271 rev linkrev nodeid p1 p2
280 rev linkrev nodeid p1 p2
272 0 0 992f4779029a 000000000000 000000000000
281 0 0 992f4779029a 000000000000 000000000000
273 1 1 a988fb43583e 992f4779029a 000000000000
282 1 1 a988fb43583e 992f4779029a 000000000000
274
283
275 Incremental pull works
284 Incremental pull works
276
285
277 $ hg --debug pull
286 $ hg --debug pull
278 pulling from http://localhost:$HGPORT/
287 pulling from http://localhost:$HGPORT/
279 using http://localhost:$HGPORT/
288 using http://localhost:$HGPORT/
280 sending capabilities command
289 sending capabilities command
281 query 1; heads
290 query 1; heads
282 sending 2 commands
291 sending 2 commands
283 sending command heads: {}
292 sending command heads: {}
284 sending command known: {
293 sending command known: {
285 'nodes': [
294 'nodes': [
286 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
295 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
287 ]
296 ]
288 }
297 }
289 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
298 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
290 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
299 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
300 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
291 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
301 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
292 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
302 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
293 received frame(size=2; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
303 received frame(size=2; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
294 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
304 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
295 searching for changes
305 searching for changes
296 all local heads known remotely
306 all local heads known remotely
297 sending 1 commands
307 sending 1 commands
298 sending command changesetdata: {
308 sending command changesetdata: {
299 'fields': set([
309 'fields': set([
300 'bookmarks',
310 'bookmarks',
301 'parents',
311 'parents',
302 'phase',
312 'phase',
303 'revision'
313 'revision'
304 ]),
314 ]),
305 'noderange': [
315 'noderange': [
306 [
316 [
307 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
317 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
308 ],
318 ],
309 [
319 [
310 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
320 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
311 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
321 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
312 ]
322 ]
313 ]
323 ]
314 }
324 }
315 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
325 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
316 received frame(size=613; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
326 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
327 received frame(size=613; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
317 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
328 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
318 add changeset cd2534766bec
329 add changeset cd2534766bec
319 add changeset e96ae20f4188
330 add changeset e96ae20f4188
320 add changeset caa2a465451d
331 add changeset caa2a465451d
321 checking for updated bookmarks
332 checking for updated bookmarks
322 sending 1 commands
333 sending 1 commands
323 sending command manifestdata: {
334 sending command manifestdata: {
324 'fields': set([
335 'fields': set([
325 'parents',
336 'parents',
326 'revision'
337 'revision'
327 ]),
338 ]),
328 'haveparents': True,
339 'haveparents': True,
329 'nodes': [
340 'nodes': [
330 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
341 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
331 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
342 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
332 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
343 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
333 ],
344 ],
334 'tree': ''
345 'tree': ''
335 }
346 }
336 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
347 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
337 received frame(size=601; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
348 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
349 received frame(size=601; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
338 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
350 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
339 sending 2 commands
351 sending 2 commands
340 sending command filedata: {
352 sending command filedata: {
341 'fields': set([
353 'fields': set([
342 'parents',
354 'parents',
343 'revision'
355 'revision'
344 ]),
356 ]),
345 'haveparents': True,
357 'haveparents': True,
346 'nodes': [
358 'nodes': [
347 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
359 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
348 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
360 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
349 ],
361 ],
350 'path': 'a'
362 'path': 'a'
351 }
363 }
352 sending command filedata: {
364 sending command filedata: {
353 'fields': set([
365 'fields': set([
354 'parents',
366 'parents',
355 'revision'
367 'revision'
356 ]),
368 ]),
357 'haveparents': True,
369 'haveparents': True,
358 'nodes': [
370 'nodes': [
359 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
371 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
360 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
372 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
361 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
373 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
362 ],
374 ],
363 'path': 'b'
375 'path': 'b'
364 }
376 }
365 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
377 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
366 received frame(size=277; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
378 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
379 received frame(size=277; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
367 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
380 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
368 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
381 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
369 received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
382 received frame(size=431; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
370 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
383 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
371 updating the branch cache
384 updating the branch cache
372 new changesets cd2534766bec:caa2a465451d (3 drafts)
385 new changesets cd2534766bec:caa2a465451d (3 drafts)
373 (run 'hg update' to get a working copy)
386 (run 'hg update' to get a working copy)
374 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
387 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
375
388
376 $ hg log -G -T '{rev} {node} {phase}\n'
389 $ hg log -G -T '{rev} {node} {phase}\n'
377 o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft
390 o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft
378 |
391 |
379 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft
392 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft
380 |
393 |
381 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
394 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
382 | |
395 | |
383 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
396 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
384 |/
397 |/
385 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
398 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
386
399
387
400
388 $ hg debugindex -m
401 $ hg debugindex -m
389 rev linkrev nodeid p1 p2
402 rev linkrev nodeid p1 p2
390 0 0 992f4779029a 000000000000 000000000000
403 0 0 992f4779029a 000000000000 000000000000
391 1 1 a988fb43583e 992f4779029a 000000000000
404 1 1 a988fb43583e 992f4779029a 000000000000
392 2 2 ec804e488c20 a988fb43583e 000000000000
405 2 2 ec804e488c20 a988fb43583e 000000000000
393 3 3 045c7f3927da 992f4779029a 000000000000
406 3 3 045c7f3927da 992f4779029a 000000000000
394 4 4 379cb0c2e664 045c7f3927da 000000000000
407 4 4 379cb0c2e664 045c7f3927da 000000000000
395
408
396 Phase-only update works
409 Phase-only update works
397
410
398 $ hg -R ../server-simple phase --public -r caa2a465451dd
411 $ hg -R ../server-simple phase --public -r caa2a465451dd
399 $ hg --debug pull
412 $ hg --debug pull
400 pulling from http://localhost:$HGPORT/
413 pulling from http://localhost:$HGPORT/
401 using http://localhost:$HGPORT/
414 using http://localhost:$HGPORT/
402 sending capabilities command
415 sending capabilities command
403 query 1; heads
416 query 1; heads
404 sending 2 commands
417 sending 2 commands
405 sending command heads: {}
418 sending command heads: {}
406 sending command known: {
419 sending command known: {
407 'nodes': [
420 'nodes': [
408 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f',
421 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f',
409 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1'
422 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1'
410 ]
423 ]
411 }
424 }
412 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
425 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
413 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
426 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
427 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
414 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
428 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
415 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
429 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
416 received frame(size=3; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
430 received frame(size=3; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
417 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
431 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
418 searching for changes
432 searching for changes
419 all remote heads known locally
433 all remote heads known locally
420 sending 1 commands
434 sending 1 commands
421 sending command changesetdata: {
435 sending command changesetdata: {
422 'fields': set([
436 'fields': set([
423 'bookmarks',
437 'bookmarks',
424 'parents',
438 'parents',
425 'phase',
439 'phase',
426 'revision'
440 'revision'
427 ]),
441 ]),
428 'noderange': [
442 'noderange': [
429 [
443 [
430 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
444 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
431 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
445 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
432 ],
446 ],
433 [
447 [
434 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
448 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
435 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
449 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
436 ]
450 ]
437 ]
451 ]
438 }
452 }
439 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
453 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
440 received frame(size=92; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
454 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
455 received frame(size=92; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
441 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
456 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
442 checking for updated bookmarks
457 checking for updated bookmarks
443 2 local changesets published
458 2 local changesets published
444 (run 'hg update' to get a working copy)
459 (run 'hg update' to get a working copy)
445 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
460 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
446
461
447 $ hg log -G -T '{rev} {node} {phase}\n'
462 $ hg log -G -T '{rev} {node} {phase}\n'
448 o 4 caa2a465451dd1facda0f5b12312c355584188a1 public
463 o 4 caa2a465451dd1facda0f5b12312c355584188a1 public
449 |
464 |
450 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 public
465 o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 public
451 |
466 |
452 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
467 | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
453 | |
468 | |
454 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
469 | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
455 |/
470 |/
456 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
471 o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public
457
472
458
473
459 $ cd ..
474 $ cd ..
460
475
461 Bookmarks are transferred on clone
476 Bookmarks are transferred on clone
462
477
463 $ hg -R server-simple bookmark -r 3390ef850073fbc2f0dfff2244342c8e9229013a book-1
478 $ hg -R server-simple bookmark -r 3390ef850073fbc2f0dfff2244342c8e9229013a book-1
464 $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-2
479 $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-2
465
480
466 $ hg --debug clone -U http://localhost:$HGPORT/ client-bookmarks
481 $ hg --debug clone -U http://localhost:$HGPORT/ client-bookmarks
467 using http://localhost:$HGPORT/
482 using http://localhost:$HGPORT/
468 sending capabilities command
483 sending capabilities command
469 query 1; heads
484 query 1; heads
470 sending 2 commands
485 sending 2 commands
471 sending command heads: {}
486 sending command heads: {}
472 sending command known: {
487 sending command known: {
473 'nodes': []
488 'nodes': []
474 }
489 }
475 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
490 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
476 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
491 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
492 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
477 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
493 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
478 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
494 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
479 received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
495 received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
480 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
496 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
481 sending 1 commands
497 sending 1 commands
482 sending command changesetdata: {
498 sending command changesetdata: {
483 'fields': set([
499 'fields': set([
484 'bookmarks',
500 'bookmarks',
485 'parents',
501 'parents',
486 'phase',
502 'phase',
487 'revision'
503 'revision'
488 ]),
504 ]),
489 'noderange': [
505 'noderange': [
490 [],
506 [],
491 [
507 [
492 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
508 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
493 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
509 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
494 ]
510 ]
495 ]
511 ]
496 }
512 }
497 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
513 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
498 received frame(size=979; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
514 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
515 received frame(size=979; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
499 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
516 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
500 add changeset 3390ef850073
517 add changeset 3390ef850073
501 add changeset 4432d83626e8
518 add changeset 4432d83626e8
502 add changeset cd2534766bec
519 add changeset cd2534766bec
503 add changeset e96ae20f4188
520 add changeset e96ae20f4188
504 add changeset caa2a465451d
521 add changeset caa2a465451d
505 checking for updated bookmarks
522 checking for updated bookmarks
506 adding remote bookmark book-1
523 adding remote bookmark book-1
507 adding remote bookmark book-2
524 adding remote bookmark book-2
508 sending 1 commands
525 sending 1 commands
509 sending command manifestdata: {
526 sending command manifestdata: {
510 'fields': set([
527 'fields': set([
511 'parents',
528 'parents',
512 'revision'
529 'revision'
513 ]),
530 ]),
514 'haveparents': True,
531 'haveparents': True,
515 'nodes': [
532 'nodes': [
516 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
533 '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
517 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
534 '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
518 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
535 '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
519 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
536 '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
520 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
537 '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
521 ],
538 ],
522 'tree': ''
539 'tree': ''
523 }
540 }
524 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
541 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
525 received frame(size=992; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
542 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
543 received frame(size=992; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
526 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
544 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
527 sending 2 commands
545 sending 2 commands
528 sending command filedata: {
546 sending command filedata: {
529 'fields': set([
547 'fields': set([
530 'parents',
548 'parents',
531 'revision'
549 'revision'
532 ]),
550 ]),
533 'haveparents': True,
551 'haveparents': True,
534 'nodes': [
552 'nodes': [
535 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
553 '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
536 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
554 '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
537 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
555 '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
538 ],
556 ],
539 'path': 'a'
557 'path': 'a'
540 }
558 }
541 sending command filedata: {
559 sending command filedata: {
542 'fields': set([
560 'fields': set([
543 'parents',
561 'parents',
544 'revision'
562 'revision'
545 ]),
563 ]),
546 'haveparents': True,
564 'haveparents': True,
547 'nodes': [
565 'nodes': [
548 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
566 '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
549 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
567 '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
550 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
568 '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
551 ],
569 ],
552 'path': 'b'
570 'path': 'b'
553 }
571 }
554 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
572 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
555 received frame(size=431; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
573 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
574 received frame(size=431; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
556 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
575 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
557 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
576 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
558 received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
577 received frame(size=431; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
559 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
578 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
560 updating the branch cache
579 updating the branch cache
561 new changesets 3390ef850073:caa2a465451d (1 drafts)
580 new changesets 3390ef850073:caa2a465451d (1 drafts)
562 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
581 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
563
582
564 $ hg -R client-bookmarks bookmarks
583 $ hg -R client-bookmarks bookmarks
565 book-1 0:3390ef850073
584 book-1 0:3390ef850073
566 book-2 2:cd2534766bec
585 book-2 2:cd2534766bec
567
586
568 Server-side bookmark moves are reflected during `hg pull`
587 Server-side bookmark moves are reflected during `hg pull`
569
588
570 $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-1
589 $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-1
571 moving bookmark 'book-1' forward from 3390ef850073
590 moving bookmark 'book-1' forward from 3390ef850073
572
591
573 $ hg -R client-bookmarks --debug pull
592 $ hg -R client-bookmarks --debug pull
574 pulling from http://localhost:$HGPORT/
593 pulling from http://localhost:$HGPORT/
575 using http://localhost:$HGPORT/
594 using http://localhost:$HGPORT/
576 sending capabilities command
595 sending capabilities command
577 query 1; heads
596 query 1; heads
578 sending 2 commands
597 sending 2 commands
579 sending command heads: {}
598 sending command heads: {}
580 sending command known: {
599 sending command known: {
581 'nodes': [
600 'nodes': [
582 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f',
601 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f',
583 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1'
602 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1'
584 ]
603 ]
585 }
604 }
586 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
605 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
587 received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
606 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
607 received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
588 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
608 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
589 received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
609 received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
590 received frame(size=3; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
610 received frame(size=3; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation)
591 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
611 received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
592 searching for changes
612 searching for changes
593 all remote heads known locally
613 all remote heads known locally
594 sending 1 commands
614 sending 1 commands
595 sending command changesetdata: {
615 sending command changesetdata: {
596 'fields': set([
616 'fields': set([
597 'bookmarks',
617 'bookmarks',
598 'parents',
618 'parents',
599 'phase',
619 'phase',
600 'revision'
620 'revision'
601 ]),
621 ]),
602 'noderange': [
622 'noderange': [
603 [
623 [
604 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
624 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
605 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
625 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
606 ],
626 ],
607 [
627 [
608 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
628 '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
609 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
629 '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
610 ]
630 ]
611 ]
631 ]
612 }
632 }
613 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
633 received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos)
614 received frame(size=144; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
634 received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
635 received frame(size=144; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation)
615 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
636 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
616 checking for updated bookmarks
637 checking for updated bookmarks
617 updating bookmark book-1
638 updating bookmark book-1
618 (run 'hg update' to get a working copy)
639 (run 'hg update' to get a working copy)
619 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
640 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
620
641
621 $ hg -R client-bookmarks bookmarks
642 $ hg -R client-bookmarks bookmarks
622 book-1 2:cd2534766bec
643 book-1 2:cd2534766bec
623 book-2 2:cd2534766bec
644 book-2 2:cd2534766bec
@@ -1,628 +1,633 b''
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import unittest
3 import unittest
4
4
5 from mercurial.thirdparty import (
5 from mercurial.thirdparty import (
6 cbor,
6 cbor,
7 )
7 )
8 from mercurial import (
8 from mercurial import (
9 ui as uimod,
9 ui as uimod,
10 util,
10 util,
11 wireprotoframing as framing,
11 wireprotoframing as framing,
12 )
12 )
13 from mercurial.utils import (
13 from mercurial.utils import (
14 cborutil,
14 cborutil,
15 )
15 )
16
16
17 ffs = framing.makeframefromhumanstring
17 ffs = framing.makeframefromhumanstring
18
18
19 OK = cbor.dumps({b'status': b'ok'})
19 OK = cbor.dumps({b'status': b'ok'})
20
20
21 def makereactor(deferoutput=False):
21 def makereactor(deferoutput=False):
22 ui = uimod.ui()
22 ui = uimod.ui()
23 return framing.serverreactor(ui, deferoutput=deferoutput)
23 return framing.serverreactor(ui, deferoutput=deferoutput)
24
24
25 def sendframes(reactor, gen):
25 def sendframes(reactor, gen):
26 """Send a generator of frame bytearray to a reactor.
26 """Send a generator of frame bytearray to a reactor.
27
27
28 Emits a generator of results from ``onframerecv()`` calls.
28 Emits a generator of results from ``onframerecv()`` calls.
29 """
29 """
30 for frame in gen:
30 for frame in gen:
31 header = framing.parseheader(frame)
31 header = framing.parseheader(frame)
32 payload = frame[framing.FRAME_HEADER_SIZE:]
32 payload = frame[framing.FRAME_HEADER_SIZE:]
33 assert len(payload) == header.length
33 assert len(payload) == header.length
34
34
35 yield reactor.onframerecv(framing.frame(header.requestid,
35 yield reactor.onframerecv(framing.frame(header.requestid,
36 header.streamid,
36 header.streamid,
37 header.streamflags,
37 header.streamflags,
38 header.typeid,
38 header.typeid,
39 header.flags,
39 header.flags,
40 payload))
40 payload))
41
41
42 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
42 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
43 """Generate frames to run a command and send them to a reactor."""
43 """Generate frames to run a command and send them to a reactor."""
44 return sendframes(reactor,
44 return sendframes(reactor,
45 framing.createcommandframes(stream, rid, cmd, args,
45 framing.createcommandframes(stream, rid, cmd, args,
46 datafh))
46 datafh))
47
47
48
48
49 class ServerReactorTests(unittest.TestCase):
49 class ServerReactorTests(unittest.TestCase):
50 def _sendsingleframe(self, reactor, f):
50 def _sendsingleframe(self, reactor, f):
51 results = list(sendframes(reactor, [f]))
51 results = list(sendframes(reactor, [f]))
52 self.assertEqual(len(results), 1)
52 self.assertEqual(len(results), 1)
53
53
54 return results[0]
54 return results[0]
55
55
56 def assertaction(self, res, expected):
56 def assertaction(self, res, expected):
57 self.assertIsInstance(res, tuple)
57 self.assertIsInstance(res, tuple)
58 self.assertEqual(len(res), 2)
58 self.assertEqual(len(res), 2)
59 self.assertIsInstance(res[1], dict)
59 self.assertIsInstance(res[1], dict)
60 self.assertEqual(res[0], expected)
60 self.assertEqual(res[0], expected)
61
61
62 def assertframesequal(self, frames, framestrings):
62 def assertframesequal(self, frames, framestrings):
63 expected = [ffs(s) for s in framestrings]
63 expected = [ffs(s) for s in framestrings]
64 self.assertEqual(list(frames), expected)
64 self.assertEqual(list(frames), expected)
65
65
66 def test1framecommand(self):
66 def test1framecommand(self):
67 """Receiving a command in a single frame yields request to run it."""
67 """Receiving a command in a single frame yields request to run it."""
68 reactor = makereactor()
68 reactor = makereactor()
69 stream = framing.stream(1)
69 stream = framing.stream(1)
70 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
70 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
71 self.assertEqual(len(results), 1)
71 self.assertEqual(len(results), 1)
72 self.assertaction(results[0], b'runcommand')
72 self.assertaction(results[0], b'runcommand')
73 self.assertEqual(results[0][1], {
73 self.assertEqual(results[0][1], {
74 b'requestid': 1,
74 b'requestid': 1,
75 b'command': b'mycommand',
75 b'command': b'mycommand',
76 b'args': {},
76 b'args': {},
77 b'redirect': None,
77 b'redirect': None,
78 b'data': None,
78 b'data': None,
79 })
79 })
80
80
81 result = reactor.oninputeof()
81 result = reactor.oninputeof()
82 self.assertaction(result, b'noop')
82 self.assertaction(result, b'noop')
83
83
84 def test1argument(self):
84 def test1argument(self):
85 reactor = makereactor()
85 reactor = makereactor()
86 stream = framing.stream(1)
86 stream = framing.stream(1)
87 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
87 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
88 {b'foo': b'bar'}))
88 {b'foo': b'bar'}))
89 self.assertEqual(len(results), 1)
89 self.assertEqual(len(results), 1)
90 self.assertaction(results[0], b'runcommand')
90 self.assertaction(results[0], b'runcommand')
91 self.assertEqual(results[0][1], {
91 self.assertEqual(results[0][1], {
92 b'requestid': 41,
92 b'requestid': 41,
93 b'command': b'mycommand',
93 b'command': b'mycommand',
94 b'args': {b'foo': b'bar'},
94 b'args': {b'foo': b'bar'},
95 b'redirect': None,
95 b'redirect': None,
96 b'data': None,
96 b'data': None,
97 })
97 })
98
98
99 def testmultiarguments(self):
99 def testmultiarguments(self):
100 reactor = makereactor()
100 reactor = makereactor()
101 stream = framing.stream(1)
101 stream = framing.stream(1)
102 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
102 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
103 {b'foo': b'bar', b'biz': b'baz'}))
103 {b'foo': b'bar', b'biz': b'baz'}))
104 self.assertEqual(len(results), 1)
104 self.assertEqual(len(results), 1)
105 self.assertaction(results[0], b'runcommand')
105 self.assertaction(results[0], b'runcommand')
106 self.assertEqual(results[0][1], {
106 self.assertEqual(results[0][1], {
107 b'requestid': 1,
107 b'requestid': 1,
108 b'command': b'mycommand',
108 b'command': b'mycommand',
109 b'args': {b'foo': b'bar', b'biz': b'baz'},
109 b'args': {b'foo': b'bar', b'biz': b'baz'},
110 b'redirect': None,
110 b'redirect': None,
111 b'data': None,
111 b'data': None,
112 })
112 })
113
113
114 def testsimplecommanddata(self):
114 def testsimplecommanddata(self):
115 reactor = makereactor()
115 reactor = makereactor()
116 stream = framing.stream(1)
116 stream = framing.stream(1)
117 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
117 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
118 util.bytesio(b'data!')))
118 util.bytesio(b'data!')))
119 self.assertEqual(len(results), 2)
119 self.assertEqual(len(results), 2)
120 self.assertaction(results[0], b'wantframe')
120 self.assertaction(results[0], b'wantframe')
121 self.assertaction(results[1], b'runcommand')
121 self.assertaction(results[1], b'runcommand')
122 self.assertEqual(results[1][1], {
122 self.assertEqual(results[1][1], {
123 b'requestid': 1,
123 b'requestid': 1,
124 b'command': b'mycommand',
124 b'command': b'mycommand',
125 b'args': {},
125 b'args': {},
126 b'redirect': None,
126 b'redirect': None,
127 b'data': b'data!',
127 b'data': b'data!',
128 })
128 })
129
129
130 def testmultipledataframes(self):
130 def testmultipledataframes(self):
131 frames = [
131 frames = [
132 ffs(b'1 1 stream-begin command-request new|have-data '
132 ffs(b'1 1 stream-begin command-request new|have-data '
133 b"cbor:{b'name': b'mycommand'}"),
133 b"cbor:{b'name': b'mycommand'}"),
134 ffs(b'1 1 0 command-data continuation data1'),
134 ffs(b'1 1 0 command-data continuation data1'),
135 ffs(b'1 1 0 command-data continuation data2'),
135 ffs(b'1 1 0 command-data continuation data2'),
136 ffs(b'1 1 0 command-data eos data3'),
136 ffs(b'1 1 0 command-data eos data3'),
137 ]
137 ]
138
138
139 reactor = makereactor()
139 reactor = makereactor()
140 results = list(sendframes(reactor, frames))
140 results = list(sendframes(reactor, frames))
141 self.assertEqual(len(results), 4)
141 self.assertEqual(len(results), 4)
142 for i in range(3):
142 for i in range(3):
143 self.assertaction(results[i], b'wantframe')
143 self.assertaction(results[i], b'wantframe')
144 self.assertaction(results[3], b'runcommand')
144 self.assertaction(results[3], b'runcommand')
145 self.assertEqual(results[3][1], {
145 self.assertEqual(results[3][1], {
146 b'requestid': 1,
146 b'requestid': 1,
147 b'command': b'mycommand',
147 b'command': b'mycommand',
148 b'args': {},
148 b'args': {},
149 b'redirect': None,
149 b'redirect': None,
150 b'data': b'data1data2data3',
150 b'data': b'data1data2data3',
151 })
151 })
152
152
153 def testargumentanddata(self):
153 def testargumentanddata(self):
154 frames = [
154 frames = [
155 ffs(b'1 1 stream-begin command-request new|have-data '
155 ffs(b'1 1 stream-begin command-request new|have-data '
156 b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
156 b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
157 b"b'foo': b'bar'}}"),
157 b"b'foo': b'bar'}}"),
158 ffs(b'1 1 0 command-data continuation value1'),
158 ffs(b'1 1 0 command-data continuation value1'),
159 ffs(b'1 1 0 command-data eos value2'),
159 ffs(b'1 1 0 command-data eos value2'),
160 ]
160 ]
161
161
162 reactor = makereactor()
162 reactor = makereactor()
163 results = list(sendframes(reactor, frames))
163 results = list(sendframes(reactor, frames))
164
164
165 self.assertaction(results[-1], b'runcommand')
165 self.assertaction(results[-1], b'runcommand')
166 self.assertEqual(results[-1][1], {
166 self.assertEqual(results[-1][1], {
167 b'requestid': 1,
167 b'requestid': 1,
168 b'command': b'command',
168 b'command': b'command',
169 b'args': {
169 b'args': {
170 b'key': b'val',
170 b'key': b'val',
171 b'foo': b'bar',
171 b'foo': b'bar',
172 },
172 },
173 b'redirect': None,
173 b'redirect': None,
174 b'data': b'value1value2',
174 b'data': b'value1value2',
175 })
175 })
176
176
177 def testnewandcontinuation(self):
177 def testnewandcontinuation(self):
178 result = self._sendsingleframe(makereactor(),
178 result = self._sendsingleframe(makereactor(),
179 ffs(b'1 1 stream-begin command-request new|continuation '))
179 ffs(b'1 1 stream-begin command-request new|continuation '))
180 self.assertaction(result, b'error')
180 self.assertaction(result, b'error')
181 self.assertEqual(result[1], {
181 self.assertEqual(result[1], {
182 b'message': b'received command request frame with both new and '
182 b'message': b'received command request frame with both new and '
183 b'continuation flags set',
183 b'continuation flags set',
184 })
184 })
185
185
186 def testneithernewnorcontinuation(self):
186 def testneithernewnorcontinuation(self):
187 result = self._sendsingleframe(makereactor(),
187 result = self._sendsingleframe(makereactor(),
188 ffs(b'1 1 stream-begin command-request 0 '))
188 ffs(b'1 1 stream-begin command-request 0 '))
189 self.assertaction(result, b'error')
189 self.assertaction(result, b'error')
190 self.assertEqual(result[1], {
190 self.assertEqual(result[1], {
191 b'message': b'received command request frame with neither new nor '
191 b'message': b'received command request frame with neither new nor '
192 b'continuation flags set',
192 b'continuation flags set',
193 })
193 })
194
194
195 def testunexpectedcommanddata(self):
195 def testunexpectedcommanddata(self):
196 """Command data frame when not running a command is an error."""
196 """Command data frame when not running a command is an error."""
197 result = self._sendsingleframe(makereactor(),
197 result = self._sendsingleframe(makereactor(),
198 ffs(b'1 1 stream-begin command-data 0 ignored'))
198 ffs(b'1 1 stream-begin command-data 0 ignored'))
199 self.assertaction(result, b'error')
199 self.assertaction(result, b'error')
200 self.assertEqual(result[1], {
200 self.assertEqual(result[1], {
201 b'message': b'expected sender protocol settings or command request '
201 b'message': b'expected sender protocol settings or command request '
202 b'frame; got 2',
202 b'frame; got 2',
203 })
203 })
204
204
205 def testunexpectedcommanddatareceiving(self):
205 def testunexpectedcommanddatareceiving(self):
206 """Same as above except the command is receiving."""
206 """Same as above except the command is receiving."""
207 results = list(sendframes(makereactor(), [
207 results = list(sendframes(makereactor(), [
208 ffs(b'1 1 stream-begin command-request new|more '
208 ffs(b'1 1 stream-begin command-request new|more '
209 b"cbor:{b'name': b'ignored'}"),
209 b"cbor:{b'name': b'ignored'}"),
210 ffs(b'1 1 0 command-data eos ignored'),
210 ffs(b'1 1 0 command-data eos ignored'),
211 ]))
211 ]))
212
212
213 self.assertaction(results[0], b'wantframe')
213 self.assertaction(results[0], b'wantframe')
214 self.assertaction(results[1], b'error')
214 self.assertaction(results[1], b'error')
215 self.assertEqual(results[1][1], {
215 self.assertEqual(results[1][1], {
216 b'message': b'received command data frame for request that is not '
216 b'message': b'received command data frame for request that is not '
217 b'expecting data: 1',
217 b'expecting data: 1',
218 })
218 })
219
219
220 def testconflictingrequestidallowed(self):
220 def testconflictingrequestidallowed(self):
221 """Multiple fully serviced commands with same request ID is allowed."""
221 """Multiple fully serviced commands with same request ID is allowed."""
222 reactor = makereactor()
222 reactor = makereactor()
223 results = []
223 results = []
224 outstream = reactor.makeoutputstream()
224 outstream = reactor.makeoutputstream()
225 results.append(self._sendsingleframe(
225 results.append(self._sendsingleframe(
226 reactor, ffs(b'1 1 stream-begin command-request new '
226 reactor, ffs(b'1 1 stream-begin command-request new '
227 b"cbor:{b'name': b'command'}")))
227 b"cbor:{b'name': b'command'}")))
228 result = reactor.oncommandresponsereadyobjects(
228 result = reactor.oncommandresponsereadyobjects(
229 outstream, 1, [b'response1'])
229 outstream, 1, [b'response1'])
230 self.assertaction(result, b'sendframes')
230 self.assertaction(result, b'sendframes')
231 list(result[1][b'framegen'])
231 list(result[1][b'framegen'])
232 results.append(self._sendsingleframe(
232 results.append(self._sendsingleframe(
233 reactor, ffs(b'1 1 stream-begin command-request new '
233 reactor, ffs(b'1 1 stream-begin command-request new '
234 b"cbor:{b'name': b'command'}")))
234 b"cbor:{b'name': b'command'}")))
235 result = reactor.oncommandresponsereadyobjects(
235 result = reactor.oncommandresponsereadyobjects(
236 outstream, 1, [b'response2'])
236 outstream, 1, [b'response2'])
237 self.assertaction(result, b'sendframes')
237 self.assertaction(result, b'sendframes')
238 list(result[1][b'framegen'])
238 list(result[1][b'framegen'])
239 results.append(self._sendsingleframe(
239 results.append(self._sendsingleframe(
240 reactor, ffs(b'1 1 stream-begin command-request new '
240 reactor, ffs(b'1 1 stream-begin command-request new '
241 b"cbor:{b'name': b'command'}")))
241 b"cbor:{b'name': b'command'}")))
242 result = reactor.oncommandresponsereadyobjects(
242 result = reactor.oncommandresponsereadyobjects(
243 outstream, 1, [b'response3'])
243 outstream, 1, [b'response3'])
244 self.assertaction(result, b'sendframes')
244 self.assertaction(result, b'sendframes')
245 list(result[1][b'framegen'])
245 list(result[1][b'framegen'])
246
246
247 for i in range(3):
247 for i in range(3):
248 self.assertaction(results[i], b'runcommand')
248 self.assertaction(results[i], b'runcommand')
249 self.assertEqual(results[i][1], {
249 self.assertEqual(results[i][1], {
250 b'requestid': 1,
250 b'requestid': 1,
251 b'command': b'command',
251 b'command': b'command',
252 b'args': {},
252 b'args': {},
253 b'redirect': None,
253 b'redirect': None,
254 b'data': None,
254 b'data': None,
255 })
255 })
256
256
257 def testconflictingrequestid(self):
257 def testconflictingrequestid(self):
258 """Request ID for new command matching in-flight command is illegal."""
258 """Request ID for new command matching in-flight command is illegal."""
259 results = list(sendframes(makereactor(), [
259 results = list(sendframes(makereactor(), [
260 ffs(b'1 1 stream-begin command-request new|more '
260 ffs(b'1 1 stream-begin command-request new|more '
261 b"cbor:{b'name': b'command'}"),
261 b"cbor:{b'name': b'command'}"),
262 ffs(b'1 1 0 command-request new '
262 ffs(b'1 1 0 command-request new '
263 b"cbor:{b'name': b'command1'}"),
263 b"cbor:{b'name': b'command1'}"),
264 ]))
264 ]))
265
265
266 self.assertaction(results[0], b'wantframe')
266 self.assertaction(results[0], b'wantframe')
267 self.assertaction(results[1], b'error')
267 self.assertaction(results[1], b'error')
268 self.assertEqual(results[1][1], {
268 self.assertEqual(results[1][1], {
269 b'message': b'request with ID 1 already received',
269 b'message': b'request with ID 1 already received',
270 })
270 })
271
271
272 def testinterleavedcommands(self):
272 def testinterleavedcommands(self):
273 cbor1 = cbor.dumps({
273 cbor1 = cbor.dumps({
274 b'name': b'command1',
274 b'name': b'command1',
275 b'args': {
275 b'args': {
276 b'foo': b'bar',
276 b'foo': b'bar',
277 b'key1': b'val',
277 b'key1': b'val',
278 }
278 }
279 }, canonical=True)
279 }, canonical=True)
280 cbor3 = cbor.dumps({
280 cbor3 = cbor.dumps({
281 b'name': b'command3',
281 b'name': b'command3',
282 b'args': {
282 b'args': {
283 b'biz': b'baz',
283 b'biz': b'baz',
284 b'key': b'val',
284 b'key': b'val',
285 },
285 },
286 }, canonical=True)
286 }, canonical=True)
287
287
288 results = list(sendframes(makereactor(), [
288 results = list(sendframes(makereactor(), [
289 ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
289 ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
290 ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
290 ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
291 ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
291 ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
292 ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
292 ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
293 ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
293 ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
294 ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
294 ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
295 ]))
295 ]))
296
296
297 self.assertEqual([t[0] for t in results], [
297 self.assertEqual([t[0] for t in results], [
298 b'wantframe',
298 b'wantframe',
299 b'wantframe',
299 b'wantframe',
300 b'wantframe',
300 b'wantframe',
301 b'wantframe',
301 b'wantframe',
302 b'runcommand',
302 b'runcommand',
303 b'runcommand',
303 b'runcommand',
304 ])
304 ])
305
305
306 self.assertEqual(results[4][1], {
306 self.assertEqual(results[4][1], {
307 b'requestid': 3,
307 b'requestid': 3,
308 b'command': b'command3',
308 b'command': b'command3',
309 b'args': {b'biz': b'baz', b'key': b'val'},
309 b'args': {b'biz': b'baz', b'key': b'val'},
310 b'redirect': None,
310 b'redirect': None,
311 b'data': None,
311 b'data': None,
312 })
312 })
313 self.assertEqual(results[5][1], {
313 self.assertEqual(results[5][1], {
314 b'requestid': 1,
314 b'requestid': 1,
315 b'command': b'command1',
315 b'command': b'command1',
316 b'args': {b'foo': b'bar', b'key1': b'val'},
316 b'args': {b'foo': b'bar', b'key1': b'val'},
317 b'redirect': None,
317 b'redirect': None,
318 b'data': None,
318 b'data': None,
319 })
319 })
320
320
321 def testmissingcommanddataframe(self):
321 def testmissingcommanddataframe(self):
322 # The reactor doesn't currently handle partially received commands.
322 # The reactor doesn't currently handle partially received commands.
323 # So this test is failing to do anything with request 1.
323 # So this test is failing to do anything with request 1.
324 frames = [
324 frames = [
325 ffs(b'1 1 stream-begin command-request new|have-data '
325 ffs(b'1 1 stream-begin command-request new|have-data '
326 b"cbor:{b'name': b'command1'}"),
326 b"cbor:{b'name': b'command1'}"),
327 ffs(b'3 1 0 command-request new '
327 ffs(b'3 1 0 command-request new '
328 b"cbor:{b'name': b'command2'}"),
328 b"cbor:{b'name': b'command2'}"),
329 ]
329 ]
330 results = list(sendframes(makereactor(), frames))
330 results = list(sendframes(makereactor(), frames))
331 self.assertEqual(len(results), 2)
331 self.assertEqual(len(results), 2)
332 self.assertaction(results[0], b'wantframe')
332 self.assertaction(results[0], b'wantframe')
333 self.assertaction(results[1], b'runcommand')
333 self.assertaction(results[1], b'runcommand')
334
334
335 def testmissingcommanddataframeflags(self):
335 def testmissingcommanddataframeflags(self):
336 frames = [
336 frames = [
337 ffs(b'1 1 stream-begin command-request new|have-data '
337 ffs(b'1 1 stream-begin command-request new|have-data '
338 b"cbor:{b'name': b'command1'}"),
338 b"cbor:{b'name': b'command1'}"),
339 ffs(b'1 1 0 command-data 0 data'),
339 ffs(b'1 1 0 command-data 0 data'),
340 ]
340 ]
341 results = list(sendframes(makereactor(), frames))
341 results = list(sendframes(makereactor(), frames))
342 self.assertEqual(len(results), 2)
342 self.assertEqual(len(results), 2)
343 self.assertaction(results[0], b'wantframe')
343 self.assertaction(results[0], b'wantframe')
344 self.assertaction(results[1], b'error')
344 self.assertaction(results[1], b'error')
345 self.assertEqual(results[1][1], {
345 self.assertEqual(results[1][1], {
346 b'message': b'command data frame without flags',
346 b'message': b'command data frame without flags',
347 })
347 })
348
348
349 def testframefornonreceivingrequest(self):
349 def testframefornonreceivingrequest(self):
350 """Receiving a frame for a command that is not receiving is illegal."""
350 """Receiving a frame for a command that is not receiving is illegal."""
351 results = list(sendframes(makereactor(), [
351 results = list(sendframes(makereactor(), [
352 ffs(b'1 1 stream-begin command-request new '
352 ffs(b'1 1 stream-begin command-request new '
353 b"cbor:{b'name': b'command1'}"),
353 b"cbor:{b'name': b'command1'}"),
354 ffs(b'3 1 0 command-request new|have-data '
354 ffs(b'3 1 0 command-request new|have-data '
355 b"cbor:{b'name': b'command3'}"),
355 b"cbor:{b'name': b'command3'}"),
356 ffs(b'5 1 0 command-data eos ignored'),
356 ffs(b'5 1 0 command-data eos ignored'),
357 ]))
357 ]))
358 self.assertaction(results[2], b'error')
358 self.assertaction(results[2], b'error')
359 self.assertEqual(results[2][1], {
359 self.assertEqual(results[2][1], {
360 b'message': b'received frame for request that is not receiving: 5',
360 b'message': b'received frame for request that is not receiving: 5',
361 })
361 })
362
362
363 def testsimpleresponse(self):
363 def testsimpleresponse(self):
364 """Bytes response to command sends result frames."""
364 """Bytes response to command sends result frames."""
365 reactor = makereactor()
365 reactor = makereactor()
366 instream = framing.stream(1)
366 instream = framing.stream(1)
367 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
367 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
368
368
369 outstream = reactor.makeoutputstream()
369 outstream = reactor.makeoutputstream()
370 result = reactor.oncommandresponsereadyobjects(
370 result = reactor.oncommandresponsereadyobjects(
371 outstream, 1, [b'response'])
371 outstream, 1, [b'response'])
372 self.assertaction(result, b'sendframes')
372 self.assertaction(result, b'sendframes')
373 self.assertframesequal(result[1][b'framegen'], [
373 self.assertframesequal(result[1][b'framegen'], [
374 b'1 2 stream-begin command-response continuation %s' % OK,
374 b'1 2 stream-begin stream-settings eos cbor:b"identity"',
375 b'1 2 0 command-response continuation cbor:b"response"',
375 b'1 2 encoded command-response continuation %s' % OK,
376 b'1 2 encoded command-response continuation cbor:b"response"',
376 b'1 2 0 command-response eos ',
377 b'1 2 0 command-response eos ',
377 ])
378 ])
378
379
379 def testmultiframeresponse(self):
380 def testmultiframeresponse(self):
380 """Bytes response spanning multiple frames is handled."""
381 """Bytes response spanning multiple frames is handled."""
381 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
382 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
382 second = b'y' * 100
383 second = b'y' * 100
383
384
384 reactor = makereactor()
385 reactor = makereactor()
385 instream = framing.stream(1)
386 instream = framing.stream(1)
386 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
387 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
387
388
388 outstream = reactor.makeoutputstream()
389 outstream = reactor.makeoutputstream()
389 result = reactor.oncommandresponsereadyobjects(
390 result = reactor.oncommandresponsereadyobjects(
390 outstream, 1, [first + second])
391 outstream, 1, [first + second])
391 self.assertaction(result, b'sendframes')
392 self.assertaction(result, b'sendframes')
392 self.assertframesequal(result[1][b'framegen'], [
393 self.assertframesequal(result[1][b'framegen'], [
393 b'1 2 stream-begin command-response continuation %s' % OK,
394 b'1 2 stream-begin stream-settings eos cbor:b"identity"',
394 b'1 2 0 command-response continuation Y\x80d',
395 b'1 2 encoded command-response continuation %s' % OK,
395 b'1 2 0 command-response continuation %s' % first,
396 b'1 2 encoded command-response continuation Y\x80d',
396 b'1 2 0 command-response continuation %s' % second,
397 b'1 2 encoded command-response continuation %s' % first,
398 b'1 2 encoded command-response continuation %s' % second,
397 b'1 2 0 command-response eos '
399 b'1 2 0 command-response eos '
398 ])
400 ])
399
401
400 def testservererror(self):
402 def testservererror(self):
401 reactor = makereactor()
403 reactor = makereactor()
402 instream = framing.stream(1)
404 instream = framing.stream(1)
403 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
405 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
404
406
405 outstream = reactor.makeoutputstream()
407 outstream = reactor.makeoutputstream()
406 result = reactor.onservererror(outstream, 1, b'some message')
408 result = reactor.onservererror(outstream, 1, b'some message')
407 self.assertaction(result, b'sendframes')
409 self.assertaction(result, b'sendframes')
408 self.assertframesequal(result[1][b'framegen'], [
410 self.assertframesequal(result[1][b'framegen'], [
409 b"1 2 stream-begin error-response 0 "
411 b"1 2 stream-begin error-response 0 "
410 b"cbor:{b'type': b'server', "
412 b"cbor:{b'type': b'server', "
411 b"b'message': [{b'msg': b'some message'}]}",
413 b"b'message': [{b'msg': b'some message'}]}",
412 ])
414 ])
413
415
414 def test1commanddeferresponse(self):
416 def test1commanddeferresponse(self):
415 """Responses when in deferred output mode are delayed until EOF."""
417 """Responses when in deferred output mode are delayed until EOF."""
416 reactor = makereactor(deferoutput=True)
418 reactor = makereactor(deferoutput=True)
417 instream = framing.stream(1)
419 instream = framing.stream(1)
418 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
420 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
419 {}))
421 {}))
420 self.assertEqual(len(results), 1)
422 self.assertEqual(len(results), 1)
421 self.assertaction(results[0], b'runcommand')
423 self.assertaction(results[0], b'runcommand')
422
424
423 outstream = reactor.makeoutputstream()
425 outstream = reactor.makeoutputstream()
424 result = reactor.oncommandresponsereadyobjects(
426 result = reactor.oncommandresponsereadyobjects(
425 outstream, 1, [b'response'])
427 outstream, 1, [b'response'])
426 self.assertaction(result, b'noop')
428 self.assertaction(result, b'noop')
427 result = reactor.oninputeof()
429 result = reactor.oninputeof()
428 self.assertaction(result, b'sendframes')
430 self.assertaction(result, b'sendframes')
429 self.assertframesequal(result[1][b'framegen'], [
431 self.assertframesequal(result[1][b'framegen'], [
430 b'1 2 stream-begin command-response continuation %s' % OK,
432 b'1 2 stream-begin stream-settings eos cbor:b"identity"',
431 b'1 2 0 command-response continuation cbor:b"response"',
433 b'1 2 encoded command-response continuation %s' % OK,
434 b'1 2 encoded command-response continuation cbor:b"response"',
432 b'1 2 0 command-response eos ',
435 b'1 2 0 command-response eos ',
433 ])
436 ])
434
437
435 def testmultiplecommanddeferresponse(self):
438 def testmultiplecommanddeferresponse(self):
436 reactor = makereactor(deferoutput=True)
439 reactor = makereactor(deferoutput=True)
437 instream = framing.stream(1)
440 instream = framing.stream(1)
438 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
441 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
439 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
442 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
440
443
441 outstream = reactor.makeoutputstream()
444 outstream = reactor.makeoutputstream()
442 result = reactor.oncommandresponsereadyobjects(
445 result = reactor.oncommandresponsereadyobjects(
443 outstream, 1, [b'response1'])
446 outstream, 1, [b'response1'])
444 self.assertaction(result, b'noop')
447 self.assertaction(result, b'noop')
445 result = reactor.oncommandresponsereadyobjects(
448 result = reactor.oncommandresponsereadyobjects(
446 outstream, 3, [b'response2'])
449 outstream, 3, [b'response2'])
447 self.assertaction(result, b'noop')
450 self.assertaction(result, b'noop')
448 result = reactor.oninputeof()
451 result = reactor.oninputeof()
449 self.assertaction(result, b'sendframes')
452 self.assertaction(result, b'sendframes')
450 self.assertframesequal(result[1][b'framegen'], [
453 self.assertframesequal(result[1][b'framegen'], [
451 b'1 2 stream-begin command-response continuation %s' % OK,
454 b'1 2 stream-begin stream-settings eos cbor:b"identity"',
452 b'1 2 0 command-response continuation cbor:b"response1"',
455 b'1 2 encoded command-response continuation %s' % OK,
456 b'1 2 encoded command-response continuation cbor:b"response1"',
453 b'1 2 0 command-response eos ',
457 b'1 2 0 command-response eos ',
454 b'3 2 0 command-response continuation %s' % OK,
458 b'3 2 encoded command-response continuation %s' % OK,
455 b'3 2 0 command-response continuation cbor:b"response2"',
459 b'3 2 encoded command-response continuation cbor:b"response2"',
456 b'3 2 0 command-response eos ',
460 b'3 2 0 command-response eos ',
457 ])
461 ])
458
462
459 def testrequestidtracking(self):
463 def testrequestidtracking(self):
460 reactor = makereactor(deferoutput=True)
464 reactor = makereactor(deferoutput=True)
461 instream = framing.stream(1)
465 instream = framing.stream(1)
462 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
466 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
463 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
467 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
464 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
468 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
465
469
466 # Register results for commands out of order.
470 # Register results for commands out of order.
467 outstream = reactor.makeoutputstream()
471 outstream = reactor.makeoutputstream()
468 reactor.oncommandresponsereadyobjects(outstream, 3, [b'response3'])
472 reactor.oncommandresponsereadyobjects(outstream, 3, [b'response3'])
469 reactor.oncommandresponsereadyobjects(outstream, 1, [b'response1'])
473 reactor.oncommandresponsereadyobjects(outstream, 1, [b'response1'])
470 reactor.oncommandresponsereadyobjects(outstream, 5, [b'response5'])
474 reactor.oncommandresponsereadyobjects(outstream, 5, [b'response5'])
471
475
472 result = reactor.oninputeof()
476 result = reactor.oninputeof()
473 self.assertaction(result, b'sendframes')
477 self.assertaction(result, b'sendframes')
474 self.assertframesequal(result[1][b'framegen'], [
478 self.assertframesequal(result[1][b'framegen'], [
475 b'3 2 stream-begin command-response continuation %s' % OK,
479 b'3 2 stream-begin stream-settings eos cbor:b"identity"',
476 b'3 2 0 command-response continuation cbor:b"response3"',
480 b'3 2 encoded command-response continuation %s' % OK,
481 b'3 2 encoded command-response continuation cbor:b"response3"',
477 b'3 2 0 command-response eos ',
482 b'3 2 0 command-response eos ',
478 b'1 2 0 command-response continuation %s' % OK,
483 b'1 2 encoded command-response continuation %s' % OK,
479 b'1 2 0 command-response continuation cbor:b"response1"',
484 b'1 2 encoded command-response continuation cbor:b"response1"',
480 b'1 2 0 command-response eos ',
485 b'1 2 0 command-response eos ',
481 b'5 2 0 command-response continuation %s' % OK,
486 b'5 2 encoded command-response continuation %s' % OK,
482 b'5 2 0 command-response continuation cbor:b"response5"',
487 b'5 2 encoded command-response continuation cbor:b"response5"',
483 b'5 2 0 command-response eos ',
488 b'5 2 0 command-response eos ',
484 ])
489 ])
485
490
486 def testduplicaterequestonactivecommand(self):
491 def testduplicaterequestonactivecommand(self):
487 """Receiving a request ID that matches a request that isn't finished."""
492 """Receiving a request ID that matches a request that isn't finished."""
488 reactor = makereactor()
493 reactor = makereactor()
489 stream = framing.stream(1)
494 stream = framing.stream(1)
490 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
495 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
491 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
496 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
492
497
493 self.assertaction(results[0], b'error')
498 self.assertaction(results[0], b'error')
494 self.assertEqual(results[0][1], {
499 self.assertEqual(results[0][1], {
495 b'message': b'request with ID 1 is already active',
500 b'message': b'request with ID 1 is already active',
496 })
501 })
497
502
498 def testduplicaterequestonactivecommandnosend(self):
503 def testduplicaterequestonactivecommandnosend(self):
499 """Same as above but we've registered a response but haven't sent it."""
504 """Same as above but we've registered a response but haven't sent it."""
500 reactor = makereactor()
505 reactor = makereactor()
501 instream = framing.stream(1)
506 instream = framing.stream(1)
502 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
507 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
503 outstream = reactor.makeoutputstream()
508 outstream = reactor.makeoutputstream()
504 reactor.oncommandresponsereadyobjects(outstream, 1, [b'response'])
509 reactor.oncommandresponsereadyobjects(outstream, 1, [b'response'])
505
510
506 # We've registered the response but haven't sent it. From the
511 # We've registered the response but haven't sent it. From the
507 # perspective of the reactor, the command is still active.
512 # perspective of the reactor, the command is still active.
508
513
509 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
514 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
510 self.assertaction(results[0], b'error')
515 self.assertaction(results[0], b'error')
511 self.assertEqual(results[0][1], {
516 self.assertEqual(results[0][1], {
512 b'message': b'request with ID 1 is already active',
517 b'message': b'request with ID 1 is already active',
513 })
518 })
514
519
515 def testduplicaterequestaftersend(self):
520 def testduplicaterequestaftersend(self):
516 """We can use a duplicate request ID after we've sent the response."""
521 """We can use a duplicate request ID after we've sent the response."""
517 reactor = makereactor()
522 reactor = makereactor()
518 instream = framing.stream(1)
523 instream = framing.stream(1)
519 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
524 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
520 outstream = reactor.makeoutputstream()
525 outstream = reactor.makeoutputstream()
521 res = reactor.oncommandresponsereadyobjects(outstream, 1, [b'response'])
526 res = reactor.oncommandresponsereadyobjects(outstream, 1, [b'response'])
522 list(res[1][b'framegen'])
527 list(res[1][b'framegen'])
523
528
524 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
529 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
525 self.assertaction(results[0], b'runcommand')
530 self.assertaction(results[0], b'runcommand')
526
531
527 def testprotocolsettingsnoflags(self):
532 def testprotocolsettingsnoflags(self):
528 result = self._sendsingleframe(
533 result = self._sendsingleframe(
529 makereactor(),
534 makereactor(),
530 ffs(b'0 1 stream-begin sender-protocol-settings 0 '))
535 ffs(b'0 1 stream-begin sender-protocol-settings 0 '))
531 self.assertaction(result, b'error')
536 self.assertaction(result, b'error')
532 self.assertEqual(result[1], {
537 self.assertEqual(result[1], {
533 b'message': b'sender protocol settings frame must have '
538 b'message': b'sender protocol settings frame must have '
534 b'continuation or end of stream flag set',
539 b'continuation or end of stream flag set',
535 })
540 })
536
541
537 def testprotocolsettingsconflictflags(self):
542 def testprotocolsettingsconflictflags(self):
538 result = self._sendsingleframe(
543 result = self._sendsingleframe(
539 makereactor(),
544 makereactor(),
540 ffs(b'0 1 stream-begin sender-protocol-settings continuation|eos '))
545 ffs(b'0 1 stream-begin sender-protocol-settings continuation|eos '))
541 self.assertaction(result, b'error')
546 self.assertaction(result, b'error')
542 self.assertEqual(result[1], {
547 self.assertEqual(result[1], {
543 b'message': b'sender protocol settings frame cannot have both '
548 b'message': b'sender protocol settings frame cannot have both '
544 b'continuation and end of stream flags set',
549 b'continuation and end of stream flags set',
545 })
550 })
546
551
547 def testprotocolsettingsemptypayload(self):
552 def testprotocolsettingsemptypayload(self):
548 result = self._sendsingleframe(
553 result = self._sendsingleframe(
549 makereactor(),
554 makereactor(),
550 ffs(b'0 1 stream-begin sender-protocol-settings eos '))
555 ffs(b'0 1 stream-begin sender-protocol-settings eos '))
551 self.assertaction(result, b'error')
556 self.assertaction(result, b'error')
552 self.assertEqual(result[1], {
557 self.assertEqual(result[1], {
553 b'message': b'sender protocol settings frame did not contain CBOR '
558 b'message': b'sender protocol settings frame did not contain CBOR '
554 b'data',
559 b'data',
555 })
560 })
556
561
557 def testprotocolsettingsmultipleobjects(self):
562 def testprotocolsettingsmultipleobjects(self):
558 result = self._sendsingleframe(
563 result = self._sendsingleframe(
559 makereactor(),
564 makereactor(),
560 ffs(b'0 1 stream-begin sender-protocol-settings eos '
565 ffs(b'0 1 stream-begin sender-protocol-settings eos '
561 b'\x46foobar\x43foo'))
566 b'\x46foobar\x43foo'))
562 self.assertaction(result, b'error')
567 self.assertaction(result, b'error')
563 self.assertEqual(result[1], {
568 self.assertEqual(result[1], {
564 b'message': b'sender protocol settings frame contained multiple '
569 b'message': b'sender protocol settings frame contained multiple '
565 b'CBOR values',
570 b'CBOR values',
566 })
571 })
567
572
568 def testprotocolsettingscontentencodings(self):
573 def testprotocolsettingscontentencodings(self):
569 reactor = makereactor()
574 reactor = makereactor()
570
575
571 result = self._sendsingleframe(
576 result = self._sendsingleframe(
572 reactor,
577 reactor,
573 ffs(b'0 1 stream-begin sender-protocol-settings eos '
578 ffs(b'0 1 stream-begin sender-protocol-settings eos '
574 b'cbor:{b"contentencodings": [b"a", b"b"]}'))
579 b'cbor:{b"contentencodings": [b"a", b"b"]}'))
575 self.assertaction(result, b'wantframe')
580 self.assertaction(result, b'wantframe')
576
581
577 self.assertEqual(reactor._state, b'idle')
582 self.assertEqual(reactor._state, b'idle')
578 self.assertEqual(reactor._sendersettings[b'contentencodings'],
583 self.assertEqual(reactor._sendersettings[b'contentencodings'],
579 [b'a', b'b'])
584 [b'a', b'b'])
580
585
581 def testprotocolsettingsmultipleframes(self):
586 def testprotocolsettingsmultipleframes(self):
582 reactor = makereactor()
587 reactor = makereactor()
583
588
584 data = b''.join(cborutil.streamencode({
589 data = b''.join(cborutil.streamencode({
585 b'contentencodings': [b'value1', b'value2'],
590 b'contentencodings': [b'value1', b'value2'],
586 }))
591 }))
587
592
588 results = list(sendframes(reactor, [
593 results = list(sendframes(reactor, [
589 ffs(b'0 1 stream-begin sender-protocol-settings continuation %s' %
594 ffs(b'0 1 stream-begin sender-protocol-settings continuation %s' %
590 data[0:5]),
595 data[0:5]),
591 ffs(b'0 1 0 sender-protocol-settings eos %s' % data[5:]),
596 ffs(b'0 1 0 sender-protocol-settings eos %s' % data[5:]),
592 ]))
597 ]))
593
598
594 self.assertEqual(len(results), 2)
599 self.assertEqual(len(results), 2)
595
600
596 self.assertaction(results[0], b'wantframe')
601 self.assertaction(results[0], b'wantframe')
597 self.assertaction(results[1], b'wantframe')
602 self.assertaction(results[1], b'wantframe')
598
603
599 self.assertEqual(reactor._state, b'idle')
604 self.assertEqual(reactor._state, b'idle')
600 self.assertEqual(reactor._sendersettings[b'contentencodings'],
605 self.assertEqual(reactor._sendersettings[b'contentencodings'],
601 [b'value1', b'value2'])
606 [b'value1', b'value2'])
602
607
603 def testprotocolsettingsbadcbor(self):
608 def testprotocolsettingsbadcbor(self):
604 result = self._sendsingleframe(
609 result = self._sendsingleframe(
605 makereactor(),
610 makereactor(),
606 ffs(b'0 1 stream-begin sender-protocol-settings eos badvalue'))
611 ffs(b'0 1 stream-begin sender-protocol-settings eos badvalue'))
607 self.assertaction(result, b'error')
612 self.assertaction(result, b'error')
608
613
609 def testprotocolsettingsnoninitial(self):
614 def testprotocolsettingsnoninitial(self):
610 # Cannot have protocol settings frames as non-initial frames.
615 # Cannot have protocol settings frames as non-initial frames.
611 reactor = makereactor()
616 reactor = makereactor()
612
617
613 stream = framing.stream(1)
618 stream = framing.stream(1)
614 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
619 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
615 self.assertEqual(len(results), 1)
620 self.assertEqual(len(results), 1)
616 self.assertaction(results[0], b'runcommand')
621 self.assertaction(results[0], b'runcommand')
617
622
618 result = self._sendsingleframe(
623 result = self._sendsingleframe(
619 reactor,
624 reactor,
620 ffs(b'0 1 0 sender-protocol-settings eos '))
625 ffs(b'0 1 0 sender-protocol-settings eos '))
621 self.assertaction(result, b'error')
626 self.assertaction(result, b'error')
622 self.assertEqual(result[1], {
627 self.assertEqual(result[1], {
623 b'message': b'expected command request frame; got 8',
628 b'message': b'expected command request frame; got 8',
624 })
629 })
625
630
626 if __name__ == '__main__':
631 if __name__ == '__main__':
627 import silenttestrunner
632 import silenttestrunner
628 silenttestrunner.main(__name__)
633 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now