##// END OF EJS Templates
wireprotov2: expose rich arguments metadata...
Gregory Szorc -
r39837:8e7e822e default
parent child Browse files
Show More
@@ -1,445 +1,457 b''
1 1 **Experimental and under active development**
2 2
3 3 This section documents the wire protocol commands exposed to transports
4 4 using the frame-based protocol. The set of commands exposed through
5 5 these transports is distinct from the set of commands exposed to legacy
6 6 transports.
7 7
8 8 The frame-based protocol uses CBOR to encode command execution requests.
9 9 All command arguments must be mapped to a specific or set of CBOR data
10 10 types.
11 11
12 12 The response to many commands is also CBOR. There is no common response
13 13 format: each command defines its own response format.
14 14
15 15 TODOs
16 16 =====
17 17
18 18 * Add "node namespace" support to each command. In order to support
19 19 SHA-1 hash transition, we want servers to be able to expose different
20 20 "node namespaces" for the same data. Every command operating on nodes
21 21 should specify which "node namespace" it is operating on and responses
22 22 should encode the "node namespace" accordingly.
23 23
24 24 Commands
25 25 ========
26 26
27 27 The sections below detail all commands available to wire protocol version
28 28 2.
29 29
30 30 branchmap
31 31 ---------
32 32
33 33 Obtain heads in named branches.
34 34
35 35 Receives no arguments.
36 36
37 37 The response is a map with bytestring keys defining the branch name.
38 38 Values are arrays of bytestring defining raw changeset nodes.
39 39
40 40 capabilities
41 41 ------------
42 42
43 43 Obtain the server's capabilities.
44 44
45 45 Receives no arguments.
46 46
47 47 This command is typically called only as part of the handshake during
48 48 initial connection establishment.
49 49
50 50 The response is a map with bytestring keys defining server information.
51 51
52 52 The defined keys are:
53 53
54 54 commands
55 55 A map defining available wire protocol commands on this server.
56 56
57 57 Keys in the map are the names of commands that can be invoked. Values
58 58 are maps defining information about that command. The bytestring keys
59 59 are:
60 60
61 61 args
62 A map of argument names and their expected types.
62 (map) Describes arguments accepted by the command.
63
64 Keys are bytestrings denoting the argument name.
65
66 Values are maps describing the argument. The map has the following
67 bytestring keys:
63 68
64 Types are defined as a representative value for the expected type.
65 e.g. an argument expecting a boolean type will have its value
66 set to true. An integer type will have its value set to 42. The
67 actual values are arbitrary and may not have meaning.
69 default
70 (varied) The default value for this argument if not specified. Only
71 present if ``required`` is not true.
72
73 required
74 (boolean) Whether the argument must be specified. Failure to send
75 required arguments will result in an error executing the command.
76
77 type
78 (bytestring) The type of the argument. e.g. ``bytes`` or ``bool``.
79
68 80 permissions
69 81 An array of permissions required to execute this command.
70 82
71 83 compression
72 84 An array of maps defining available compression format support.
73 85
74 86 The array is sorted from most preferred to least preferred.
75 87
76 88 Each entry has the following bytestring keys:
77 89
78 90 name
79 91 Name of the compression engine. e.g. ``zstd`` or ``zlib``.
80 92
81 93 framingmediatypes
82 94 An array of bytestrings defining the supported framing protocol
83 95 media types. Servers will not accept media types not in this list.
84 96
85 97 pathfilterprefixes
86 98 (set of bytestring) Matcher prefixes that are recognized when performing
87 99 path filtering. Specifying a path filter whose type/prefix does not
88 100 match one in this set will likely be rejected by the server.
89 101
90 102 rawrepoformats
91 103 An array of storage formats the repository is using. This set of
92 104 requirements can be used to determine whether a client can read a
93 105 *raw* copy of file data available.
94 106
95 107 changesetdata
96 108 -------------
97 109
98 110 Obtain various data related to changesets.
99 111
100 112 The command accepts the following arguments:
101 113
102 114 noderange
103 115 (array of arrays of bytestrings) An array of 2 elements, each being an
104 116 array of node bytestrings. The first array denotes the changelog revisions
105 117 that are already known to the client. The second array denotes the changelog
106 118 revision DAG heads to fetch. The argument essentially defines a DAG range
107 119 bounded by root and head nodes to fetch.
108 120
109 121 The roots array may be empty. The heads array must be defined.
110 122
111 123 nodes
112 124 (array of bytestrings) Changelog revisions to request explicitly.
113 125
114 126 fields
115 127 (set of bytestring) Which data associated with changelog revisions to
116 128 fetch. The following values are recognized:
117 129
118 130 bookmarks
119 131 Bookmarks associated with a revision.
120 132
121 133 parents
122 134 Parent revisions.
123 135
124 136 phase
125 137 The phase state of a revision.
126 138
127 139 revision
128 140 The raw, revision data for the changelog entry. The hash of this data
129 141 will match the revision's node value.
130 142
131 143 The server resolves the set of revisions relevant to the request by taking
132 144 the union of the ``noderange`` and ``nodes`` arguments. At least one of these
133 145 arguments must be defined.
134 146
135 147 The response bytestream starts with a CBOR map describing the data that follows.
136 148 This map has the following bytestring keys:
137 149
138 150 totalitems
139 151 (unsigned integer) Total number of changelog revisions whose data is being
140 152 transferred. This maps to the set of revisions in the requested node
141 153 range, not the total number of records that follow (see below for why).
142 154
143 155 Following the map header is a series of 0 or more CBOR values. If values
144 156 are present, the first value will always be a map describing a single changeset
145 157 revision. If revision data is requested, the raw revision data (encoded as
146 158 a CBOR bytestring) will follow the map describing it. Otherwise, another CBOR
147 159 map describing the next changeset revision will occur.
148 160
149 161 Each map has the following bytestring keys:
150 162
151 163 node
152 164 (bytestring) The node value for this revision. This is the SHA-1 hash of
153 165 the raw revision data.
154 166
155 167 bookmarks (optional)
156 168 (array of bytestrings) Bookmarks attached to this revision. Only present
157 169 if ``bookmarks`` data is being requested and the revision has bookmarks
158 170 attached.
159 171
160 172 parents (optional)
161 173 (array of bytestrings) The nodes representing the parent revisions of this
162 174 revision. Only present if ``parents`` data is being requested.
163 175
164 176 phase (optional)
165 177 (bytestring) The phase that a revision is in. Recognized values are
166 178 ``secret``, ``draft``, and ``public``. Only present if ``phase`` data
167 179 is being requested.
168 180
169 181 revisionsize (optional)
170 182 (unsigned integer) Indicates the size of raw revision data that follows this
171 183 map. The following data contains a serialized form of the changeset data,
172 184 including the author, date, commit message, set of changed files, manifest
173 185 node, and other metadata.
174 186
175 187 Only present if ``revision`` data was requested and the data follows this
176 188 map.
177 189
178 190 If nodes are requested via ``noderange``, they will be emitted in DAG order,
179 191 parents always before children.
180 192
181 193 If nodes are requested via ``nodes``, they will be emitted in requested order.
182 194
183 195 Nodes from ``nodes`` are emitted before nodes from ``noderange``.
184 196
185 197 The set of changeset revisions emitted may not match the exact set of
186 198 changesets requested. Furthermore, the set of keys present on each
187 199 map may vary. This is to facilitate emitting changeset updates as well
188 200 as new revisions.
189 201
190 202 For example, if the request wants ``phase`` and ``revision`` data,
191 203 the response may contain entries for each changeset in the common nodes
192 204 set with the ``phase`` key and without the ``revision`` key in order
193 205 to reflect a phase-only update.
194 206
195 207 TODO support different revision selection mechanisms (e.g. non-public, specific
196 208 revisions)
197 209 TODO support different hash "namespaces" for revisions (e.g. sha-1 versus other)
198 210 TODO support emitting obsolescence data
199 211 TODO support filtering based on relevant paths (narrow clone)
200 212 TODO support depth limiting
201 213 TODO support hgtagsfnodes cache / tags data
202 214 TODO support branch heads cache
203 215
204 216 filedata
205 217 --------
206 218
207 219 Obtain various data related to an individual tracked file.
208 220
209 221 The command accepts the following arguments:
210 222
211 223 fields
212 224 (set of bytestring) Which data associated with a file to fetch.
213 225 The following values are recognized:
214 226
215 227 parents
216 228 Parent nodes for the revision.
217 229
218 230 revision
219 231 The raw revision data for a file.
220 232
221 233 haveparents
222 234 (bool) Whether the client has the parent revisions of all requested
223 235 nodes. If set, the server may emit revision data as deltas against
224 236 any parent revision. If not set, the server MUST only emit deltas for
225 237 revisions previously emitted by this command.
226 238
227 239 False is assumed in the absence of any value.
228 240
229 241 nodes
230 242 (array of bytestrings) File nodes whose data to retrieve.
231 243
232 244 path
233 245 (bytestring) Path of the tracked file whose data to retrieve.
234 246
235 247 TODO allow specifying revisions via alternate means (such as from
236 248 changeset revisions or ranges)
237 249
238 250 The response bytestream starts with a CBOR map describing the data that
239 251 follows. It has the following bytestream keys:
240 252
241 253 totalitems
242 254 (unsigned integer) Total number of file revisions whose data is
243 255 being returned.
244 256
245 257 Following the header map is a series of 0 or more CBOR values. The first
246 258 value is always a map describing a file revision. If this map has the
247 259 ``deltasize`` or ``revisionsize`` keys, a bytestring containing the delta
248 260 or revision, respectively, will immediately follow the map. Otherwise
249 261 the next value will be a map describing the next file revision.
250 262
251 263 Each map has the following bytestring keys:
252 264
253 265 node
254 266 (bytestring) The node of the file revision whose data is represented.
255 267
256 268 deltabasenode
257 269 (bytestring) Node of the file revision the following delta is against.
258 270
259 271 Only present if the ``revision`` field is requested and delta data
260 272 follows this map.
261 273
262 274 deltasize
263 275 (unsigned integer) The size of the delta data that follows this map.
264 276
265 277 Only present if the ``revision`` field is requested and delta data
266 278 follows this map.
267 279
268 280 parents
269 281 (array of bytestring) The nodes of the parents of this file revision.
270 282
271 283 Only present if the ``parents`` field is requested.
272 284
273 285 revisionsize
274 286 (unsigned integer) The size of the fulltext revision data that follows
275 287 this map.
276 288
277 289 Only present if the ``revision`` field is requested and fulltext revision
278 290 data follows this map.
279 291
280 292 When ``revision`` data is requested, the server chooses to emit either fulltext
281 293 revision data or a delta. What the server decides can be inferred by looking
282 294 for the presence of the ``deltasize`` or ``revisionsize`` keys in the map.
283 295 Servers MUST NOT define both keys.
284 296
285 297 heads
286 298 -----
287 299
288 300 Obtain DAG heads in the repository.
289 301
290 302 The command accepts the following arguments:
291 303
292 304 publiconly (optional)
293 305 (boolean) If set, operate on the DAG for public phase changesets only.
294 306 Non-public (i.e. draft) phase DAG heads will not be returned.
295 307
296 308 The response is a CBOR array of bytestrings defining changeset nodes
297 309 of DAG heads. The array can be empty if the repository is empty or no
298 310 changesets satisfied the request.
299 311
300 312 TODO consider exposing phase of heads in response
301 313
302 314 known
303 315 -----
304 316
305 317 Determine whether a series of changeset nodes is known to the server.
306 318
307 319 The command accepts the following arguments:
308 320
309 321 nodes
310 322 (array of bytestrings) List of changeset nodes whose presence to
311 323 query.
312 324
313 325 The response is a bytestring where each byte contains a 0 or 1 for the
314 326 corresponding requested node at the same index.
315 327
316 328 TODO use a bit array for even more compact response
317 329
318 330 listkeys
319 331 --------
320 332
321 333 List values in a specified ``pushkey`` namespace.
322 334
323 335 The command receives the following arguments:
324 336
325 337 namespace
326 338 (bytestring) Pushkey namespace to query.
327 339
328 340 The response is a map with bytestring keys and values.
329 341
330 342 TODO consider using binary to represent nodes in certain pushkey namespaces.
331 343
332 344 lookup
333 345 ------
334 346
335 347 Try to resolve a value to a changeset revision.
336 348
337 349 Unlike ``known`` which operates on changeset nodes, lookup operates on
338 350 node fragments and other names that a user may use.
339 351
340 352 The command receives the following arguments:
341 353
342 354 key
343 355 (bytestring) Value to try to resolve.
344 356
345 357 On success, returns a bytestring containing the resolved node.
346 358
347 359 manifestdata
348 360 ------------
349 361
350 362 Obtain various data related to manifests (which are lists of files in
351 363 a revision).
352 364
353 365 The command accepts the following arguments:
354 366
355 367 fields
356 368 (set of bytestring) Which data associated with manifests to fetch.
357 369 The following values are recognized:
358 370
359 371 parents
360 372 Parent nodes for the manifest.
361 373
362 374 revision
363 375 The raw revision data for the manifest.
364 376
365 377 haveparents
366 378 (bool) Whether the client has the parent revisions of all requested
367 379 nodes. If set, the server may emit revision data as deltas against
368 380 any parent revision. If not set, the server MUST only emit deltas for
369 381 revisions previously emitted by this command.
370 382
371 383 False is assumed in the absence of any value.
372 384
373 385 nodes
374 386 (array of bytestring) Manifest nodes whose data to retrieve.
375 387
376 388 tree
377 389 (bytestring) Path to manifest to retrieve. The empty bytestring represents
378 390 the root manifest. All other values represent directories/trees within
379 391 the repository.
380 392
381 393 TODO allow specifying revisions via alternate means (such as from changeset
382 394 revisions or ranges)
383 395 TODO consider recursive expansion of manifests (with path filtering for
384 396 narrow use cases)
385 397
386 398 The response bytestream starts with a CBOR map describing the data that
387 399 follows. It has the following bytestring keys:
388 400
389 401 totalitems
390 402 (unsigned integer) Total number of manifest revisions whose data is
391 403 being returned.
392 404
393 405 Following the header map is a series of 0 or more CBOR values. The first
394 406 value is always a map describing a manifest revision. If this map has the
395 407 ``deltasize`` or ``revisionsize`` keys, a bytestring containing the delta
396 408 or revision, respectively, will immediately follow the map. Otherwise
397 409 the next value will be a map describing the next manifest revision.
398 410
399 411 Each map has the following bytestring keys:
400 412
401 413 node
402 414 (bytestring) The node of the manifest revision whose data is represented.
403 415
404 416 deltabasenode
405 417 (bytestring) The node that the delta representation of this revision is
406 418 computed against. Only present if the ``revision`` field is requested and
407 419 a delta is being emitted.
408 420
409 421 deltasize
410 422 (unsigned integer) The size of the delta data that follows this map.
411 423 Only present if the ``revision`` field is requested and a delta is
412 424 being emitted.
413 425
414 426 parents
415 427 (array of bytestring) The nodes of the parents of this manifest revision.
416 428 Only present if the ``parents`` field is requested.
417 429
418 430 revisionsize
419 431 (unsigned integer) The size of the fulltext revision data that follows
420 432 this map. Only present if the ``revision`` field is requested and a fulltext
421 433 revision is being emitted.
422 434
423 435 When ``revision`` data is requested, the server chooses to emit either fulltext
424 436 revision data or a delta. What the server decides can be inferred by looking
425 437 for the presence of the ``deltasize`` or ``revisionsize`` keys in the map.
426 438 Servers MUST NOT define both keys.
427 439
428 440 pushkey
429 441 -------
430 442
431 443 Set a value using the ``pushkey`` protocol.
432 444
433 445 The command receives the following arguments:
434 446
435 447 namespace
436 448 (bytestring) Pushkey namespace to operate on.
437 449 key
438 450 (bytestring) The pushkey key to set.
439 451 old
440 452 (bytestring) Old value for this key.
441 453 new
442 454 (bytestring) New value for this key.
443 455
444 456 TODO consider using binary to represent nodes is certain pushkey namespaces.
445 457 TODO better define response type and meaning.
@@ -1,1060 +1,1071 b''
1 1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 3 #
4 4 # This software may be used and distributed according to the terms of the
5 5 # GNU General Public License version 2 or any later version.
6 6
7 7 from __future__ import absolute_import
8 8
9 9 import contextlib
10 10
11 11 from .i18n import _
12 12 from .node import (
13 13 hex,
14 14 nullid,
15 15 nullrev,
16 16 )
17 17 from . import (
18 18 changegroup,
19 19 dagop,
20 20 discovery,
21 21 encoding,
22 22 error,
23 23 narrowspec,
24 24 pycompat,
25 25 streamclone,
26 26 util,
27 27 wireprotoframing,
28 28 wireprototypes,
29 29 )
30 30 from .utils import (
31 31 interfaceutil,
32 32 )
33 33
34 34 FRAMINGTYPE = b'application/mercurial-exp-framing-0005'
35 35
36 36 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
37 37
38 38 COMMANDS = wireprototypes.commanddict()
39 39
40 40 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
41 41 from .hgweb import common as hgwebcommon
42 42
43 43 # URL space looks like: <permissions>/<command>, where <permission> can
44 44 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
45 45
46 46 # Root URL does nothing meaningful... yet.
47 47 if not urlparts:
48 48 res.status = b'200 OK'
49 49 res.headers[b'Content-Type'] = b'text/plain'
50 50 res.setbodybytes(_('HTTP version 2 API handler'))
51 51 return
52 52
53 53 if len(urlparts) == 1:
54 54 res.status = b'404 Not Found'
55 55 res.headers[b'Content-Type'] = b'text/plain'
56 56 res.setbodybytes(_('do not know how to process %s\n') %
57 57 req.dispatchpath)
58 58 return
59 59
60 60 permission, command = urlparts[0:2]
61 61
62 62 if permission not in (b'ro', b'rw'):
63 63 res.status = b'404 Not Found'
64 64 res.headers[b'Content-Type'] = b'text/plain'
65 65 res.setbodybytes(_('unknown permission: %s') % permission)
66 66 return
67 67
68 68 if req.method != 'POST':
69 69 res.status = b'405 Method Not Allowed'
70 70 res.headers[b'Allow'] = b'POST'
71 71 res.setbodybytes(_('commands require POST requests'))
72 72 return
73 73
74 74 # At some point we'll want to use our own API instead of recycling the
75 75 # behavior of version 1 of the wire protocol...
76 76 # TODO return reasonable responses - not responses that overload the
77 77 # HTTP status line message for error reporting.
78 78 try:
79 79 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
80 80 except hgwebcommon.ErrorResponse as e:
81 81 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
82 82 for k, v in e.headers:
83 83 res.headers[k] = v
84 84 res.setbodybytes('permission denied')
85 85 return
86 86
87 87 # We have a special endpoint to reflect the request back at the client.
88 88 if command == b'debugreflect':
89 89 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
90 90 return
91 91
92 92 # Extra commands that we handle that aren't really wire protocol
93 93 # commands. Think extra hard before making this hackery available to
94 94 # extension.
95 95 extracommands = {'multirequest'}
96 96
97 97 if command not in COMMANDS and command not in extracommands:
98 98 res.status = b'404 Not Found'
99 99 res.headers[b'Content-Type'] = b'text/plain'
100 100 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
101 101 return
102 102
103 103 repo = rctx.repo
104 104 ui = repo.ui
105 105
106 106 proto = httpv2protocolhandler(req, ui)
107 107
108 108 if (not COMMANDS.commandavailable(command, proto)
109 109 and command not in extracommands):
110 110 res.status = b'404 Not Found'
111 111 res.headers[b'Content-Type'] = b'text/plain'
112 112 res.setbodybytes(_('invalid wire protocol command: %s') % command)
113 113 return
114 114
115 115 # TODO consider cases where proxies may add additional Accept headers.
116 116 if req.headers.get(b'Accept') != FRAMINGTYPE:
117 117 res.status = b'406 Not Acceptable'
118 118 res.headers[b'Content-Type'] = b'text/plain'
119 119 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
120 120 % FRAMINGTYPE)
121 121 return
122 122
123 123 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
124 124 res.status = b'415 Unsupported Media Type'
125 125 # TODO we should send a response with appropriate media type,
126 126 # since client does Accept it.
127 127 res.headers[b'Content-Type'] = b'text/plain'
128 128 res.setbodybytes(_('client MUST send Content-Type header with '
129 129 'value: %s\n') % FRAMINGTYPE)
130 130 return
131 131
132 132 _processhttpv2request(ui, repo, req, res, permission, command, proto)
133 133
134 134 def _processhttpv2reflectrequest(ui, repo, req, res):
135 135 """Reads unified frame protocol request and dumps out state to client.
136 136
137 137 This special endpoint can be used to help debug the wire protocol.
138 138
139 139 Instead of routing the request through the normal dispatch mechanism,
140 140 we instead read all frames, decode them, and feed them into our state
141 141 tracker. We then dump the log of all that activity back out to the
142 142 client.
143 143 """
144 144 import json
145 145
146 146 # Reflection APIs have a history of being abused, accidentally disclosing
147 147 # sensitive data, etc. So we have a config knob.
148 148 if not ui.configbool('experimental', 'web.api.debugreflect'):
149 149 res.status = b'404 Not Found'
150 150 res.headers[b'Content-Type'] = b'text/plain'
151 151 res.setbodybytes(_('debugreflect service not available'))
152 152 return
153 153
154 154 # We assume we have a unified framing protocol request body.
155 155
156 156 reactor = wireprotoframing.serverreactor()
157 157 states = []
158 158
159 159 while True:
160 160 frame = wireprotoframing.readframe(req.bodyfh)
161 161
162 162 if not frame:
163 163 states.append(b'received: <no frame>')
164 164 break
165 165
166 166 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
167 167 frame.requestid,
168 168 frame.payload))
169 169
170 170 action, meta = reactor.onframerecv(frame)
171 171 states.append(json.dumps((action, meta), sort_keys=True,
172 172 separators=(', ', ': ')))
173 173
174 174 action, meta = reactor.oninputeof()
175 175 meta['action'] = action
176 176 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
177 177
178 178 res.status = b'200 OK'
179 179 res.headers[b'Content-Type'] = b'text/plain'
180 180 res.setbodybytes(b'\n'.join(states))
181 181
182 182 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
183 183 """Post-validation handler for HTTPv2 requests.
184 184
185 185 Called when the HTTP request contains unified frame-based protocol
186 186 frames for evaluation.
187 187 """
188 188 # TODO Some HTTP clients are full duplex and can receive data before
189 189 # the entire request is transmitted. Figure out a way to indicate support
190 190 # for that so we can opt into full duplex mode.
191 191 reactor = wireprotoframing.serverreactor(deferoutput=True)
192 192 seencommand = False
193 193
194 194 outstream = reactor.makeoutputstream()
195 195
196 196 while True:
197 197 frame = wireprotoframing.readframe(req.bodyfh)
198 198 if not frame:
199 199 break
200 200
201 201 action, meta = reactor.onframerecv(frame)
202 202
203 203 if action == 'wantframe':
204 204 # Need more data before we can do anything.
205 205 continue
206 206 elif action == 'runcommand':
207 207 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
208 208 reqcommand, reactor, outstream,
209 209 meta, issubsequent=seencommand)
210 210
211 211 if sentoutput:
212 212 return
213 213
214 214 seencommand = True
215 215
216 216 elif action == 'error':
217 217 # TODO define proper error mechanism.
218 218 res.status = b'200 OK'
219 219 res.headers[b'Content-Type'] = b'text/plain'
220 220 res.setbodybytes(meta['message'] + b'\n')
221 221 return
222 222 else:
223 223 raise error.ProgrammingError(
224 224 'unhandled action from frame processor: %s' % action)
225 225
226 226 action, meta = reactor.oninputeof()
227 227 if action == 'sendframes':
228 228 # We assume we haven't started sending the response yet. If we're
229 229 # wrong, the response type will raise an exception.
230 230 res.status = b'200 OK'
231 231 res.headers[b'Content-Type'] = FRAMINGTYPE
232 232 res.setbodygen(meta['framegen'])
233 233 elif action == 'noop':
234 234 pass
235 235 else:
236 236 raise error.ProgrammingError('unhandled action from frame processor: %s'
237 237 % action)
238 238
239 239 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
240 240 outstream, command, issubsequent):
241 241 """Dispatch a wire protocol command made from HTTPv2 requests.
242 242
243 243 The authenticated permission (``authedperm``) along with the original
244 244 command from the URL (``reqcommand``) are passed in.
245 245 """
246 246 # We already validated that the session has permissions to perform the
247 247 # actions in ``authedperm``. In the unified frame protocol, the canonical
248 248 # command to run is expressed in a frame. However, the URL also requested
249 249 # to run a specific command. We need to be careful that the command we
250 250 # run doesn't have permissions requirements greater than what was granted
251 251 # by ``authedperm``.
252 252 #
253 253 # Our rule for this is we only allow one command per HTTP request and
254 254 # that command must match the command in the URL. However, we make
255 255 # an exception for the ``multirequest`` URL. This URL is allowed to
256 256 # execute multiple commands. We double check permissions of each command
257 257 # as it is invoked to ensure there is no privilege escalation.
258 258 # TODO consider allowing multiple commands to regular command URLs
259 259 # iff each command is the same.
260 260
261 261 proto = httpv2protocolhandler(req, ui, args=command['args'])
262 262
263 263 if reqcommand == b'multirequest':
264 264 if not COMMANDS.commandavailable(command['command'], proto):
265 265 # TODO proper error mechanism
266 266 res.status = b'200 OK'
267 267 res.headers[b'Content-Type'] = b'text/plain'
268 268 res.setbodybytes(_('wire protocol command not available: %s') %
269 269 command['command'])
270 270 return True
271 271
272 272 # TODO don't use assert here, since it may be elided by -O.
273 273 assert authedperm in (b'ro', b'rw')
274 274 wirecommand = COMMANDS[command['command']]
275 275 assert wirecommand.permission in ('push', 'pull')
276 276
277 277 if authedperm == b'ro' and wirecommand.permission != 'pull':
278 278 # TODO proper error mechanism
279 279 res.status = b'403 Forbidden'
280 280 res.headers[b'Content-Type'] = b'text/plain'
281 281 res.setbodybytes(_('insufficient permissions to execute '
282 282 'command: %s') % command['command'])
283 283 return True
284 284
285 285 # TODO should we also call checkperm() here? Maybe not if we're going
286 286 # to overhaul that API. The granted scope from the URL check should
287 287 # be good enough.
288 288
289 289 else:
290 290 # Don't allow multiple commands outside of ``multirequest`` URL.
291 291 if issubsequent:
292 292 # TODO proper error mechanism
293 293 res.status = b'200 OK'
294 294 res.headers[b'Content-Type'] = b'text/plain'
295 295 res.setbodybytes(_('multiple commands cannot be issued to this '
296 296 'URL'))
297 297 return True
298 298
299 299 if reqcommand != command['command']:
300 300 # TODO define proper error mechanism
301 301 res.status = b'200 OK'
302 302 res.headers[b'Content-Type'] = b'text/plain'
303 303 res.setbodybytes(_('command in frame must match command in URL'))
304 304 return True
305 305
306 306 res.status = b'200 OK'
307 307 res.headers[b'Content-Type'] = FRAMINGTYPE
308 308
309 309 try:
310 310 objs = dispatch(repo, proto, command['command'])
311 311
312 312 action, meta = reactor.oncommandresponsereadyobjects(
313 313 outstream, command['requestid'], objs)
314 314
315 315 except error.WireprotoCommandError as e:
316 316 action, meta = reactor.oncommanderror(
317 317 outstream, command['requestid'], e.message, e.messageargs)
318 318
319 319 except Exception as e:
320 320 action, meta = reactor.onservererror(
321 321 outstream, command['requestid'],
322 322 _('exception when invoking command: %s') % e)
323 323
324 324 if action == 'sendframes':
325 325 res.setbodygen(meta['framegen'])
326 326 return True
327 327 elif action == 'noop':
328 328 return False
329 329 else:
330 330 raise error.ProgrammingError('unhandled event from reactor: %s' %
331 331 action)
332 332
333 333 def getdispatchrepo(repo, proto, command):
334 334 return repo.filtered('served')
335 335
336 336 def dispatch(repo, proto, command):
337 337 repo = getdispatchrepo(repo, proto, command)
338 338
339 339 func, spec = COMMANDS[command]
340 340 args = proto.getargs(spec)
341 341
342 342 return func(repo, proto, **args)
343 343
344 344 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
345 345 class httpv2protocolhandler(object):
346 346 def __init__(self, req, ui, args=None):
347 347 self._req = req
348 348 self._ui = ui
349 349 self._args = args
350 350
351 351 @property
352 352 def name(self):
353 353 return HTTP_WIREPROTO_V2
354 354
355 355 def getargs(self, args):
356 356 # First look for args that were passed but aren't registered on this
357 357 # command.
358 358 extra = set(self._args) - set(args)
359 359 if extra:
360 360 raise error.WireprotoCommandError(
361 361 'unsupported argument to command: %s' %
362 362 ', '.join(sorted(extra)))
363 363
364 364 # And look for required arguments that are missing.
365 365 missing = {a for a in args if args[a]['required']} - set(self._args)
366 366
367 367 if missing:
368 368 raise error.WireprotoCommandError(
369 369 'missing required arguments: %s' % ', '.join(sorted(missing)))
370 370
371 371 # Now derive the arguments to pass to the command, taking into
372 372 # account the arguments specified by the client.
373 373 data = {}
374 374 for k, meta in sorted(args.items()):
375 375 # This argument wasn't passed by the client.
376 376 if k not in self._args:
377 377 data[k] = meta['default']()
378 378 continue
379 379
380 380 v = self._args[k]
381 381
382 382 # Sets may be expressed as lists. Silently normalize.
383 383 if meta['type'] == 'set' and isinstance(v, list):
384 384 v = set(v)
385 385
386 386 # TODO consider more/stronger type validation.
387 387
388 388 data[k] = v
389 389
390 390 return data
391 391
392 392 def getprotocaps(self):
393 393 # Protocol capabilities are currently not implemented for HTTP V2.
394 394 return set()
395 395
396 396 def getpayload(self):
397 397 raise NotImplementedError
398 398
399 399 @contextlib.contextmanager
400 400 def mayberedirectstdio(self):
401 401 raise NotImplementedError
402 402
403 403 def client(self):
404 404 raise NotImplementedError
405 405
406 406 def addcapabilities(self, repo, caps):
407 407 return caps
408 408
409 409 def checkperm(self, perm):
410 410 raise NotImplementedError
411 411
412 412 def httpv2apidescriptor(req, repo):
413 413 proto = httpv2protocolhandler(req, repo.ui)
414 414
415 415 return _capabilitiesv2(repo, proto)
416 416
417 417 def _capabilitiesv2(repo, proto):
418 418 """Obtain the set of capabilities for version 2 transports.
419 419
420 420 These capabilities are distinct from the capabilities for version 1
421 421 transports.
422 422 """
423 423 compression = []
424 424 for engine in wireprototypes.supportedcompengines(repo.ui, util.SERVERROLE):
425 425 compression.append({
426 426 b'name': engine.wireprotosupport().name,
427 427 })
428 428
429 429 caps = {
430 430 'commands': {},
431 431 'compression': compression,
432 432 'framingmediatypes': [FRAMINGTYPE],
433 433 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
434 434 }
435 435
436 436 # TODO expose available changesetdata fields.
437 437
438 438 for command, entry in COMMANDS.items():
439 args = {arg: meta['example'] for arg, meta in entry.args.items()}
439 args = {}
440
441 for arg, meta in entry.args.items():
442 args[arg] = {
443 # TODO should this be a normalized type using CBOR's
444 # terminology?
445 b'type': meta['type'],
446 b'required': meta['required'],
447 }
448
449 if not meta['required']:
450 args[arg][b'default'] = meta['default']()
440 451
441 452 caps['commands'][command] = {
442 453 'args': args,
443 454 'permissions': [entry.permission],
444 455 }
445 456
446 457 if streamclone.allowservergeneration(repo):
447 458 caps['rawrepoformats'] = sorted(repo.requirements &
448 459 repo.supportedformats)
449 460
450 461 return proto.addcapabilities(repo, caps)
451 462
452 463 def builddeltarequests(store, nodes, haveparents):
453 464 """Build a series of revision delta requests against a backend store.
454 465
455 466 Returns a list of revision numbers in the order they should be sent
456 467 and a list of ``irevisiondeltarequest`` instances to be made against
457 468 the backend store.
458 469 """
459 470 # We sort and send nodes in DAG order because this is optimal for
460 471 # storage emission.
461 472 # TODO we may want a better storage API here - one where we can throw
462 473 # a list of nodes and delta preconditions over a figurative wall and
463 474 # have the storage backend figure it out for us.
464 475 revs = dagop.linearize({store.rev(n) for n in nodes}, store.parentrevs)
465 476
466 477 requests = []
467 478 seenrevs = set()
468 479
469 480 for rev in revs:
470 481 node = store.node(rev)
471 482 parentnodes = store.parents(node)
472 483 parentrevs = [store.rev(n) for n in parentnodes]
473 484 deltabaserev = store.deltaparent(rev)
474 485 deltabasenode = store.node(deltabaserev)
475 486
476 487 # The choice of whether to send a fulltext revision or a delta and
477 488 # what delta to send is governed by a few factors.
478 489 #
479 490 # To send a delta, we need to ensure the receiver is capable of
480 491 # decoding it. And that requires the receiver to have the base
481 492 # revision the delta is against.
482 493 #
483 494 # We can only guarantee the receiver has the base revision if
484 495 # a) we've already sent the revision as part of this group
485 496 # b) the receiver has indicated they already have the revision.
486 497 # And the mechanism for "b" is the client indicating they have
487 498 # parent revisions. So this means we can only send the delta if
488 499 # it is sent before or it is against a delta and the receiver says
489 500 # they have a parent.
490 501
491 502 # We can send storage delta if it is against a revision we've sent
492 503 # in this group.
493 504 if deltabaserev != nullrev and deltabaserev in seenrevs:
494 505 basenode = deltabasenode
495 506
496 507 # We can send storage delta if it is against a parent revision and
497 508 # the receiver indicates they have the parents.
498 509 elif (deltabaserev != nullrev and deltabaserev in parentrevs
499 510 and haveparents):
500 511 basenode = deltabasenode
501 512
502 513 # Otherwise the storage delta isn't appropriate. Fall back to
503 514 # using another delta, if possible.
504 515
505 516 # Use p1 if we've emitted it or receiver says they have it.
506 517 elif parentrevs[0] != nullrev and (
507 518 parentrevs[0] in seenrevs or haveparents):
508 519 basenode = parentnodes[0]
509 520
510 521 # Use p2 if we've emitted it or receiver says they have it.
511 522 elif parentrevs[1] != nullrev and (
512 523 parentrevs[1] in seenrevs or haveparents):
513 524 basenode = parentnodes[1]
514 525
515 526 # Nothing appropriate to delta against. Send the full revision.
516 527 else:
517 528 basenode = nullid
518 529
519 530 requests.append(changegroup.revisiondeltarequest(
520 531 node=node,
521 532 p1node=parentnodes[0],
522 533 p2node=parentnodes[1],
523 534 # Receiver deals with linknode resolution.
524 535 linknode=nullid,
525 536 basenode=basenode,
526 537 ))
527 538
528 539 seenrevs.add(rev)
529 540
530 541 return revs, requests
531 542
532 543 def wireprotocommand(name, args=None, permission='push'):
533 544 """Decorator to declare a wire protocol command.
534 545
535 546 ``name`` is the name of the wire protocol command being provided.
536 547
537 548 ``args`` is a dict defining arguments accepted by the command. Keys are
538 549 the argument name. Values are dicts with the following keys:
539 550
540 551 ``type``
541 552 The argument data type. Must be one of the following string
542 553 literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
543 554 or ``bool``.
544 555
545 556 ``default``
546 557 A callable returning the default value for this argument. If not
547 558 specified, ``None`` will be the default value.
548 559
549 560 ``required``
550 561 Bool indicating whether the argument is required.
551 562
552 563 ``example``
553 564 An example value for this argument.
554 565
555 566 ``permission`` defines the permission type needed to run this command.
556 567 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
557 568 respectively. Default is to assume command requires ``push`` permissions
558 569 because otherwise commands not declaring their permissions could modify
559 570 a repository that is supposed to be read-only.
560 571
561 572 Wire protocol commands are generators of objects to be serialized and
562 573 sent to the client.
563 574
564 575 If a command raises an uncaught exception, this will be translated into
565 576 a command error.
566 577 """
567 578 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
568 579 if v['version'] == 2}
569 580
570 581 if permission not in ('push', 'pull'):
571 582 raise error.ProgrammingError('invalid wire protocol permission; '
572 583 'got %s; expected "push" or "pull"' %
573 584 permission)
574 585
575 586 if args is None:
576 587 args = {}
577 588
578 589 if not isinstance(args, dict):
579 590 raise error.ProgrammingError('arguments for version 2 commands '
580 591 'must be declared as dicts')
581 592
582 593 for arg, meta in args.items():
583 594 if arg == '*':
584 595 raise error.ProgrammingError('* argument name not allowed on '
585 596 'version 2 commands')
586 597
587 598 if not isinstance(meta, dict):
588 599 raise error.ProgrammingError('arguments for version 2 commands '
589 600 'must declare metadata as a dict')
590 601
591 602 if 'type' not in meta:
592 603 raise error.ProgrammingError('%s argument for command %s does not '
593 604 'declare type field' % (arg, name))
594 605
595 606 if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'):
596 607 raise error.ProgrammingError('%s argument for command %s has '
597 608 'illegal type: %s' % (arg, name,
598 609 meta['type']))
599 610
600 611 if 'example' not in meta:
601 612 raise error.ProgrammingError('%s argument for command %s does not '
602 613 'declare example field' % (arg, name))
603 614
604 615 if 'default' in meta and meta.get('required'):
605 616 raise error.ProgrammingError('%s argument for command %s is marked '
606 617 'as required but has a default value' %
607 618 (arg, name))
608 619
609 620 meta.setdefault('default', lambda: None)
610 621 meta.setdefault('required', False)
611 622
612 623 def register(func):
613 624 if name in COMMANDS:
614 625 raise error.ProgrammingError('%s command already registered '
615 626 'for version 2' % name)
616 627
617 628 COMMANDS[name] = wireprototypes.commandentry(
618 629 func, args=args, transports=transports, permission=permission)
619 630
620 631 return func
621 632
622 633 return register
623 634
624 635 @wireprotocommand('branchmap', permission='pull')
625 636 def branchmapv2(repo, proto):
626 637 yield {encoding.fromlocal(k): v
627 638 for k, v in repo.branchmap().iteritems()}
628 639
629 640 @wireprotocommand('capabilities', permission='pull')
630 641 def capabilitiesv2(repo, proto):
631 642 yield _capabilitiesv2(repo, proto)
632 643
633 644 @wireprotocommand(
634 645 'changesetdata',
635 646 args={
636 647 'noderange': {
637 648 'type': 'list',
638 649 'example': [[b'0123456...'], [b'abcdef...']],
639 650 },
640 651 'nodes': {
641 652 'type': 'list',
642 653 'example': [b'0123456...'],
643 654 },
644 655 'fields': {
645 656 'type': 'set',
646 657 'default': set,
647 658 'example': {b'parents', b'revision'},
648 659 },
649 660 },
650 661 permission='pull')
651 662 def changesetdata(repo, proto, noderange, nodes, fields):
652 663 # TODO look for unknown fields and abort when they can't be serviced.
653 664
654 665 if noderange is None and nodes is None:
655 666 raise error.WireprotoCommandError(
656 667 'noderange or nodes must be defined')
657 668
658 669 if noderange is not None:
659 670 if len(noderange) != 2:
660 671 raise error.WireprotoCommandError(
661 672 'noderange must consist of 2 elements')
662 673
663 674 if not noderange[1]:
664 675 raise error.WireprotoCommandError(
665 676 'heads in noderange request cannot be empty')
666 677
667 678 cl = repo.changelog
668 679 hasnode = cl.hasnode
669 680
670 681 seen = set()
671 682 outgoing = []
672 683
673 684 if nodes is not None:
674 685 outgoing.extend(n for n in nodes if hasnode(n))
675 686 seen |= set(outgoing)
676 687
677 688 if noderange is not None:
678 689 if noderange[0]:
679 690 common = [n for n in noderange[0] if hasnode(n)]
680 691 else:
681 692 common = [nullid]
682 693
683 694 for n in discovery.outgoing(repo, common, noderange[1]).missing:
684 695 if n not in seen:
685 696 outgoing.append(n)
686 697 # Don't need to add to seen here because this is the final
687 698 # source of nodes and there should be no duplicates in this
688 699 # list.
689 700
690 701 seen.clear()
691 702 publishing = repo.publishing()
692 703
693 704 if outgoing:
694 705 repo.hook('preoutgoing', throw=True, source='serve')
695 706
696 707 yield {
697 708 b'totalitems': len(outgoing),
698 709 }
699 710
700 711 # The phases of nodes already transferred to the client may have changed
701 712 # since the client last requested data. We send phase-only records
702 713 # for these revisions, if requested.
703 714 if b'phase' in fields and noderange is not None:
704 715 # TODO skip nodes whose phase will be reflected by a node in the
705 716 # outgoing set. This is purely an optimization to reduce data
706 717 # size.
707 718 for node in noderange[0]:
708 719 yield {
709 720 b'node': node,
710 721 b'phase': b'public' if publishing else repo[node].phasestr()
711 722 }
712 723
713 724 nodebookmarks = {}
714 725 for mark, node in repo._bookmarks.items():
715 726 nodebookmarks.setdefault(node, set()).add(mark)
716 727
717 728 # It is already topologically sorted by revision number.
718 729 for node in outgoing:
719 730 d = {
720 731 b'node': node,
721 732 }
722 733
723 734 if b'parents' in fields:
724 735 d[b'parents'] = cl.parents(node)
725 736
726 737 if b'phase' in fields:
727 738 if publishing:
728 739 d[b'phase'] = b'public'
729 740 else:
730 741 ctx = repo[node]
731 742 d[b'phase'] = ctx.phasestr()
732 743
733 744 if b'bookmarks' in fields and node in nodebookmarks:
734 745 d[b'bookmarks'] = sorted(nodebookmarks[node])
735 746 del nodebookmarks[node]
736 747
737 748 revisiondata = None
738 749
739 750 if b'revision' in fields:
740 751 revisiondata = cl.revision(node, raw=True)
741 752 d[b'revisionsize'] = len(revisiondata)
742 753
743 754 # TODO make it possible for extensions to wrap a function or register
744 755 # a handler to service custom fields.
745 756
746 757 yield d
747 758
748 759 if revisiondata is not None:
749 760 yield revisiondata
750 761
751 762 # If requested, send bookmarks from nodes that didn't have revision
752 763 # data sent so receiver is aware of any bookmark updates.
753 764 if b'bookmarks' in fields:
754 765 for node, marks in sorted(nodebookmarks.iteritems()):
755 766 yield {
756 767 b'node': node,
757 768 b'bookmarks': sorted(marks),
758 769 }
759 770
760 771 class FileAccessError(Exception):
761 772 """Represents an error accessing a specific file."""
762 773
763 774 def __init__(self, path, msg, args):
764 775 self.path = path
765 776 self.msg = msg
766 777 self.args = args
767 778
768 779 def getfilestore(repo, proto, path):
769 780 """Obtain a file storage object for use with wire protocol.
770 781
771 782 Exists as a standalone function so extensions can monkeypatch to add
772 783 access control.
773 784 """
774 785 # This seems to work even if the file doesn't exist. So catch
775 786 # "empty" files and return an error.
776 787 fl = repo.file(path)
777 788
778 789 if not len(fl):
779 790 raise FileAccessError(path, 'unknown file: %s', (path,))
780 791
781 792 return fl
782 793
783 794 @wireprotocommand(
784 795 'filedata',
785 796 args={
786 797 'haveparents': {
787 798 'type': 'bool',
788 799 'default': lambda: False,
789 800 'example': True,
790 801 },
791 802 'nodes': {
792 803 'type': 'list',
793 804 'required': True,
794 805 'example': [b'0123456...'],
795 806 },
796 807 'fields': {
797 808 'type': 'set',
798 809 'default': set,
799 810 'example': {b'parents', b'revision'},
800 811 },
801 812 'path': {
802 813 'type': 'bytes',
803 814 'required': True,
804 815 'example': b'foo.txt',
805 816 }
806 817 },
807 818 permission='pull')
808 819 def filedata(repo, proto, haveparents, nodes, fields, path):
809 820 try:
810 821 # Extensions may wish to access the protocol handler.
811 822 store = getfilestore(repo, proto, path)
812 823 except FileAccessError as e:
813 824 raise error.WireprotoCommandError(e.msg, e.args)
814 825
815 826 # Validate requested nodes.
816 827 for node in nodes:
817 828 try:
818 829 store.rev(node)
819 830 except error.LookupError:
820 831 raise error.WireprotoCommandError('unknown file node: %s',
821 832 (hex(node),))
822 833
823 834 revs, requests = builddeltarequests(store, nodes, haveparents)
824 835
825 836 yield {
826 837 b'totalitems': len(revs),
827 838 }
828 839
829 840 if b'revision' in fields:
830 841 deltas = store.emitrevisiondeltas(requests)
831 842 else:
832 843 deltas = None
833 844
834 845 for rev in revs:
835 846 node = store.node(rev)
836 847
837 848 if deltas is not None:
838 849 delta = next(deltas)
839 850 else:
840 851 delta = None
841 852
842 853 d = {
843 854 b'node': node,
844 855 }
845 856
846 857 if b'parents' in fields:
847 858 d[b'parents'] = store.parents(node)
848 859
849 860 if b'revision' in fields:
850 861 assert delta is not None
851 862 assert delta.flags == 0
852 863 assert d[b'node'] == delta.node
853 864
854 865 if delta.revision is not None:
855 866 revisiondata = delta.revision
856 867 d[b'revisionsize'] = len(revisiondata)
857 868 else:
858 869 d[b'deltabasenode'] = delta.basenode
859 870 revisiondata = delta.delta
860 871 d[b'deltasize'] = len(revisiondata)
861 872 else:
862 873 revisiondata = None
863 874
864 875 yield d
865 876
866 877 if revisiondata is not None:
867 878 yield revisiondata
868 879
869 880 if deltas is not None:
870 881 try:
871 882 next(deltas)
872 883 raise error.ProgrammingError('should not have more deltas')
873 884 except GeneratorExit:
874 885 pass
875 886
876 887 @wireprotocommand(
877 888 'heads',
878 889 args={
879 890 'publiconly': {
880 891 'type': 'bool',
881 892 'default': lambda: False,
882 893 'example': False,
883 894 },
884 895 },
885 896 permission='pull')
886 897 def headsv2(repo, proto, publiconly):
887 898 if publiconly:
888 899 repo = repo.filtered('immutable')
889 900
890 901 yield repo.heads()
891 902
892 903 @wireprotocommand(
893 904 'known',
894 905 args={
895 906 'nodes': {
896 907 'type': 'list',
897 908 'default': list,
898 909 'example': [b'deadbeef'],
899 910 },
900 911 },
901 912 permission='pull')
902 913 def knownv2(repo, proto, nodes):
903 914 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
904 915 yield result
905 916
906 917 @wireprotocommand(
907 918 'listkeys',
908 919 args={
909 920 'namespace': {
910 921 'type': 'bytes',
911 922 'required': True,
912 923 'example': b'ns',
913 924 },
914 925 },
915 926 permission='pull')
916 927 def listkeysv2(repo, proto, namespace):
917 928 keys = repo.listkeys(encoding.tolocal(namespace))
918 929 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
919 930 for k, v in keys.iteritems()}
920 931
921 932 yield keys
922 933
923 934 @wireprotocommand(
924 935 'lookup',
925 936 args={
926 937 'key': {
927 938 'type': 'bytes',
928 939 'required': True,
929 940 'example': b'foo',
930 941 },
931 942 },
932 943 permission='pull')
933 944 def lookupv2(repo, proto, key):
934 945 key = encoding.tolocal(key)
935 946
936 947 # TODO handle exception.
937 948 node = repo.lookup(key)
938 949
939 950 yield node
940 951
941 952 @wireprotocommand(
942 953 'manifestdata',
943 954 args={
944 955 'nodes': {
945 956 'type': 'list',
946 957 'required': True,
947 958 'example': [b'0123456...'],
948 959 },
949 960 'haveparents': {
950 961 'type': 'bool',
951 962 'default': lambda: False,
952 963 'example': True,
953 964 },
954 965 'fields': {
955 966 'type': 'set',
956 967 'default': set,
957 968 'example': {b'parents', b'revision'},
958 969 },
959 970 'tree': {
960 971 'type': 'bytes',
961 972 'required': True,
962 973 'example': b'',
963 974 },
964 975 },
965 976 permission='pull')
966 977 def manifestdata(repo, proto, haveparents, nodes, fields, tree):
967 978 store = repo.manifestlog.getstorage(tree)
968 979
969 980 # Validate the node is known and abort on unknown revisions.
970 981 for node in nodes:
971 982 try:
972 983 store.rev(node)
973 984 except error.LookupError:
974 985 raise error.WireprotoCommandError(
975 986 'unknown node: %s', (node,))
976 987
977 988 revs, requests = builddeltarequests(store, nodes, haveparents)
978 989
979 990 yield {
980 991 b'totalitems': len(revs),
981 992 }
982 993
983 994 if b'revision' in fields:
984 995 deltas = store.emitrevisiondeltas(requests)
985 996 else:
986 997 deltas = None
987 998
988 999 for rev in revs:
989 1000 node = store.node(rev)
990 1001
991 1002 if deltas is not None:
992 1003 delta = next(deltas)
993 1004 else:
994 1005 delta = None
995 1006
996 1007 d = {
997 1008 b'node': node,
998 1009 }
999 1010
1000 1011 if b'parents' in fields:
1001 1012 d[b'parents'] = store.parents(node)
1002 1013
1003 1014 if b'revision' in fields:
1004 1015 assert delta is not None
1005 1016 assert delta.flags == 0
1006 1017 assert d[b'node'] == delta.node
1007 1018
1008 1019 if delta.revision is not None:
1009 1020 revisiondata = delta.revision
1010 1021 d[b'revisionsize'] = len(revisiondata)
1011 1022 else:
1012 1023 d[b'deltabasenode'] = delta.basenode
1013 1024 revisiondata = delta.delta
1014 1025 d[b'deltasize'] = len(revisiondata)
1015 1026 else:
1016 1027 revisiondata = None
1017 1028
1018 1029 yield d
1019 1030
1020 1031 if revisiondata is not None:
1021 1032 yield revisiondata
1022 1033
1023 1034 if deltas is not None:
1024 1035 try:
1025 1036 next(deltas)
1026 1037 raise error.ProgrammingError('should not have more deltas')
1027 1038 except GeneratorExit:
1028 1039 pass
1029 1040
1030 1041 @wireprotocommand(
1031 1042 'pushkey',
1032 1043 args={
1033 1044 'namespace': {
1034 1045 'type': 'bytes',
1035 1046 'required': True,
1036 1047 'example': b'ns',
1037 1048 },
1038 1049 'key': {
1039 1050 'type': 'bytes',
1040 1051 'required': True,
1041 1052 'example': b'key',
1042 1053 },
1043 1054 'old': {
1044 1055 'type': 'bytes',
1045 1056 'required': True,
1046 1057 'example': b'old',
1047 1058 },
1048 1059 'new': {
1049 1060 'type': 'bytes',
1050 1061 'required': True,
1051 1062 'example': 'new',
1052 1063 },
1053 1064 },
1054 1065 permission='push')
1055 1066 def pushkeyv2(repo, proto, namespace, key, old, new):
1056 1067 # TODO handle ui output redirection
1057 1068 yield repo.pushkey(encoding.tolocal(namespace),
1058 1069 encoding.tolocal(key),
1059 1070 encoding.tolocal(old),
1060 1071 encoding.tolocal(new))
@@ -1,749 +1,749 b''
1 1 #require no-chg
2 2
3 3 $ . $TESTDIR/wireprotohelpers.sh
4 4
5 5 $ cat >> $HGRCPATH << EOF
6 6 > [web]
7 7 > push_ssl = false
8 8 > allow_push = *
9 9 > EOF
10 10
11 11 $ hg init server
12 12 $ cd server
13 13 $ touch a
14 14 $ hg -q commit -A -m initial
15 15 $ cd ..
16 16
17 17 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
18 18 $ cat hg.pid >> $DAEMON_PIDS
19 19
20 20 compression formats are advertised in compression capability
21 21
22 22 #if zstd
23 23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
24 24 #else
25 25 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
26 26 #endif
27 27
28 28 $ killdaemons.py
29 29
30 30 server.compressionengines can replace engines list wholesale
31 31
32 32 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
33 33 $ cat hg.pid > $DAEMON_PIDS
34 34 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
35 35
36 36 $ killdaemons.py
37 37
38 38 Order of engines can also change
39 39
40 40 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
41 41 $ cat hg.pid > $DAEMON_PIDS
42 42 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
43 43
44 44 $ killdaemons.py
45 45
46 46 Start a default server again
47 47
48 48 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
49 49 $ cat hg.pid > $DAEMON_PIDS
50 50
51 51 Server should send application/mercurial-0.1 to clients if no Accept is used
52 52
53 53 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
54 54 200 Script output follows
55 55 content-type: application/mercurial-0.1
56 56 date: $HTTP_DATE$
57 57 server: testing stub value
58 58 transfer-encoding: chunked
59 59
60 60 Server should send application/mercurial-0.1 when client says it wants it
61 61
62 62 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
63 63 200 Script output follows
64 64 content-type: application/mercurial-0.1
65 65 date: $HTTP_DATE$
66 66 server: testing stub value
67 67 transfer-encoding: chunked
68 68
69 69 Server should send application/mercurial-0.2 when client says it wants it
70 70
71 71 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
72 72 200 Script output follows
73 73 content-type: application/mercurial-0.2
74 74 date: $HTTP_DATE$
75 75 server: testing stub value
76 76 transfer-encoding: chunked
77 77
78 78 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
79 79 200 Script output follows
80 80 content-type: application/mercurial-0.2
81 81 date: $HTTP_DATE$
82 82 server: testing stub value
83 83 transfer-encoding: chunked
84 84
85 85 Requesting a compression format that server doesn't support results will fall back to 0.1
86 86
87 87 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
88 88 200 Script output follows
89 89 content-type: application/mercurial-0.1
90 90 date: $HTTP_DATE$
91 91 server: testing stub value
92 92 transfer-encoding: chunked
93 93
94 94 #if zstd
95 95 zstd is used if available
96 96
97 97 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
98 98 $ f --size --hexdump --bytes 36 --sha1 resp
99 99 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
100 100 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
101 101 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
102 102 0020: 28 b5 2f fd |(./.|
103 103
104 104 #endif
105 105
106 106 application/mercurial-0.2 is not yet used on non-streaming responses
107 107
108 108 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
109 109 200 Script output follows
110 110 content-length: 41
111 111 content-type: application/mercurial-0.1
112 112 date: $HTTP_DATE$
113 113 server: testing stub value
114 114
115 115 e93700bd72895c5addab234c56d4024b487a362f
116 116
117 117 Now test protocol preference usage
118 118
119 119 $ killdaemons.py
120 120 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
121 121 $ cat hg.pid > $DAEMON_PIDS
122 122
123 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 125 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
126 126 200 Script output follows
127 127 content-type: application/mercurial-0.1
128 128
129 129 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
130 130 $ f --size --hexdump --bytes 28 --sha1 resp
131 131 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
132 132 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
133 133 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
134 134
135 135 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
136 136
137 137 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
138 138 $ f --size --hexdump --bytes 28 --sha1 resp
139 139 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
140 140 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
141 141 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
142 142
143 143 0.2 with no compression will get "none" because that is server's preference
144 144 (spec says ZL and UN are implicitly supported)
145 145
146 146 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
147 147 $ f --size --hexdump --bytes 32 --sha1 resp
148 148 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
149 149 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
150 150 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
151 151
152 152 Client receives server preference even if local order doesn't match
153 153
154 154 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
155 155 $ f --size --hexdump --bytes 32 --sha1 resp
156 156 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
157 157 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
158 158 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
159 159
160 160 Client receives only supported format even if not server preferred format
161 161
162 162 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
163 163 $ f --size --hexdump --bytes 33 --sha1 resp
164 164 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
165 165 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
166 166 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
167 167 0020: 78 |x|
168 168
169 169 $ killdaemons.py
170 170 $ cd ..
171 171
172 172 Test listkeys for listing namespaces
173 173
174 174 $ hg init empty
175 175 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
176 176 $ cat hg.pid > $DAEMON_PIDS
177 177
178 178 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
179 179 > command listkeys
180 180 > namespace namespaces
181 181 > EOF
182 182 s> GET /?cmd=capabilities HTTP/1.1\r\n
183 183 s> Accept-Encoding: identity\r\n
184 184 s> accept: application/mercurial-0.1\r\n
185 185 s> host: $LOCALIP:$HGPORT\r\n (glob)
186 186 s> user-agent: Mercurial debugwireproto\r\n
187 187 s> \r\n
188 188 s> makefile('rb', None)
189 189 s> HTTP/1.1 200 Script output follows\r\n
190 190 s> Server: testing stub value\r\n
191 191 s> Date: $HTTP_DATE$\r\n
192 192 s> Content-Type: application/mercurial-0.1\r\n
193 193 s> Content-Length: *\r\n (glob)
194 194 s> \r\n
195 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 196 sending listkeys command
197 197 s> GET /?cmd=listkeys HTTP/1.1\r\n
198 198 s> Accept-Encoding: identity\r\n
199 199 s> vary: X-HgArg-1,X-HgProto-1\r\n
200 200 s> x-hgarg-1: namespace=namespaces\r\n
201 201 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
202 202 s> accept: application/mercurial-0.1\r\n
203 203 s> host: $LOCALIP:$HGPORT\r\n (glob)
204 204 s> user-agent: Mercurial debugwireproto\r\n
205 205 s> \r\n
206 206 s> makefile('rb', None)
207 207 s> HTTP/1.1 200 Script output follows\r\n
208 208 s> Server: testing stub value\r\n
209 209 s> Date: $HTTP_DATE$\r\n
210 210 s> Content-Type: application/mercurial-0.1\r\n
211 211 s> Content-Length: 30\r\n
212 212 s> \r\n
213 213 s> bookmarks\t\n
214 214 s> namespaces\t\n
215 215 s> phases\t
216 216 response: {
217 217 b'bookmarks': b'',
218 218 b'namespaces': b'',
219 219 b'phases': b''
220 220 }
221 221
222 222 Same thing, but with "httprequest" command
223 223
224 224 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
225 225 > httprequest GET ?cmd=listkeys
226 226 > user-agent: test
227 227 > x-hgarg-1: namespace=namespaces
228 228 > EOF
229 229 using raw connection to peer
230 230 s> GET /?cmd=listkeys HTTP/1.1\r\n
231 231 s> Accept-Encoding: identity\r\n
232 232 s> user-agent: test\r\n
233 233 s> x-hgarg-1: namespace=namespaces\r\n
234 234 s> host: $LOCALIP:$HGPORT\r\n (glob)
235 235 s> \r\n
236 236 s> makefile('rb', None)
237 237 s> HTTP/1.1 200 Script output follows\r\n
238 238 s> Server: testing stub value\r\n
239 239 s> Date: $HTTP_DATE$\r\n
240 240 s> Content-Type: application/mercurial-0.1\r\n
241 241 s> Content-Length: 30\r\n
242 242 s> \r\n
243 243 s> bookmarks\t\n
244 244 s> namespaces\t\n
245 245 s> phases\t
246 246
247 247 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
248 248
249 249 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
250 250 > command heads
251 251 > EOF
252 252 s> GET /?cmd=capabilities HTTP/1.1\r\n
253 253 s> Accept-Encoding: identity\r\n
254 254 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
255 255 s> x-hgproto-1: cbor\r\n
256 256 s> x-hgupgrade-1: exp-http-v2-0001\r\n
257 257 s> accept: application/mercurial-0.1\r\n
258 258 s> host: $LOCALIP:$HGPORT\r\n (glob)
259 259 s> user-agent: Mercurial debugwireproto\r\n
260 260 s> \r\n
261 261 s> makefile('rb', None)
262 262 s> HTTP/1.1 200 Script output follows\r\n
263 263 s> Server: testing stub value\r\n
264 264 s> Date: $HTTP_DATE$\r\n
265 265 s> Content-Type: application/mercurial-0.1\r\n
266 266 s> Content-Length: *\r\n (glob)
267 267 s> \r\n
268 268 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 269 sending heads command
270 270 s> GET /?cmd=heads HTTP/1.1\r\n
271 271 s> Accept-Encoding: identity\r\n
272 272 s> vary: X-HgProto-1\r\n
273 273 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
274 274 s> accept: application/mercurial-0.1\r\n
275 275 s> host: $LOCALIP:$HGPORT\r\n (glob)
276 276 s> user-agent: Mercurial debugwireproto\r\n
277 277 s> \r\n
278 278 s> makefile('rb', None)
279 279 s> HTTP/1.1 200 Script output follows\r\n
280 280 s> Server: testing stub value\r\n
281 281 s> Date: $HTTP_DATE$\r\n
282 282 s> Content-Type: application/mercurial-0.1\r\n
283 283 s> Content-Length: 41\r\n
284 284 s> \r\n
285 285 s> 0000000000000000000000000000000000000000\n
286 286 response: [
287 287 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
288 288 ]
289 289
290 290 $ killdaemons.py
291 291 $ enablehttpv2 empty
292 292 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
293 293 $ cat hg.pid > $DAEMON_PIDS
294 294
295 295 Client with HTTPv2 enabled automatically upgrades if the server supports it
296 296
297 297 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
298 298 > command heads
299 299 > EOF
300 300 s> GET /?cmd=capabilities HTTP/1.1\r\n
301 301 s> Accept-Encoding: identity\r\n
302 302 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
303 303 s> x-hgproto-1: cbor\r\n
304 304 s> x-hgupgrade-1: exp-http-v2-0001\r\n
305 305 s> accept: application/mercurial-0.1\r\n
306 306 s> host: $LOCALIP:$HGPORT\r\n (glob)
307 307 s> user-agent: Mercurial debugwireproto\r\n
308 308 s> \r\n
309 309 s> makefile('rb', None)
310 310 s> HTTP/1.1 200 OK\r\n
311 311 s> Server: testing stub value\r\n
312 312 s> Date: $HTTP_DATE$\r\n
313 313 s> Content-Type: application/mercurial-cbor\r\n
314 314 s> Content-Length: *\r\n (glob)
315 315 s> \r\n
316 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa3Ffields\xd9\x01\x02\x82GparentsHrevisionInoderange\x82\x81J0123456...\x81Iabcdef...Enodes\x81J0123456...Kpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xf5Enodes\x81J0123456...DpathGfoo.txtKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xf5Enodes\x81J0123456...Dtree@Kpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\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
316 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa3Ffields\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKhaveparents\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\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\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
317 317 sending heads command
318 318 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
319 319 s> Accept-Encoding: identity\r\n
320 320 s> accept: application/mercurial-exp-framing-0005\r\n
321 321 s> content-type: application/mercurial-exp-framing-0005\r\n
322 322 s> content-length: 20\r\n
323 323 s> host: $LOCALIP:$HGPORT\r\n (glob)
324 324 s> user-agent: Mercurial debugwireproto\r\n
325 325 s> \r\n
326 326 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
327 327 s> makefile('rb', None)
328 328 s> HTTP/1.1 200 OK\r\n
329 329 s> Server: testing stub value\r\n
330 330 s> Date: $HTTP_DATE$\r\n
331 331 s> Content-Type: application/mercurial-exp-framing-0005\r\n
332 332 s> Transfer-Encoding: chunked\r\n
333 333 s> \r\n
334 334 s> 13\r\n
335 335 s> \x0b\x00\x00\x01\x00\x02\x011
336 336 s> \xa1FstatusBok
337 337 s> \r\n
338 338 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
339 339 s> 1e\r\n
340 340 s> \x16\x00\x00\x01\x00\x02\x001
341 341 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
342 342 s> \r\n
343 343 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
344 344 s> 8\r\n
345 345 s> \x00\x00\x00\x01\x00\x02\x002
346 346 s> \r\n
347 347 s> 0\r\n
348 348 s> \r\n
349 349 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
350 350 response: [
351 351 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
352 352 ]
353 353
354 354 $ killdaemons.py
355 355
356 356 HTTP client follows HTTP redirect on handshake to new repo
357 357
358 358 $ cd $TESTTMP
359 359
360 360 $ hg init redirector
361 361 $ hg init redirected
362 362 $ cd redirected
363 363 $ touch foo
364 364 $ hg -q commit -A -m initial
365 365 $ cd ..
366 366
367 367 $ cat > paths.conf << EOF
368 368 > [paths]
369 369 > / = $TESTTMP/*
370 370 > EOF
371 371
372 372 $ cat > redirectext.py << EOF
373 373 > from mercurial import extensions, wireprotoserver
374 374 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
375 375 > path = req.advertisedurl[len(req.advertisedbaseurl):]
376 376 > if not path.startswith(b'/redirector'):
377 377 > return orig(repo, req, res, proto, cmd)
378 378 > relpath = path[len(b'/redirector'):]
379 379 > res.status = b'301 Redirect'
380 380 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
381 381 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
382 382 > newurl = newurl[0:newurl.index(b'?')]
383 383 > res.headers[b'Location'] = newurl
384 384 > res.headers[b'Content-Type'] = b'text/plain'
385 385 > res.setbodybytes(b'redirected')
386 386 > return True
387 387 >
388 388 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
389 389 > EOF
390 390
391 391 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
392 392 > --config server.compressionengines=zlib \
393 393 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
394 394 $ cat hg.pid > $DAEMON_PIDS
395 395
396 396 Verify our HTTP 301 is served properly
397 397
398 398 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
399 399 > httprequest GET /redirector?cmd=capabilities
400 400 > user-agent: test
401 401 > EOF
402 402 using raw connection to peer
403 403 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
404 404 s> Accept-Encoding: identity\r\n
405 405 s> user-agent: test\r\n
406 406 s> host: $LOCALIP:$HGPORT\r\n (glob)
407 407 s> \r\n
408 408 s> makefile('rb', None)
409 409 s> HTTP/1.1 301 Redirect\r\n
410 410 s> Server: testing stub value\r\n
411 411 s> Date: $HTTP_DATE$\r\n
412 412 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
413 413 s> Content-Type: text/plain\r\n
414 414 s> Content-Length: 10\r\n
415 415 s> \r\n
416 416 s> redirected
417 417 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
418 418 s> Accept-Encoding: identity\r\n
419 419 s> user-agent: test\r\n
420 420 s> host: $LOCALIP:$HGPORT\r\n (glob)
421 421 s> \r\n
422 422 s> makefile('rb', None)
423 423 s> HTTP/1.1 200 Script output follows\r\n
424 424 s> Server: testing stub value\r\n
425 425 s> Date: $HTTP_DATE$\r\n
426 426 s> Content-Type: application/mercurial-0.1\r\n
427 427 s> Content-Length: 467\r\n
428 428 s> \r\n
429 429 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
430 430
431 431 Test with the HTTP peer
432 432
433 433 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
434 434 > command heads
435 435 > EOF
436 436 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
437 437 s> Accept-Encoding: identity\r\n
438 438 s> accept: application/mercurial-0.1\r\n
439 439 s> host: $LOCALIP:$HGPORT\r\n (glob)
440 440 s> user-agent: Mercurial debugwireproto\r\n
441 441 s> \r\n
442 442 s> makefile('rb', None)
443 443 s> HTTP/1.1 301 Redirect\r\n
444 444 s> Server: testing stub value\r\n
445 445 s> Date: $HTTP_DATE$\r\n
446 446 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
447 447 s> Content-Type: text/plain\r\n
448 448 s> Content-Length: 10\r\n
449 449 s> \r\n
450 450 s> redirected
451 451 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
452 452 s> Accept-Encoding: identity\r\n
453 453 s> accept: application/mercurial-0.1\r\n
454 454 s> host: $LOCALIP:$HGPORT\r\n (glob)
455 455 s> user-agent: Mercurial debugwireproto\r\n
456 456 s> \r\n
457 457 s> makefile('rb', None)
458 458 s> HTTP/1.1 200 Script output follows\r\n
459 459 s> Server: testing stub value\r\n
460 460 s> Date: $HTTP_DATE$\r\n
461 461 s> Content-Type: application/mercurial-0.1\r\n
462 462 s> Content-Length: 467\r\n
463 463 s> \r\n
464 464 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
465 465 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
466 466 sending heads command
467 467 s> GET /redirected?cmd=heads HTTP/1.1\r\n
468 468 s> Accept-Encoding: identity\r\n
469 469 s> vary: X-HgProto-1\r\n
470 470 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
471 471 s> accept: application/mercurial-0.1\r\n
472 472 s> host: $LOCALIP:$HGPORT\r\n (glob)
473 473 s> user-agent: Mercurial debugwireproto\r\n
474 474 s> \r\n
475 475 s> makefile('rb', None)
476 476 s> HTTP/1.1 200 Script output follows\r\n
477 477 s> Server: testing stub value\r\n
478 478 s> Date: $HTTP_DATE$\r\n
479 479 s> Content-Type: application/mercurial-0.1\r\n
480 480 s> Content-Length: 41\r\n
481 481 s> \r\n
482 482 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
483 483 response: [
484 484 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
485 485 ]
486 486
487 487 $ killdaemons.py
488 488
489 489 Now test a variation where we strip the query string from the redirect URL.
490 490 (SCM Manager apparently did this and clients would recover from it)
491 491
492 492 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
493 493 > --config server.compressionengines=zlib \
494 494 > --config testing.redirectqs=false \
495 495 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
496 496 $ cat hg.pid > $DAEMON_PIDS
497 497
498 498 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
499 499 > httprequest GET /redirector?cmd=capabilities
500 500 > user-agent: test
501 501 > EOF
502 502 using raw connection to peer
503 503 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
504 504 s> Accept-Encoding: identity\r\n
505 505 s> user-agent: test\r\n
506 506 s> host: $LOCALIP:$HGPORT\r\n (glob)
507 507 s> \r\n
508 508 s> makefile('rb', None)
509 509 s> HTTP/1.1 301 Redirect\r\n
510 510 s> Server: testing stub value\r\n
511 511 s> Date: $HTTP_DATE$\r\n
512 512 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
513 513 s> Content-Type: text/plain\r\n
514 514 s> Content-Length: 10\r\n
515 515 s> \r\n
516 516 s> redirected
517 517 s> GET /redirected HTTP/1.1\r\n
518 518 s> Accept-Encoding: identity\r\n
519 519 s> user-agent: test\r\n
520 520 s> host: $LOCALIP:$HGPORT\r\n (glob)
521 521 s> \r\n
522 522 s> makefile('rb', None)
523 523 s> HTTP/1.1 200 Script output follows\r\n
524 524 s> Server: testing stub value\r\n
525 525 s> Date: $HTTP_DATE$\r\n
526 526 s> ETag: W/"*"\r\n (glob)
527 527 s> Content-Type: text/html; charset=ascii\r\n
528 528 s> Transfer-Encoding: chunked\r\n
529 529 s> \r\n
530 530 s> 414\r\n
531 531 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
532 532 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
533 533 s> <head>\n
534 534 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
535 535 s> <meta name="robots" content="index, nofollow" />\n
536 536 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
537 537 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
538 538 s> \n
539 539 s> <title>redirected: log</title>\n
540 540 s> <link rel="alternate" type="application/atom+xml"\n
541 541 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
542 542 s> <link rel="alternate" type="application/rss+xml"\n
543 543 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
544 544 s> </head>\n
545 545 s> <body>\n
546 546 s> \n
547 547 s> <div class="container">\n
548 548 s> <div class="menu">\n
549 549 s> <div class="logo">\n
550 550 s> <a href="https://mercurial-scm.org/">\n
551 551 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
552 552 s> </div>\n
553 553 s> <ul>\n
554 554 s> <li class="active">log</li>\n
555 555 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
556 556 s> <li><a href="/redirected/tags">tags</a></li>\n
557 557 s> <li><a href="
558 558 s> \r\n
559 559 s> 810\r\n
560 560 s> /redirected/bookmarks">bookmarks</a></li>\n
561 561 s> <li><a href="/redirected/branches">branches</a></li>\n
562 562 s> </ul>\n
563 563 s> <ul>\n
564 564 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
565 565 s> <li><a href="/redirected/file/tip">browse</a></li>\n
566 566 s> </ul>\n
567 567 s> <ul>\n
568 568 s> \n
569 569 s> </ul>\n
570 570 s> <ul>\n
571 571 s> <li><a href="/redirected/help">help</a></li>\n
572 572 s> </ul>\n
573 573 s> <div class="atom-logo">\n
574 574 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
575 575 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
576 576 s> </a>\n
577 577 s> </div>\n
578 578 s> </div>\n
579 579 s> \n
580 580 s> <div class="main">\n
581 581 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
582 582 s> <h3>log</h3>\n
583 583 s> \n
584 584 s> \n
585 585 s> <form class="search" action="/redirected/log">\n
586 586 s> \n
587 587 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
588 588 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
589 589 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
590 590 s> </form>\n
591 591 s> \n
592 592 s> <div class="navigate">\n
593 593 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
594 594 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
595 595 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
596 596 s> </div>\n
597 597 s> \n
598 598 s> <table class="bigtable">\n
599 599 s> <thead>\n
600 600 s> <tr>\n
601 601 s> <th class="age">age</th>\n
602 602 s> <th class="author">author</th>\n
603 603 s> <th class="description">description</th>\n
604 604 s> </tr>\n
605 605 s> </thead>\n
606 606 s> <tbody class="stripes2">\n
607 607 s> <tr>\n
608 608 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
609 609 s> <td class="author">test</td>\n
610 610 s> <td class="description">\n
611 611 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
612 612 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
613 613 s> </td>\n
614 614 s> </tr>\n
615 615 s> \n
616 616 s> </tbody>\n
617 617 s> </table>\n
618 618 s> \n
619 619 s> <div class="navigate">\n
620 620 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
621 621 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
622 622 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
623 623 s> </div>\n
624 624 s> \n
625 625 s> <script type="text/javascript">\n
626 626 s> ajaxScrollInit(\n
627 627 s> \'/redirected/shortlog/%next%\',\n
628 628 s> \'\', <!-- NEXTHASH\n
629 629 s> function (htmlText) {
630 630 s> \r\n
631 631 s> 14a\r\n
632 632 s> \n
633 633 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
634 634 s> return m ? m[1] : null;\n
635 635 s> },\n
636 636 s> \'.bigtable > tbody\',\n
637 637 s> \'<tr class="%class%">\\\n
638 638 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
639 639 s> </tr>\'\n
640 640 s> );\n
641 641 s> </script>\n
642 642 s> \n
643 643 s> </div>\n
644 644 s> </div>\n
645 645 s> \n
646 646 s> \n
647 647 s> \n
648 648 s> </body>\n
649 649 s> </html>\n
650 650 s> \n
651 651 s> \r\n
652 652 s> 0\r\n
653 653 s> \r\n
654 654
655 655 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
656 656 > command heads
657 657 > EOF
658 658 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
659 659 s> Accept-Encoding: identity\r\n
660 660 s> accept: application/mercurial-0.1\r\n
661 661 s> host: $LOCALIP:$HGPORT\r\n (glob)
662 662 s> user-agent: Mercurial debugwireproto\r\n
663 663 s> \r\n
664 664 s> makefile('rb', None)
665 665 s> HTTP/1.1 301 Redirect\r\n
666 666 s> Server: testing stub value\r\n
667 667 s> Date: $HTTP_DATE$\r\n
668 668 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
669 669 s> Content-Type: text/plain\r\n
670 670 s> Content-Length: 10\r\n
671 671 s> \r\n
672 672 s> redirected
673 673 s> GET /redirected HTTP/1.1\r\n
674 674 s> Accept-Encoding: identity\r\n
675 675 s> accept: application/mercurial-0.1\r\n
676 676 s> host: $LOCALIP:$HGPORT\r\n (glob)
677 677 s> user-agent: Mercurial debugwireproto\r\n
678 678 s> \r\n
679 679 s> makefile('rb', None)
680 680 s> HTTP/1.1 200 Script output follows\r\n
681 681 s> Server: testing stub value\r\n
682 682 s> Date: $HTTP_DATE$\r\n
683 683 s> ETag: W/"*"\r\n (glob)
684 684 s> Content-Type: text/html; charset=ascii\r\n
685 685 s> Transfer-Encoding: chunked\r\n
686 686 s> \r\n
687 687 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
688 688 s> 414\r\n
689 689 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
690 690 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
691 691 s> <head>\n
692 692 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
693 693 s> <meta name="robots" content="index, nofollow" />\n
694 694 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
695 695 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
696 696 s> \n
697 697 s> <title>redirected: log</title>\n
698 698 s> <link rel="alternate" type="application/atom+xml"\n
699 699 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
700 700 s> <link rel="alternate" type="application/rss+xml"\n
701 701 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
702 702 s> </head>\n
703 703 s> <body>\n
704 704 s> \n
705 705 s> <div class="container">\n
706 706 s> <div class="menu">\n
707 707 s> <div class="logo">\n
708 708 s> <a href="https://mercurial-scm.org/">\n
709 709 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
710 710 s> </div>\n
711 711 s> <ul>\n
712 712 s> <li class="active">log</li>\n
713 713 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
714 714 s> <li><a href="/redirected/tags">tags</a
715 715 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
716 716 s> Accept-Encoding: identity\r\n
717 717 s> accept: application/mercurial-0.1\r\n
718 718 s> host: $LOCALIP:$HGPORT\r\n (glob)
719 719 s> user-agent: Mercurial debugwireproto\r\n
720 720 s> \r\n
721 721 s> makefile('rb', None)
722 722 s> HTTP/1.1 200 Script output follows\r\n
723 723 s> Server: testing stub value\r\n
724 724 s> Date: $HTTP_DATE$\r\n
725 725 s> Content-Type: application/mercurial-0.1\r\n
726 726 s> Content-Length: 467\r\n
727 727 s> \r\n
728 728 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
729 729 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
730 730 sending heads command
731 731 s> GET /redirected?cmd=heads HTTP/1.1\r\n
732 732 s> Accept-Encoding: identity\r\n
733 733 s> vary: X-HgProto-1\r\n
734 734 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
735 735 s> accept: application/mercurial-0.1\r\n
736 736 s> host: $LOCALIP:$HGPORT\r\n (glob)
737 737 s> user-agent: Mercurial debugwireproto\r\n
738 738 s> \r\n
739 739 s> makefile('rb', None)
740 740 s> HTTP/1.1 200 Script output follows\r\n
741 741 s> Server: testing stub value\r\n
742 742 s> Date: $HTTP_DATE$\r\n
743 743 s> Content-Type: application/mercurial-0.1\r\n
744 744 s> Content-Length: 41\r\n
745 745 s> \r\n
746 746 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
747 747 response: [
748 748 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
749 749 ]
@@ -1,544 +1,628 b''
1 1 #require no-chg
2 2
3 3 $ . $TESTDIR/wireprotohelpers.sh
4 4
5 5 $ hg init server
6 6
7 7 zstd isn't present in plain builds. Make tests easier by removing
8 8 zstd from the equation.
9 9
10 10 $ cat >> server/.hg/hgrc << EOF
11 11 > [server]
12 12 > compressionengines = zlib
13 13 > EOF
14 14
15 15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
16 16 $ cat hg.pid > $DAEMON_PIDS
17 17
18 18 A normal capabilities request is serviced for version 1
19 19
20 20 $ sendhttpraw << EOF
21 21 > httprequest GET ?cmd=capabilities
22 22 > user-agent: test
23 23 > EOF
24 24 using raw connection to peer
25 25 s> GET /?cmd=capabilities HTTP/1.1\r\n
26 26 s> Accept-Encoding: identity\r\n
27 27 s> user-agent: test\r\n
28 28 s> host: $LOCALIP:$HGPORT\r\n (glob)
29 29 s> \r\n
30 30 s> makefile('rb', None)
31 31 s> HTTP/1.1 200 Script output follows\r\n
32 32 s> Server: testing stub value\r\n
33 33 s> Date: $HTTP_DATE$\r\n
34 34 s> Content-Type: application/mercurial-0.1\r\n
35 35 s> Content-Length: *\r\n (glob)
36 36 s> \r\n
37 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 39 A proper request without the API server enabled returns the legacy response
40 40
41 41 $ sendhttpraw << EOF
42 42 > httprequest GET ?cmd=capabilities
43 43 > user-agent: test
44 44 > x-hgupgrade-1: foo
45 45 > x-hgproto-1: cbor
46 46 > EOF
47 47 using raw connection to peer
48 48 s> GET /?cmd=capabilities HTTP/1.1\r\n
49 49 s> Accept-Encoding: identity\r\n
50 50 s> user-agent: test\r\n
51 51 s> x-hgproto-1: cbor\r\n
52 52 s> x-hgupgrade-1: foo\r\n
53 53 s> host: $LOCALIP:$HGPORT\r\n (glob)
54 54 s> \r\n
55 55 s> makefile('rb', None)
56 56 s> HTTP/1.1 200 Script output follows\r\n
57 57 s> Server: testing stub value\r\n
58 58 s> Date: $HTTP_DATE$\r\n
59 59 s> Content-Type: application/mercurial-0.1\r\n
60 60 s> Content-Length: *\r\n (glob)
61 61 s> \r\n
62 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 64 Restart with just API server enabled. This enables serving the new format.
65 65
66 66 $ killdaemons.py
67 67 $ cat error.log
68 68
69 69 $ cat >> server/.hg/hgrc << EOF
70 70 > [experimental]
71 71 > web.apiserver = true
72 72 > EOF
73 73
74 74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
75 75 $ cat hg.pid > $DAEMON_PIDS
76 76
77 77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
78 78
79 79 $ sendhttpraw << EOF
80 80 > httprequest GET ?cmd=capabilities
81 81 > user-agent: test
82 82 > x-hgupgrade-1: foo bar
83 83 > EOF
84 84 using raw connection to peer
85 85 s> GET /?cmd=capabilities HTTP/1.1\r\n
86 86 s> Accept-Encoding: identity\r\n
87 87 s> user-agent: test\r\n
88 88 s> x-hgupgrade-1: foo bar\r\n
89 89 s> host: $LOCALIP:$HGPORT\r\n (glob)
90 90 s> \r\n
91 91 s> makefile('rb', None)
92 92 s> HTTP/1.1 200 Script output follows\r\n
93 93 s> Server: testing stub value\r\n
94 94 s> Date: $HTTP_DATE$\r\n
95 95 s> Content-Type: application/mercurial-0.1\r\n
96 96 s> Content-Length: *\r\n (glob)
97 97 s> \r\n
98 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 100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
101 101
102 102 $ sendhttpraw << EOF
103 103 > httprequest GET ?cmd=capabilities
104 104 > user-agent: test
105 105 > x-hgupgrade-1: foo bar
106 106 > x-hgproto-1: some value
107 107 > EOF
108 108 using raw connection to peer
109 109 s> GET /?cmd=capabilities HTTP/1.1\r\n
110 110 s> Accept-Encoding: identity\r\n
111 111 s> user-agent: test\r\n
112 112 s> x-hgproto-1: some value\r\n
113 113 s> x-hgupgrade-1: foo bar\r\n
114 114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 115 s> \r\n
116 116 s> makefile('rb', None)
117 117 s> HTTP/1.1 200 Script output follows\r\n
118 118 s> Server: testing stub value\r\n
119 119 s> Date: $HTTP_DATE$\r\n
120 120 s> Content-Type: application/mercurial-0.1\r\n
121 121 s> Content-Length: *\r\n (glob)
122 122 s> \r\n
123 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 125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
126 126
127 127 $ sendhttpraw << EOF
128 128 > httprequest GET ?cmd=capabilities
129 129 > user-agent: test
130 130 > x-hgupgrade-1: foo bar
131 131 > x-hgproto-1: cbor
132 132 > EOF
133 133 using raw connection to peer
134 134 s> GET /?cmd=capabilities HTTP/1.1\r\n
135 135 s> Accept-Encoding: identity\r\n
136 136 s> user-agent: test\r\n
137 137 s> x-hgproto-1: cbor\r\n
138 138 s> x-hgupgrade-1: foo bar\r\n
139 139 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 140 s> \r\n
141 141 s> makefile('rb', None)
142 142 s> HTTP/1.1 200 OK\r\n
143 143 s> Server: testing stub value\r\n
144 144 s> Date: $HTTP_DATE$\r\n
145 145 s> Content-Type: application/mercurial-cbor\r\n
146 146 s> Content-Length: *\r\n (glob)
147 147 s> \r\n
148 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 149 cbor> {
150 150 b'apibase': b'api/',
151 151 b'apis': {},
152 152 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 153 }
154 154
155 155 Restart server to enable HTTPv2
156 156
157 157 $ killdaemons.py
158 158 $ enablehttpv2 server
159 159 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
160 160 $ cat hg.pid > $DAEMON_PIDS
161 161
162 162 Only requested API services are returned
163 163
164 164 $ sendhttpraw << EOF
165 165 > httprequest GET ?cmd=capabilities
166 166 > user-agent: test
167 167 > x-hgupgrade-1: foo bar
168 168 > x-hgproto-1: cbor
169 169 > EOF
170 170 using raw connection to peer
171 171 s> GET /?cmd=capabilities HTTP/1.1\r\n
172 172 s> Accept-Encoding: identity\r\n
173 173 s> user-agent: test\r\n
174 174 s> x-hgproto-1: cbor\r\n
175 175 s> x-hgupgrade-1: foo bar\r\n
176 176 s> host: $LOCALIP:$HGPORT\r\n (glob)
177 177 s> \r\n
178 178 s> makefile('rb', None)
179 179 s> HTTP/1.1 200 OK\r\n
180 180 s> Server: testing stub value\r\n
181 181 s> Date: $HTTP_DATE$\r\n
182 182 s> Content-Type: application/mercurial-cbor\r\n
183 183 s> Content-Length: *\r\n (glob)
184 184 s> \r\n
185 185 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
186 186 cbor> {
187 187 b'apibase': b'api/',
188 188 b'apis': {},
189 189 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'
190 190 }
191 191
192 192 Request for HTTPv2 service returns information about it
193 193
194 194 $ sendhttpraw << EOF
195 195 > httprequest GET ?cmd=capabilities
196 196 > user-agent: test
197 197 > x-hgupgrade-1: exp-http-v2-0001 foo bar
198 198 > x-hgproto-1: cbor
199 199 > EOF
200 200 using raw connection to peer
201 201 s> GET /?cmd=capabilities HTTP/1.1\r\n
202 202 s> Accept-Encoding: identity\r\n
203 203 s> user-agent: test\r\n
204 204 s> x-hgproto-1: cbor\r\n
205 205 s> x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
206 206 s> host: $LOCALIP:$HGPORT\r\n (glob)
207 207 s> \r\n
208 208 s> makefile('rb', None)
209 209 s> HTTP/1.1 200 OK\r\n
210 210 s> Server: testing stub value\r\n
211 211 s> Date: $HTTP_DATE$\r\n
212 212 s> Content-Type: application/mercurial-cbor\r\n
213 213 s> Content-Length: *\r\n (glob)
214 214 s> \r\n
215 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa3Ffields\xd9\x01\x02\x82GparentsHrevisionInoderange\x82\x81J0123456...\x81Iabcdef...Enodes\x81J0123456...Kpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xf5Enodes\x81J0123456...DpathGfoo.txtKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xf5Enodes\x81J0123456...Dtree@Kpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\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
215 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa3Ffields\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKhaveparents\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\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\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
216 216 cbor> {
217 217 b'apibase': b'api/',
218 218 b'apis': {
219 219 b'exp-http-v2-0001': {
220 220 b'commands': {
221 221 b'branchmap': {
222 222 b'args': {},
223 223 b'permissions': [
224 224 b'pull'
225 225 ]
226 226 },
227 227 b'capabilities': {
228 228 b'args': {},
229 229 b'permissions': [
230 230 b'pull'
231 231 ]
232 232 },
233 233 b'changesetdata': {
234 234 b'args': {
235 b'fields': set([
236 b'parents',
237 b'revision'
238 ]),
239 b'noderange': [
240 [
241 b'0123456...'
242 ],
243 [
244 b'abcdef...'
245 ]
246 ],
247 b'nodes': [
248 b'0123456...'
249 ]
235 b'fields': {
236 b'default': set([]),
237 b'required': False,
238 b'type': b'set'
239 },
240 b'noderange': {
241 b'default': None,
242 b'required': False,
243 b'type': b'list'
244 },
245 b'nodes': {
246 b'default': None,
247 b'required': False,
248 b'type': b'list'
249 }
250 250 },
251 251 b'permissions': [
252 252 b'pull'
253 253 ]
254 254 },
255 255 b'filedata': {
256 256 b'args': {
257 b'fields': set([
258 b'parents',
259 b'revision'
260 ]),
261 b'haveparents': True,
262 b'nodes': [
263 b'0123456...'
264 ],
265 b'path': b'foo.txt'
257 b'fields': {
258 b'default': set([]),
259 b'required': False,
260 b'type': b'set'
261 },
262 b'haveparents': {
263 b'default': False,
264 b'required': False,
265 b'type': b'bool'
266 },
267 b'nodes': {
268 b'required': True,
269 b'type': b'list'
270 },
271 b'path': {
272 b'required': True,
273 b'type': b'bytes'
274 }
266 275 },
267 276 b'permissions': [
268 277 b'pull'
269 278 ]
270 279 },
271 280 b'heads': {
272 281 b'args': {
273 b'publiconly': False
282 b'publiconly': {
283 b'default': False,
284 b'required': False,
285 b'type': b'bool'
286 }
274 287 },
275 288 b'permissions': [
276 289 b'pull'
277 290 ]
278 291 },
279 292 b'known': {
280 293 b'args': {
281 b'nodes': [
282 b'deadbeef'
283 ]
294 b'nodes': {
295 b'default': [],
296 b'required': False,
297 b'type': b'list'
298 }
284 299 },
285 300 b'permissions': [
286 301 b'pull'
287 302 ]
288 303 },
289 304 b'listkeys': {
290 305 b'args': {
291 b'namespace': b'ns'
306 b'namespace': {
307 b'required': True,
308 b'type': b'bytes'
309 }
292 310 },
293 311 b'permissions': [
294 312 b'pull'
295 313 ]
296 314 },
297 315 b'lookup': {
298 316 b'args': {
299 b'key': b'foo'
317 b'key': {
318 b'required': True,
319 b'type': b'bytes'
320 }
300 321 },
301 322 b'permissions': [
302 323 b'pull'
303 324 ]
304 325 },
305 326 b'manifestdata': {
306 327 b'args': {
307 b'fields': set([
308 b'parents',
309 b'revision'
310 ]),
311 b'haveparents': True,
312 b'nodes': [
313 b'0123456...'
314 ],
315 b'tree': b''
328 b'fields': {
329 b'default': set([]),
330 b'required': False,
331 b'type': b'set'
332 },
333 b'haveparents': {
334 b'default': False,
335 b'required': False,
336 b'type': b'bool'
337 },
338 b'nodes': {
339 b'required': True,
340 b'type': b'list'
341 },
342 b'tree': {
343 b'required': True,
344 b'type': b'bytes'
345 }
316 346 },
317 347 b'permissions': [
318 348 b'pull'
319 349 ]
320 350 },
321 351 b'pushkey': {
322 352 b'args': {
323 b'key': b'key',
324 b'namespace': b'ns',
325 b'new': b'new',
326 b'old': b'old'
353 b'key': {
354 b'required': True,
355 b'type': b'bytes'
356 },
357 b'namespace': {
358 b'required': True,
359 b'type': b'bytes'
360 },
361 b'new': {
362 b'required': True,
363 b'type': b'bytes'
364 },
365 b'old': {
366 b'required': True,
367 b'type': b'bytes'
368 }
327 369 },
328 370 b'permissions': [
329 371 b'push'
330 372 ]
331 373 }
332 374 },
333 375 b'compression': [
334 376 {
335 377 b'name': b'zlib'
336 378 }
337 379 ],
338 380 b'framingmediatypes': [
339 381 b'application/mercurial-exp-framing-0005'
340 382 ],
341 383 b'pathfilterprefixes': set([
342 384 b'path:',
343 385 b'rootfilesin:'
344 386 ]),
345 387 b'rawrepoformats': [
346 388 b'generaldelta',
347 389 b'revlogv1'
348 390 ]
349 391 }
350 392 },
351 393 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'
352 394 }
353 395
354 396 capabilities command returns expected info
355 397
356 398 $ sendhttpv2peerhandshake << EOF
357 399 > command capabilities
358 400 > EOF
359 401 creating http peer for wire protocol version 2
360 402 s> GET /?cmd=capabilities HTTP/1.1\r\n
361 403 s> Accept-Encoding: identity\r\n
362 404 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
363 405 s> x-hgproto-1: cbor\r\n
364 406 s> x-hgupgrade-1: exp-http-v2-0001\r\n
365 407 s> accept: application/mercurial-0.1\r\n
366 408 s> host: $LOCALIP:$HGPORT\r\n (glob)
367 409 s> user-agent: Mercurial debugwireproto\r\n
368 410 s> \r\n
369 411 s> makefile('rb', None)
370 412 s> HTTP/1.1 200 OK\r\n
371 413 s> Server: testing stub value\r\n
372 414 s> Date: $HTTP_DATE$\r\n
373 415 s> Content-Type: application/mercurial-cbor\r\n
374 416 s> Content-Length: *\r\n (glob)
375 417 s> \r\n
376 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa3Ffields\xd9\x01\x02\x82GparentsHrevisionInoderange\x82\x81J0123456...\x81Iabcdef...Enodes\x81J0123456...Kpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xf5Enodes\x81J0123456...DpathGfoo.txtKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xf5Enodes\x81J0123456...Dtree@Kpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\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
418 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa3Ffields\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKhaveparents\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\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\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
377 419 sending capabilities command
378 420 s> POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
379 421 s> Accept-Encoding: identity\r\n
380 422 s> accept: application/mercurial-exp-framing-0005\r\n
381 423 s> content-type: application/mercurial-exp-framing-0005\r\n
382 424 s> content-length: 27\r\n
383 425 s> host: $LOCALIP:$HGPORT\r\n (glob)
384 426 s> user-agent: Mercurial debugwireproto\r\n
385 427 s> \r\n
386 428 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
387 429 s> makefile('rb', None)
388 430 s> HTTP/1.1 200 OK\r\n
389 431 s> Server: testing stub value\r\n
390 432 s> Date: $HTTP_DATE$\r\n
391 433 s> Content-Type: application/mercurial-exp-framing-0005\r\n
392 434 s> Transfer-Encoding: chunked\r\n
393 435 s> \r\n
394 436 s> 13\r\n
395 437 s> \x0b\x00\x00\x01\x00\x02\x011
396 438 s> \xa1FstatusBok
397 439 s> \r\n
398 440 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
399 s> 33e\r\n
400 s> 6\x03\x00\x01\x00\x02\x001
401 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa3Ffields\xd9\x01\x02\x82GparentsHrevisionInoderange\x82\x81J0123456...\x81Iabcdef...Enodes\x81J0123456...Kpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xf5Enodes\x81J0123456...DpathGfoo.txtKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xf5Enodes\x81J0123456...Dtree@Kpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
441 s> 485\r\n
442 s> }\x04\x00\x01\x00\x02\x001
443 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa3Ffields\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKhaveparents\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\xa3Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
402 444 s> \r\n
403 received frame(size=822; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
445 received frame(size=1149; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
404 446 s> 8\r\n
405 447 s> \x00\x00\x00\x01\x00\x02\x002
406 448 s> \r\n
407 449 s> 0\r\n
408 450 s> \r\n
409 451 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
410 452 response: gen[
411 453 {
412 454 b'commands': {
413 455 b'branchmap': {
414 456 b'args': {},
415 457 b'permissions': [
416 458 b'pull'
417 459 ]
418 460 },
419 461 b'capabilities': {
420 462 b'args': {},
421 463 b'permissions': [
422 464 b'pull'
423 465 ]
424 466 },
425 467 b'changesetdata': {
426 468 b'args': {
427 b'fields': set([
428 b'parents',
429 b'revision'
430 ]),
431 b'noderange': [
432 [
433 b'0123456...'
434 ],
435 [
436 b'abcdef...'
437 ]
438 ],
439 b'nodes': [
440 b'0123456...'
441 ]
469 b'fields': {
470 b'default': set([]),
471 b'required': False,
472 b'type': b'set'
473 },
474 b'noderange': {
475 b'default': None,
476 b'required': False,
477 b'type': b'list'
478 },
479 b'nodes': {
480 b'default': None,
481 b'required': False,
482 b'type': b'list'
483 }
442 484 },
443 485 b'permissions': [
444 486 b'pull'
445 487 ]
446 488 },
447 489 b'filedata': {
448 490 b'args': {
449 b'fields': set([
450 b'parents',
451 b'revision'
452 ]),
453 b'haveparents': True,
454 b'nodes': [
455 b'0123456...'
456 ],
457 b'path': b'foo.txt'
491 b'fields': {
492 b'default': set([]),
493 b'required': False,
494 b'type': b'set'
495 },
496 b'haveparents': {
497 b'default': False,
498 b'required': False,
499 b'type': b'bool'
500 },
501 b'nodes': {
502 b'required': True,
503 b'type': b'list'
504 },
505 b'path': {
506 b'required': True,
507 b'type': b'bytes'
508 }
458 509 },
459 510 b'permissions': [
460 511 b'pull'
461 512 ]
462 513 },
463 514 b'heads': {
464 515 b'args': {
465 b'publiconly': False
516 b'publiconly': {
517 b'default': False,
518 b'required': False,
519 b'type': b'bool'
520 }
466 521 },
467 522 b'permissions': [
468 523 b'pull'
469 524 ]
470 525 },
471 526 b'known': {
472 527 b'args': {
473 b'nodes': [
474 b'deadbeef'
475 ]
528 b'nodes': {
529 b'default': [],
530 b'required': False,
531 b'type': b'list'
532 }
476 533 },
477 534 b'permissions': [
478 535 b'pull'
479 536 ]
480 537 },
481 538 b'listkeys': {
482 539 b'args': {
483 b'namespace': b'ns'
540 b'namespace': {
541 b'required': True,
542 b'type': b'bytes'
543 }
484 544 },
485 545 b'permissions': [
486 546 b'pull'
487 547 ]
488 548 },
489 549 b'lookup': {
490 550 b'args': {
491 b'key': b'foo'
551 b'key': {
552 b'required': True,
553 b'type': b'bytes'
554 }
492 555 },
493 556 b'permissions': [
494 557 b'pull'
495 558 ]
496 559 },
497 560 b'manifestdata': {
498 561 b'args': {
499 b'fields': set([
500 b'parents',
501 b'revision'
502 ]),
503 b'haveparents': True,
504 b'nodes': [
505 b'0123456...'
506 ],
507 b'tree': b''
562 b'fields': {
563 b'default': set([]),
564 b'required': False,
565 b'type': b'set'
566 },
567 b'haveparents': {
568 b'default': False,
569 b'required': False,
570 b'type': b'bool'
571 },
572 b'nodes': {
573 b'required': True,
574 b'type': b'list'
575 },
576 b'tree': {
577 b'required': True,
578 b'type': b'bytes'
579 }
508 580 },
509 581 b'permissions': [
510 582 b'pull'
511 583 ]
512 584 },
513 585 b'pushkey': {
514 586 b'args': {
515 b'key': b'key',
516 b'namespace': b'ns',
517 b'new': b'new',
518 b'old': b'old'
587 b'key': {
588 b'required': True,
589 b'type': b'bytes'
590 },
591 b'namespace': {
592 b'required': True,
593 b'type': b'bytes'
594 },
595 b'new': {
596 b'required': True,
597 b'type': b'bytes'
598 },
599 b'old': {
600 b'required': True,
601 b'type': b'bytes'
602 }
519 603 },
520 604 b'permissions': [
521 605 b'push'
522 606 ]
523 607 }
524 608 },
525 609 b'compression': [
526 610 {
527 611 b'name': b'zlib'
528 612 }
529 613 ],
530 614 b'framingmediatypes': [
531 615 b'application/mercurial-exp-framing-0005'
532 616 ],
533 617 b'pathfilterprefixes': set([
534 618 b'path:',
535 619 b'rootfilesin:'
536 620 ]),
537 621 b'rawrepoformats': [
538 622 b'generaldelta',
539 623 b'revlogv1'
540 624 ]
541 625 }
542 626 ]
543 627
544 628 $ cat error.log
General Comments 0
You need to be logged in to leave comments. Login now