##// END OF EJS Templates
wireprotov2: send protocol settings frame from client...
Gregory Szorc -
r40168:762ef19a default
parent child Browse files
Show More
@@ -1,1418 +1,1421
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import functools
11 11 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in sorted(configtable.items()):
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = "extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config='warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31 class configitem(object):
32 32 """represent a known config item
33 33
34 34 :section: the official config section where to find this item,
35 35 :name: the official name within the section,
36 36 :default: default value for this item,
37 37 :alias: optional list of tuples as alternatives,
38 38 :generic: this is a generic definition, match name using regular expression.
39 39 """
40 40
41 41 def __init__(self, section, name, default=None, alias=(),
42 42 generic=False, priority=0):
43 43 self.section = section
44 44 self.name = name
45 45 self.default = default
46 46 self.alias = list(alias)
47 47 self.generic = generic
48 48 self.priority = priority
49 49 self._re = None
50 50 if generic:
51 51 self._re = re.compile(self.name)
52 52
53 53 class itemregister(dict):
54 54 """A specialized dictionary that can handle wild-card selection"""
55 55
56 56 def __init__(self):
57 57 super(itemregister, self).__init__()
58 58 self._generics = set()
59 59
60 60 def update(self, other):
61 61 super(itemregister, self).update(other)
62 62 self._generics.update(other._generics)
63 63
64 64 def __setitem__(self, key, item):
65 65 super(itemregister, self).__setitem__(key, item)
66 66 if item.generic:
67 67 self._generics.add(item)
68 68
69 69 def get(self, key):
70 70 baseitem = super(itemregister, self).get(key)
71 71 if baseitem is not None and not baseitem.generic:
72 72 return baseitem
73 73
74 74 # search for a matching generic item
75 75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 76 for item in generics:
77 77 # we use 'match' instead of 'search' to make the matching simpler
78 78 # for people unfamiliar with regular expression. Having the match
79 79 # rooted to the start of the string will produce less surprising
80 80 # result for user writing simple regex for sub-attribute.
81 81 #
82 82 # For example using "color\..*" match produces an unsurprising
83 83 # result, while using search could suddenly match apparently
84 84 # unrelated configuration that happens to contains "color."
85 85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 86 # some match to avoid the need to prefix most pattern with "^".
87 87 # The "^" seems more error prone.
88 88 if item._re.match(key):
89 89 return item
90 90
91 91 return None
92 92
93 93 coreitems = {}
94 94
95 95 def _register(configtable, *args, **kwargs):
96 96 item = configitem(*args, **kwargs)
97 97 section = configtable.setdefault(item.section, itemregister())
98 98 if item.name in section:
99 99 msg = "duplicated config item registration for '%s.%s'"
100 100 raise error.ProgrammingError(msg % (item.section, item.name))
101 101 section[item.name] = item
102 102
103 103 # special value for case where the default is derived from other values
104 104 dynamicdefault = object()
105 105
106 106 # Registering actual config items
107 107
108 108 def getitemregister(configtable):
109 109 f = functools.partial(_register, configtable)
110 110 # export pseudo enum as configitem.*
111 111 f.dynamicdefault = dynamicdefault
112 112 return f
113 113
114 114 coreconfigitem = getitemregister(coreitems)
115 115
116 116 coreconfigitem('alias', '.*',
117 117 default=dynamicdefault,
118 118 generic=True,
119 119 )
120 120 coreconfigitem('annotate', 'nodates',
121 121 default=False,
122 122 )
123 123 coreconfigitem('annotate', 'showfunc',
124 124 default=False,
125 125 )
126 126 coreconfigitem('annotate', 'unified',
127 127 default=None,
128 128 )
129 129 coreconfigitem('annotate', 'git',
130 130 default=False,
131 131 )
132 132 coreconfigitem('annotate', 'ignorews',
133 133 default=False,
134 134 )
135 135 coreconfigitem('annotate', 'ignorewsamount',
136 136 default=False,
137 137 )
138 138 coreconfigitem('annotate', 'ignoreblanklines',
139 139 default=False,
140 140 )
141 141 coreconfigitem('annotate', 'ignorewseol',
142 142 default=False,
143 143 )
144 144 coreconfigitem('annotate', 'nobinary',
145 145 default=False,
146 146 )
147 147 coreconfigitem('annotate', 'noprefix',
148 148 default=False,
149 149 )
150 150 coreconfigitem('annotate', 'word-diff',
151 151 default=False,
152 152 )
153 153 coreconfigitem('auth', 'cookiefile',
154 154 default=None,
155 155 )
156 156 # bookmarks.pushing: internal hack for discovery
157 157 coreconfigitem('bookmarks', 'pushing',
158 158 default=list,
159 159 )
160 160 # bundle.mainreporoot: internal hack for bundlerepo
161 161 coreconfigitem('bundle', 'mainreporoot',
162 162 default='',
163 163 )
164 164 coreconfigitem('censor', 'policy',
165 165 default='abort',
166 166 )
167 167 coreconfigitem('chgserver', 'idletimeout',
168 168 default=3600,
169 169 )
170 170 coreconfigitem('chgserver', 'skiphash',
171 171 default=False,
172 172 )
173 173 coreconfigitem('cmdserver', 'log',
174 174 default=None,
175 175 )
176 176 coreconfigitem('color', '.*',
177 177 default=None,
178 178 generic=True,
179 179 )
180 180 coreconfigitem('color', 'mode',
181 181 default='auto',
182 182 )
183 183 coreconfigitem('color', 'pagermode',
184 184 default=dynamicdefault,
185 185 )
186 186 coreconfigitem('commands', 'grep.all-files',
187 187 default=False,
188 188 )
189 189 coreconfigitem('commands', 'resolve.confirm',
190 190 default=False,
191 191 )
192 192 coreconfigitem('commands', 'resolve.explicit-re-merge',
193 193 default=False,
194 194 )
195 195 coreconfigitem('commands', 'resolve.mark-check',
196 196 default='none',
197 197 )
198 198 coreconfigitem('commands', 'show.aliasprefix',
199 199 default=list,
200 200 )
201 201 coreconfigitem('commands', 'status.relative',
202 202 default=False,
203 203 )
204 204 coreconfigitem('commands', 'status.skipstates',
205 205 default=[],
206 206 )
207 207 coreconfigitem('commands', 'status.terse',
208 208 default='',
209 209 )
210 210 coreconfigitem('commands', 'status.verbose',
211 211 default=False,
212 212 )
213 213 coreconfigitem('commands', 'update.check',
214 214 default=None,
215 215 )
216 216 coreconfigitem('commands', 'update.requiredest',
217 217 default=False,
218 218 )
219 219 coreconfigitem('committemplate', '.*',
220 220 default=None,
221 221 generic=True,
222 222 )
223 223 coreconfigitem('convert', 'bzr.saverev',
224 224 default=True,
225 225 )
226 226 coreconfigitem('convert', 'cvsps.cache',
227 227 default=True,
228 228 )
229 229 coreconfigitem('convert', 'cvsps.fuzz',
230 230 default=60,
231 231 )
232 232 coreconfigitem('convert', 'cvsps.logencoding',
233 233 default=None,
234 234 )
235 235 coreconfigitem('convert', 'cvsps.mergefrom',
236 236 default=None,
237 237 )
238 238 coreconfigitem('convert', 'cvsps.mergeto',
239 239 default=None,
240 240 )
241 241 coreconfigitem('convert', 'git.committeractions',
242 242 default=lambda: ['messagedifferent'],
243 243 )
244 244 coreconfigitem('convert', 'git.extrakeys',
245 245 default=list,
246 246 )
247 247 coreconfigitem('convert', 'git.findcopiesharder',
248 248 default=False,
249 249 )
250 250 coreconfigitem('convert', 'git.remoteprefix',
251 251 default='remote',
252 252 )
253 253 coreconfigitem('convert', 'git.renamelimit',
254 254 default=400,
255 255 )
256 256 coreconfigitem('convert', 'git.saverev',
257 257 default=True,
258 258 )
259 259 coreconfigitem('convert', 'git.similarity',
260 260 default=50,
261 261 )
262 262 coreconfigitem('convert', 'git.skipsubmodules',
263 263 default=False,
264 264 )
265 265 coreconfigitem('convert', 'hg.clonebranches',
266 266 default=False,
267 267 )
268 268 coreconfigitem('convert', 'hg.ignoreerrors',
269 269 default=False,
270 270 )
271 271 coreconfigitem('convert', 'hg.revs',
272 272 default=None,
273 273 )
274 274 coreconfigitem('convert', 'hg.saverev',
275 275 default=False,
276 276 )
277 277 coreconfigitem('convert', 'hg.sourcename',
278 278 default=None,
279 279 )
280 280 coreconfigitem('convert', 'hg.startrev',
281 281 default=None,
282 282 )
283 283 coreconfigitem('convert', 'hg.tagsbranch',
284 284 default='default',
285 285 )
286 286 coreconfigitem('convert', 'hg.usebranchnames',
287 287 default=True,
288 288 )
289 289 coreconfigitem('convert', 'ignoreancestorcheck',
290 290 default=False,
291 291 )
292 292 coreconfigitem('convert', 'localtimezone',
293 293 default=False,
294 294 )
295 295 coreconfigitem('convert', 'p4.encoding',
296 296 default=dynamicdefault,
297 297 )
298 298 coreconfigitem('convert', 'p4.startrev',
299 299 default=0,
300 300 )
301 301 coreconfigitem('convert', 'skiptags',
302 302 default=False,
303 303 )
304 304 coreconfigitem('convert', 'svn.debugsvnlog',
305 305 default=True,
306 306 )
307 307 coreconfigitem('convert', 'svn.trunk',
308 308 default=None,
309 309 )
310 310 coreconfigitem('convert', 'svn.tags',
311 311 default=None,
312 312 )
313 313 coreconfigitem('convert', 'svn.branches',
314 314 default=None,
315 315 )
316 316 coreconfigitem('convert', 'svn.startrev',
317 317 default=0,
318 318 )
319 319 coreconfigitem('debug', 'dirstate.delaywrite',
320 320 default=0,
321 321 )
322 322 coreconfigitem('defaults', '.*',
323 323 default=None,
324 324 generic=True,
325 325 )
326 326 coreconfigitem('devel', 'all-warnings',
327 327 default=False,
328 328 )
329 329 coreconfigitem('devel', 'bundle2.debug',
330 330 default=False,
331 331 )
332 332 coreconfigitem('devel', 'cache-vfs',
333 333 default=None,
334 334 )
335 335 coreconfigitem('devel', 'check-locks',
336 336 default=False,
337 337 )
338 338 coreconfigitem('devel', 'check-relroot',
339 339 default=False,
340 340 )
341 341 coreconfigitem('devel', 'default-date',
342 342 default=None,
343 343 )
344 344 coreconfigitem('devel', 'deprec-warn',
345 345 default=False,
346 346 )
347 347 coreconfigitem('devel', 'disableloaddefaultcerts',
348 348 default=False,
349 349 )
350 350 coreconfigitem('devel', 'warn-empty-changegroup',
351 351 default=False,
352 352 )
353 353 coreconfigitem('devel', 'legacy.exchange',
354 354 default=list,
355 355 )
356 356 coreconfigitem('devel', 'servercafile',
357 357 default='',
358 358 )
359 359 coreconfigitem('devel', 'serverexactprotocol',
360 360 default='',
361 361 )
362 362 coreconfigitem('devel', 'serverrequirecert',
363 363 default=False,
364 364 )
365 365 coreconfigitem('devel', 'strip-obsmarkers',
366 366 default=True,
367 367 )
368 368 coreconfigitem('devel', 'warn-config',
369 369 default=None,
370 370 )
371 371 coreconfigitem('devel', 'warn-config-default',
372 372 default=None,
373 373 )
374 374 coreconfigitem('devel', 'user.obsmarker',
375 375 default=None,
376 376 )
377 377 coreconfigitem('devel', 'warn-config-unknown',
378 378 default=None,
379 379 )
380 380 coreconfigitem('devel', 'debug.copies',
381 381 default=False,
382 382 )
383 383 coreconfigitem('devel', 'debug.extensions',
384 384 default=False,
385 385 )
386 386 coreconfigitem('devel', 'debug.peer-request',
387 387 default=False,
388 388 )
389 389 coreconfigitem('diff', 'nodates',
390 390 default=False,
391 391 )
392 392 coreconfigitem('diff', 'showfunc',
393 393 default=False,
394 394 )
395 395 coreconfigitem('diff', 'unified',
396 396 default=None,
397 397 )
398 398 coreconfigitem('diff', 'git',
399 399 default=False,
400 400 )
401 401 coreconfigitem('diff', 'ignorews',
402 402 default=False,
403 403 )
404 404 coreconfigitem('diff', 'ignorewsamount',
405 405 default=False,
406 406 )
407 407 coreconfigitem('diff', 'ignoreblanklines',
408 408 default=False,
409 409 )
410 410 coreconfigitem('diff', 'ignorewseol',
411 411 default=False,
412 412 )
413 413 coreconfigitem('diff', 'nobinary',
414 414 default=False,
415 415 )
416 416 coreconfigitem('diff', 'noprefix',
417 417 default=False,
418 418 )
419 419 coreconfigitem('diff', 'word-diff',
420 420 default=False,
421 421 )
422 422 coreconfigitem('email', 'bcc',
423 423 default=None,
424 424 )
425 425 coreconfigitem('email', 'cc',
426 426 default=None,
427 427 )
428 428 coreconfigitem('email', 'charsets',
429 429 default=list,
430 430 )
431 431 coreconfigitem('email', 'from',
432 432 default=None,
433 433 )
434 434 coreconfigitem('email', 'method',
435 435 default='smtp',
436 436 )
437 437 coreconfigitem('email', 'reply-to',
438 438 default=None,
439 439 )
440 440 coreconfigitem('email', 'to',
441 441 default=None,
442 442 )
443 443 coreconfigitem('experimental', 'archivemetatemplate',
444 444 default=dynamicdefault,
445 445 )
446 446 coreconfigitem('experimental', 'bundle-phases',
447 447 default=False,
448 448 )
449 449 coreconfigitem('experimental', 'bundle2-advertise',
450 450 default=True,
451 451 )
452 452 coreconfigitem('experimental', 'bundle2-output-capture',
453 453 default=False,
454 454 )
455 455 coreconfigitem('experimental', 'bundle2.pushback',
456 456 default=False,
457 457 )
458 458 coreconfigitem('experimental', 'bundle2lazylocking',
459 459 default=False,
460 460 )
461 461 coreconfigitem('experimental', 'bundlecomplevel',
462 462 default=None,
463 463 )
464 464 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
465 465 default=None,
466 466 )
467 467 coreconfigitem('experimental', 'bundlecomplevel.gzip',
468 468 default=None,
469 469 )
470 470 coreconfigitem('experimental', 'bundlecomplevel.none',
471 471 default=None,
472 472 )
473 473 coreconfigitem('experimental', 'bundlecomplevel.zstd',
474 474 default=None,
475 475 )
476 476 coreconfigitem('experimental', 'changegroup3',
477 477 default=False,
478 478 )
479 479 coreconfigitem('experimental', 'clientcompressionengines',
480 480 default=list,
481 481 )
482 482 coreconfigitem('experimental', 'copytrace',
483 483 default='on',
484 484 )
485 485 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
486 486 default=100,
487 487 )
488 488 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
489 489 default=100,
490 490 )
491 491 coreconfigitem('experimental', 'crecordtest',
492 492 default=None,
493 493 )
494 494 coreconfigitem('experimental', 'directaccess',
495 495 default=False,
496 496 )
497 497 coreconfigitem('experimental', 'directaccess.revnums',
498 498 default=False,
499 499 )
500 500 coreconfigitem('experimental', 'editortmpinhg',
501 501 default=False,
502 502 )
503 503 coreconfigitem('experimental', 'evolution',
504 504 default=list,
505 505 )
506 506 coreconfigitem('experimental', 'evolution.allowdivergence',
507 507 default=False,
508 508 alias=[('experimental', 'allowdivergence')]
509 509 )
510 510 coreconfigitem('experimental', 'evolution.allowunstable',
511 511 default=None,
512 512 )
513 513 coreconfigitem('experimental', 'evolution.createmarkers',
514 514 default=None,
515 515 )
516 516 coreconfigitem('experimental', 'evolution.effect-flags',
517 517 default=True,
518 518 alias=[('experimental', 'effect-flags')]
519 519 )
520 520 coreconfigitem('experimental', 'evolution.exchange',
521 521 default=None,
522 522 )
523 523 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
524 524 default=False,
525 525 )
526 526 coreconfigitem('experimental', 'evolution.report-instabilities',
527 527 default=True,
528 528 )
529 529 coreconfigitem('experimental', 'evolution.track-operation',
530 530 default=True,
531 531 )
532 532 coreconfigitem('experimental', 'maxdeltachainspan',
533 533 default=-1,
534 534 )
535 535 coreconfigitem('experimental', 'mergetempdirprefix',
536 536 default=None,
537 537 )
538 538 coreconfigitem('experimental', 'mmapindexthreshold',
539 539 default=None,
540 540 )
541 541 coreconfigitem('experimental', 'narrow',
542 542 default=False,
543 543 )
544 544 coreconfigitem('experimental', 'nonnormalparanoidcheck',
545 545 default=False,
546 546 )
547 547 coreconfigitem('experimental', 'exportableenviron',
548 548 default=list,
549 549 )
550 550 coreconfigitem('experimental', 'extendedheader.index',
551 551 default=None,
552 552 )
553 553 coreconfigitem('experimental', 'extendedheader.similarity',
554 554 default=False,
555 555 )
556 556 coreconfigitem('experimental', 'format.compression',
557 557 default='zlib',
558 558 )
559 559 coreconfigitem('experimental', 'graphshorten',
560 560 default=False,
561 561 )
562 562 coreconfigitem('experimental', 'graphstyle.parent',
563 563 default=dynamicdefault,
564 564 )
565 565 coreconfigitem('experimental', 'graphstyle.missing',
566 566 default=dynamicdefault,
567 567 )
568 568 coreconfigitem('experimental', 'graphstyle.grandparent',
569 569 default=dynamicdefault,
570 570 )
571 571 coreconfigitem('experimental', 'hook-track-tags',
572 572 default=False,
573 573 )
574 574 coreconfigitem('experimental', 'httppeer.advertise-v2',
575 575 default=False,
576 576 )
577 coreconfigitem('experimental', 'httppeer.v2-encoder-order',
578 default=None,
579 )
577 580 coreconfigitem('experimental', 'httppostargs',
578 581 default=False,
579 582 )
580 583 coreconfigitem('experimental', 'mergedriver',
581 584 default=None,
582 585 )
583 586 coreconfigitem('experimental', 'nointerrupt', default=False)
584 587 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
585 588
586 589 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
587 590 default=False,
588 591 )
589 592 coreconfigitem('experimental', 'remotenames',
590 593 default=False,
591 594 )
592 595 coreconfigitem('experimental', 'removeemptydirs',
593 596 default=True,
594 597 )
595 598 coreconfigitem('experimental', 'revisions.prefixhexnode',
596 599 default=False,
597 600 )
598 601 coreconfigitem('experimental', 'revlogv2',
599 602 default=None,
600 603 )
601 604 coreconfigitem('experimental', 'revisions.disambiguatewithin',
602 605 default=None,
603 606 )
604 607 coreconfigitem('experimental', 'single-head-per-branch',
605 608 default=False,
606 609 )
607 610 coreconfigitem('experimental', 'sshserver.support-v2',
608 611 default=False,
609 612 )
610 613 coreconfigitem('experimental', 'spacemovesdown',
611 614 default=False,
612 615 )
613 616 coreconfigitem('experimental', 'sparse-read',
614 617 default=False,
615 618 )
616 619 coreconfigitem('experimental', 'sparse-read.density-threshold',
617 620 default=0.50,
618 621 )
619 622 coreconfigitem('experimental', 'sparse-read.min-gap-size',
620 623 default='65K',
621 624 )
622 625 coreconfigitem('experimental', 'treemanifest',
623 626 default=False,
624 627 )
625 628 coreconfigitem('experimental', 'update.atomic-file',
626 629 default=False,
627 630 )
628 631 coreconfigitem('experimental', 'sshpeer.advertise-v2',
629 632 default=False,
630 633 )
631 634 coreconfigitem('experimental', 'web.apiserver',
632 635 default=False,
633 636 )
634 637 coreconfigitem('experimental', 'web.api.http-v2',
635 638 default=False,
636 639 )
637 640 coreconfigitem('experimental', 'web.api.debugreflect',
638 641 default=False,
639 642 )
640 643 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
641 644 default=False,
642 645 )
643 646 coreconfigitem('experimental', 'xdiff',
644 647 default=False,
645 648 )
646 649 coreconfigitem('extensions', '.*',
647 650 default=None,
648 651 generic=True,
649 652 )
650 653 coreconfigitem('extdata', '.*',
651 654 default=None,
652 655 generic=True,
653 656 )
654 657 coreconfigitem('format', 'chunkcachesize',
655 658 default=None,
656 659 )
657 660 coreconfigitem('format', 'dotencode',
658 661 default=True,
659 662 )
660 663 coreconfigitem('format', 'generaldelta',
661 664 default=False,
662 665 )
663 666 coreconfigitem('format', 'manifestcachesize',
664 667 default=None,
665 668 )
666 669 coreconfigitem('format', 'maxchainlen',
667 670 default=dynamicdefault,
668 671 )
669 672 coreconfigitem('format', 'obsstore-version',
670 673 default=None,
671 674 )
672 675 coreconfigitem('format', 'sparse-revlog',
673 676 default=False,
674 677 )
675 678 coreconfigitem('format', 'usefncache',
676 679 default=True,
677 680 )
678 681 coreconfigitem('format', 'usegeneraldelta',
679 682 default=True,
680 683 )
681 684 coreconfigitem('format', 'usestore',
682 685 default=True,
683 686 )
684 687 coreconfigitem('format', 'internal-phase',
685 688 default=False,
686 689 )
687 690 coreconfigitem('fsmonitor', 'warn_when_unused',
688 691 default=True,
689 692 )
690 693 coreconfigitem('fsmonitor', 'warn_update_file_count',
691 694 default=50000,
692 695 )
693 696 coreconfigitem('hooks', '.*',
694 697 default=dynamicdefault,
695 698 generic=True,
696 699 )
697 700 coreconfigitem('hgweb-paths', '.*',
698 701 default=list,
699 702 generic=True,
700 703 )
701 704 coreconfigitem('hostfingerprints', '.*',
702 705 default=list,
703 706 generic=True,
704 707 )
705 708 coreconfigitem('hostsecurity', 'ciphers',
706 709 default=None,
707 710 )
708 711 coreconfigitem('hostsecurity', 'disabletls10warning',
709 712 default=False,
710 713 )
711 714 coreconfigitem('hostsecurity', 'minimumprotocol',
712 715 default=dynamicdefault,
713 716 )
714 717 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
715 718 default=dynamicdefault,
716 719 generic=True,
717 720 )
718 721 coreconfigitem('hostsecurity', '.*:ciphers$',
719 722 default=dynamicdefault,
720 723 generic=True,
721 724 )
722 725 coreconfigitem('hostsecurity', '.*:fingerprints$',
723 726 default=list,
724 727 generic=True,
725 728 )
726 729 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
727 730 default=None,
728 731 generic=True,
729 732 )
730 733
731 734 coreconfigitem('http_proxy', 'always',
732 735 default=False,
733 736 )
734 737 coreconfigitem('http_proxy', 'host',
735 738 default=None,
736 739 )
737 740 coreconfigitem('http_proxy', 'no',
738 741 default=list,
739 742 )
740 743 coreconfigitem('http_proxy', 'passwd',
741 744 default=None,
742 745 )
743 746 coreconfigitem('http_proxy', 'user',
744 747 default=None,
745 748 )
746 749
747 750 coreconfigitem('http', 'timeout',
748 751 default=None,
749 752 )
750 753
751 754 coreconfigitem('logtoprocess', 'commandexception',
752 755 default=None,
753 756 )
754 757 coreconfigitem('logtoprocess', 'commandfinish',
755 758 default=None,
756 759 )
757 760 coreconfigitem('logtoprocess', 'command',
758 761 default=None,
759 762 )
760 763 coreconfigitem('logtoprocess', 'develwarn',
761 764 default=None,
762 765 )
763 766 coreconfigitem('logtoprocess', 'uiblocked',
764 767 default=None,
765 768 )
766 769 coreconfigitem('merge', 'checkunknown',
767 770 default='abort',
768 771 )
769 772 coreconfigitem('merge', 'checkignored',
770 773 default='abort',
771 774 )
772 775 coreconfigitem('experimental', 'merge.checkpathconflicts',
773 776 default=False,
774 777 )
775 778 coreconfigitem('merge', 'followcopies',
776 779 default=True,
777 780 )
778 781 coreconfigitem('merge', 'on-failure',
779 782 default='continue',
780 783 )
781 784 coreconfigitem('merge', 'preferancestor',
782 785 default=lambda: ['*'],
783 786 )
784 787 coreconfigitem('merge', 'strict-capability-check',
785 788 default=False,
786 789 )
787 790 coreconfigitem('merge-tools', '.*',
788 791 default=None,
789 792 generic=True,
790 793 )
791 794 coreconfigitem('merge-tools', br'.*\.args$',
792 795 default="$local $base $other",
793 796 generic=True,
794 797 priority=-1,
795 798 )
796 799 coreconfigitem('merge-tools', br'.*\.binary$',
797 800 default=False,
798 801 generic=True,
799 802 priority=-1,
800 803 )
801 804 coreconfigitem('merge-tools', br'.*\.check$',
802 805 default=list,
803 806 generic=True,
804 807 priority=-1,
805 808 )
806 809 coreconfigitem('merge-tools', br'.*\.checkchanged$',
807 810 default=False,
808 811 generic=True,
809 812 priority=-1,
810 813 )
811 814 coreconfigitem('merge-tools', br'.*\.executable$',
812 815 default=dynamicdefault,
813 816 generic=True,
814 817 priority=-1,
815 818 )
816 819 coreconfigitem('merge-tools', br'.*\.fixeol$',
817 820 default=False,
818 821 generic=True,
819 822 priority=-1,
820 823 )
821 824 coreconfigitem('merge-tools', br'.*\.gui$',
822 825 default=False,
823 826 generic=True,
824 827 priority=-1,
825 828 )
826 829 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
827 830 default='basic',
828 831 generic=True,
829 832 priority=-1,
830 833 )
831 834 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
832 835 default=dynamicdefault, # take from ui.mergemarkertemplate
833 836 generic=True,
834 837 priority=-1,
835 838 )
836 839 coreconfigitem('merge-tools', br'.*\.priority$',
837 840 default=0,
838 841 generic=True,
839 842 priority=-1,
840 843 )
841 844 coreconfigitem('merge-tools', br'.*\.premerge$',
842 845 default=dynamicdefault,
843 846 generic=True,
844 847 priority=-1,
845 848 )
846 849 coreconfigitem('merge-tools', br'.*\.symlink$',
847 850 default=False,
848 851 generic=True,
849 852 priority=-1,
850 853 )
851 854 coreconfigitem('pager', 'attend-.*',
852 855 default=dynamicdefault,
853 856 generic=True,
854 857 )
855 858 coreconfigitem('pager', 'ignore',
856 859 default=list,
857 860 )
858 861 coreconfigitem('pager', 'pager',
859 862 default=dynamicdefault,
860 863 )
861 864 coreconfigitem('patch', 'eol',
862 865 default='strict',
863 866 )
864 867 coreconfigitem('patch', 'fuzz',
865 868 default=2,
866 869 )
867 870 coreconfigitem('paths', 'default',
868 871 default=None,
869 872 )
870 873 coreconfigitem('paths', 'default-push',
871 874 default=None,
872 875 )
873 876 coreconfigitem('paths', '.*',
874 877 default=None,
875 878 generic=True,
876 879 )
877 880 coreconfigitem('phases', 'checksubrepos',
878 881 default='follow',
879 882 )
880 883 coreconfigitem('phases', 'new-commit',
881 884 default='draft',
882 885 )
883 886 coreconfigitem('phases', 'publish',
884 887 default=True,
885 888 )
886 889 coreconfigitem('profiling', 'enabled',
887 890 default=False,
888 891 )
889 892 coreconfigitem('profiling', 'format',
890 893 default='text',
891 894 )
892 895 coreconfigitem('profiling', 'freq',
893 896 default=1000,
894 897 )
895 898 coreconfigitem('profiling', 'limit',
896 899 default=30,
897 900 )
898 901 coreconfigitem('profiling', 'nested',
899 902 default=0,
900 903 )
901 904 coreconfigitem('profiling', 'output',
902 905 default=None,
903 906 )
904 907 coreconfigitem('profiling', 'showmax',
905 908 default=0.999,
906 909 )
907 910 coreconfigitem('profiling', 'showmin',
908 911 default=dynamicdefault,
909 912 )
910 913 coreconfigitem('profiling', 'sort',
911 914 default='inlinetime',
912 915 )
913 916 coreconfigitem('profiling', 'statformat',
914 917 default='hotpath',
915 918 )
916 919 coreconfigitem('profiling', 'time-track',
917 920 default='cpu',
918 921 )
919 922 coreconfigitem('profiling', 'type',
920 923 default='stat',
921 924 )
922 925 coreconfigitem('progress', 'assume-tty',
923 926 default=False,
924 927 )
925 928 coreconfigitem('progress', 'changedelay',
926 929 default=1,
927 930 )
928 931 coreconfigitem('progress', 'clear-complete',
929 932 default=True,
930 933 )
931 934 coreconfigitem('progress', 'debug',
932 935 default=False,
933 936 )
934 937 coreconfigitem('progress', 'delay',
935 938 default=3,
936 939 )
937 940 coreconfigitem('progress', 'disable',
938 941 default=False,
939 942 )
940 943 coreconfigitem('progress', 'estimateinterval',
941 944 default=60.0,
942 945 )
943 946 coreconfigitem('progress', 'format',
944 947 default=lambda: ['topic', 'bar', 'number', 'estimate'],
945 948 )
946 949 coreconfigitem('progress', 'refresh',
947 950 default=0.1,
948 951 )
949 952 coreconfigitem('progress', 'width',
950 953 default=dynamicdefault,
951 954 )
952 955 coreconfigitem('push', 'pushvars.server',
953 956 default=False,
954 957 )
955 958 coreconfigitem('storage', 'new-repo-backend',
956 959 default='revlogv1',
957 960 )
958 961 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
959 962 default=True,
960 963 alias=[('format', 'aggressivemergedeltas')],
961 964 )
962 965 coreconfigitem('server', 'bookmarks-pushkey-compat',
963 966 default=True,
964 967 )
965 968 coreconfigitem('server', 'bundle1',
966 969 default=True,
967 970 )
968 971 coreconfigitem('server', 'bundle1gd',
969 972 default=None,
970 973 )
971 974 coreconfigitem('server', 'bundle1.pull',
972 975 default=None,
973 976 )
974 977 coreconfigitem('server', 'bundle1gd.pull',
975 978 default=None,
976 979 )
977 980 coreconfigitem('server', 'bundle1.push',
978 981 default=None,
979 982 )
980 983 coreconfigitem('server', 'bundle1gd.push',
981 984 default=None,
982 985 )
983 986 coreconfigitem('server', 'bundle2.stream',
984 987 default=True,
985 988 alias=[('experimental', 'bundle2.stream')]
986 989 )
987 990 coreconfigitem('server', 'compressionengines',
988 991 default=list,
989 992 )
990 993 coreconfigitem('server', 'concurrent-push-mode',
991 994 default='strict',
992 995 )
993 996 coreconfigitem('server', 'disablefullbundle',
994 997 default=False,
995 998 )
996 999 coreconfigitem('server', 'maxhttpheaderlen',
997 1000 default=1024,
998 1001 )
999 1002 coreconfigitem('server', 'pullbundle',
1000 1003 default=False,
1001 1004 )
1002 1005 coreconfigitem('server', 'preferuncompressed',
1003 1006 default=False,
1004 1007 )
1005 1008 coreconfigitem('server', 'streamunbundle',
1006 1009 default=False,
1007 1010 )
1008 1011 coreconfigitem('server', 'uncompressed',
1009 1012 default=True,
1010 1013 )
1011 1014 coreconfigitem('server', 'uncompressedallowsecret',
1012 1015 default=False,
1013 1016 )
1014 1017 coreconfigitem('server', 'validate',
1015 1018 default=False,
1016 1019 )
1017 1020 coreconfigitem('server', 'zliblevel',
1018 1021 default=-1,
1019 1022 )
1020 1023 coreconfigitem('server', 'zstdlevel',
1021 1024 default=3,
1022 1025 )
1023 1026 coreconfigitem('share', 'pool',
1024 1027 default=None,
1025 1028 )
1026 1029 coreconfigitem('share', 'poolnaming',
1027 1030 default='identity',
1028 1031 )
1029 1032 coreconfigitem('smtp', 'host',
1030 1033 default=None,
1031 1034 )
1032 1035 coreconfigitem('smtp', 'local_hostname',
1033 1036 default=None,
1034 1037 )
1035 1038 coreconfigitem('smtp', 'password',
1036 1039 default=None,
1037 1040 )
1038 1041 coreconfigitem('smtp', 'port',
1039 1042 default=dynamicdefault,
1040 1043 )
1041 1044 coreconfigitem('smtp', 'tls',
1042 1045 default='none',
1043 1046 )
1044 1047 coreconfigitem('smtp', 'username',
1045 1048 default=None,
1046 1049 )
1047 1050 coreconfigitem('sparse', 'missingwarning',
1048 1051 default=True,
1049 1052 )
1050 1053 coreconfigitem('subrepos', 'allowed',
1051 1054 default=dynamicdefault, # to make backporting simpler
1052 1055 )
1053 1056 coreconfigitem('subrepos', 'hg:allowed',
1054 1057 default=dynamicdefault,
1055 1058 )
1056 1059 coreconfigitem('subrepos', 'git:allowed',
1057 1060 default=dynamicdefault,
1058 1061 )
1059 1062 coreconfigitem('subrepos', 'svn:allowed',
1060 1063 default=dynamicdefault,
1061 1064 )
1062 1065 coreconfigitem('templates', '.*',
1063 1066 default=None,
1064 1067 generic=True,
1065 1068 )
1066 1069 coreconfigitem('trusted', 'groups',
1067 1070 default=list,
1068 1071 )
1069 1072 coreconfigitem('trusted', 'users',
1070 1073 default=list,
1071 1074 )
1072 1075 coreconfigitem('ui', '_usedassubrepo',
1073 1076 default=False,
1074 1077 )
1075 1078 coreconfigitem('ui', 'allowemptycommit',
1076 1079 default=False,
1077 1080 )
1078 1081 coreconfigitem('ui', 'archivemeta',
1079 1082 default=True,
1080 1083 )
1081 1084 coreconfigitem('ui', 'askusername',
1082 1085 default=False,
1083 1086 )
1084 1087 coreconfigitem('ui', 'clonebundlefallback',
1085 1088 default=False,
1086 1089 )
1087 1090 coreconfigitem('ui', 'clonebundleprefers',
1088 1091 default=list,
1089 1092 )
1090 1093 coreconfigitem('ui', 'clonebundles',
1091 1094 default=True,
1092 1095 )
1093 1096 coreconfigitem('ui', 'color',
1094 1097 default='auto',
1095 1098 )
1096 1099 coreconfigitem('ui', 'commitsubrepos',
1097 1100 default=False,
1098 1101 )
1099 1102 coreconfigitem('ui', 'debug',
1100 1103 default=False,
1101 1104 )
1102 1105 coreconfigitem('ui', 'debugger',
1103 1106 default=None,
1104 1107 )
1105 1108 coreconfigitem('ui', 'editor',
1106 1109 default=dynamicdefault,
1107 1110 )
1108 1111 coreconfigitem('ui', 'fallbackencoding',
1109 1112 default=None,
1110 1113 )
1111 1114 coreconfigitem('ui', 'forcecwd',
1112 1115 default=None,
1113 1116 )
1114 1117 coreconfigitem('ui', 'forcemerge',
1115 1118 default=None,
1116 1119 )
1117 1120 coreconfigitem('ui', 'formatdebug',
1118 1121 default=False,
1119 1122 )
1120 1123 coreconfigitem('ui', 'formatjson',
1121 1124 default=False,
1122 1125 )
1123 1126 coreconfigitem('ui', 'formatted',
1124 1127 default=None,
1125 1128 )
1126 1129 coreconfigitem('ui', 'graphnodetemplate',
1127 1130 default=None,
1128 1131 )
1129 1132 coreconfigitem('ui', 'history-editing-backup',
1130 1133 default=True,
1131 1134 )
1132 1135 coreconfigitem('ui', 'interactive',
1133 1136 default=None,
1134 1137 )
1135 1138 coreconfigitem('ui', 'interface',
1136 1139 default=None,
1137 1140 )
1138 1141 coreconfigitem('ui', 'interface.chunkselector',
1139 1142 default=None,
1140 1143 )
1141 1144 coreconfigitem('ui', 'large-file-limit',
1142 1145 default=10000000,
1143 1146 )
1144 1147 coreconfigitem('ui', 'logblockedtimes',
1145 1148 default=False,
1146 1149 )
1147 1150 coreconfigitem('ui', 'logtemplate',
1148 1151 default=None,
1149 1152 )
1150 1153 coreconfigitem('ui', 'merge',
1151 1154 default=None,
1152 1155 )
1153 1156 coreconfigitem('ui', 'mergemarkers',
1154 1157 default='basic',
1155 1158 )
1156 1159 coreconfigitem('ui', 'mergemarkertemplate',
1157 1160 default=('{node|short} '
1158 1161 '{ifeq(tags, "tip", "", '
1159 1162 'ifeq(tags, "", "", "{tags} "))}'
1160 1163 '{if(bookmarks, "{bookmarks} ")}'
1161 1164 '{ifeq(branch, "default", "", "{branch} ")}'
1162 1165 '- {author|user}: {desc|firstline}')
1163 1166 )
1164 1167 coreconfigitem('ui', 'nontty',
1165 1168 default=False,
1166 1169 )
1167 1170 coreconfigitem('ui', 'origbackuppath',
1168 1171 default=None,
1169 1172 )
1170 1173 coreconfigitem('ui', 'paginate',
1171 1174 default=True,
1172 1175 )
1173 1176 coreconfigitem('ui', 'patch',
1174 1177 default=None,
1175 1178 )
1176 1179 coreconfigitem('ui', 'portablefilenames',
1177 1180 default='warn',
1178 1181 )
1179 1182 coreconfigitem('ui', 'promptecho',
1180 1183 default=False,
1181 1184 )
1182 1185 coreconfigitem('ui', 'quiet',
1183 1186 default=False,
1184 1187 )
1185 1188 coreconfigitem('ui', 'quietbookmarkmove',
1186 1189 default=False,
1187 1190 )
1188 1191 coreconfigitem('ui', 'remotecmd',
1189 1192 default='hg',
1190 1193 )
1191 1194 coreconfigitem('ui', 'report_untrusted',
1192 1195 default=True,
1193 1196 )
1194 1197 coreconfigitem('ui', 'rollback',
1195 1198 default=True,
1196 1199 )
1197 1200 coreconfigitem('ui', 'signal-safe-lock',
1198 1201 default=True,
1199 1202 )
1200 1203 coreconfigitem('ui', 'slash',
1201 1204 default=False,
1202 1205 )
1203 1206 coreconfigitem('ui', 'ssh',
1204 1207 default='ssh',
1205 1208 )
1206 1209 coreconfigitem('ui', 'ssherrorhint',
1207 1210 default=None,
1208 1211 )
1209 1212 coreconfigitem('ui', 'statuscopies',
1210 1213 default=False,
1211 1214 )
1212 1215 coreconfigitem('ui', 'strict',
1213 1216 default=False,
1214 1217 )
1215 1218 coreconfigitem('ui', 'style',
1216 1219 default='',
1217 1220 )
1218 1221 coreconfigitem('ui', 'supportcontact',
1219 1222 default=None,
1220 1223 )
1221 1224 coreconfigitem('ui', 'textwidth',
1222 1225 default=78,
1223 1226 )
1224 1227 coreconfigitem('ui', 'timeout',
1225 1228 default='600',
1226 1229 )
1227 1230 coreconfigitem('ui', 'timeout.warn',
1228 1231 default=0,
1229 1232 )
1230 1233 coreconfigitem('ui', 'traceback',
1231 1234 default=False,
1232 1235 )
1233 1236 coreconfigitem('ui', 'tweakdefaults',
1234 1237 default=False,
1235 1238 )
1236 1239 coreconfigitem('ui', 'username',
1237 1240 alias=[('ui', 'user')]
1238 1241 )
1239 1242 coreconfigitem('ui', 'verbose',
1240 1243 default=False,
1241 1244 )
1242 1245 coreconfigitem('verify', 'skipflags',
1243 1246 default=None,
1244 1247 )
1245 1248 coreconfigitem('web', 'allowbz2',
1246 1249 default=False,
1247 1250 )
1248 1251 coreconfigitem('web', 'allowgz',
1249 1252 default=False,
1250 1253 )
1251 1254 coreconfigitem('web', 'allow-pull',
1252 1255 alias=[('web', 'allowpull')],
1253 1256 default=True,
1254 1257 )
1255 1258 coreconfigitem('web', 'allow-push',
1256 1259 alias=[('web', 'allow_push')],
1257 1260 default=list,
1258 1261 )
1259 1262 coreconfigitem('web', 'allowzip',
1260 1263 default=False,
1261 1264 )
1262 1265 coreconfigitem('web', 'archivesubrepos',
1263 1266 default=False,
1264 1267 )
1265 1268 coreconfigitem('web', 'cache',
1266 1269 default=True,
1267 1270 )
1268 1271 coreconfigitem('web', 'contact',
1269 1272 default=None,
1270 1273 )
1271 1274 coreconfigitem('web', 'deny_push',
1272 1275 default=list,
1273 1276 )
1274 1277 coreconfigitem('web', 'guessmime',
1275 1278 default=False,
1276 1279 )
1277 1280 coreconfigitem('web', 'hidden',
1278 1281 default=False,
1279 1282 )
1280 1283 coreconfigitem('web', 'labels',
1281 1284 default=list,
1282 1285 )
1283 1286 coreconfigitem('web', 'logoimg',
1284 1287 default='hglogo.png',
1285 1288 )
1286 1289 coreconfigitem('web', 'logourl',
1287 1290 default='https://mercurial-scm.org/',
1288 1291 )
1289 1292 coreconfigitem('web', 'accesslog',
1290 1293 default='-',
1291 1294 )
1292 1295 coreconfigitem('web', 'address',
1293 1296 default='',
1294 1297 )
1295 1298 coreconfigitem('web', 'allow-archive',
1296 1299 alias=[('web', 'allow_archive')],
1297 1300 default=list,
1298 1301 )
1299 1302 coreconfigitem('web', 'allow_read',
1300 1303 default=list,
1301 1304 )
1302 1305 coreconfigitem('web', 'baseurl',
1303 1306 default=None,
1304 1307 )
1305 1308 coreconfigitem('web', 'cacerts',
1306 1309 default=None,
1307 1310 )
1308 1311 coreconfigitem('web', 'certificate',
1309 1312 default=None,
1310 1313 )
1311 1314 coreconfigitem('web', 'collapse',
1312 1315 default=False,
1313 1316 )
1314 1317 coreconfigitem('web', 'csp',
1315 1318 default=None,
1316 1319 )
1317 1320 coreconfigitem('web', 'deny_read',
1318 1321 default=list,
1319 1322 )
1320 1323 coreconfigitem('web', 'descend',
1321 1324 default=True,
1322 1325 )
1323 1326 coreconfigitem('web', 'description',
1324 1327 default="",
1325 1328 )
1326 1329 coreconfigitem('web', 'encoding',
1327 1330 default=lambda: encoding.encoding,
1328 1331 )
1329 1332 coreconfigitem('web', 'errorlog',
1330 1333 default='-',
1331 1334 )
1332 1335 coreconfigitem('web', 'ipv6',
1333 1336 default=False,
1334 1337 )
1335 1338 coreconfigitem('web', 'maxchanges',
1336 1339 default=10,
1337 1340 )
1338 1341 coreconfigitem('web', 'maxfiles',
1339 1342 default=10,
1340 1343 )
1341 1344 coreconfigitem('web', 'maxshortchanges',
1342 1345 default=60,
1343 1346 )
1344 1347 coreconfigitem('web', 'motd',
1345 1348 default='',
1346 1349 )
1347 1350 coreconfigitem('web', 'name',
1348 1351 default=dynamicdefault,
1349 1352 )
1350 1353 coreconfigitem('web', 'port',
1351 1354 default=8000,
1352 1355 )
1353 1356 coreconfigitem('web', 'prefix',
1354 1357 default='',
1355 1358 )
1356 1359 coreconfigitem('web', 'push_ssl',
1357 1360 default=True,
1358 1361 )
1359 1362 coreconfigitem('web', 'refreshinterval',
1360 1363 default=20,
1361 1364 )
1362 1365 coreconfigitem('web', 'server-header',
1363 1366 default=None,
1364 1367 )
1365 1368 coreconfigitem('web', 'static',
1366 1369 default=None,
1367 1370 )
1368 1371 coreconfigitem('web', 'staticurl',
1369 1372 default=None,
1370 1373 )
1371 1374 coreconfigitem('web', 'stripes',
1372 1375 default=1,
1373 1376 )
1374 1377 coreconfigitem('web', 'style',
1375 1378 default='paper',
1376 1379 )
1377 1380 coreconfigitem('web', 'templates',
1378 1381 default=None,
1379 1382 )
1380 1383 coreconfigitem('web', 'view',
1381 1384 default='served',
1382 1385 )
1383 1386 coreconfigitem('worker', 'backgroundclose',
1384 1387 default=dynamicdefault,
1385 1388 )
1386 1389 # Windows defaults to a limit of 512 open files. A buffer of 128
1387 1390 # should give us enough headway.
1388 1391 coreconfigitem('worker', 'backgroundclosemaxqueue',
1389 1392 default=384,
1390 1393 )
1391 1394 coreconfigitem('worker', 'backgroundcloseminfilecount',
1392 1395 default=2048,
1393 1396 )
1394 1397 coreconfigitem('worker', 'backgroundclosethreadcount',
1395 1398 default=4,
1396 1399 )
1397 1400 coreconfigitem('worker', 'enabled',
1398 1401 default=True,
1399 1402 )
1400 1403 coreconfigitem('worker', 'numcpus',
1401 1404 default=None,
1402 1405 )
1403 1406
1404 1407 # Rebase related configuration moved to core because other extension are doing
1405 1408 # strange things. For example, shelve import the extensions to reuse some bit
1406 1409 # without formally loading it.
1407 1410 coreconfigitem('commands', 'rebase.requiredest',
1408 1411 default=False,
1409 1412 )
1410 1413 coreconfigitem('experimental', 'rebaseskipobsolete',
1411 1414 default=True,
1412 1415 )
1413 1416 coreconfigitem('rebase', 'singletransaction',
1414 1417 default=False,
1415 1418 )
1416 1419 coreconfigitem('rebase', 'experimental.inmemory',
1417 1420 default=False,
1418 1421 )
@@ -1,987 +1,1005
1 1 # httppeer.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import io
13 13 import os
14 14 import socket
15 15 import struct
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from . import (
20 20 bundle2,
21 21 error,
22 22 httpconnection,
23 23 pycompat,
24 24 repository,
25 25 statichttprepo,
26 26 url as urlmod,
27 27 util,
28 28 wireprotoframing,
29 29 wireprototypes,
30 30 wireprotov1peer,
31 31 wireprotov2peer,
32 32 wireprotov2server,
33 33 )
34 34 from .utils import (
35 35 cborutil,
36 36 interfaceutil,
37 37 stringutil,
38 38 )
39 39
40 40 httplib = util.httplib
41 41 urlerr = util.urlerr
42 42 urlreq = util.urlreq
43 43
44 44 def encodevalueinheaders(value, header, limit):
45 45 """Encode a string value into multiple HTTP headers.
46 46
47 47 ``value`` will be encoded into 1 or more HTTP headers with the names
48 48 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
49 49 name + value will be at most ``limit`` bytes long.
50 50
51 51 Returns an iterable of 2-tuples consisting of header names and
52 52 values as native strings.
53 53 """
54 54 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
55 55 # not bytes. This function always takes bytes in as arguments.
56 56 fmt = pycompat.strurl(header) + r'-%s'
57 57 # Note: it is *NOT* a bug that the last bit here is a bytestring
58 58 # and not a unicode: we're just getting the encoded length anyway,
59 59 # and using an r-string to make it portable between Python 2 and 3
60 60 # doesn't work because then the \r is a literal backslash-r
61 61 # instead of a carriage return.
62 62 valuelen = limit - len(fmt % r'000') - len(': \r\n')
63 63 result = []
64 64
65 65 n = 0
66 66 for i in pycompat.xrange(0, len(value), valuelen):
67 67 n += 1
68 68 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
69 69
70 70 return result
71 71
72 72 class _multifile(object):
73 73 def __init__(self, *fileobjs):
74 74 for f in fileobjs:
75 75 if not util.safehasattr(f, 'length'):
76 76 raise ValueError(
77 77 '_multifile only supports file objects that '
78 78 'have a length but this one does not:', type(f), f)
79 79 self._fileobjs = fileobjs
80 80 self._index = 0
81 81
82 82 @property
83 83 def length(self):
84 84 return sum(f.length for f in self._fileobjs)
85 85
86 86 def read(self, amt=None):
87 87 if amt <= 0:
88 88 return ''.join(f.read() for f in self._fileobjs)
89 89 parts = []
90 90 while amt and self._index < len(self._fileobjs):
91 91 parts.append(self._fileobjs[self._index].read(amt))
92 92 got = len(parts[-1])
93 93 if got < amt:
94 94 self._index += 1
95 95 amt -= got
96 96 return ''.join(parts)
97 97
98 98 def seek(self, offset, whence=os.SEEK_SET):
99 99 if whence != os.SEEK_SET:
100 100 raise NotImplementedError(
101 101 '_multifile does not support anything other'
102 102 ' than os.SEEK_SET for whence on seek()')
103 103 if offset != 0:
104 104 raise NotImplementedError(
105 105 '_multifile only supports seeking to start, but that '
106 106 'could be fixed if you need it')
107 107 for f in self._fileobjs:
108 108 f.seek(0)
109 109 self._index = 0
110 110
111 111 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
112 112 repobaseurl, cmd, args):
113 113 """Make an HTTP request to run a command for a version 1 client.
114 114
115 115 ``caps`` is a set of known server capabilities. The value may be
116 116 None if capabilities are not yet known.
117 117
118 118 ``capablefn`` is a function to evaluate a capability.
119 119
120 120 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
121 121 raw data to pass to it.
122 122 """
123 123 if cmd == 'pushkey':
124 124 args['data'] = ''
125 125 data = args.pop('data', None)
126 126 headers = args.pop('headers', {})
127 127
128 128 ui.debug("sending %s command\n" % cmd)
129 129 q = [('cmd', cmd)]
130 130 headersize = 0
131 131 # Important: don't use self.capable() here or else you end up
132 132 # with infinite recursion when trying to look up capabilities
133 133 # for the first time.
134 134 postargsok = caps is not None and 'httppostargs' in caps
135 135
136 136 # Send arguments via POST.
137 137 if postargsok and args:
138 138 strargs = urlreq.urlencode(sorted(args.items()))
139 139 if not data:
140 140 data = strargs
141 141 else:
142 142 if isinstance(data, bytes):
143 143 i = io.BytesIO(data)
144 144 i.length = len(data)
145 145 data = i
146 146 argsio = io.BytesIO(strargs)
147 147 argsio.length = len(strargs)
148 148 data = _multifile(argsio, data)
149 149 headers[r'X-HgArgs-Post'] = len(strargs)
150 150 elif args:
151 151 # Calling self.capable() can infinite loop if we are calling
152 152 # "capabilities". But that command should never accept wire
153 153 # protocol arguments. So this should never happen.
154 154 assert cmd != 'capabilities'
155 155 httpheader = capablefn('httpheader')
156 156 if httpheader:
157 157 headersize = int(httpheader.split(',', 1)[0])
158 158
159 159 # Send arguments via HTTP headers.
160 160 if headersize > 0:
161 161 # The headers can typically carry more data than the URL.
162 162 encargs = urlreq.urlencode(sorted(args.items()))
163 163 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
164 164 headersize):
165 165 headers[header] = value
166 166 # Send arguments via query string (Mercurial <1.9).
167 167 else:
168 168 q += sorted(args.items())
169 169
170 170 qs = '?%s' % urlreq.urlencode(q)
171 171 cu = "%s%s" % (repobaseurl, qs)
172 172 size = 0
173 173 if util.safehasattr(data, 'length'):
174 174 size = data.length
175 175 elif data is not None:
176 176 size = len(data)
177 177 if data is not None and r'Content-Type' not in headers:
178 178 headers[r'Content-Type'] = r'application/mercurial-0.1'
179 179
180 180 # Tell the server we accept application/mercurial-0.2 and multiple
181 181 # compression formats if the server is capable of emitting those
182 182 # payloads.
183 183 # Note: Keep this set empty by default, as client advertisement of
184 184 # protocol parameters should only occur after the handshake.
185 185 protoparams = set()
186 186
187 187 mediatypes = set()
188 188 if caps is not None:
189 189 mt = capablefn('httpmediatype')
190 190 if mt:
191 191 protoparams.add('0.1')
192 192 mediatypes = set(mt.split(','))
193 193
194 194 protoparams.add('partial-pull')
195 195
196 196 if '0.2tx' in mediatypes:
197 197 protoparams.add('0.2')
198 198
199 199 if '0.2tx' in mediatypes and capablefn('compression'):
200 200 # We /could/ compare supported compression formats and prune
201 201 # non-mutually supported or error if nothing is mutually supported.
202 202 # For now, send the full list to the server and have it error.
203 203 comps = [e.wireprotosupport().name for e in
204 204 util.compengines.supportedwireengines(util.CLIENTROLE)]
205 205 protoparams.add('comp=%s' % ','.join(comps))
206 206
207 207 if protoparams:
208 208 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
209 209 'X-HgProto',
210 210 headersize or 1024)
211 211 for header, value in protoheaders:
212 212 headers[header] = value
213 213
214 214 varyheaders = []
215 215 for header in headers:
216 216 if header.lower().startswith(r'x-hg'):
217 217 varyheaders.append(header)
218 218
219 219 if varyheaders:
220 220 headers[r'Vary'] = r','.join(sorted(varyheaders))
221 221
222 222 req = requestbuilder(pycompat.strurl(cu), data, headers)
223 223
224 224 if data is not None:
225 225 ui.debug("sending %d bytes\n" % size)
226 226 req.add_unredirected_header(r'Content-Length', r'%d' % size)
227 227
228 228 return req, cu, qs
229 229
230 230 def _reqdata(req):
231 231 """Get request data, if any. If no data, returns None."""
232 232 if pycompat.ispy3:
233 233 return req.data
234 234 if not req.has_data():
235 235 return None
236 236 return req.get_data()
237 237
238 238 def sendrequest(ui, opener, req):
239 239 """Send a prepared HTTP request.
240 240
241 241 Returns the response object.
242 242 """
243 243 dbg = ui.debug
244 244 if (ui.debugflag
245 245 and ui.configbool('devel', 'debug.peer-request')):
246 246 line = 'devel-peer-request: %s\n'
247 247 dbg(line % '%s %s' % (pycompat.bytesurl(req.get_method()),
248 248 pycompat.bytesurl(req.get_full_url())))
249 249 hgargssize = None
250 250
251 251 for header, value in sorted(req.header_items()):
252 252 header = pycompat.bytesurl(header)
253 253 value = pycompat.bytesurl(value)
254 254 if header.startswith('X-hgarg-'):
255 255 if hgargssize is None:
256 256 hgargssize = 0
257 257 hgargssize += len(value)
258 258 else:
259 259 dbg(line % ' %s %s' % (header, value))
260 260
261 261 if hgargssize is not None:
262 262 dbg(line % ' %d bytes of commands arguments in headers'
263 263 % hgargssize)
264 264 data = _reqdata(req)
265 265 if data is not None:
266 266 length = getattr(data, 'length', None)
267 267 if length is None:
268 268 length = len(data)
269 269 dbg(line % ' %d bytes of data' % length)
270 270
271 271 start = util.timer()
272 272
273 273 res = None
274 274 try:
275 275 res = opener.open(req)
276 276 except urlerr.httperror as inst:
277 277 if inst.code == 401:
278 278 raise error.Abort(_('authorization failed'))
279 279 raise
280 280 except httplib.HTTPException as inst:
281 281 ui.debug('http error requesting %s\n' %
282 282 util.hidepassword(req.get_full_url()))
283 283 ui.traceback()
284 284 raise IOError(None, inst)
285 285 finally:
286 286 if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
287 287 code = res.code if res else -1
288 288 dbg(line % ' finished in %.4f seconds (%d)'
289 289 % (util.timer() - start, code))
290 290
291 291 # Insert error handlers for common I/O failures.
292 292 urlmod.wrapresponse(res)
293 293
294 294 return res
295 295
296 296 class RedirectedRepoError(error.RepoError):
297 297 def __init__(self, msg, respurl):
298 298 super(RedirectedRepoError, self).__init__(msg)
299 299 self.respurl = respurl
300 300
301 301 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,
302 302 allowcbor=False):
303 303 # record the url we got redirected to
304 304 redirected = False
305 305 respurl = pycompat.bytesurl(resp.geturl())
306 306 if respurl.endswith(qs):
307 307 respurl = respurl[:-len(qs)]
308 308 qsdropped = False
309 309 else:
310 310 qsdropped = True
311 311
312 312 if baseurl.rstrip('/') != respurl.rstrip('/'):
313 313 redirected = True
314 314 if not ui.quiet:
315 315 ui.warn(_('real URL is %s\n') % respurl)
316 316
317 317 try:
318 318 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
319 319 except AttributeError:
320 320 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
321 321
322 322 safeurl = util.hidepassword(baseurl)
323 323 if proto.startswith('application/hg-error'):
324 324 raise error.OutOfBandError(resp.read())
325 325
326 326 # Pre 1.0 versions of Mercurial used text/plain and
327 327 # application/hg-changegroup. We don't support such old servers.
328 328 if not proto.startswith('application/mercurial-'):
329 329 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
330 330 msg = _("'%s' does not appear to be an hg repository:\n"
331 331 "---%%<--- (%s)\n%s\n---%%<---\n") % (
332 332 safeurl, proto or 'no content-type', resp.read(1024))
333 333
334 334 # Some servers may strip the query string from the redirect. We
335 335 # raise a special error type so callers can react to this specially.
336 336 if redirected and qsdropped:
337 337 raise RedirectedRepoError(msg, respurl)
338 338 else:
339 339 raise error.RepoError(msg)
340 340
341 341 try:
342 342 subtype = proto.split('-', 1)[1]
343 343
344 344 # Unless we end up supporting CBOR in the legacy wire protocol,
345 345 # this should ONLY be encountered for the initial capabilities
346 346 # request during handshake.
347 347 if subtype == 'cbor':
348 348 if allowcbor:
349 349 return respurl, proto, resp
350 350 else:
351 351 raise error.RepoError(_('unexpected CBOR response from '
352 352 'server'))
353 353
354 354 version_info = tuple([int(n) for n in subtype.split('.')])
355 355 except ValueError:
356 356 raise error.RepoError(_("'%s' sent a broken Content-Type "
357 357 "header (%s)") % (safeurl, proto))
358 358
359 359 # TODO consider switching to a decompression reader that uses
360 360 # generators.
361 361 if version_info == (0, 1):
362 362 if compressible:
363 363 resp = util.compengines['zlib'].decompressorreader(resp)
364 364
365 365 elif version_info == (0, 2):
366 366 # application/mercurial-0.2 always identifies the compression
367 367 # engine in the payload header.
368 368 elen = struct.unpack('B', util.readexactly(resp, 1))[0]
369 369 ename = util.readexactly(resp, elen)
370 370 engine = util.compengines.forwiretype(ename)
371 371
372 372 resp = engine.decompressorreader(resp)
373 373 else:
374 374 raise error.RepoError(_("'%s' uses newer protocol %s") %
375 375 (safeurl, subtype))
376 376
377 377 return respurl, proto, resp
378 378
379 379 class httppeer(wireprotov1peer.wirepeer):
380 380 def __init__(self, ui, path, url, opener, requestbuilder, caps):
381 381 self.ui = ui
382 382 self._path = path
383 383 self._url = url
384 384 self._caps = caps
385 385 self._urlopener = opener
386 386 self._requestbuilder = requestbuilder
387 387
388 388 def __del__(self):
389 389 for h in self._urlopener.handlers:
390 390 h.close()
391 391 getattr(h, "close_all", lambda: None)()
392 392
393 393 # Begin of ipeerconnection interface.
394 394
395 395 def url(self):
396 396 return self._path
397 397
398 398 def local(self):
399 399 return None
400 400
401 401 def peer(self):
402 402 return self
403 403
404 404 def canpush(self):
405 405 return True
406 406
407 407 def close(self):
408 408 self.ui.note(_('(sent %d HTTP requests and %d bytes; '
409 409 'received %d bytes in responses)\n') %
410 410 (self._urlopener.requestscount,
411 411 self._urlopener.sentbytescount,
412 412 self._urlopener.receivedbytescount))
413 413
414 414 # End of ipeerconnection interface.
415 415
416 416 # Begin of ipeercommands interface.
417 417
418 418 def capabilities(self):
419 419 return self._caps
420 420
421 421 # End of ipeercommands interface.
422 422
423 423 def _callstream(self, cmd, _compressible=False, **args):
424 424 args = pycompat.byteskwargs(args)
425 425
426 426 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
427 427 self._caps, self.capable,
428 428 self._url, cmd, args)
429 429
430 430 resp = sendrequest(self.ui, self._urlopener, req)
431 431
432 432 self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
433 433 resp, _compressible)
434 434
435 435 return resp
436 436
437 437 def _call(self, cmd, **args):
438 438 fp = self._callstream(cmd, **args)
439 439 try:
440 440 return fp.read()
441 441 finally:
442 442 # if using keepalive, allow connection to be reused
443 443 fp.close()
444 444
445 445 def _callpush(self, cmd, cg, **args):
446 446 # have to stream bundle to a temp file because we do not have
447 447 # http 1.1 chunked transfer.
448 448
449 449 types = self.capable('unbundle')
450 450 try:
451 451 types = types.split(',')
452 452 except AttributeError:
453 453 # servers older than d1b16a746db6 will send 'unbundle' as a
454 454 # boolean capability. They only support headerless/uncompressed
455 455 # bundles.
456 456 types = [""]
457 457 for x in types:
458 458 if x in bundle2.bundletypes:
459 459 type = x
460 460 break
461 461
462 462 tempname = bundle2.writebundle(self.ui, cg, None, type)
463 463 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
464 464 headers = {r'Content-Type': r'application/mercurial-0.1'}
465 465
466 466 try:
467 467 r = self._call(cmd, data=fp, headers=headers, **args)
468 468 vals = r.split('\n', 1)
469 469 if len(vals) < 2:
470 470 raise error.ResponseError(_("unexpected response:"), r)
471 471 return vals
472 472 except urlerr.httperror:
473 473 # Catch and re-raise these so we don't try and treat them
474 474 # like generic socket errors. They lack any values in
475 475 # .args on Python 3 which breaks our socket.error block.
476 476 raise
477 477 except socket.error as err:
478 478 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
479 479 raise error.Abort(_('push failed: %s') % err.args[1])
480 480 raise error.Abort(err.args[1])
481 481 finally:
482 482 fp.close()
483 483 os.unlink(tempname)
484 484
485 485 def _calltwowaystream(self, cmd, fp, **args):
486 486 fh = None
487 487 fp_ = None
488 488 filename = None
489 489 try:
490 490 # dump bundle to disk
491 491 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
492 492 fh = os.fdopen(fd, r"wb")
493 493 d = fp.read(4096)
494 494 while d:
495 495 fh.write(d)
496 496 d = fp.read(4096)
497 497 fh.close()
498 498 # start http push
499 499 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
500 500 headers = {r'Content-Type': r'application/mercurial-0.1'}
501 501 return self._callstream(cmd, data=fp_, headers=headers, **args)
502 502 finally:
503 503 if fp_ is not None:
504 504 fp_.close()
505 505 if fh is not None:
506 506 fh.close()
507 507 os.unlink(filename)
508 508
509 509 def _callcompressable(self, cmd, **args):
510 510 return self._callstream(cmd, _compressible=True, **args)
511 511
512 512 def _abort(self, exception):
513 513 raise exception
514 514
515 515 def sendv2request(ui, opener, requestbuilder, apiurl, permission, requests,
516 516 redirect):
517 wireprotoframing.populatestreamencoders()
518
519 uiencoders = ui.configlist(b'experimental', b'httppeer.v2-encoder-order')
520
521 if uiencoders:
522 encoders = []
523
524 for encoder in uiencoders:
525 if encoder not in wireprotoframing.STREAM_ENCODERS:
526 ui.warn(_(b'wire protocol version 2 encoder referenced in '
527 b'config (%s) is not known; ignoring\n') % encoder)
528 else:
529 encoders.append(encoder)
530
531 else:
532 encoders = wireprotoframing.STREAM_ENCODERS_ORDER
533
517 534 reactor = wireprotoframing.clientreactor(ui,
518 535 hasmultiplesend=False,
519 buffersends=True)
536 buffersends=True,
537 clientcontentencoders=encoders)
520 538
521 539 handler = wireprotov2peer.clienthandler(ui, reactor,
522 540 opener=opener,
523 541 requestbuilder=requestbuilder)
524 542
525 543 url = '%s/%s' % (apiurl, permission)
526 544
527 545 if len(requests) > 1:
528 546 url += '/multirequest'
529 547 else:
530 548 url += '/%s' % requests[0][0]
531 549
532 550 ui.debug('sending %d commands\n' % len(requests))
533 551 for command, args, f in requests:
534 552 ui.debug('sending command %s: %s\n' % (
535 553 command, stringutil.pprint(args, indent=2)))
536 554 assert not list(handler.callcommand(command, args, f,
537 555 redirect=redirect))
538 556
539 557 # TODO stream this.
540 558 body = b''.join(map(bytes, handler.flushcommands()))
541 559
542 560 # TODO modify user-agent to reflect v2
543 561 headers = {
544 562 r'Accept': wireprotov2server.FRAMINGTYPE,
545 563 r'Content-Type': wireprotov2server.FRAMINGTYPE,
546 564 }
547 565
548 566 req = requestbuilder(pycompat.strurl(url), body, headers)
549 567 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
550 568
551 569 try:
552 570 res = opener.open(req)
553 571 except urlerr.httperror as e:
554 572 if e.code == 401:
555 573 raise error.Abort(_('authorization failed'))
556 574
557 575 raise
558 576 except httplib.HTTPException as e:
559 577 ui.traceback()
560 578 raise IOError(None, e)
561 579
562 580 return handler, res
563 581
564 582 class queuedcommandfuture(pycompat.futures.Future):
565 583 """Wraps result() on command futures to trigger submission on call."""
566 584
567 585 def result(self, timeout=None):
568 586 if self.done():
569 587 return pycompat.futures.Future.result(self, timeout)
570 588
571 589 self._peerexecutor.sendcommands()
572 590
573 591 # sendcommands() will restore the original __class__ and self.result
574 592 # will resolve to Future.result.
575 593 return self.result(timeout)
576 594
577 595 @interfaceutil.implementer(repository.ipeercommandexecutor)
578 596 class httpv2executor(object):
579 597 def __init__(self, ui, opener, requestbuilder, apiurl, descriptor,
580 598 redirect):
581 599 self._ui = ui
582 600 self._opener = opener
583 601 self._requestbuilder = requestbuilder
584 602 self._apiurl = apiurl
585 603 self._descriptor = descriptor
586 604 self._redirect = redirect
587 605 self._sent = False
588 606 self._closed = False
589 607 self._neededpermissions = set()
590 608 self._calls = []
591 609 self._futures = weakref.WeakSet()
592 610 self._responseexecutor = None
593 611 self._responsef = None
594 612
595 613 def __enter__(self):
596 614 return self
597 615
598 616 def __exit__(self, exctype, excvalue, exctb):
599 617 self.close()
600 618
601 619 def callcommand(self, command, args):
602 620 if self._sent:
603 621 raise error.ProgrammingError('callcommand() cannot be used after '
604 622 'commands are sent')
605 623
606 624 if self._closed:
607 625 raise error.ProgrammingError('callcommand() cannot be used after '
608 626 'close()')
609 627
610 628 # The service advertises which commands are available. So if we attempt
611 629 # to call an unknown command or pass an unknown argument, we can screen
612 630 # for this.
613 631 if command not in self._descriptor['commands']:
614 632 raise error.ProgrammingError(
615 633 'wire protocol command %s is not available' % command)
616 634
617 635 cmdinfo = self._descriptor['commands'][command]
618 636 unknownargs = set(args.keys()) - set(cmdinfo.get('args', {}))
619 637
620 638 if unknownargs:
621 639 raise error.ProgrammingError(
622 640 'wire protocol command %s does not accept argument: %s' % (
623 641 command, ', '.join(sorted(unknownargs))))
624 642
625 643 self._neededpermissions |= set(cmdinfo['permissions'])
626 644
627 645 # TODO we /could/ also validate types here, since the API descriptor
628 646 # includes types...
629 647
630 648 f = pycompat.futures.Future()
631 649
632 650 # Monkeypatch it so result() triggers sendcommands(), otherwise result()
633 651 # could deadlock.
634 652 f.__class__ = queuedcommandfuture
635 653 f._peerexecutor = self
636 654
637 655 self._futures.add(f)
638 656 self._calls.append((command, args, f))
639 657
640 658 return f
641 659
642 660 def sendcommands(self):
643 661 if self._sent:
644 662 return
645 663
646 664 if not self._calls:
647 665 return
648 666
649 667 self._sent = True
650 668
651 669 # Unhack any future types so caller sees a clean type and so we
652 670 # break reference cycle.
653 671 for f in self._futures:
654 672 if isinstance(f, queuedcommandfuture):
655 673 f.__class__ = pycompat.futures.Future
656 674 f._peerexecutor = None
657 675
658 676 # Mark the future as running and filter out cancelled futures.
659 677 calls = [(command, args, f)
660 678 for command, args, f in self._calls
661 679 if f.set_running_or_notify_cancel()]
662 680
663 681 # Clear out references, prevent improper object usage.
664 682 self._calls = None
665 683
666 684 if not calls:
667 685 return
668 686
669 687 permissions = set(self._neededpermissions)
670 688
671 689 if 'push' in permissions and 'pull' in permissions:
672 690 permissions.remove('pull')
673 691
674 692 if len(permissions) > 1:
675 693 raise error.RepoError(_('cannot make request requiring multiple '
676 694 'permissions: %s') %
677 695 _(', ').join(sorted(permissions)))
678 696
679 697 permission = {
680 698 'push': 'rw',
681 699 'pull': 'ro',
682 700 }[permissions.pop()]
683 701
684 702 handler, resp = sendv2request(
685 703 self._ui, self._opener, self._requestbuilder, self._apiurl,
686 704 permission, calls, self._redirect)
687 705
688 706 # TODO we probably want to validate the HTTP code, media type, etc.
689 707
690 708 self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)
691 709 self._responsef = self._responseexecutor.submit(self._handleresponse,
692 710 handler, resp)
693 711
694 712 def close(self):
695 713 if self._closed:
696 714 return
697 715
698 716 self.sendcommands()
699 717
700 718 self._closed = True
701 719
702 720 if not self._responsef:
703 721 return
704 722
705 723 # TODO ^C here may not result in immediate program termination.
706 724
707 725 try:
708 726 self._responsef.result()
709 727 finally:
710 728 self._responseexecutor.shutdown(wait=True)
711 729 self._responsef = None
712 730 self._responseexecutor = None
713 731
714 732 # If any of our futures are still in progress, mark them as
715 733 # errored, otherwise a result() could wait indefinitely.
716 734 for f in self._futures:
717 735 if not f.done():
718 736 f.set_exception(error.ResponseError(
719 737 _('unfulfilled command response')))
720 738
721 739 self._futures = None
722 740
723 741 def _handleresponse(self, handler, resp):
724 742 # Called in a thread to read the response.
725 743
726 744 while handler.readdata(resp):
727 745 pass
728 746
729 747 # TODO implement interface for version 2 peers
730 748 @interfaceutil.implementer(repository.ipeerconnection,
731 749 repository.ipeercapabilities,
732 750 repository.ipeerrequests)
733 751 class httpv2peer(object):
734 752 def __init__(self, ui, repourl, apipath, opener, requestbuilder,
735 753 apidescriptor):
736 754 self.ui = ui
737 755
738 756 if repourl.endswith('/'):
739 757 repourl = repourl[:-1]
740 758
741 759 self._url = repourl
742 760 self._apipath = apipath
743 761 self._apiurl = '%s/%s' % (repourl, apipath)
744 762 self._opener = opener
745 763 self._requestbuilder = requestbuilder
746 764 self._descriptor = apidescriptor
747 765
748 766 self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor)
749 767
750 768 # Start of ipeerconnection.
751 769
752 770 def url(self):
753 771 return self._url
754 772
755 773 def local(self):
756 774 return None
757 775
758 776 def peer(self):
759 777 return self
760 778
761 779 def canpush(self):
762 780 # TODO change once implemented.
763 781 return False
764 782
765 783 def close(self):
766 784 self.ui.note(_('(sent %d HTTP requests and %d bytes; '
767 785 'received %d bytes in responses)\n') %
768 786 (self._opener.requestscount,
769 787 self._opener.sentbytescount,
770 788 self._opener.receivedbytescount))
771 789
772 790 # End of ipeerconnection.
773 791
774 792 # Start of ipeercapabilities.
775 793
776 794 def capable(self, name):
777 795 # The capabilities used internally historically map to capabilities
778 796 # advertised from the "capabilities" wire protocol command. However,
779 797 # version 2 of that command works differently.
780 798
781 799 # Maps to commands that are available.
782 800 if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):
783 801 return True
784 802
785 803 # Other concepts.
786 804 if name in ('bundle2'):
787 805 return True
788 806
789 807 # Alias command-* to presence of command of that name.
790 808 if name.startswith('command-'):
791 809 return name[len('command-'):] in self._descriptor['commands']
792 810
793 811 return False
794 812
795 813 def requirecap(self, name, purpose):
796 814 if self.capable(name):
797 815 return
798 816
799 817 raise error.CapabilityError(
800 818 _('cannot %s; client or remote repository does not support the %r '
801 819 'capability') % (purpose, name))
802 820
803 821 # End of ipeercapabilities.
804 822
805 823 def _call(self, name, **args):
806 824 with self.commandexecutor() as e:
807 825 return e.callcommand(name, args).result()
808 826
809 827 def commandexecutor(self):
810 828 return httpv2executor(self.ui, self._opener, self._requestbuilder,
811 829 self._apiurl, self._descriptor, self._redirect)
812 830
813 831 # Registry of API service names to metadata about peers that handle it.
814 832 #
815 833 # The following keys are meaningful:
816 834 #
817 835 # init
818 836 # Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
819 837 # apidescriptor) to create a peer.
820 838 #
821 839 # priority
822 840 # Integer priority for the service. If we could choose from multiple
823 841 # services, we choose the one with the highest priority.
824 842 API_PEERS = {
825 843 wireprototypes.HTTP_WIREPROTO_V2: {
826 844 'init': httpv2peer,
827 845 'priority': 50,
828 846 },
829 847 }
830 848
831 849 def performhandshake(ui, url, opener, requestbuilder):
832 850 # The handshake is a request to the capabilities command.
833 851
834 852 caps = None
835 853 def capable(x):
836 854 raise error.ProgrammingError('should not be called')
837 855
838 856 args = {}
839 857
840 858 # The client advertises support for newer protocols by adding an
841 859 # X-HgUpgrade-* header with a list of supported APIs and an
842 860 # X-HgProto-* header advertising which serializing formats it supports.
843 861 # We only support the HTTP version 2 transport and CBOR responses for
844 862 # now.
845 863 advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')
846 864
847 865 if advertisev2:
848 866 args['headers'] = {
849 867 r'X-HgProto-1': r'cbor',
850 868 }
851 869
852 870 args['headers'].update(
853 871 encodevalueinheaders(' '.join(sorted(API_PEERS)),
854 872 'X-HgUpgrade',
855 873 # We don't know the header limit this early.
856 874 # So make it small.
857 875 1024))
858 876
859 877 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
860 878 capable, url, 'capabilities',
861 879 args)
862 880 resp = sendrequest(ui, opener, req)
863 881
864 882 # The server may redirect us to the repo root, stripping the
865 883 # ?cmd=capabilities query string from the URL. The server would likely
866 884 # return HTML in this case and ``parsev1commandresponse()`` would raise.
867 885 # We catch this special case and re-issue the capabilities request against
868 886 # the new URL.
869 887 #
870 888 # We should ideally not do this, as a redirect that drops the query
871 889 # string from the URL is arguably a server bug. (Garbage in, garbage out).
872 890 # However, Mercurial clients for several years appeared to handle this
873 891 # issue without behavior degradation. And according to issue 5860, it may
874 892 # be a longstanding bug in some server implementations. So we allow a
875 893 # redirect that drops the query string to "just work."
876 894 try:
877 895 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
878 896 compressible=False,
879 897 allowcbor=advertisev2)
880 898 except RedirectedRepoError as e:
881 899 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
882 900 capable, e.respurl,
883 901 'capabilities', args)
884 902 resp = sendrequest(ui, opener, req)
885 903 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
886 904 compressible=False,
887 905 allowcbor=advertisev2)
888 906
889 907 try:
890 908 rawdata = resp.read()
891 909 finally:
892 910 resp.close()
893 911
894 912 if not ct.startswith('application/mercurial-'):
895 913 raise error.ProgrammingError('unexpected content-type: %s' % ct)
896 914
897 915 if advertisev2:
898 916 if ct == 'application/mercurial-cbor':
899 917 try:
900 918 info = cborutil.decodeall(rawdata)[0]
901 919 except cborutil.CBORDecodeError:
902 920 raise error.Abort(_('error decoding CBOR from remote server'),
903 921 hint=_('try again and consider contacting '
904 922 'the server operator'))
905 923
906 924 # We got a legacy response. That's fine.
907 925 elif ct in ('application/mercurial-0.1', 'application/mercurial-0.2'):
908 926 info = {
909 927 'v1capabilities': set(rawdata.split())
910 928 }
911 929
912 930 else:
913 931 raise error.RepoError(
914 932 _('unexpected response type from server: %s') % ct)
915 933 else:
916 934 info = {
917 935 'v1capabilities': set(rawdata.split())
918 936 }
919 937
920 938 return respurl, info
921 939
922 940 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
923 941 """Construct an appropriate HTTP peer instance.
924 942
925 943 ``opener`` is an ``url.opener`` that should be used to establish
926 944 connections, perform HTTP requests.
927 945
928 946 ``requestbuilder`` is the type used for constructing HTTP requests.
929 947 It exists as an argument so extensions can override the default.
930 948 """
931 949 u = util.url(path)
932 950 if u.query or u.fragment:
933 951 raise error.Abort(_('unsupported URL component: "%s"') %
934 952 (u.query or u.fragment))
935 953
936 954 # urllib cannot handle URLs with embedded user or passwd.
937 955 url, authinfo = u.authinfo()
938 956 ui.debug('using %s\n' % url)
939 957
940 958 opener = opener or urlmod.opener(ui, authinfo)
941 959
942 960 respurl, info = performhandshake(ui, url, opener, requestbuilder)
943 961
944 962 # Given the intersection of APIs that both we and the server support,
945 963 # sort by their advertised priority and pick the first one.
946 964 #
947 965 # TODO consider making this request-based and interface driven. For
948 966 # example, the caller could say "I want a peer that does X." It's quite
949 967 # possible that not all peers would do that. Since we know the service
950 968 # capabilities, we could filter out services not meeting the
951 969 # requirements. Possibly by consulting the interfaces defined by the
952 970 # peer type.
953 971 apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())
954 972
955 973 preferredchoices = sorted(apipeerchoices,
956 974 key=lambda x: API_PEERS[x]['priority'],
957 975 reverse=True)
958 976
959 977 for service in preferredchoices:
960 978 apipath = '%s/%s' % (info['apibase'].rstrip('/'), service)
961 979
962 980 return API_PEERS[service]['init'](ui, respurl, apipath, opener,
963 981 requestbuilder,
964 982 info['apis'][service])
965 983
966 984 # Failed to construct an API peer. Fall back to legacy.
967 985 return httppeer(ui, path, respurl, opener, requestbuilder,
968 986 info['v1capabilities'])
969 987
970 988 def instance(ui, path, create, intents=None, createopts=None):
971 989 if create:
972 990 raise error.Abort(_('cannot create new http repository'))
973 991 try:
974 992 if path.startswith('https:') and not urlmod.has_https:
975 993 raise error.Abort(_('Python support for SSL and HTTPS '
976 994 'is not installed'))
977 995
978 996 inst = makepeer(ui, path)
979 997
980 998 return inst
981 999 except error.RepoError as httpexception:
982 1000 try:
983 1001 r = statichttprepo.instance(ui, "static-" + path, create)
984 1002 ui.note(_('(falling back to static-http)\n'))
985 1003 return r
986 1004 except error.RepoError:
987 1005 raise httpexception # use the original http RepoError instead
@@ -1,1806 +1,1827
1 1 # wireprotoframing.py - unified framing protocol for wire protocol
2 2 #
3 3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # This file contains functionality to support the unified frame-based wire
9 9 # protocol. For details about the protocol, see
10 10 # `hg help internals.wireprotocol`.
11 11
12 12 from __future__ import absolute_import
13 13
14 14 import collections
15 15 import struct
16 16
17 17 from .i18n import _
18 18 from .thirdparty import (
19 19 attr,
20 20 )
21 21 from . import (
22 22 encoding,
23 23 error,
24 24 pycompat,
25 25 util,
26 26 wireprototypes,
27 27 )
28 28 from .utils import (
29 29 cborutil,
30 30 stringutil,
31 31 )
32 32
33 33 FRAME_HEADER_SIZE = 8
34 34 DEFAULT_MAX_FRAME_SIZE = 32768
35 35
36 36 STREAM_FLAG_BEGIN_STREAM = 0x01
37 37 STREAM_FLAG_END_STREAM = 0x02
38 38 STREAM_FLAG_ENCODING_APPLIED = 0x04
39 39
40 40 STREAM_FLAGS = {
41 41 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
42 42 b'stream-end': STREAM_FLAG_END_STREAM,
43 43 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
44 44 }
45 45
46 46 FRAME_TYPE_COMMAND_REQUEST = 0x01
47 47 FRAME_TYPE_COMMAND_DATA = 0x02
48 48 FRAME_TYPE_COMMAND_RESPONSE = 0x03
49 49 FRAME_TYPE_ERROR_RESPONSE = 0x05
50 50 FRAME_TYPE_TEXT_OUTPUT = 0x06
51 51 FRAME_TYPE_PROGRESS = 0x07
52 52 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS = 0x08
53 53 FRAME_TYPE_STREAM_SETTINGS = 0x09
54 54
55 55 FRAME_TYPES = {
56 56 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
57 57 b'command-data': FRAME_TYPE_COMMAND_DATA,
58 58 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
59 59 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
60 60 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
61 61 b'progress': FRAME_TYPE_PROGRESS,
62 62 b'sender-protocol-settings': FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
63 63 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
64 64 }
65 65
66 66 FLAG_COMMAND_REQUEST_NEW = 0x01
67 67 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
68 68 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
69 69 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
70 70
71 71 FLAGS_COMMAND_REQUEST = {
72 72 b'new': FLAG_COMMAND_REQUEST_NEW,
73 73 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
74 74 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
75 75 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
76 76 }
77 77
78 78 FLAG_COMMAND_DATA_CONTINUATION = 0x01
79 79 FLAG_COMMAND_DATA_EOS = 0x02
80 80
81 81 FLAGS_COMMAND_DATA = {
82 82 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
83 83 b'eos': FLAG_COMMAND_DATA_EOS,
84 84 }
85 85
86 86 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
87 87 FLAG_COMMAND_RESPONSE_EOS = 0x02
88 88
89 89 FLAGS_COMMAND_RESPONSE = {
90 90 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
91 91 b'eos': FLAG_COMMAND_RESPONSE_EOS,
92 92 }
93 93
94 94 FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION = 0x01
95 95 FLAG_SENDER_PROTOCOL_SETTINGS_EOS = 0x02
96 96
97 97 FLAGS_SENDER_PROTOCOL_SETTINGS = {
98 98 b'continuation': FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION,
99 99 b'eos': FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
100 100 }
101 101
102 102 FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION = 0x01
103 103 FLAG_STREAM_ENCODING_SETTINGS_EOS = 0x02
104 104
105 105 FLAGS_STREAM_ENCODING_SETTINGS = {
106 106 b'continuation': FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION,
107 107 b'eos': FLAG_STREAM_ENCODING_SETTINGS_EOS,
108 108 }
109 109
110 110 # Maps frame types to their available flags.
111 111 FRAME_TYPE_FLAGS = {
112 112 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
113 113 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
114 114 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
115 115 FRAME_TYPE_ERROR_RESPONSE: {},
116 116 FRAME_TYPE_TEXT_OUTPUT: {},
117 117 FRAME_TYPE_PROGRESS: {},
118 118 FRAME_TYPE_SENDER_PROTOCOL_SETTINGS: FLAGS_SENDER_PROTOCOL_SETTINGS,
119 119 FRAME_TYPE_STREAM_SETTINGS: FLAGS_STREAM_ENCODING_SETTINGS,
120 120 }
121 121
122 122 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
123 123
124 124 def humanflags(mapping, value):
125 125 """Convert a numeric flags value to a human value, using a mapping table."""
126 126 namemap = {v: k for k, v in mapping.iteritems()}
127 127 flags = []
128 128 val = 1
129 129 while value >= val:
130 130 if value & val:
131 131 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
132 132 val <<= 1
133 133
134 134 return b'|'.join(flags)
135 135
136 136 @attr.s(slots=True)
137 137 class frameheader(object):
138 138 """Represents the data in a frame header."""
139 139
140 140 length = attr.ib()
141 141 requestid = attr.ib()
142 142 streamid = attr.ib()
143 143 streamflags = attr.ib()
144 144 typeid = attr.ib()
145 145 flags = attr.ib()
146 146
147 147 @attr.s(slots=True, repr=False)
148 148 class frame(object):
149 149 """Represents a parsed frame."""
150 150
151 151 requestid = attr.ib()
152 152 streamid = attr.ib()
153 153 streamflags = attr.ib()
154 154 typeid = attr.ib()
155 155 flags = attr.ib()
156 156 payload = attr.ib()
157 157
158 158 @encoding.strmethod
159 159 def __repr__(self):
160 160 typename = '<unknown 0x%02x>' % self.typeid
161 161 for name, value in FRAME_TYPES.iteritems():
162 162 if value == self.typeid:
163 163 typename = name
164 164 break
165 165
166 166 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
167 167 'type=%s; flags=%s)' % (
168 168 len(self.payload), self.requestid, self.streamid,
169 169 humanflags(STREAM_FLAGS, self.streamflags), typename,
170 170 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
171 171
172 172 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
173 173 """Assemble a frame into a byte array."""
174 174 # TODO assert size of payload.
175 175 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
176 176
177 177 # 24 bits length
178 178 # 16 bits request id
179 179 # 8 bits stream id
180 180 # 8 bits stream flags
181 181 # 4 bits type
182 182 # 4 bits flags
183 183
184 184 l = struct.pack(r'<I', len(payload))
185 185 frame[0:3] = l[0:3]
186 186 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
187 187 frame[7] = (typeid << 4) | flags
188 188 frame[8:] = payload
189 189
190 190 return frame
191 191
192 192 def makeframefromhumanstring(s):
193 193 """Create a frame from a human readable string
194 194
195 195 Strings have the form:
196 196
197 197 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
198 198
199 199 This can be used by user-facing applications and tests for creating
200 200 frames easily without having to type out a bunch of constants.
201 201
202 202 Request ID and stream IDs are integers.
203 203
204 204 Stream flags, frame type, and flags can be specified by integer or
205 205 named constant.
206 206
207 207 Flags can be delimited by `|` to bitwise OR them together.
208 208
209 209 If the payload begins with ``cbor:``, the following string will be
210 210 evaluated as Python literal and the resulting object will be fed into
211 211 a CBOR encoder. Otherwise, the payload is interpreted as a Python
212 212 byte string literal.
213 213 """
214 214 fields = s.split(b' ', 5)
215 215 requestid, streamid, streamflags, frametype, frameflags, payload = fields
216 216
217 217 requestid = int(requestid)
218 218 streamid = int(streamid)
219 219
220 220 finalstreamflags = 0
221 221 for flag in streamflags.split(b'|'):
222 222 if flag in STREAM_FLAGS:
223 223 finalstreamflags |= STREAM_FLAGS[flag]
224 224 else:
225 225 finalstreamflags |= int(flag)
226 226
227 227 if frametype in FRAME_TYPES:
228 228 frametype = FRAME_TYPES[frametype]
229 229 else:
230 230 frametype = int(frametype)
231 231
232 232 finalflags = 0
233 233 validflags = FRAME_TYPE_FLAGS[frametype]
234 234 for flag in frameflags.split(b'|'):
235 235 if flag in validflags:
236 236 finalflags |= validflags[flag]
237 237 else:
238 238 finalflags |= int(flag)
239 239
240 240 if payload.startswith(b'cbor:'):
241 241 payload = b''.join(cborutil.streamencode(
242 242 stringutil.evalpythonliteral(payload[5:])))
243 243
244 244 else:
245 245 payload = stringutil.unescapestr(payload)
246 246
247 247 return makeframe(requestid=requestid, streamid=streamid,
248 248 streamflags=finalstreamflags, typeid=frametype,
249 249 flags=finalflags, payload=payload)
250 250
251 251 def parseheader(data):
252 252 """Parse a unified framing protocol frame header from a buffer.
253 253
254 254 The header is expected to be in the buffer at offset 0 and the
255 255 buffer is expected to be large enough to hold a full header.
256 256 """
257 257 # 24 bits payload length (little endian)
258 258 # 16 bits request ID
259 259 # 8 bits stream ID
260 260 # 8 bits stream flags
261 261 # 4 bits frame type
262 262 # 4 bits frame flags
263 263 # ... payload
264 264 framelength = data[0] + 256 * data[1] + 16384 * data[2]
265 265 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
266 266 typeflags = data[7]
267 267
268 268 frametype = (typeflags & 0xf0) >> 4
269 269 frameflags = typeflags & 0x0f
270 270
271 271 return frameheader(framelength, requestid, streamid, streamflags,
272 272 frametype, frameflags)
273 273
274 274 def readframe(fh):
275 275 """Read a unified framing protocol frame from a file object.
276 276
277 277 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
278 278 None if no frame is available. May raise if a malformed frame is
279 279 seen.
280 280 """
281 281 header = bytearray(FRAME_HEADER_SIZE)
282 282
283 283 readcount = fh.readinto(header)
284 284
285 285 if readcount == 0:
286 286 return None
287 287
288 288 if readcount != FRAME_HEADER_SIZE:
289 289 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
290 290 (readcount, header))
291 291
292 292 h = parseheader(header)
293 293
294 294 payload = fh.read(h.length)
295 295 if len(payload) != h.length:
296 296 raise error.Abort(_('frame length error: expected %d; got %d') %
297 297 (h.length, len(payload)))
298 298
299 299 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
300 300 payload)
301 301
302 302 def createcommandframes(stream, requestid, cmd, args, datafh=None,
303 303 maxframesize=DEFAULT_MAX_FRAME_SIZE,
304 304 redirect=None):
305 305 """Create frames necessary to transmit a request to run a command.
306 306
307 307 This is a generator of bytearrays. Each item represents a frame
308 308 ready to be sent over the wire to a peer.
309 309 """
310 310 data = {b'name': cmd}
311 311 if args:
312 312 data[b'args'] = args
313 313
314 314 if redirect:
315 315 data[b'redirect'] = redirect
316 316
317 317 data = b''.join(cborutil.streamencode(data))
318 318
319 319 offset = 0
320 320
321 321 while True:
322 322 flags = 0
323 323
324 324 # Must set new or continuation flag.
325 325 if not offset:
326 326 flags |= FLAG_COMMAND_REQUEST_NEW
327 327 else:
328 328 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
329 329
330 330 # Data frames is set on all frames.
331 331 if datafh:
332 332 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
333 333
334 334 payload = data[offset:offset + maxframesize]
335 335 offset += len(payload)
336 336
337 337 if len(payload) == maxframesize and offset < len(data):
338 338 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
339 339
340 340 yield stream.makeframe(requestid=requestid,
341 341 typeid=FRAME_TYPE_COMMAND_REQUEST,
342 342 flags=flags,
343 343 payload=payload)
344 344
345 345 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
346 346 break
347 347
348 348 if datafh:
349 349 while True:
350 350 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
351 351
352 352 done = False
353 353 if len(data) == DEFAULT_MAX_FRAME_SIZE:
354 354 flags = FLAG_COMMAND_DATA_CONTINUATION
355 355 else:
356 356 flags = FLAG_COMMAND_DATA_EOS
357 357 assert datafh.read(1) == b''
358 358 done = True
359 359
360 360 yield stream.makeframe(requestid=requestid,
361 361 typeid=FRAME_TYPE_COMMAND_DATA,
362 362 flags=flags,
363 363 payload=data)
364 364
365 365 if done:
366 366 break
367 367
368 368 def createcommandresponseframesfrombytes(stream, requestid, data,
369 369 maxframesize=DEFAULT_MAX_FRAME_SIZE):
370 370 """Create a raw frame to send a bytes response from static bytes input.
371 371
372 372 Returns a generator of bytearrays.
373 373 """
374 374 # Automatically send the overall CBOR response map.
375 375 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
376 376 if len(overall) > maxframesize:
377 377 raise error.ProgrammingError('not yet implemented')
378 378
379 379 # Simple case where we can fit the full response in a single frame.
380 380 if len(overall) + len(data) <= maxframesize:
381 381 flags = FLAG_COMMAND_RESPONSE_EOS
382 382 yield stream.makeframe(requestid=requestid,
383 383 typeid=FRAME_TYPE_COMMAND_RESPONSE,
384 384 flags=flags,
385 385 payload=overall + data)
386 386 return
387 387
388 388 # It's easier to send the overall CBOR map in its own frame than to track
389 389 # offsets.
390 390 yield stream.makeframe(requestid=requestid,
391 391 typeid=FRAME_TYPE_COMMAND_RESPONSE,
392 392 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
393 393 payload=overall)
394 394
395 395 offset = 0
396 396 while True:
397 397 chunk = data[offset:offset + maxframesize]
398 398 offset += len(chunk)
399 399 done = offset == len(data)
400 400
401 401 if done:
402 402 flags = FLAG_COMMAND_RESPONSE_EOS
403 403 else:
404 404 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
405 405
406 406 yield stream.makeframe(requestid=requestid,
407 407 typeid=FRAME_TYPE_COMMAND_RESPONSE,
408 408 flags=flags,
409 409 payload=chunk)
410 410
411 411 if done:
412 412 break
413 413
414 414 def createbytesresponseframesfromgen(stream, requestid, gen,
415 415 maxframesize=DEFAULT_MAX_FRAME_SIZE):
416 416 """Generator of frames from a generator of byte chunks.
417 417
418 418 This assumes that another frame will follow whatever this emits. i.e.
419 419 this always emits the continuation flag and never emits the end-of-stream
420 420 flag.
421 421 """
422 422 cb = util.chunkbuffer(gen)
423 423 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
424 424
425 425 while True:
426 426 chunk = cb.read(maxframesize)
427 427 if not chunk:
428 428 break
429 429
430 430 yield stream.makeframe(requestid=requestid,
431 431 typeid=FRAME_TYPE_COMMAND_RESPONSE,
432 432 flags=flags,
433 433 payload=chunk)
434 434
435 435 flags |= FLAG_COMMAND_RESPONSE_CONTINUATION
436 436
437 437 def createcommandresponseokframe(stream, requestid):
438 438 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
439 439
440 440 return stream.makeframe(requestid=requestid,
441 441 typeid=FRAME_TYPE_COMMAND_RESPONSE,
442 442 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
443 443 payload=overall)
444 444
445 445 def createcommandresponseeosframe(stream, requestid):
446 446 """Create an empty payload frame representing command end-of-stream."""
447 447 return stream.makeframe(requestid=requestid,
448 448 typeid=FRAME_TYPE_COMMAND_RESPONSE,
449 449 flags=FLAG_COMMAND_RESPONSE_EOS,
450 450 payload=b'')
451 451
452 452 def createalternatelocationresponseframe(stream, requestid, location):
453 453 data = {
454 454 b'status': b'redirect',
455 455 b'location': {
456 456 b'url': location.url,
457 457 b'mediatype': location.mediatype,
458 458 }
459 459 }
460 460
461 461 for a in (r'size', r'fullhashes', r'fullhashseed', r'serverdercerts',
462 462 r'servercadercerts'):
463 463 value = getattr(location, a)
464 464 if value is not None:
465 465 data[b'location'][pycompat.bytestr(a)] = value
466 466
467 467 return stream.makeframe(requestid=requestid,
468 468 typeid=FRAME_TYPE_COMMAND_RESPONSE,
469 469 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
470 470 payload=b''.join(cborutil.streamencode(data)))
471 471
472 472 def createcommanderrorresponse(stream, requestid, message, args=None):
473 473 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
474 474 # formatting works consistently?
475 475 m = {
476 476 b'status': b'error',
477 477 b'error': {
478 478 b'message': message,
479 479 }
480 480 }
481 481
482 482 if args:
483 483 m[b'error'][b'args'] = args
484 484
485 485 overall = b''.join(cborutil.streamencode(m))
486 486
487 487 yield stream.makeframe(requestid=requestid,
488 488 typeid=FRAME_TYPE_COMMAND_RESPONSE,
489 489 flags=FLAG_COMMAND_RESPONSE_EOS,
490 490 payload=overall)
491 491
492 492 def createerrorframe(stream, requestid, msg, errtype):
493 493 # TODO properly handle frame size limits.
494 494 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
495 495
496 496 payload = b''.join(cborutil.streamencode({
497 497 b'type': errtype,
498 498 b'message': [{b'msg': msg}],
499 499 }))
500 500
501 501 yield stream.makeframe(requestid=requestid,
502 502 typeid=FRAME_TYPE_ERROR_RESPONSE,
503 503 flags=0,
504 504 payload=payload)
505 505
506 506 def createtextoutputframe(stream, requestid, atoms,
507 507 maxframesize=DEFAULT_MAX_FRAME_SIZE):
508 508 """Create a text output frame to render text to people.
509 509
510 510 ``atoms`` is a 3-tuple of (formatting string, args, labels).
511 511
512 512 The formatting string contains ``%s`` tokens to be replaced by the
513 513 corresponding indexed entry in ``args``. ``labels`` is an iterable of
514 514 formatters to be applied at rendering time. In terms of the ``ui``
515 515 class, each atom corresponds to a ``ui.write()``.
516 516 """
517 517 atomdicts = []
518 518
519 519 for (formatting, args, labels) in atoms:
520 520 # TODO look for localstr, other types here?
521 521
522 522 if not isinstance(formatting, bytes):
523 523 raise ValueError('must use bytes formatting strings')
524 524 for arg in args:
525 525 if not isinstance(arg, bytes):
526 526 raise ValueError('must use bytes for arguments')
527 527 for label in labels:
528 528 if not isinstance(label, bytes):
529 529 raise ValueError('must use bytes for labels')
530 530
531 531 # Formatting string must be ASCII.
532 532 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
533 533
534 534 # Arguments must be UTF-8.
535 535 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
536 536
537 537 # Labels must be ASCII.
538 538 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
539 539 for l in labels]
540 540
541 541 atom = {b'msg': formatting}
542 542 if args:
543 543 atom[b'args'] = args
544 544 if labels:
545 545 atom[b'labels'] = labels
546 546
547 547 atomdicts.append(atom)
548 548
549 549 payload = b''.join(cborutil.streamencode(atomdicts))
550 550
551 551 if len(payload) > maxframesize:
552 552 raise ValueError('cannot encode data in a single frame')
553 553
554 554 yield stream.makeframe(requestid=requestid,
555 555 typeid=FRAME_TYPE_TEXT_OUTPUT,
556 556 flags=0,
557 557 payload=payload)
558 558
559 559 class bufferingcommandresponseemitter(object):
560 560 """Helper object to emit command response frames intelligently.
561 561
562 562 Raw command response data is likely emitted in chunks much smaller
563 563 than what can fit in a single frame. This class exists to buffer
564 564 chunks until enough data is available to fit in a single frame.
565 565
566 566 TODO we'll need something like this when compression is supported.
567 567 So it might make sense to implement this functionality at the stream
568 568 level.
569 569 """
570 570 def __init__(self, stream, requestid, maxframesize=DEFAULT_MAX_FRAME_SIZE):
571 571 self._stream = stream
572 572 self._requestid = requestid
573 573 self._maxsize = maxframesize
574 574 self._chunks = []
575 575 self._chunkssize = 0
576 576
577 577 def send(self, data):
578 578 """Send new data for emission.
579 579
580 580 Is a generator of new frames that were derived from the new input.
581 581
582 582 If the special input ``None`` is received, flushes all buffered
583 583 data to frames.
584 584 """
585 585
586 586 if data is None:
587 587 for frame in self._flush():
588 588 yield frame
589 589 return
590 590
591 591 # There is a ton of potential to do more complicated things here.
592 592 # Our immediate goal is to coalesce small chunks into big frames,
593 593 # not achieve the fewest number of frames possible. So we go with
594 594 # a simple implementation:
595 595 #
596 596 # * If a chunk is too large for a frame, we flush and emit frames
597 597 # for the new chunk.
598 598 # * If a chunk can be buffered without total buffered size limits
599 599 # being exceeded, we do that.
600 600 # * If a chunk causes us to go over our buffering limit, we flush
601 601 # and then buffer the new chunk.
602 602
603 603 if len(data) > self._maxsize:
604 604 for frame in self._flush():
605 605 yield frame
606 606
607 607 # Now emit frames for the big chunk.
608 608 offset = 0
609 609 while True:
610 610 chunk = data[offset:offset + self._maxsize]
611 611 offset += len(chunk)
612 612
613 613 yield self._stream.makeframe(
614 614 self._requestid,
615 615 typeid=FRAME_TYPE_COMMAND_RESPONSE,
616 616 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
617 617 payload=chunk)
618 618
619 619 if offset == len(data):
620 620 return
621 621
622 622 # If we don't have enough to constitute a full frame, buffer and
623 623 # return.
624 624 if len(data) + self._chunkssize < self._maxsize:
625 625 self._chunks.append(data)
626 626 self._chunkssize += len(data)
627 627 return
628 628
629 629 # Else flush what we have and buffer the new chunk. We could do
630 630 # something more intelligent here, like break the chunk. Let's
631 631 # keep things simple for now.
632 632 for frame in self._flush():
633 633 yield frame
634 634
635 635 self._chunks.append(data)
636 636 self._chunkssize = len(data)
637 637
638 638 def _flush(self):
639 639 payload = b''.join(self._chunks)
640 640 assert len(payload) <= self._maxsize
641 641
642 642 self._chunks[:] = []
643 643 self._chunkssize = 0
644 644
645 645 yield self._stream.makeframe(
646 646 self._requestid,
647 647 typeid=FRAME_TYPE_COMMAND_RESPONSE,
648 648 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
649 649 payload=payload)
650 650
651 651 # TODO consider defining encoders/decoders using the util.compressionengine
652 652 # mechanism.
653 653
654 654 class identityencoder(object):
655 655 """Encoder for the "identity" stream encoding profile."""
656 656 def __init__(self, ui):
657 657 pass
658 658
659 659 def encode(self, data):
660 660 return data
661 661
662 662 def flush(self):
663 663 return b''
664 664
665 665 def finish(self):
666 666 return b''
667 667
668 668 class identitydecoder(object):
669 669 """Decoder for the "identity" stream encoding profile."""
670 670
671 671 def __init__(self, ui, extraobjs):
672 672 if extraobjs:
673 673 raise error.Abort(_('identity decoder received unexpected '
674 674 'additional values'))
675 675
676 676 def decode(self, data):
677 677 return data
678 678
679 679 class zlibencoder(object):
680 680 def __init__(self, ui):
681 681 import zlib
682 682 self._zlib = zlib
683 683 self._compressor = zlib.compressobj()
684 684
685 685 def encode(self, data):
686 686 return self._compressor.compress(data)
687 687
688 688 def flush(self):
689 689 # Z_SYNC_FLUSH doesn't reset compression context, which is
690 690 # what we want.
691 691 return self._compressor.flush(self._zlib.Z_SYNC_FLUSH)
692 692
693 693 def finish(self):
694 694 res = self._compressor.flush(self._zlib.Z_FINISH)
695 695 self._compressor = None
696 696 return res
697 697
698 698 class zlibdecoder(object):
699 699 def __init__(self, ui, extraobjs):
700 700 import zlib
701 701
702 702 if extraobjs:
703 703 raise error.Abort(_('zlib decoder received unexpected '
704 704 'additional values'))
705 705
706 706 self._decompressor = zlib.decompressobj()
707 707
708 708 def decode(self, data):
709 709 # Python 2's zlib module doesn't use the buffer protocol and can't
710 710 # handle all bytes-like types.
711 711 if not pycompat.ispy3 and isinstance(data, bytearray):
712 712 data = bytes(data)
713 713
714 714 return self._decompressor.decompress(data)
715 715
716 716 class zstdbaseencoder(object):
717 717 def __init__(self, level):
718 718 from . import zstd
719 719
720 720 self._zstd = zstd
721 721 cctx = zstd.ZstdCompressor(level=level)
722 722 self._compressor = cctx.compressobj()
723 723
724 724 def encode(self, data):
725 725 return self._compressor.compress(data)
726 726
727 727 def flush(self):
728 728 # COMPRESSOBJ_FLUSH_BLOCK flushes all data previously fed into the
729 729 # compressor and allows a decompressor to access all encoded data
730 730 # up to this point.
731 731 return self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_BLOCK)
732 732
733 733 def finish(self):
734 734 res = self._compressor.flush(self._zstd.COMPRESSOBJ_FLUSH_FINISH)
735 735 self._compressor = None
736 736 return res
737 737
738 738 class zstd8mbencoder(zstdbaseencoder):
739 739 def __init__(self, ui):
740 740 super(zstd8mbencoder, self).__init__(3)
741 741
742 742 class zstdbasedecoder(object):
743 743 def __init__(self, maxwindowsize):
744 744 from . import zstd
745 745 dctx = zstd.ZstdDecompressor(max_window_size=maxwindowsize)
746 746 self._decompressor = dctx.decompressobj()
747 747
748 748 def decode(self, data):
749 749 return self._decompressor.decompress(data)
750 750
751 751 class zstd8mbdecoder(zstdbasedecoder):
752 752 def __init__(self, ui, extraobjs):
753 753 if extraobjs:
754 754 raise error.Abort(_('zstd8mb decoder received unexpected '
755 755 'additional values'))
756 756
757 757 super(zstd8mbdecoder, self).__init__(maxwindowsize=8 * 1048576)
758 758
759 759 # We lazily populate this to avoid excessive module imports when importing
760 760 # this module.
761 761 STREAM_ENCODERS = {}
762 762 STREAM_ENCODERS_ORDER = []
763 763
764 764 def populatestreamencoders():
765 765 if STREAM_ENCODERS:
766 766 return
767 767
768 768 try:
769 769 from . import zstd
770 770 zstd.__version__
771 771 except ImportError:
772 772 zstd = None
773 773
774 774 # zstandard is fastest and is preferred.
775 775 if zstd:
776 776 STREAM_ENCODERS[b'zstd-8mb'] = (zstd8mbencoder, zstd8mbdecoder)
777 777 STREAM_ENCODERS_ORDER.append(b'zstd-8mb')
778 778
779 779 STREAM_ENCODERS[b'zlib'] = (zlibencoder, zlibdecoder)
780 780 STREAM_ENCODERS_ORDER.append(b'zlib')
781 781
782 782 STREAM_ENCODERS[b'identity'] = (identityencoder, identitydecoder)
783 783 STREAM_ENCODERS_ORDER.append(b'identity')
784 784
785 785 class stream(object):
786 786 """Represents a logical unidirectional series of frames."""
787 787
788 788 def __init__(self, streamid, active=False):
789 789 self.streamid = streamid
790 790 self._active = active
791 791
792 792 def makeframe(self, requestid, typeid, flags, payload):
793 793 """Create a frame to be sent out over this stream.
794 794
795 795 Only returns the frame instance. Does not actually send it.
796 796 """
797 797 streamflags = 0
798 798 if not self._active:
799 799 streamflags |= STREAM_FLAG_BEGIN_STREAM
800 800 self._active = True
801 801
802 802 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
803 803 payload)
804 804
805 805 class inputstream(stream):
806 806 """Represents a stream used for receiving data."""
807 807
808 808 def __init__(self, streamid, active=False):
809 809 super(inputstream, self).__init__(streamid, active=active)
810 810 self._decoder = None
811 811
812 812 def setdecoder(self, ui, name, extraobjs):
813 813 """Set the decoder for this stream.
814 814
815 815 Receives the stream profile name and any additional CBOR objects
816 816 decoded from the stream encoding settings frame payloads.
817 817 """
818 818 if name not in STREAM_ENCODERS:
819 819 raise error.Abort(_('unknown stream decoder: %s') % name)
820 820
821 821 self._decoder = STREAM_ENCODERS[name][1](ui, extraobjs)
822 822
823 823 def decode(self, data):
824 824 # Default is identity decoder. We don't bother instantiating one
825 825 # because it is trivial.
826 826 if not self._decoder:
827 827 return data
828 828
829 829 return self._decoder.decode(data)
830 830
831 831 def flush(self):
832 832 if not self._decoder:
833 833 return b''
834 834
835 835 return self._decoder.flush()
836 836
837 837 class outputstream(stream):
838 838 """Represents a stream used for sending data."""
839 839
840 840 def __init__(self, streamid, active=False):
841 841 super(outputstream, self).__init__(streamid, active=active)
842 842 self._encoder = None
843 843
844 844 def setencoder(self, ui, name):
845 845 """Set the encoder for this stream.
846 846
847 847 Receives the stream profile name.
848 848 """
849 849 if name not in STREAM_ENCODERS:
850 850 raise error.Abort(_('unknown stream encoder: %s') % name)
851 851
852 852 self._encoder = STREAM_ENCODERS[name][0](ui)
853 853
854 854 def encode(self, data):
855 855 if not self._encoder:
856 856 return data
857 857
858 858 return self._encoder.encode(data)
859 859
860 860 def flush(self):
861 861 if not self._encoder:
862 862 return b''
863 863
864 864 return self._encoder.flush()
865 865
866 866 def finish(self):
867 867 if not self._encoder:
868 868 return b''
869 869
870 870 self._encoder.finish()
871 871
872 872 def ensureserverstream(stream):
873 873 if stream.streamid % 2:
874 874 raise error.ProgrammingError('server should only write to even '
875 875 'numbered streams; %d is not even' %
876 876 stream.streamid)
877 877
878 878 DEFAULT_PROTOCOL_SETTINGS = {
879 879 'contentencodings': [b'identity'],
880 880 }
881 881
882 882 class serverreactor(object):
883 883 """Holds state of a server handling frame-based protocol requests.
884 884
885 885 This class is the "brain" of the unified frame-based protocol server
886 886 component. While the protocol is stateless from the perspective of
887 887 requests/commands, something needs to track which frames have been
888 888 received, what frames to expect, etc. This class is that thing.
889 889
890 890 Instances are modeled as a state machine of sorts. Instances are also
891 891 reactionary to external events. The point of this class is to encapsulate
892 892 the state of the connection and the exchange of frames, not to perform
893 893 work. Instead, callers tell this class when something occurs, like a
894 894 frame arriving. If that activity is worthy of a follow-up action (say
895 895 *run a command*), the return value of that handler will say so.
896 896
897 897 I/O and CPU intensive operations are purposefully delegated outside of
898 898 this class.
899 899
900 900 Consumers are expected to tell instances when events occur. They do so by
901 901 calling the various ``on*`` methods. These methods return a 2-tuple
902 902 describing any follow-up action(s) to take. The first element is the
903 903 name of an action to perform. The second is a data structure (usually
904 904 a dict) specific to that action that contains more information. e.g.
905 905 if the server wants to send frames back to the client, the data structure
906 906 will contain a reference to those frames.
907 907
908 908 Valid actions that consumers can be instructed to take are:
909 909
910 910 sendframes
911 911 Indicates that frames should be sent to the client. The ``framegen``
912 912 key contains a generator of frames that should be sent. The server
913 913 assumes that all frames are sent to the client.
914 914
915 915 error
916 916 Indicates that an error occurred. Consumer should probably abort.
917 917
918 918 runcommand
919 919 Indicates that the consumer should run a wire protocol command. Details
920 920 of the command to run are given in the data structure.
921 921
922 922 wantframe
923 923 Indicates that nothing of interest happened and the server is waiting on
924 924 more frames from the client before anything interesting can be done.
925 925
926 926 noop
927 927 Indicates no additional action is required.
928 928
929 929 Known Issues
930 930 ------------
931 931
932 932 There are no limits to the number of partially received commands or their
933 933 size. A malicious client could stream command request data and exhaust the
934 934 server's memory.
935 935
936 936 Partially received commands are not acted upon when end of input is
937 937 reached. Should the server error if it receives a partial request?
938 938 Should the client send a message to abort a partially transmitted request
939 939 to facilitate graceful shutdown?
940 940
941 941 Active requests that haven't been responded to aren't tracked. This means
942 942 that if we receive a command and instruct its dispatch, another command
943 943 with its request ID can come in over the wire and there will be a race
944 944 between who responds to what.
945 945 """
946 946
947 947 def __init__(self, ui, deferoutput=False):
948 948 """Construct a new server reactor.
949 949
950 950 ``deferoutput`` can be used to indicate that no output frames should be
951 951 instructed to be sent until input has been exhausted. In this mode,
952 952 events that would normally generate output frames (such as a command
953 953 response being ready) will instead defer instructing the consumer to
954 954 send those frames. This is useful for half-duplex transports where the
955 955 sender cannot receive until all data has been transmitted.
956 956 """
957 957 self._ui = ui
958 958 self._deferoutput = deferoutput
959 959 self._state = 'initial'
960 960 self._nextoutgoingstreamid = 2
961 961 self._bufferedframegens = []
962 962 # stream id -> stream instance for all active streams from the client.
963 963 self._incomingstreams = {}
964 964 self._outgoingstreams = {}
965 965 # request id -> dict of commands that are actively being received.
966 966 self._receivingcommands = {}
967 967 # Request IDs that have been received and are actively being processed.
968 968 # Once all output for a request has been sent, it is removed from this
969 969 # set.
970 970 self._activecommands = set()
971 971
972 972 self._protocolsettingsdecoder = None
973 973
974 974 # Sender protocol settings are optional. Set implied default values.
975 975 self._sendersettings = dict(DEFAULT_PROTOCOL_SETTINGS)
976 976
977 977 populatestreamencoders()
978 978
979 979 def onframerecv(self, frame):
980 980 """Process a frame that has been received off the wire.
981 981
982 982 Returns a dict with an ``action`` key that details what action,
983 983 if any, the consumer should take next.
984 984 """
985 985 if not frame.streamid % 2:
986 986 self._state = 'errored'
987 987 return self._makeerrorresult(
988 988 _('received frame with even numbered stream ID: %d') %
989 989 frame.streamid)
990 990
991 991 if frame.streamid not in self._incomingstreams:
992 992 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
993 993 self._state = 'errored'
994 994 return self._makeerrorresult(
995 995 _('received frame on unknown inactive stream without '
996 996 'beginning of stream flag set'))
997 997
998 998 self._incomingstreams[frame.streamid] = inputstream(frame.streamid)
999 999
1000 1000 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1001 1001 # TODO handle decoding frames
1002 1002 self._state = 'errored'
1003 1003 raise error.ProgrammingError('support for decoding stream payloads '
1004 1004 'not yet implemented')
1005 1005
1006 1006 if frame.streamflags & STREAM_FLAG_END_STREAM:
1007 1007 del self._incomingstreams[frame.streamid]
1008 1008
1009 1009 handlers = {
1010 1010 'initial': self._onframeinitial,
1011 1011 'protocol-settings-receiving': self._onframeprotocolsettings,
1012 1012 'idle': self._onframeidle,
1013 1013 'command-receiving': self._onframecommandreceiving,
1014 1014 'errored': self._onframeerrored,
1015 1015 }
1016 1016
1017 1017 meth = handlers.get(self._state)
1018 1018 if not meth:
1019 1019 raise error.ProgrammingError('unhandled state: %s' % self._state)
1020 1020
1021 1021 return meth(frame)
1022 1022
1023 1023 def oncommandresponseready(self, stream, requestid, data):
1024 1024 """Signal that a bytes response is ready to be sent to the client.
1025 1025
1026 1026 The raw bytes response is passed as an argument.
1027 1027 """
1028 1028 ensureserverstream(stream)
1029 1029
1030 1030 def sendframes():
1031 1031 for frame in createcommandresponseframesfrombytes(stream, requestid,
1032 1032 data):
1033 1033 yield frame
1034 1034
1035 1035 self._activecommands.remove(requestid)
1036 1036
1037 1037 result = sendframes()
1038 1038
1039 1039 if self._deferoutput:
1040 1040 self._bufferedframegens.append(result)
1041 1041 return 'noop', {}
1042 1042 else:
1043 1043 return 'sendframes', {
1044 1044 'framegen': result,
1045 1045 }
1046 1046
1047 1047 def oncommandresponsereadyobjects(self, stream, requestid, objs):
1048 1048 """Signal that objects are ready to be sent to the client.
1049 1049
1050 1050 ``objs`` is an iterable of objects (typically a generator) that will
1051 1051 be encoded via CBOR and added to frames, which will be sent to the
1052 1052 client.
1053 1053 """
1054 1054 ensureserverstream(stream)
1055 1055
1056 1056 # We need to take care over exception handling. Uncaught exceptions
1057 1057 # when generating frames could lead to premature end of the frame
1058 1058 # stream and the possibility of the server or client process getting
1059 1059 # in a bad state.
1060 1060 #
1061 1061 # Keep in mind that if ``objs`` is a generator, advancing it could
1062 1062 # raise exceptions that originated in e.g. wire protocol command
1063 1063 # functions. That is why we differentiate between exceptions raised
1064 1064 # when iterating versus other exceptions that occur.
1065 1065 #
1066 1066 # In all cases, when the function finishes, the request is fully
1067 1067 # handled and no new frames for it should be seen.
1068 1068
1069 1069 def sendframes():
1070 1070 emitted = False
1071 1071 alternatelocationsent = False
1072 1072 emitter = bufferingcommandresponseemitter(stream, requestid)
1073 1073 while True:
1074 1074 try:
1075 1075 o = next(objs)
1076 1076 except StopIteration:
1077 1077 for frame in emitter.send(None):
1078 1078 yield frame
1079 1079
1080 1080 if emitted:
1081 1081 yield createcommandresponseeosframe(stream, requestid)
1082 1082 break
1083 1083
1084 1084 except error.WireprotoCommandError as e:
1085 1085 for frame in createcommanderrorresponse(
1086 1086 stream, requestid, e.message, e.messageargs):
1087 1087 yield frame
1088 1088 break
1089 1089
1090 1090 except Exception as e:
1091 1091 for frame in createerrorframe(
1092 1092 stream, requestid, '%s' % stringutil.forcebytestr(e),
1093 1093 errtype='server'):
1094 1094
1095 1095 yield frame
1096 1096
1097 1097 break
1098 1098
1099 1099 try:
1100 1100 # Alternate location responses can only be the first and
1101 1101 # only object in the output stream.
1102 1102 if isinstance(o, wireprototypes.alternatelocationresponse):
1103 1103 if emitted:
1104 1104 raise error.ProgrammingError(
1105 1105 'alternatelocationresponse seen after initial '
1106 1106 'output object')
1107 1107
1108 1108 yield createalternatelocationresponseframe(
1109 1109 stream, requestid, o)
1110 1110
1111 1111 alternatelocationsent = True
1112 1112 emitted = True
1113 1113 continue
1114 1114
1115 1115 if alternatelocationsent:
1116 1116 raise error.ProgrammingError(
1117 1117 'object follows alternatelocationresponse')
1118 1118
1119 1119 if not emitted:
1120 1120 yield createcommandresponseokframe(stream, requestid)
1121 1121 emitted = True
1122 1122
1123 1123 # Objects emitted by command functions can be serializable
1124 1124 # data structures or special types.
1125 1125 # TODO consider extracting the content normalization to a
1126 1126 # standalone function, as it may be useful for e.g. cachers.
1127 1127
1128 1128 # A pre-encoded object is sent directly to the emitter.
1129 1129 if isinstance(o, wireprototypes.encodedresponse):
1130 1130 for frame in emitter.send(o.data):
1131 1131 yield frame
1132 1132
1133 1133 # A regular object is CBOR encoded.
1134 1134 else:
1135 1135 for chunk in cborutil.streamencode(o):
1136 1136 for frame in emitter.send(chunk):
1137 1137 yield frame
1138 1138
1139 1139 except Exception as e:
1140 1140 for frame in createerrorframe(stream, requestid,
1141 1141 '%s' % e,
1142 1142 errtype='server'):
1143 1143 yield frame
1144 1144
1145 1145 break
1146 1146
1147 1147 self._activecommands.remove(requestid)
1148 1148
1149 1149 return self._handlesendframes(sendframes())
1150 1150
1151 1151 def oninputeof(self):
1152 1152 """Signals that end of input has been received.
1153 1153
1154 1154 No more frames will be received. All pending activity should be
1155 1155 completed.
1156 1156 """
1157 1157 # TODO should we do anything about in-flight commands?
1158 1158
1159 1159 if not self._deferoutput or not self._bufferedframegens:
1160 1160 return 'noop', {}
1161 1161
1162 1162 # If we buffered all our responses, emit those.
1163 1163 def makegen():
1164 1164 for gen in self._bufferedframegens:
1165 1165 for frame in gen:
1166 1166 yield frame
1167 1167
1168 1168 return 'sendframes', {
1169 1169 'framegen': makegen(),
1170 1170 }
1171 1171
1172 1172 def _handlesendframes(self, framegen):
1173 1173 if self._deferoutput:
1174 1174 self._bufferedframegens.append(framegen)
1175 1175 return 'noop', {}
1176 1176 else:
1177 1177 return 'sendframes', {
1178 1178 'framegen': framegen,
1179 1179 }
1180 1180
1181 1181 def onservererror(self, stream, requestid, msg):
1182 1182 ensureserverstream(stream)
1183 1183
1184 1184 def sendframes():
1185 1185 for frame in createerrorframe(stream, requestid, msg,
1186 1186 errtype='server'):
1187 1187 yield frame
1188 1188
1189 1189 self._activecommands.remove(requestid)
1190 1190
1191 1191 return self._handlesendframes(sendframes())
1192 1192
1193 1193 def oncommanderror(self, stream, requestid, message, args=None):
1194 1194 """Called when a command encountered an error before sending output."""
1195 1195 ensureserverstream(stream)
1196 1196
1197 1197 def sendframes():
1198 1198 for frame in createcommanderrorresponse(stream, requestid, message,
1199 1199 args):
1200 1200 yield frame
1201 1201
1202 1202 self._activecommands.remove(requestid)
1203 1203
1204 1204 return self._handlesendframes(sendframes())
1205 1205
1206 1206 def makeoutputstream(self):
1207 1207 """Create a stream to be used for sending data to the client."""
1208 1208 streamid = self._nextoutgoingstreamid
1209 1209 self._nextoutgoingstreamid += 2
1210 1210
1211 1211 s = outputstream(streamid)
1212 1212 self._outgoingstreams[streamid] = s
1213 1213
1214 1214 return s
1215 1215
1216 1216 def _makeerrorresult(self, msg):
1217 1217 return 'error', {
1218 1218 'message': msg,
1219 1219 }
1220 1220
1221 1221 def _makeruncommandresult(self, requestid):
1222 1222 entry = self._receivingcommands[requestid]
1223 1223
1224 1224 if not entry['requestdone']:
1225 1225 self._state = 'errored'
1226 1226 raise error.ProgrammingError('should not be called without '
1227 1227 'requestdone set')
1228 1228
1229 1229 del self._receivingcommands[requestid]
1230 1230
1231 1231 if self._receivingcommands:
1232 1232 self._state = 'command-receiving'
1233 1233 else:
1234 1234 self._state = 'idle'
1235 1235
1236 1236 # Decode the payloads as CBOR.
1237 1237 entry['payload'].seek(0)
1238 1238 request = cborutil.decodeall(entry['payload'].getvalue())[0]
1239 1239
1240 1240 if b'name' not in request:
1241 1241 self._state = 'errored'
1242 1242 return self._makeerrorresult(
1243 1243 _('command request missing "name" field'))
1244 1244
1245 1245 if b'args' not in request:
1246 1246 request[b'args'] = {}
1247 1247
1248 1248 assert requestid not in self._activecommands
1249 1249 self._activecommands.add(requestid)
1250 1250
1251 1251 return 'runcommand', {
1252 1252 'requestid': requestid,
1253 1253 'command': request[b'name'],
1254 1254 'args': request[b'args'],
1255 1255 'redirect': request.get(b'redirect'),
1256 1256 'data': entry['data'].getvalue() if entry['data'] else None,
1257 1257 }
1258 1258
1259 1259 def _makewantframeresult(self):
1260 1260 return 'wantframe', {
1261 1261 'state': self._state,
1262 1262 }
1263 1263
1264 1264 def _validatecommandrequestframe(self, frame):
1265 1265 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1266 1266 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
1267 1267
1268 1268 if new and continuation:
1269 1269 self._state = 'errored'
1270 1270 return self._makeerrorresult(
1271 1271 _('received command request frame with both new and '
1272 1272 'continuation flags set'))
1273 1273
1274 1274 if not new and not continuation:
1275 1275 self._state = 'errored'
1276 1276 return self._makeerrorresult(
1277 1277 _('received command request frame with neither new nor '
1278 1278 'continuation flags set'))
1279 1279
1280 1280 def _onframeinitial(self, frame):
1281 1281 # Called when we receive a frame when in the "initial" state.
1282 1282 if frame.typeid == FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1283 1283 self._state = 'protocol-settings-receiving'
1284 1284 self._protocolsettingsdecoder = cborutil.bufferingdecoder()
1285 1285 return self._onframeprotocolsettings(frame)
1286 1286
1287 1287 elif frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1288 1288 self._state = 'idle'
1289 1289 return self._onframeidle(frame)
1290 1290
1291 1291 else:
1292 1292 self._state = 'errored'
1293 1293 return self._makeerrorresult(
1294 1294 _('expected sender protocol settings or command request '
1295 1295 'frame; got %d') % frame.typeid)
1296 1296
1297 1297 def _onframeprotocolsettings(self, frame):
1298 1298 assert self._state == 'protocol-settings-receiving'
1299 1299 assert self._protocolsettingsdecoder is not None
1300 1300
1301 1301 if frame.typeid != FRAME_TYPE_SENDER_PROTOCOL_SETTINGS:
1302 1302 self._state = 'errored'
1303 1303 return self._makeerrorresult(
1304 1304 _('expected sender protocol settings frame; got %d') %
1305 1305 frame.typeid)
1306 1306
1307 1307 more = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_CONTINUATION
1308 1308 eos = frame.flags & FLAG_SENDER_PROTOCOL_SETTINGS_EOS
1309 1309
1310 1310 if more and eos:
1311 1311 self._state = 'errored'
1312 1312 return self._makeerrorresult(
1313 1313 _('sender protocol settings frame cannot have both '
1314 1314 'continuation and end of stream flags set'))
1315 1315
1316 1316 if not more and not eos:
1317 1317 self._state = 'errored'
1318 1318 return self._makeerrorresult(
1319 1319 _('sender protocol settings frame must have continuation or '
1320 1320 'end of stream flag set'))
1321 1321
1322 1322 # TODO establish limits for maximum amount of data that can be
1323 1323 # buffered.
1324 1324 try:
1325 1325 self._protocolsettingsdecoder.decode(frame.payload)
1326 1326 except Exception as e:
1327 1327 self._state = 'errored'
1328 1328 return self._makeerrorresult(
1329 1329 _('error decoding CBOR from sender protocol settings frame: %s')
1330 1330 % stringutil.forcebytestr(e))
1331 1331
1332 1332 if more:
1333 1333 return self._makewantframeresult()
1334 1334
1335 1335 assert eos
1336 1336
1337 1337 decoded = self._protocolsettingsdecoder.getavailable()
1338 1338 self._protocolsettingsdecoder = None
1339 1339
1340 1340 if not decoded:
1341 1341 self._state = 'errored'
1342 1342 return self._makeerrorresult(
1343 1343 _('sender protocol settings frame did not contain CBOR data'))
1344 1344 elif len(decoded) > 1:
1345 1345 self._state = 'errored'
1346 1346 return self._makeerrorresult(
1347 1347 _('sender protocol settings frame contained multiple CBOR '
1348 1348 'values'))
1349 1349
1350 1350 d = decoded[0]
1351 1351
1352 1352 if b'contentencodings' in d:
1353 1353 self._sendersettings['contentencodings'] = d[b'contentencodings']
1354 1354
1355 1355 self._state = 'idle'
1356 1356
1357 1357 return self._makewantframeresult()
1358 1358
1359 1359 def _onframeidle(self, frame):
1360 1360 # The only frame type that should be received in this state is a
1361 1361 # command request.
1362 1362 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
1363 1363 self._state = 'errored'
1364 1364 return self._makeerrorresult(
1365 1365 _('expected command request frame; got %d') % frame.typeid)
1366 1366
1367 1367 res = self._validatecommandrequestframe(frame)
1368 1368 if res:
1369 1369 return res
1370 1370
1371 1371 if frame.requestid in self._receivingcommands:
1372 1372 self._state = 'errored'
1373 1373 return self._makeerrorresult(
1374 1374 _('request with ID %d already received') % frame.requestid)
1375 1375
1376 1376 if frame.requestid in self._activecommands:
1377 1377 self._state = 'errored'
1378 1378 return self._makeerrorresult(
1379 1379 _('request with ID %d is already active') % frame.requestid)
1380 1380
1381 1381 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
1382 1382 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1383 1383 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
1384 1384
1385 1385 if not new:
1386 1386 self._state = 'errored'
1387 1387 return self._makeerrorresult(
1388 1388 _('received command request frame without new flag set'))
1389 1389
1390 1390 payload = util.bytesio()
1391 1391 payload.write(frame.payload)
1392 1392
1393 1393 self._receivingcommands[frame.requestid] = {
1394 1394 'payload': payload,
1395 1395 'data': None,
1396 1396 'requestdone': not moreframes,
1397 1397 'expectingdata': bool(expectingdata),
1398 1398 }
1399 1399
1400 1400 # This is the final frame for this request. Dispatch it.
1401 1401 if not moreframes and not expectingdata:
1402 1402 return self._makeruncommandresult(frame.requestid)
1403 1403
1404 1404 assert moreframes or expectingdata
1405 1405 self._state = 'command-receiving'
1406 1406 return self._makewantframeresult()
1407 1407
1408 1408 def _onframecommandreceiving(self, frame):
1409 1409 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1410 1410 # Process new command requests as such.
1411 1411 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
1412 1412 return self._onframeidle(frame)
1413 1413
1414 1414 res = self._validatecommandrequestframe(frame)
1415 1415 if res:
1416 1416 return res
1417 1417
1418 1418 # All other frames should be related to a command that is currently
1419 1419 # receiving but is not active.
1420 1420 if frame.requestid in self._activecommands:
1421 1421 self._state = 'errored'
1422 1422 return self._makeerrorresult(
1423 1423 _('received frame for request that is still active: %d') %
1424 1424 frame.requestid)
1425 1425
1426 1426 if frame.requestid not in self._receivingcommands:
1427 1427 self._state = 'errored'
1428 1428 return self._makeerrorresult(
1429 1429 _('received frame for request that is not receiving: %d') %
1430 1430 frame.requestid)
1431 1431
1432 1432 entry = self._receivingcommands[frame.requestid]
1433 1433
1434 1434 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
1435 1435 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
1436 1436 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
1437 1437
1438 1438 if entry['requestdone']:
1439 1439 self._state = 'errored'
1440 1440 return self._makeerrorresult(
1441 1441 _('received command request frame when request frames '
1442 1442 'were supposedly done'))
1443 1443
1444 1444 if expectingdata != entry['expectingdata']:
1445 1445 self._state = 'errored'
1446 1446 return self._makeerrorresult(
1447 1447 _('mismatch between expect data flag and previous frame'))
1448 1448
1449 1449 entry['payload'].write(frame.payload)
1450 1450
1451 1451 if not moreframes:
1452 1452 entry['requestdone'] = True
1453 1453
1454 1454 if not moreframes and not expectingdata:
1455 1455 return self._makeruncommandresult(frame.requestid)
1456 1456
1457 1457 return self._makewantframeresult()
1458 1458
1459 1459 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
1460 1460 if not entry['expectingdata']:
1461 1461 self._state = 'errored'
1462 1462 return self._makeerrorresult(_(
1463 1463 'received command data frame for request that is not '
1464 1464 'expecting data: %d') % frame.requestid)
1465 1465
1466 1466 if entry['data'] is None:
1467 1467 entry['data'] = util.bytesio()
1468 1468
1469 1469 return self._handlecommanddataframe(frame, entry)
1470 1470 else:
1471 1471 self._state = 'errored'
1472 1472 return self._makeerrorresult(_(
1473 1473 'received unexpected frame type: %d') % frame.typeid)
1474 1474
1475 1475 def _handlecommanddataframe(self, frame, entry):
1476 1476 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
1477 1477
1478 1478 # TODO support streaming data instead of buffering it.
1479 1479 entry['data'].write(frame.payload)
1480 1480
1481 1481 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
1482 1482 return self._makewantframeresult()
1483 1483 elif frame.flags & FLAG_COMMAND_DATA_EOS:
1484 1484 entry['data'].seek(0)
1485 1485 return self._makeruncommandresult(frame.requestid)
1486 1486 else:
1487 1487 self._state = 'errored'
1488 1488 return self._makeerrorresult(_('command data frame without '
1489 1489 'flags'))
1490 1490
1491 1491 def _onframeerrored(self, frame):
1492 1492 return self._makeerrorresult(_('server already errored'))
1493 1493
1494 1494 class commandrequest(object):
1495 1495 """Represents a request to run a command."""
1496 1496
1497 1497 def __init__(self, requestid, name, args, datafh=None, redirect=None):
1498 1498 self.requestid = requestid
1499 1499 self.name = name
1500 1500 self.args = args
1501 1501 self.datafh = datafh
1502 1502 self.redirect = redirect
1503 1503 self.state = 'pending'
1504 1504
1505 1505 class clientreactor(object):
1506 1506 """Holds state of a client issuing frame-based protocol requests.
1507 1507
1508 1508 This is like ``serverreactor`` but for client-side state.
1509 1509
1510 1510 Each instance is bound to the lifetime of a connection. For persistent
1511 1511 connection transports using e.g. TCP sockets and speaking the raw
1512 1512 framing protocol, there will be a single instance for the lifetime of
1513 1513 the TCP socket. For transports where there are multiple discrete
1514 1514 interactions (say tunneled within in HTTP request), there will be a
1515 1515 separate instance for each distinct interaction.
1516 1516
1517 1517 Consumers are expected to tell instances when events occur by calling
1518 1518 various methods. These methods return a 2-tuple describing any follow-up
1519 1519 action(s) to take. The first element is the name of an action to
1520 1520 perform. The second is a data structure (usually a dict) specific to
1521 1521 that action that contains more information. e.g. if the reactor wants
1522 1522 to send frames to the server, the data structure will contain a reference
1523 1523 to those frames.
1524 1524
1525 1525 Valid actions that consumers can be instructed to take are:
1526 1526
1527 1527 noop
1528 1528 Indicates no additional action is required.
1529 1529
1530 1530 sendframes
1531 1531 Indicates that frames should be sent to the server. The ``framegen``
1532 1532 key contains a generator of frames that should be sent. The reactor
1533 1533 assumes that all frames in this generator are sent to the server.
1534 1534
1535 1535 error
1536 1536 Indicates that an error occurred. The ``message`` key contains an
1537 1537 error message describing the failure.
1538 1538
1539 1539 responsedata
1540 1540 Indicates a response to a previously-issued command was received.
1541 1541
1542 1542 The ``request`` key contains the ``commandrequest`` instance that
1543 1543 represents the request this data is for.
1544 1544
1545 1545 The ``data`` key contains the decoded data from the server.
1546 1546
1547 1547 ``expectmore`` and ``eos`` evaluate to True when more response data
1548 1548 is expected to follow or we're at the end of the response stream,
1549 1549 respectively.
1550 1550 """
1551 def __init__(self, ui, hasmultiplesend=False, buffersends=True):
1551 def __init__(self, ui, hasmultiplesend=False, buffersends=True,
1552 clientcontentencoders=None):
1552 1553 """Create a new instance.
1553 1554
1554 1555 ``hasmultiplesend`` indicates whether multiple sends are supported
1555 1556 by the transport. When True, it is possible to send commands immediately
1556 1557 instead of buffering until the caller signals an intent to finish a
1557 1558 send operation.
1558 1559
1559 1560 ``buffercommands`` indicates whether sends should be buffered until the
1560 1561 last request has been issued.
1562
1563 ``clientcontentencoders`` is an iterable of content encoders the client
1564 will advertise to the server and that the server can use for encoding
1565 data. If not defined, the client will not advertise content encoders
1566 to the server.
1561 1567 """
1562 1568 self._ui = ui
1563 1569 self._hasmultiplesend = hasmultiplesend
1564 1570 self._buffersends = buffersends
1571 self._clientcontentencoders = clientcontentencoders
1565 1572
1566 1573 self._canissuecommands = True
1567 1574 self._cansend = True
1575 self._protocolsettingssent = False
1568 1576
1569 1577 self._nextrequestid = 1
1570 1578 # We only support a single outgoing stream for now.
1571 1579 self._outgoingstream = outputstream(1)
1572 1580 self._pendingrequests = collections.deque()
1573 1581 self._activerequests = {}
1574 1582 self._incomingstreams = {}
1575 1583 self._streamsettingsdecoders = {}
1576 1584
1577 1585 populatestreamencoders()
1578 1586
1579 1587 def callcommand(self, name, args, datafh=None, redirect=None):
1580 1588 """Request that a command be executed.
1581 1589
1582 1590 Receives the command name, a dict of arguments to pass to the command,
1583 1591 and an optional file object containing the raw data for the command.
1584 1592
1585 1593 Returns a 3-tuple of (request, action, action data).
1586 1594 """
1587 1595 if not self._canissuecommands:
1588 1596 raise error.ProgrammingError('cannot issue new commands')
1589 1597
1590 1598 requestid = self._nextrequestid
1591 1599 self._nextrequestid += 2
1592 1600
1593 1601 request = commandrequest(requestid, name, args, datafh=datafh,
1594 1602 redirect=redirect)
1595 1603
1596 1604 if self._buffersends:
1597 1605 self._pendingrequests.append(request)
1598 1606 return request, 'noop', {}
1599 1607 else:
1600 1608 if not self._cansend:
1601 1609 raise error.ProgrammingError('sends cannot be performed on '
1602 1610 'this instance')
1603 1611
1604 1612 if not self._hasmultiplesend:
1605 1613 self._cansend = False
1606 1614 self._canissuecommands = False
1607 1615
1608 1616 return request, 'sendframes', {
1609 1617 'framegen': self._makecommandframes(request),
1610 1618 }
1611 1619
1612 1620 def flushcommands(self):
1613 1621 """Request that all queued commands be sent.
1614 1622
1615 1623 If any commands are buffered, this will instruct the caller to send
1616 1624 them over the wire. If no commands are buffered it instructs the client
1617 1625 to no-op.
1618 1626
1619 1627 If instances aren't configured for multiple sends, no new command
1620 1628 requests are allowed after this is called.
1621 1629 """
1622 1630 if not self._pendingrequests:
1623 1631 return 'noop', {}
1624 1632
1625 1633 if not self._cansend:
1626 1634 raise error.ProgrammingError('sends cannot be performed on this '
1627 1635 'instance')
1628 1636
1629 1637 # If the instance only allows sending once, mark that we have fired
1630 1638 # our one shot.
1631 1639 if not self._hasmultiplesend:
1632 1640 self._canissuecommands = False
1633 1641 self._cansend = False
1634 1642
1635 1643 def makeframes():
1636 1644 while self._pendingrequests:
1637 1645 request = self._pendingrequests.popleft()
1638 1646 for frame in self._makecommandframes(request):
1639 1647 yield frame
1640 1648
1641 1649 return 'sendframes', {
1642 1650 'framegen': makeframes(),
1643 1651 }
1644 1652
1645 1653 def _makecommandframes(self, request):
1646 1654 """Emit frames to issue a command request.
1647 1655
1648 1656 As a side-effect, update request accounting to reflect its changed
1649 1657 state.
1650 1658 """
1651 1659 self._activerequests[request.requestid] = request
1652 1660 request.state = 'sending'
1653 1661
1662 if not self._protocolsettingssent and self._clientcontentencoders:
1663 self._protocolsettingssent = True
1664
1665 payload = b''.join(cborutil.streamencode({
1666 b'contentencodings': self._clientcontentencoders,
1667 }))
1668
1669 yield self._outgoingstream.makeframe(
1670 requestid=request.requestid,
1671 typeid=FRAME_TYPE_SENDER_PROTOCOL_SETTINGS,
1672 flags=FLAG_SENDER_PROTOCOL_SETTINGS_EOS,
1673 payload=payload)
1674
1654 1675 res = createcommandframes(self._outgoingstream,
1655 1676 request.requestid,
1656 1677 request.name,
1657 1678 request.args,
1658 1679 datafh=request.datafh,
1659 1680 redirect=request.redirect)
1660 1681
1661 1682 for frame in res:
1662 1683 yield frame
1663 1684
1664 1685 request.state = 'sent'
1665 1686
1666 1687 def onframerecv(self, frame):
1667 1688 """Process a frame that has been received off the wire.
1668 1689
1669 1690 Returns a 2-tuple of (action, meta) describing further action the
1670 1691 caller needs to take as a result of receiving this frame.
1671 1692 """
1672 1693 if frame.streamid % 2:
1673 1694 return 'error', {
1674 1695 'message': (
1675 1696 _('received frame with odd numbered stream ID: %d') %
1676 1697 frame.streamid),
1677 1698 }
1678 1699
1679 1700 if frame.streamid not in self._incomingstreams:
1680 1701 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1681 1702 return 'error', {
1682 1703 'message': _('received frame on unknown stream '
1683 1704 'without beginning of stream flag set'),
1684 1705 }
1685 1706
1686 1707 self._incomingstreams[frame.streamid] = inputstream(
1687 1708 frame.streamid)
1688 1709
1689 1710 stream = self._incomingstreams[frame.streamid]
1690 1711
1691 1712 # If the payload is encoded, ask the stream to decode it. We
1692 1713 # merely substitute the decoded result into the frame payload as
1693 1714 # if it had been transferred all along.
1694 1715 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1695 1716 frame.payload = stream.decode(frame.payload)
1696 1717
1697 1718 if frame.streamflags & STREAM_FLAG_END_STREAM:
1698 1719 del self._incomingstreams[frame.streamid]
1699 1720
1700 1721 if frame.typeid == FRAME_TYPE_STREAM_SETTINGS:
1701 1722 return self._onstreamsettingsframe(frame)
1702 1723
1703 1724 if frame.requestid not in self._activerequests:
1704 1725 return 'error', {
1705 1726 'message': (_('received frame for inactive request ID: %d') %
1706 1727 frame.requestid),
1707 1728 }
1708 1729
1709 1730 request = self._activerequests[frame.requestid]
1710 1731 request.state = 'receiving'
1711 1732
1712 1733 handlers = {
1713 1734 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1714 1735 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1715 1736 }
1716 1737
1717 1738 meth = handlers.get(frame.typeid)
1718 1739 if not meth:
1719 1740 raise error.ProgrammingError('unhandled frame type: %d' %
1720 1741 frame.typeid)
1721 1742
1722 1743 return meth(request, frame)
1723 1744
1724 1745 def _onstreamsettingsframe(self, frame):
1725 1746 assert frame.typeid == FRAME_TYPE_STREAM_SETTINGS
1726 1747
1727 1748 more = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_CONTINUATION
1728 1749 eos = frame.flags & FLAG_STREAM_ENCODING_SETTINGS_EOS
1729 1750
1730 1751 if more and eos:
1731 1752 return 'error', {
1732 1753 'message': (_('stream encoding settings frame cannot have both '
1733 1754 'continuation and end of stream flags set')),
1734 1755 }
1735 1756
1736 1757 if not more and not eos:
1737 1758 return 'error', {
1738 1759 'message': _('stream encoding settings frame must have '
1739 1760 'continuation or end of stream flag set'),
1740 1761 }
1741 1762
1742 1763 if frame.streamid not in self._streamsettingsdecoders:
1743 1764 decoder = cborutil.bufferingdecoder()
1744 1765 self._streamsettingsdecoders[frame.streamid] = decoder
1745 1766
1746 1767 decoder = self._streamsettingsdecoders[frame.streamid]
1747 1768
1748 1769 try:
1749 1770 decoder.decode(frame.payload)
1750 1771 except Exception as e:
1751 1772 return 'error', {
1752 1773 'message': (_('error decoding CBOR from stream encoding '
1753 1774 'settings frame: %s') %
1754 1775 stringutil.forcebytestr(e)),
1755 1776 }
1756 1777
1757 1778 if more:
1758 1779 return 'noop', {}
1759 1780
1760 1781 assert eos
1761 1782
1762 1783 decoded = decoder.getavailable()
1763 1784 del self._streamsettingsdecoders[frame.streamid]
1764 1785
1765 1786 if not decoded:
1766 1787 return 'error', {
1767 1788 'message': _('stream encoding settings frame did not contain '
1768 1789 'CBOR data'),
1769 1790 }
1770 1791
1771 1792 try:
1772 1793 self._incomingstreams[frame.streamid].setdecoder(self._ui,
1773 1794 decoded[0],
1774 1795 decoded[1:])
1775 1796 except Exception as e:
1776 1797 return 'error', {
1777 1798 'message': (_('error setting stream decoder: %s') %
1778 1799 stringutil.forcebytestr(e)),
1779 1800 }
1780 1801
1781 1802 return 'noop', {}
1782 1803
1783 1804 def _oncommandresponseframe(self, request, frame):
1784 1805 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1785 1806 request.state = 'received'
1786 1807 del self._activerequests[request.requestid]
1787 1808
1788 1809 return 'responsedata', {
1789 1810 'request': request,
1790 1811 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1791 1812 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1792 1813 'data': frame.payload,
1793 1814 }
1794 1815
1795 1816 def _onerrorresponseframe(self, request, frame):
1796 1817 request.state = 'errored'
1797 1818 del self._activerequests[request.requestid]
1798 1819
1799 1820 # The payload should be a CBOR map.
1800 1821 m = cborutil.decodeall(frame.payload)[0]
1801 1822
1802 1823 return 'error', {
1803 1824 'request': request,
1804 1825 'type': m['type'],
1805 1826 'message': m['message'],
1806 1827 }
@@ -1,1187 +1,1187
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 import hashlib
11 11
12 12 from .i18n import _
13 13 from .node import (
14 14 hex,
15 15 nullid,
16 16 )
17 17 from . import (
18 18 discovery,
19 19 encoding,
20 20 error,
21 21 narrowspec,
22 22 pycompat,
23 23 wireprotoframing,
24 24 wireprototypes,
25 25 )
26 26 from .utils import (
27 27 cborutil,
28 28 interfaceutil,
29 29 stringutil,
30 30 )
31 31
32 FRAMINGTYPE = b'application/mercurial-exp-framing-0005'
32 FRAMINGTYPE = b'application/mercurial-exp-framing-0006'
33 33
34 34 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
35 35
36 36 COMMANDS = wireprototypes.commanddict()
37 37
38 38 # Value inserted into cache key computation function. Change the value to
39 39 # force new cache keys for every command request. This should be done when
40 40 # there is a change to how caching works, etc.
41 41 GLOBAL_CACHE_VERSION = 1
42 42
43 43 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
44 44 from .hgweb import common as hgwebcommon
45 45
46 46 # URL space looks like: <permissions>/<command>, where <permission> can
47 47 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
48 48
49 49 # Root URL does nothing meaningful... yet.
50 50 if not urlparts:
51 51 res.status = b'200 OK'
52 52 res.headers[b'Content-Type'] = b'text/plain'
53 53 res.setbodybytes(_('HTTP version 2 API handler'))
54 54 return
55 55
56 56 if len(urlparts) == 1:
57 57 res.status = b'404 Not Found'
58 58 res.headers[b'Content-Type'] = b'text/plain'
59 59 res.setbodybytes(_('do not know how to process %s\n') %
60 60 req.dispatchpath)
61 61 return
62 62
63 63 permission, command = urlparts[0:2]
64 64
65 65 if permission not in (b'ro', b'rw'):
66 66 res.status = b'404 Not Found'
67 67 res.headers[b'Content-Type'] = b'text/plain'
68 68 res.setbodybytes(_('unknown permission: %s') % permission)
69 69 return
70 70
71 71 if req.method != 'POST':
72 72 res.status = b'405 Method Not Allowed'
73 73 res.headers[b'Allow'] = b'POST'
74 74 res.setbodybytes(_('commands require POST requests'))
75 75 return
76 76
77 77 # At some point we'll want to use our own API instead of recycling the
78 78 # behavior of version 1 of the wire protocol...
79 79 # TODO return reasonable responses - not responses that overload the
80 80 # HTTP status line message for error reporting.
81 81 try:
82 82 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
83 83 except hgwebcommon.ErrorResponse as e:
84 84 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
85 85 for k, v in e.headers:
86 86 res.headers[k] = v
87 87 res.setbodybytes('permission denied')
88 88 return
89 89
90 90 # We have a special endpoint to reflect the request back at the client.
91 91 if command == b'debugreflect':
92 92 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
93 93 return
94 94
95 95 # Extra commands that we handle that aren't really wire protocol
96 96 # commands. Think extra hard before making this hackery available to
97 97 # extension.
98 98 extracommands = {'multirequest'}
99 99
100 100 if command not in COMMANDS and command not in extracommands:
101 101 res.status = b'404 Not Found'
102 102 res.headers[b'Content-Type'] = b'text/plain'
103 103 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
104 104 return
105 105
106 106 repo = rctx.repo
107 107 ui = repo.ui
108 108
109 109 proto = httpv2protocolhandler(req, ui)
110 110
111 111 if (not COMMANDS.commandavailable(command, proto)
112 112 and command not in extracommands):
113 113 res.status = b'404 Not Found'
114 114 res.headers[b'Content-Type'] = b'text/plain'
115 115 res.setbodybytes(_('invalid wire protocol command: %s') % command)
116 116 return
117 117
118 118 # TODO consider cases where proxies may add additional Accept headers.
119 119 if req.headers.get(b'Accept') != FRAMINGTYPE:
120 120 res.status = b'406 Not Acceptable'
121 121 res.headers[b'Content-Type'] = b'text/plain'
122 122 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
123 123 % FRAMINGTYPE)
124 124 return
125 125
126 126 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
127 127 res.status = b'415 Unsupported Media Type'
128 128 # TODO we should send a response with appropriate media type,
129 129 # since client does Accept it.
130 130 res.headers[b'Content-Type'] = b'text/plain'
131 131 res.setbodybytes(_('client MUST send Content-Type header with '
132 132 'value: %s\n') % FRAMINGTYPE)
133 133 return
134 134
135 135 _processhttpv2request(ui, repo, req, res, permission, command, proto)
136 136
137 137 def _processhttpv2reflectrequest(ui, repo, req, res):
138 138 """Reads unified frame protocol request and dumps out state to client.
139 139
140 140 This special endpoint can be used to help debug the wire protocol.
141 141
142 142 Instead of routing the request through the normal dispatch mechanism,
143 143 we instead read all frames, decode them, and feed them into our state
144 144 tracker. We then dump the log of all that activity back out to the
145 145 client.
146 146 """
147 147 import json
148 148
149 149 # Reflection APIs have a history of being abused, accidentally disclosing
150 150 # sensitive data, etc. So we have a config knob.
151 151 if not ui.configbool('experimental', 'web.api.debugreflect'):
152 152 res.status = b'404 Not Found'
153 153 res.headers[b'Content-Type'] = b'text/plain'
154 154 res.setbodybytes(_('debugreflect service not available'))
155 155 return
156 156
157 157 # We assume we have a unified framing protocol request body.
158 158
159 159 reactor = wireprotoframing.serverreactor(ui)
160 160 states = []
161 161
162 162 while True:
163 163 frame = wireprotoframing.readframe(req.bodyfh)
164 164
165 165 if not frame:
166 166 states.append(b'received: <no frame>')
167 167 break
168 168
169 169 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
170 170 frame.requestid,
171 171 frame.payload))
172 172
173 173 action, meta = reactor.onframerecv(frame)
174 174 states.append(json.dumps((action, meta), sort_keys=True,
175 175 separators=(', ', ': ')))
176 176
177 177 action, meta = reactor.oninputeof()
178 178 meta['action'] = action
179 179 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
180 180
181 181 res.status = b'200 OK'
182 182 res.headers[b'Content-Type'] = b'text/plain'
183 183 res.setbodybytes(b'\n'.join(states))
184 184
185 185 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
186 186 """Post-validation handler for HTTPv2 requests.
187 187
188 188 Called when the HTTP request contains unified frame-based protocol
189 189 frames for evaluation.
190 190 """
191 191 # TODO Some HTTP clients are full duplex and can receive data before
192 192 # the entire request is transmitted. Figure out a way to indicate support
193 193 # for that so we can opt into full duplex mode.
194 194 reactor = wireprotoframing.serverreactor(ui, deferoutput=True)
195 195 seencommand = False
196 196
197 197 outstream = reactor.makeoutputstream()
198 198
199 199 while True:
200 200 frame = wireprotoframing.readframe(req.bodyfh)
201 201 if not frame:
202 202 break
203 203
204 204 action, meta = reactor.onframerecv(frame)
205 205
206 206 if action == 'wantframe':
207 207 # Need more data before we can do anything.
208 208 continue
209 209 elif action == 'runcommand':
210 210 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
211 211 reqcommand, reactor, outstream,
212 212 meta, issubsequent=seencommand)
213 213
214 214 if sentoutput:
215 215 return
216 216
217 217 seencommand = True
218 218
219 219 elif action == 'error':
220 220 # TODO define proper error mechanism.
221 221 res.status = b'200 OK'
222 222 res.headers[b'Content-Type'] = b'text/plain'
223 223 res.setbodybytes(meta['message'] + b'\n')
224 224 return
225 225 else:
226 226 raise error.ProgrammingError(
227 227 'unhandled action from frame processor: %s' % action)
228 228
229 229 action, meta = reactor.oninputeof()
230 230 if action == 'sendframes':
231 231 # We assume we haven't started sending the response yet. If we're
232 232 # wrong, the response type will raise an exception.
233 233 res.status = b'200 OK'
234 234 res.headers[b'Content-Type'] = FRAMINGTYPE
235 235 res.setbodygen(meta['framegen'])
236 236 elif action == 'noop':
237 237 pass
238 238 else:
239 239 raise error.ProgrammingError('unhandled action from frame processor: %s'
240 240 % action)
241 241
242 242 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
243 243 outstream, command, issubsequent):
244 244 """Dispatch a wire protocol command made from HTTPv2 requests.
245 245
246 246 The authenticated permission (``authedperm``) along with the original
247 247 command from the URL (``reqcommand``) are passed in.
248 248 """
249 249 # We already validated that the session has permissions to perform the
250 250 # actions in ``authedperm``. In the unified frame protocol, the canonical
251 251 # command to run is expressed in a frame. However, the URL also requested
252 252 # to run a specific command. We need to be careful that the command we
253 253 # run doesn't have permissions requirements greater than what was granted
254 254 # by ``authedperm``.
255 255 #
256 256 # Our rule for this is we only allow one command per HTTP request and
257 257 # that command must match the command in the URL. However, we make
258 258 # an exception for the ``multirequest`` URL. This URL is allowed to
259 259 # execute multiple commands. We double check permissions of each command
260 260 # as it is invoked to ensure there is no privilege escalation.
261 261 # TODO consider allowing multiple commands to regular command URLs
262 262 # iff each command is the same.
263 263
264 264 proto = httpv2protocolhandler(req, ui, args=command['args'])
265 265
266 266 if reqcommand == b'multirequest':
267 267 if not COMMANDS.commandavailable(command['command'], proto):
268 268 # TODO proper error mechanism
269 269 res.status = b'200 OK'
270 270 res.headers[b'Content-Type'] = b'text/plain'
271 271 res.setbodybytes(_('wire protocol command not available: %s') %
272 272 command['command'])
273 273 return True
274 274
275 275 # TODO don't use assert here, since it may be elided by -O.
276 276 assert authedperm in (b'ro', b'rw')
277 277 wirecommand = COMMANDS[command['command']]
278 278 assert wirecommand.permission in ('push', 'pull')
279 279
280 280 if authedperm == b'ro' and wirecommand.permission != 'pull':
281 281 # TODO proper error mechanism
282 282 res.status = b'403 Forbidden'
283 283 res.headers[b'Content-Type'] = b'text/plain'
284 284 res.setbodybytes(_('insufficient permissions to execute '
285 285 'command: %s') % command['command'])
286 286 return True
287 287
288 288 # TODO should we also call checkperm() here? Maybe not if we're going
289 289 # to overhaul that API. The granted scope from the URL check should
290 290 # be good enough.
291 291
292 292 else:
293 293 # Don't allow multiple commands outside of ``multirequest`` URL.
294 294 if issubsequent:
295 295 # TODO proper error mechanism
296 296 res.status = b'200 OK'
297 297 res.headers[b'Content-Type'] = b'text/plain'
298 298 res.setbodybytes(_('multiple commands cannot be issued to this '
299 299 'URL'))
300 300 return True
301 301
302 302 if reqcommand != command['command']:
303 303 # TODO define proper error mechanism
304 304 res.status = b'200 OK'
305 305 res.headers[b'Content-Type'] = b'text/plain'
306 306 res.setbodybytes(_('command in frame must match command in URL'))
307 307 return True
308 308
309 309 res.status = b'200 OK'
310 310 res.headers[b'Content-Type'] = FRAMINGTYPE
311 311
312 312 try:
313 313 objs = dispatch(repo, proto, command['command'], command['redirect'])
314 314
315 315 action, meta = reactor.oncommandresponsereadyobjects(
316 316 outstream, command['requestid'], objs)
317 317
318 318 except error.WireprotoCommandError as e:
319 319 action, meta = reactor.oncommanderror(
320 320 outstream, command['requestid'], e.message, e.messageargs)
321 321
322 322 except Exception as e:
323 323 action, meta = reactor.onservererror(
324 324 outstream, command['requestid'],
325 325 _('exception when invoking command: %s') %
326 326 stringutil.forcebytestr(e))
327 327
328 328 if action == 'sendframes':
329 329 res.setbodygen(meta['framegen'])
330 330 return True
331 331 elif action == 'noop':
332 332 return False
333 333 else:
334 334 raise error.ProgrammingError('unhandled event from reactor: %s' %
335 335 action)
336 336
337 337 def getdispatchrepo(repo, proto, command):
338 338 return repo.filtered('served')
339 339
340 340 def dispatch(repo, proto, command, redirect):
341 341 """Run a wire protocol command.
342 342
343 343 Returns an iterable of objects that will be sent to the client.
344 344 """
345 345 repo = getdispatchrepo(repo, proto, command)
346 346
347 347 entry = COMMANDS[command]
348 348 func = entry.func
349 349 spec = entry.args
350 350
351 351 args = proto.getargs(spec)
352 352
353 353 # There is some duplicate boilerplate code here for calling the command and
354 354 # emitting objects. It is either that or a lot of indented code that looks
355 355 # like a pyramid (since there are a lot of code paths that result in not
356 356 # using the cacher).
357 357 callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args))
358 358
359 359 # Request is not cacheable. Don't bother instantiating a cacher.
360 360 if not entry.cachekeyfn:
361 361 for o in callcommand():
362 362 yield o
363 363 return
364 364
365 365 if redirect:
366 366 redirecttargets = redirect[b'targets']
367 367 redirecthashes = redirect[b'hashes']
368 368 else:
369 369 redirecttargets = []
370 370 redirecthashes = []
371 371
372 372 cacher = makeresponsecacher(repo, proto, command, args,
373 373 cborutil.streamencode,
374 374 redirecttargets=redirecttargets,
375 375 redirecthashes=redirecthashes)
376 376
377 377 # But we have no cacher. Do default handling.
378 378 if not cacher:
379 379 for o in callcommand():
380 380 yield o
381 381 return
382 382
383 383 with cacher:
384 384 cachekey = entry.cachekeyfn(repo, proto, cacher, **args)
385 385
386 386 # No cache key or the cacher doesn't like it. Do default handling.
387 387 if cachekey is None or not cacher.setcachekey(cachekey):
388 388 for o in callcommand():
389 389 yield o
390 390 return
391 391
392 392 # Serve it from the cache, if possible.
393 393 cached = cacher.lookup()
394 394
395 395 if cached:
396 396 for o in cached['objs']:
397 397 yield o
398 398 return
399 399
400 400 # Else call the command and feed its output into the cacher, allowing
401 401 # the cacher to buffer/mutate objects as it desires.
402 402 for o in callcommand():
403 403 for o in cacher.onobject(o):
404 404 yield o
405 405
406 406 for o in cacher.onfinished():
407 407 yield o
408 408
409 409 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
410 410 class httpv2protocolhandler(object):
411 411 def __init__(self, req, ui, args=None):
412 412 self._req = req
413 413 self._ui = ui
414 414 self._args = args
415 415
416 416 @property
417 417 def name(self):
418 418 return HTTP_WIREPROTO_V2
419 419
420 420 def getargs(self, args):
421 421 # First look for args that were passed but aren't registered on this
422 422 # command.
423 423 extra = set(self._args) - set(args)
424 424 if extra:
425 425 raise error.WireprotoCommandError(
426 426 'unsupported argument to command: %s' %
427 427 ', '.join(sorted(extra)))
428 428
429 429 # And look for required arguments that are missing.
430 430 missing = {a for a in args if args[a]['required']} - set(self._args)
431 431
432 432 if missing:
433 433 raise error.WireprotoCommandError(
434 434 'missing required arguments: %s' % ', '.join(sorted(missing)))
435 435
436 436 # Now derive the arguments to pass to the command, taking into
437 437 # account the arguments specified by the client.
438 438 data = {}
439 439 for k, meta in sorted(args.items()):
440 440 # This argument wasn't passed by the client.
441 441 if k not in self._args:
442 442 data[k] = meta['default']()
443 443 continue
444 444
445 445 v = self._args[k]
446 446
447 447 # Sets may be expressed as lists. Silently normalize.
448 448 if meta['type'] == 'set' and isinstance(v, list):
449 449 v = set(v)
450 450
451 451 # TODO consider more/stronger type validation.
452 452
453 453 data[k] = v
454 454
455 455 return data
456 456
457 457 def getprotocaps(self):
458 458 # Protocol capabilities are currently not implemented for HTTP V2.
459 459 return set()
460 460
461 461 def getpayload(self):
462 462 raise NotImplementedError
463 463
464 464 @contextlib.contextmanager
465 465 def mayberedirectstdio(self):
466 466 raise NotImplementedError
467 467
468 468 def client(self):
469 469 raise NotImplementedError
470 470
471 471 def addcapabilities(self, repo, caps):
472 472 return caps
473 473
474 474 def checkperm(self, perm):
475 475 raise NotImplementedError
476 476
477 477 def httpv2apidescriptor(req, repo):
478 478 proto = httpv2protocolhandler(req, repo.ui)
479 479
480 480 return _capabilitiesv2(repo, proto)
481 481
482 482 def _capabilitiesv2(repo, proto):
483 483 """Obtain the set of capabilities for version 2 transports.
484 484
485 485 These capabilities are distinct from the capabilities for version 1
486 486 transports.
487 487 """
488 488 caps = {
489 489 'commands': {},
490 490 'framingmediatypes': [FRAMINGTYPE],
491 491 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
492 492 }
493 493
494 494 for command, entry in COMMANDS.items():
495 495 args = {}
496 496
497 497 for arg, meta in entry.args.items():
498 498 args[arg] = {
499 499 # TODO should this be a normalized type using CBOR's
500 500 # terminology?
501 501 b'type': meta['type'],
502 502 b'required': meta['required'],
503 503 }
504 504
505 505 if not meta['required']:
506 506 args[arg][b'default'] = meta['default']()
507 507
508 508 if meta['validvalues']:
509 509 args[arg][b'validvalues'] = meta['validvalues']
510 510
511 511 caps['commands'][command] = {
512 512 'args': args,
513 513 'permissions': [entry.permission],
514 514 }
515 515
516 516 caps['rawrepoformats'] = sorted(repo.requirements &
517 517 repo.supportedformats)
518 518
519 519 targets = getadvertisedredirecttargets(repo, proto)
520 520 if targets:
521 521 caps[b'redirect'] = {
522 522 b'targets': [],
523 523 b'hashes': [b'sha256', b'sha1'],
524 524 }
525 525
526 526 for target in targets:
527 527 entry = {
528 528 b'name': target['name'],
529 529 b'protocol': target['protocol'],
530 530 b'uris': target['uris'],
531 531 }
532 532
533 533 for key in ('snirequired', 'tlsversions'):
534 534 if key in target:
535 535 entry[key] = target[key]
536 536
537 537 caps[b'redirect'][b'targets'].append(entry)
538 538
539 539 return proto.addcapabilities(repo, caps)
540 540
541 541 def getadvertisedredirecttargets(repo, proto):
542 542 """Obtain a list of content redirect targets.
543 543
544 544 Returns a list containing potential redirect targets that will be
545 545 advertised in capabilities data. Each dict MUST have the following
546 546 keys:
547 547
548 548 name
549 549 The name of this redirect target. This is the identifier clients use
550 550 to refer to a target. It is transferred as part of every command
551 551 request.
552 552
553 553 protocol
554 554 Network protocol used by this target. Typically this is the string
555 555 in front of the ``://`` in a URL. e.g. ``https``.
556 556
557 557 uris
558 558 List of representative URIs for this target. Clients can use the
559 559 URIs to test parsing for compatibility or for ordering preference
560 560 for which target to use.
561 561
562 562 The following optional keys are recognized:
563 563
564 564 snirequired
565 565 Bool indicating if Server Name Indication (SNI) is required to
566 566 connect to this target.
567 567
568 568 tlsversions
569 569 List of bytes indicating which TLS versions are supported by this
570 570 target.
571 571
572 572 By default, clients reflect the target order advertised by servers
573 573 and servers will use the first client-advertised target when picking
574 574 a redirect target. So targets should be advertised in the order the
575 575 server prefers they be used.
576 576 """
577 577 return []
578 578
579 579 def wireprotocommand(name, args=None, permission='push', cachekeyfn=None):
580 580 """Decorator to declare a wire protocol command.
581 581
582 582 ``name`` is the name of the wire protocol command being provided.
583 583
584 584 ``args`` is a dict defining arguments accepted by the command. Keys are
585 585 the argument name. Values are dicts with the following keys:
586 586
587 587 ``type``
588 588 The argument data type. Must be one of the following string
589 589 literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
590 590 or ``bool``.
591 591
592 592 ``default``
593 593 A callable returning the default value for this argument. If not
594 594 specified, ``None`` will be the default value.
595 595
596 596 ``example``
597 597 An example value for this argument.
598 598
599 599 ``validvalues``
600 600 Set of recognized values for this argument.
601 601
602 602 ``permission`` defines the permission type needed to run this command.
603 603 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
604 604 respectively. Default is to assume command requires ``push`` permissions
605 605 because otherwise commands not declaring their permissions could modify
606 606 a repository that is supposed to be read-only.
607 607
608 608 ``cachekeyfn`` defines an optional callable that can derive the
609 609 cache key for this request.
610 610
611 611 Wire protocol commands are generators of objects to be serialized and
612 612 sent to the client.
613 613
614 614 If a command raises an uncaught exception, this will be translated into
615 615 a command error.
616 616
617 617 All commands can opt in to being cacheable by defining a function
618 618 (``cachekeyfn``) that is called to derive a cache key. This function
619 619 receives the same arguments as the command itself plus a ``cacher``
620 620 argument containing the active cacher for the request and returns a bytes
621 621 containing the key in a cache the response to this command may be cached
622 622 under.
623 623 """
624 624 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
625 625 if v['version'] == 2}
626 626
627 627 if permission not in ('push', 'pull'):
628 628 raise error.ProgrammingError('invalid wire protocol permission; '
629 629 'got %s; expected "push" or "pull"' %
630 630 permission)
631 631
632 632 if args is None:
633 633 args = {}
634 634
635 635 if not isinstance(args, dict):
636 636 raise error.ProgrammingError('arguments for version 2 commands '
637 637 'must be declared as dicts')
638 638
639 639 for arg, meta in args.items():
640 640 if arg == '*':
641 641 raise error.ProgrammingError('* argument name not allowed on '
642 642 'version 2 commands')
643 643
644 644 if not isinstance(meta, dict):
645 645 raise error.ProgrammingError('arguments for version 2 commands '
646 646 'must declare metadata as a dict')
647 647
648 648 if 'type' not in meta:
649 649 raise error.ProgrammingError('%s argument for command %s does not '
650 650 'declare type field' % (arg, name))
651 651
652 652 if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'):
653 653 raise error.ProgrammingError('%s argument for command %s has '
654 654 'illegal type: %s' % (arg, name,
655 655 meta['type']))
656 656
657 657 if 'example' not in meta:
658 658 raise error.ProgrammingError('%s argument for command %s does not '
659 659 'declare example field' % (arg, name))
660 660
661 661 meta['required'] = 'default' not in meta
662 662
663 663 meta.setdefault('default', lambda: None)
664 664 meta.setdefault('validvalues', None)
665 665
666 666 def register(func):
667 667 if name in COMMANDS:
668 668 raise error.ProgrammingError('%s command already registered '
669 669 'for version 2' % name)
670 670
671 671 COMMANDS[name] = wireprototypes.commandentry(
672 672 func, args=args, transports=transports, permission=permission,
673 673 cachekeyfn=cachekeyfn)
674 674
675 675 return func
676 676
677 677 return register
678 678
679 679 def makecommandcachekeyfn(command, localversion=None, allargs=False):
680 680 """Construct a cache key derivation function with common features.
681 681
682 682 By default, the cache key is a hash of:
683 683
684 684 * The command name.
685 685 * A global cache version number.
686 686 * A local cache version number (passed via ``localversion``).
687 687 * All the arguments passed to the command.
688 688 * The media type used.
689 689 * Wire protocol version string.
690 690 * The repository path.
691 691 """
692 692 if not allargs:
693 693 raise error.ProgrammingError('only allargs=True is currently supported')
694 694
695 695 if localversion is None:
696 696 raise error.ProgrammingError('must set localversion argument value')
697 697
698 698 def cachekeyfn(repo, proto, cacher, **args):
699 699 spec = COMMANDS[command]
700 700
701 701 # Commands that mutate the repo can not be cached.
702 702 if spec.permission == 'push':
703 703 return None
704 704
705 705 # TODO config option to disable caching.
706 706
707 707 # Our key derivation strategy is to construct a data structure
708 708 # holding everything that could influence cacheability and to hash
709 709 # the CBOR representation of that. Using CBOR seems like it might
710 710 # be overkill. However, simpler hashing mechanisms are prone to
711 711 # duplicate input issues. e.g. if you just concatenate two values,
712 712 # "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides
713 713 # "padding" between values and prevents these problems.
714 714
715 715 # Seed the hash with various data.
716 716 state = {
717 717 # To invalidate all cache keys.
718 718 b'globalversion': GLOBAL_CACHE_VERSION,
719 719 # More granular cache key invalidation.
720 720 b'localversion': localversion,
721 721 # Cache keys are segmented by command.
722 722 b'command': pycompat.sysbytes(command),
723 723 # Throw in the media type and API version strings so changes
724 724 # to exchange semantics invalid cache.
725 725 b'mediatype': FRAMINGTYPE,
726 726 b'version': HTTP_WIREPROTO_V2,
727 727 # So same requests for different repos don't share cache keys.
728 728 b'repo': repo.root,
729 729 }
730 730
731 731 # The arguments passed to us will have already been normalized.
732 732 # Default values will be set, etc. This is important because it
733 733 # means that it doesn't matter if clients send an explicit argument
734 734 # or rely on the default value: it will all normalize to the same
735 735 # set of arguments on the server and therefore the same cache key.
736 736 #
737 737 # Arguments by their very nature must support being encoded to CBOR.
738 738 # And the CBOR encoder is deterministic. So we hash the arguments
739 739 # by feeding the CBOR of their representation into the hasher.
740 740 if allargs:
741 741 state[b'args'] = pycompat.byteskwargs(args)
742 742
743 743 cacher.adjustcachekeystate(state)
744 744
745 745 hasher = hashlib.sha1()
746 746 for chunk in cborutil.streamencode(state):
747 747 hasher.update(chunk)
748 748
749 749 return pycompat.sysbytes(hasher.hexdigest())
750 750
751 751 return cachekeyfn
752 752
753 753 def makeresponsecacher(repo, proto, command, args, objencoderfn,
754 754 redirecttargets, redirecthashes):
755 755 """Construct a cacher for a cacheable command.
756 756
757 757 Returns an ``iwireprotocolcommandcacher`` instance.
758 758
759 759 Extensions can monkeypatch this function to provide custom caching
760 760 backends.
761 761 """
762 762 return None
763 763
764 764 @wireprotocommand('branchmap', permission='pull')
765 765 def branchmapv2(repo, proto):
766 766 yield {encoding.fromlocal(k): v
767 767 for k, v in repo.branchmap().iteritems()}
768 768
769 769 @wireprotocommand('capabilities', permission='pull')
770 770 def capabilitiesv2(repo, proto):
771 771 yield _capabilitiesv2(repo, proto)
772 772
773 773 @wireprotocommand(
774 774 'changesetdata',
775 775 args={
776 776 'noderange': {
777 777 'type': 'list',
778 778 'default': lambda: None,
779 779 'example': [[b'0123456...'], [b'abcdef...']],
780 780 },
781 781 'nodes': {
782 782 'type': 'list',
783 783 'default': lambda: None,
784 784 'example': [b'0123456...'],
785 785 },
786 786 'nodesdepth': {
787 787 'type': 'int',
788 788 'default': lambda: None,
789 789 'example': 10,
790 790 },
791 791 'fields': {
792 792 'type': 'set',
793 793 'default': set,
794 794 'example': {b'parents', b'revision'},
795 795 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
796 796 },
797 797 },
798 798 permission='pull')
799 799 def changesetdata(repo, proto, noderange, nodes, nodesdepth, fields):
800 800 # TODO look for unknown fields and abort when they can't be serviced.
801 801 # This could probably be validated by dispatcher using validvalues.
802 802
803 803 if noderange is None and nodes is None:
804 804 raise error.WireprotoCommandError(
805 805 'noderange or nodes must be defined')
806 806
807 807 if nodesdepth is not None and nodes is None:
808 808 raise error.WireprotoCommandError(
809 809 'nodesdepth requires the nodes argument')
810 810
811 811 if noderange is not None:
812 812 if len(noderange) != 2:
813 813 raise error.WireprotoCommandError(
814 814 'noderange must consist of 2 elements')
815 815
816 816 if not noderange[1]:
817 817 raise error.WireprotoCommandError(
818 818 'heads in noderange request cannot be empty')
819 819
820 820 cl = repo.changelog
821 821 hasnode = cl.hasnode
822 822
823 823 seen = set()
824 824 outgoing = []
825 825
826 826 if nodes is not None:
827 827 outgoing = [n for n in nodes if hasnode(n)]
828 828
829 829 if nodesdepth:
830 830 outgoing = [cl.node(r) for r in
831 831 repo.revs(b'ancestors(%ln, %d)', outgoing,
832 832 nodesdepth - 1)]
833 833
834 834 seen |= set(outgoing)
835 835
836 836 if noderange is not None:
837 837 if noderange[0]:
838 838 common = [n for n in noderange[0] if hasnode(n)]
839 839 else:
840 840 common = [nullid]
841 841
842 842 for n in discovery.outgoing(repo, common, noderange[1]).missing:
843 843 if n not in seen:
844 844 outgoing.append(n)
845 845 # Don't need to add to seen here because this is the final
846 846 # source of nodes and there should be no duplicates in this
847 847 # list.
848 848
849 849 seen.clear()
850 850 publishing = repo.publishing()
851 851
852 852 if outgoing:
853 853 repo.hook('preoutgoing', throw=True, source='serve')
854 854
855 855 yield {
856 856 b'totalitems': len(outgoing),
857 857 }
858 858
859 859 # The phases of nodes already transferred to the client may have changed
860 860 # since the client last requested data. We send phase-only records
861 861 # for these revisions, if requested.
862 862 if b'phase' in fields and noderange is not None:
863 863 # TODO skip nodes whose phase will be reflected by a node in the
864 864 # outgoing set. This is purely an optimization to reduce data
865 865 # size.
866 866 for node in noderange[0]:
867 867 yield {
868 868 b'node': node,
869 869 b'phase': b'public' if publishing else repo[node].phasestr()
870 870 }
871 871
872 872 nodebookmarks = {}
873 873 for mark, node in repo._bookmarks.items():
874 874 nodebookmarks.setdefault(node, set()).add(mark)
875 875
876 876 # It is already topologically sorted by revision number.
877 877 for node in outgoing:
878 878 d = {
879 879 b'node': node,
880 880 }
881 881
882 882 if b'parents' in fields:
883 883 d[b'parents'] = cl.parents(node)
884 884
885 885 if b'phase' in fields:
886 886 if publishing:
887 887 d[b'phase'] = b'public'
888 888 else:
889 889 ctx = repo[node]
890 890 d[b'phase'] = ctx.phasestr()
891 891
892 892 if b'bookmarks' in fields and node in nodebookmarks:
893 893 d[b'bookmarks'] = sorted(nodebookmarks[node])
894 894 del nodebookmarks[node]
895 895
896 896 followingmeta = []
897 897 followingdata = []
898 898
899 899 if b'revision' in fields:
900 900 revisiondata = cl.revision(node, raw=True)
901 901 followingmeta.append((b'revision', len(revisiondata)))
902 902 followingdata.append(revisiondata)
903 903
904 904 # TODO make it possible for extensions to wrap a function or register
905 905 # a handler to service custom fields.
906 906
907 907 if followingmeta:
908 908 d[b'fieldsfollowing'] = followingmeta
909 909
910 910 yield d
911 911
912 912 for extra in followingdata:
913 913 yield extra
914 914
915 915 # If requested, send bookmarks from nodes that didn't have revision
916 916 # data sent so receiver is aware of any bookmark updates.
917 917 if b'bookmarks' in fields:
918 918 for node, marks in sorted(nodebookmarks.iteritems()):
919 919 yield {
920 920 b'node': node,
921 921 b'bookmarks': sorted(marks),
922 922 }
923 923
924 924 class FileAccessError(Exception):
925 925 """Represents an error accessing a specific file."""
926 926
927 927 def __init__(self, path, msg, args):
928 928 self.path = path
929 929 self.msg = msg
930 930 self.args = args
931 931
932 932 def getfilestore(repo, proto, path):
933 933 """Obtain a file storage object for use with wire protocol.
934 934
935 935 Exists as a standalone function so extensions can monkeypatch to add
936 936 access control.
937 937 """
938 938 # This seems to work even if the file doesn't exist. So catch
939 939 # "empty" files and return an error.
940 940 fl = repo.file(path)
941 941
942 942 if not len(fl):
943 943 raise FileAccessError(path, 'unknown file: %s', (path,))
944 944
945 945 return fl
946 946
947 947 @wireprotocommand(
948 948 'filedata',
949 949 args={
950 950 'haveparents': {
951 951 'type': 'bool',
952 952 'default': lambda: False,
953 953 'example': True,
954 954 },
955 955 'nodes': {
956 956 'type': 'list',
957 957 'example': [b'0123456...'],
958 958 },
959 959 'fields': {
960 960 'type': 'set',
961 961 'default': set,
962 962 'example': {b'parents', b'revision'},
963 963 'validvalues': {b'parents', b'revision'},
964 964 },
965 965 'path': {
966 966 'type': 'bytes',
967 967 'example': b'foo.txt',
968 968 }
969 969 },
970 970 permission='pull',
971 971 # TODO censoring a file revision won't invalidate the cache.
972 972 # Figure out a way to take censoring into account when deriving
973 973 # the cache key.
974 974 cachekeyfn=makecommandcachekeyfn('filedata', 1, allargs=True))
975 975 def filedata(repo, proto, haveparents, nodes, fields, path):
976 976 try:
977 977 # Extensions may wish to access the protocol handler.
978 978 store = getfilestore(repo, proto, path)
979 979 except FileAccessError as e:
980 980 raise error.WireprotoCommandError(e.msg, e.args)
981 981
982 982 # Validate requested nodes.
983 983 for node in nodes:
984 984 try:
985 985 store.rev(node)
986 986 except error.LookupError:
987 987 raise error.WireprotoCommandError('unknown file node: %s',
988 988 (hex(node),))
989 989
990 990 revisions = store.emitrevisions(nodes,
991 991 revisiondata=b'revision' in fields,
992 992 assumehaveparentrevisions=haveparents)
993 993
994 994 yield {
995 995 b'totalitems': len(nodes),
996 996 }
997 997
998 998 for revision in revisions:
999 999 d = {
1000 1000 b'node': revision.node,
1001 1001 }
1002 1002
1003 1003 if b'parents' in fields:
1004 1004 d[b'parents'] = [revision.p1node, revision.p2node]
1005 1005
1006 1006 followingmeta = []
1007 1007 followingdata = []
1008 1008
1009 1009 if b'revision' in fields:
1010 1010 if revision.revision is not None:
1011 1011 followingmeta.append((b'revision', len(revision.revision)))
1012 1012 followingdata.append(revision.revision)
1013 1013 else:
1014 1014 d[b'deltabasenode'] = revision.basenode
1015 1015 followingmeta.append((b'delta', len(revision.delta)))
1016 1016 followingdata.append(revision.delta)
1017 1017
1018 1018 if followingmeta:
1019 1019 d[b'fieldsfollowing'] = followingmeta
1020 1020
1021 1021 yield d
1022 1022
1023 1023 for extra in followingdata:
1024 1024 yield extra
1025 1025
1026 1026 @wireprotocommand(
1027 1027 'heads',
1028 1028 args={
1029 1029 'publiconly': {
1030 1030 'type': 'bool',
1031 1031 'default': lambda: False,
1032 1032 'example': False,
1033 1033 },
1034 1034 },
1035 1035 permission='pull')
1036 1036 def headsv2(repo, proto, publiconly):
1037 1037 if publiconly:
1038 1038 repo = repo.filtered('immutable')
1039 1039
1040 1040 yield repo.heads()
1041 1041
1042 1042 @wireprotocommand(
1043 1043 'known',
1044 1044 args={
1045 1045 'nodes': {
1046 1046 'type': 'list',
1047 1047 'default': list,
1048 1048 'example': [b'deadbeef'],
1049 1049 },
1050 1050 },
1051 1051 permission='pull')
1052 1052 def knownv2(repo, proto, nodes):
1053 1053 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
1054 1054 yield result
1055 1055
1056 1056 @wireprotocommand(
1057 1057 'listkeys',
1058 1058 args={
1059 1059 'namespace': {
1060 1060 'type': 'bytes',
1061 1061 'example': b'ns',
1062 1062 },
1063 1063 },
1064 1064 permission='pull')
1065 1065 def listkeysv2(repo, proto, namespace):
1066 1066 keys = repo.listkeys(encoding.tolocal(namespace))
1067 1067 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
1068 1068 for k, v in keys.iteritems()}
1069 1069
1070 1070 yield keys
1071 1071
1072 1072 @wireprotocommand(
1073 1073 'lookup',
1074 1074 args={
1075 1075 'key': {
1076 1076 'type': 'bytes',
1077 1077 'example': b'foo',
1078 1078 },
1079 1079 },
1080 1080 permission='pull')
1081 1081 def lookupv2(repo, proto, key):
1082 1082 key = encoding.tolocal(key)
1083 1083
1084 1084 # TODO handle exception.
1085 1085 node = repo.lookup(key)
1086 1086
1087 1087 yield node
1088 1088
1089 1089 @wireprotocommand(
1090 1090 'manifestdata',
1091 1091 args={
1092 1092 'nodes': {
1093 1093 'type': 'list',
1094 1094 'example': [b'0123456...'],
1095 1095 },
1096 1096 'haveparents': {
1097 1097 'type': 'bool',
1098 1098 'default': lambda: False,
1099 1099 'example': True,
1100 1100 },
1101 1101 'fields': {
1102 1102 'type': 'set',
1103 1103 'default': set,
1104 1104 'example': {b'parents', b'revision'},
1105 1105 'validvalues': {b'parents', b'revision'},
1106 1106 },
1107 1107 'tree': {
1108 1108 'type': 'bytes',
1109 1109 'example': b'',
1110 1110 },
1111 1111 },
1112 1112 permission='pull',
1113 1113 cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True))
1114 1114 def manifestdata(repo, proto, haveparents, nodes, fields, tree):
1115 1115 store = repo.manifestlog.getstorage(tree)
1116 1116
1117 1117 # Validate the node is known and abort on unknown revisions.
1118 1118 for node in nodes:
1119 1119 try:
1120 1120 store.rev(node)
1121 1121 except error.LookupError:
1122 1122 raise error.WireprotoCommandError(
1123 1123 'unknown node: %s', (node,))
1124 1124
1125 1125 revisions = store.emitrevisions(nodes,
1126 1126 revisiondata=b'revision' in fields,
1127 1127 assumehaveparentrevisions=haveparents)
1128 1128
1129 1129 yield {
1130 1130 b'totalitems': len(nodes),
1131 1131 }
1132 1132
1133 1133 for revision in revisions:
1134 1134 d = {
1135 1135 b'node': revision.node,
1136 1136 }
1137 1137
1138 1138 if b'parents' in fields:
1139 1139 d[b'parents'] = [revision.p1node, revision.p2node]
1140 1140
1141 1141 followingmeta = []
1142 1142 followingdata = []
1143 1143
1144 1144 if b'revision' in fields:
1145 1145 if revision.revision is not None:
1146 1146 followingmeta.append((b'revision', len(revision.revision)))
1147 1147 followingdata.append(revision.revision)
1148 1148 else:
1149 1149 d[b'deltabasenode'] = revision.basenode
1150 1150 followingmeta.append((b'delta', len(revision.delta)))
1151 1151 followingdata.append(revision.delta)
1152 1152
1153 1153 if followingmeta:
1154 1154 d[b'fieldsfollowing'] = followingmeta
1155 1155
1156 1156 yield d
1157 1157
1158 1158 for extra in followingdata:
1159 1159 yield extra
1160 1160
1161 1161 @wireprotocommand(
1162 1162 'pushkey',
1163 1163 args={
1164 1164 'namespace': {
1165 1165 'type': 'bytes',
1166 1166 'example': b'ns',
1167 1167 },
1168 1168 'key': {
1169 1169 'type': 'bytes',
1170 1170 'example': b'key',
1171 1171 },
1172 1172 'old': {
1173 1173 'type': 'bytes',
1174 1174 'example': b'old',
1175 1175 },
1176 1176 'new': {
1177 1177 'type': 'bytes',
1178 1178 'example': 'new',
1179 1179 },
1180 1180 },
1181 1181 permission='push')
1182 1182 def pushkeyv2(repo, proto, namespace, key, old, new):
1183 1183 # TODO handle ui output redirection
1184 1184 yield repo.pushkey(encoding.tolocal(namespace),
1185 1185 encoding.tolocal(key),
1186 1186 encoding.tolocal(old),
1187 1187 encoding.tolocal(new))
@@ -1,620 +1,711
1 1 #require no-chg
2 2
3 3 $ . $TESTDIR/wireprotohelpers.sh
4 4 $ enabledummycommands
5 5
6 6 $ hg init server
7 7 $ cat > server/.hg/hgrc << EOF
8 8 > [experimental]
9 9 > web.apiserver = true
10 10 > EOF
11 11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
12 12 $ cat hg.pid > $DAEMON_PIDS
13 13
14 14 HTTP v2 protocol not enabled by default
15 15
16 16 $ sendhttpraw << EOF
17 17 > httprequest GET api/$HTTPV2
18 18 > user-agent: test
19 19 > EOF
20 20 using raw connection to peer
21 21 s> GET /api/exp-http-v2-0002 HTTP/1.1\r\n
22 22 s> Accept-Encoding: identity\r\n
23 23 s> user-agent: test\r\n
24 24 s> host: $LOCALIP:$HGPORT\r\n (glob)
25 25 s> \r\n
26 26 s> makefile('rb', None)
27 27 s> HTTP/1.1 404 Not Found\r\n
28 28 s> Server: testing stub value\r\n
29 29 s> Date: $HTTP_DATE$\r\n
30 30 s> Content-Type: text/plain\r\n
31 31 s> Content-Length: 33\r\n
32 32 s> \r\n
33 33 s> API exp-http-v2-0002 not enabled\n
34 34
35 35 Restart server with support for HTTP v2 API
36 36
37 37 $ killdaemons.py
38 38 $ enablehttpv2 server
39 39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
40 40 $ cat hg.pid > $DAEMON_PIDS
41 41
42 42 Request to unknown command yields 404
43 43
44 44 $ sendhttpraw << EOF
45 45 > httprequest POST api/$HTTPV2/ro/badcommand
46 46 > user-agent: test
47 47 > EOF
48 48 using raw connection to peer
49 49 s> POST /api/exp-http-v2-0002/ro/badcommand HTTP/1.1\r\n
50 50 s> Accept-Encoding: identity\r\n
51 51 s> user-agent: test\r\n
52 52 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 53 s> \r\n
54 54 s> makefile('rb', None)
55 55 s> HTTP/1.1 404 Not Found\r\n
56 56 s> Server: testing stub value\r\n
57 57 s> Date: $HTTP_DATE$\r\n
58 58 s> Content-Type: text/plain\r\n
59 59 s> Content-Length: 42\r\n
60 60 s> \r\n
61 61 s> unknown wire protocol command: badcommand\n
62 62
63 63 GET to read-only command yields a 405
64 64
65 65 $ sendhttpraw << EOF
66 66 > httprequest GET api/$HTTPV2/ro/customreadonly
67 67 > user-agent: test
68 68 > EOF
69 69 using raw connection to peer
70 70 s> GET /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
71 71 s> Accept-Encoding: identity\r\n
72 72 s> user-agent: test\r\n
73 73 s> host: $LOCALIP:$HGPORT\r\n (glob)
74 74 s> \r\n
75 75 s> makefile('rb', None)
76 76 s> HTTP/1.1 405 Method Not Allowed\r\n
77 77 s> Server: testing stub value\r\n
78 78 s> Date: $HTTP_DATE$\r\n
79 79 s> Allow: POST\r\n
80 80 s> Content-Length: 30\r\n
81 81 s> \r\n
82 82 s> commands require POST requests
83 83
84 84 Missing Accept header results in 406
85 85
86 86 $ sendhttpraw << EOF
87 87 > httprequest POST api/$HTTPV2/ro/customreadonly
88 88 > user-agent: test
89 89 > EOF
90 90 using raw connection to peer
91 91 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
92 92 s> Accept-Encoding: identity\r\n
93 93 s> user-agent: test\r\n
94 94 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 95 s> \r\n
96 96 s> makefile('rb', None)
97 97 s> HTTP/1.1 406 Not Acceptable\r\n
98 98 s> Server: testing stub value\r\n
99 99 s> Date: $HTTP_DATE$\r\n
100 100 s> Content-Type: text/plain\r\n
101 101 s> Content-Length: 85\r\n
102 102 s> \r\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
104 104
105 105 Bad Accept header results in 406
106 106
107 107 $ sendhttpraw << EOF
108 108 > httprequest POST api/$HTTPV2/ro/customreadonly
109 109 > accept: invalid
110 110 > user-agent: test
111 111 > EOF
112 112 using raw connection to peer
113 113 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
114 114 s> Accept-Encoding: identity\r\n
115 115 s> accept: invalid\r\n
116 116 s> user-agent: test\r\n
117 117 s> host: $LOCALIP:$HGPORT\r\n (glob)
118 118 s> \r\n
119 119 s> makefile('rb', None)
120 120 s> HTTP/1.1 406 Not Acceptable\r\n
121 121 s> Server: testing stub value\r\n
122 122 s> Date: $HTTP_DATE$\r\n
123 123 s> Content-Type: text/plain\r\n
124 124 s> Content-Length: 85\r\n
125 125 s> \r\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n
127 127
128 128 Bad Content-Type header results in 415
129 129
130 130 $ sendhttpraw << EOF
131 131 > httprequest POST api/$HTTPV2/ro/customreadonly
132 132 > accept: $MEDIATYPE
133 133 > user-agent: test
134 134 > content-type: badmedia
135 135 > EOF
136 136 using raw connection to peer
137 137 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
138 138 s> Accept-Encoding: identity\r\n
139 s> accept: application/mercurial-exp-framing-0005\r\n
139 s> accept: application/mercurial-exp-framing-0006\r\n
140 140 s> content-type: badmedia\r\n
141 141 s> user-agent: test\r\n
142 142 s> host: $LOCALIP:$HGPORT\r\n (glob)
143 143 s> \r\n
144 144 s> makefile('rb', None)
145 145 s> HTTP/1.1 415 Unsupported Media Type\r\n
146 146 s> Server: testing stub value\r\n
147 147 s> Date: $HTTP_DATE$\r\n
148 148 s> Content-Type: text/plain\r\n
149 149 s> Content-Length: 88\r\n
150 150 s> \r\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0006\n
152 152
153 153 Request to read-only command works out of the box
154 154
155 155 $ sendhttpraw << EOF
156 156 > httprequest POST api/$HTTPV2/ro/customreadonly
157 157 > accept: $MEDIATYPE
158 158 > content-type: $MEDIATYPE
159 159 > user-agent: test
160 160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
161 161 > EOF
162 162 using raw connection to peer
163 163 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
164 164 s> Accept-Encoding: identity\r\n
165 165 s> *\r\n (glob)
166 s> content-type: application/mercurial-exp-framing-0005\r\n
166 s> content-type: application/mercurial-exp-framing-0006\r\n
167 167 s> user-agent: test\r\n
168 168 s> content-length: 29\r\n
169 169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 170 s> \r\n
171 171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
172 172 s> makefile('rb', None)
173 173 s> HTTP/1.1 200 OK\r\n
174 174 s> Server: testing stub value\r\n
175 175 s> Date: $HTTP_DATE$\r\n
176 s> Content-Type: application/mercurial-exp-framing-0005\r\n
176 s> Content-Type: application/mercurial-exp-framing-0006\r\n
177 177 s> Transfer-Encoding: chunked\r\n
178 178 s> \r\n
179 179 s> 13\r\n
180 180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
181 181 s> \r\n
182 182 s> 27\r\n
183 183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
184 184 s> \r\n
185 185 s> 8\r\n
186 186 s> \x00\x00\x00\x01\x00\x02\x002
187 187 s> \r\n
188 188 s> 0\r\n
189 189 s> \r\n
190 190
191 191 $ sendhttpv2peerverbose << EOF
192 192 > command customreadonly
193 193 > EOF
194 194 creating http peer for wire protocol version 2
195 195 sending customreadonly command
196 196 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
197 197 s> Accept-Encoding: identity\r\n
198 s> accept: application/mercurial-exp-framing-0005\r\n
199 s> content-type: application/mercurial-exp-framing-0005\r\n
200 s> content-length: 29\r\n
198 s> accept: application/mercurial-exp-framing-0006\r\n
199 s> content-type: application/mercurial-exp-framing-0006\r\n
200 s> content-length: 65\r\n
201 201 s> host: $LOCALIP:$HGPORT\r\n (glob)
202 202 s> user-agent: Mercurial debugwireproto\r\n
203 203 s> \r\n
204 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
204 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x15\x00\x00\x01\x00\x01\x00\x11\xa1DnameNcustomreadonly
205 205 s> makefile('rb', None)
206 206 s> HTTP/1.1 200 OK\r\n
207 207 s> Server: testing stub value\r\n
208 208 s> Date: $HTTP_DATE$\r\n
209 s> Content-Type: application/mercurial-exp-framing-0005\r\n
209 s> Content-Type: application/mercurial-exp-framing-0006\r\n
210 210 s> Transfer-Encoding: chunked\r\n
211 211 s> \r\n
212 212 s> 13\r\n
213 213 s> \x0b\x00\x00\x01\x00\x02\x011
214 214 s> \xa1FstatusBok
215 215 s> \r\n
216 216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
217 217 s> 27\r\n
218 218 s> \x1f\x00\x00\x01\x00\x02\x001
219 219 s> X\x1dcustomreadonly bytes response
220 220 s> \r\n
221 221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
222 222 s> 8\r\n
223 223 s> \x00\x00\x00\x01\x00\x02\x002
224 224 s> \r\n
225 225 s> 0\r\n
226 226 s> \r\n
227 227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
228 228 response: gen[
229 229 b'customreadonly bytes response'
230 230 ]
231 231 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
232 232
233 233 Request to read-write command fails because server is read-only by default
234 234
235 235 GET to read-write request yields 405
236 236
237 237 $ sendhttpraw << EOF
238 238 > httprequest GET api/$HTTPV2/rw/customreadonly
239 239 > user-agent: test
240 240 > EOF
241 241 using raw connection to peer
242 242 s> GET /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
243 243 s> Accept-Encoding: identity\r\n
244 244 s> user-agent: test\r\n
245 245 s> host: $LOCALIP:$HGPORT\r\n (glob)
246 246 s> \r\n
247 247 s> makefile('rb', None)
248 248 s> HTTP/1.1 405 Method Not Allowed\r\n
249 249 s> Server: testing stub value\r\n
250 250 s> Date: $HTTP_DATE$\r\n
251 251 s> Allow: POST\r\n
252 252 s> Content-Length: 30\r\n
253 253 s> \r\n
254 254 s> commands require POST requests
255 255
256 256 Even for unknown commands
257 257
258 258 $ sendhttpraw << EOF
259 259 > httprequest GET api/$HTTPV2/rw/badcommand
260 260 > user-agent: test
261 261 > EOF
262 262 using raw connection to peer
263 263 s> GET /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
264 264 s> Accept-Encoding: identity\r\n
265 265 s> user-agent: test\r\n
266 266 s> host: $LOCALIP:$HGPORT\r\n (glob)
267 267 s> \r\n
268 268 s> makefile('rb', None)
269 269 s> HTTP/1.1 405 Method Not Allowed\r\n
270 270 s> Server: testing stub value\r\n
271 271 s> Date: $HTTP_DATE$\r\n
272 272 s> Allow: POST\r\n
273 273 s> Content-Length: 30\r\n
274 274 s> \r\n
275 275 s> commands require POST requests
276 276
277 277 SSL required by default
278 278
279 279 $ sendhttpraw << EOF
280 280 > httprequest POST api/$HTTPV2/rw/customreadonly
281 281 > user-agent: test
282 282 > EOF
283 283 using raw connection to peer
284 284 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
285 285 s> Accept-Encoding: identity\r\n
286 286 s> user-agent: test\r\n
287 287 s> host: $LOCALIP:$HGPORT\r\n (glob)
288 288 s> \r\n
289 289 s> makefile('rb', None)
290 290 s> HTTP/1.1 403 ssl required\r\n
291 291 s> Server: testing stub value\r\n
292 292 s> Date: $HTTP_DATE$\r\n
293 293 s> Content-Length: 17\r\n
294 294 s> \r\n
295 295 s> permission denied
296 296
297 297 Restart server to allow non-ssl read-write operations
298 298
299 299 $ killdaemons.py
300 300 $ cat > server/.hg/hgrc << EOF
301 301 > [experimental]
302 302 > web.apiserver = true
303 303 > web.api.http-v2 = true
304 304 > [web]
305 305 > push_ssl = false
306 306 > allow-push = *
307 307 > EOF
308 308
309 309 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
310 310 $ cat hg.pid > $DAEMON_PIDS
311 311
312 312 Authorized request for valid read-write command works
313 313
314 314 $ sendhttpraw << EOF
315 315 > httprequest POST api/$HTTPV2/rw/customreadonly
316 316 > user-agent: test
317 317 > accept: $MEDIATYPE
318 318 > content-type: $MEDIATYPE
319 319 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
320 320 > EOF
321 321 using raw connection to peer
322 322 s> POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
323 323 s> Accept-Encoding: identity\r\n
324 s> accept: application/mercurial-exp-framing-0005\r\n
325 s> content-type: application/mercurial-exp-framing-0005\r\n
324 s> accept: application/mercurial-exp-framing-0006\r\n
325 s> content-type: application/mercurial-exp-framing-0006\r\n
326 326 s> user-agent: test\r\n
327 327 s> content-length: 29\r\n
328 328 s> host: $LOCALIP:$HGPORT\r\n (glob)
329 329 s> \r\n
330 330 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
331 331 s> makefile('rb', None)
332 332 s> HTTP/1.1 200 OK\r\n
333 333 s> Server: testing stub value\r\n
334 334 s> Date: $HTTP_DATE$\r\n
335 s> Content-Type: application/mercurial-exp-framing-0005\r\n
335 s> Content-Type: application/mercurial-exp-framing-0006\r\n
336 336 s> Transfer-Encoding: chunked\r\n
337 337 s> \r\n
338 338 s> 13\r\n
339 339 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
340 340 s> \r\n
341 341 s> 27\r\n
342 342 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
343 343 s> \r\n
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
350 350 Authorized request for unknown command is rejected
351 351
352 352 $ sendhttpraw << EOF
353 353 > httprequest POST api/$HTTPV2/rw/badcommand
354 354 > user-agent: test
355 355 > accept: $MEDIATYPE
356 356 > EOF
357 357 using raw connection to peer
358 358 s> POST /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
359 359 s> Accept-Encoding: identity\r\n
360 s> accept: application/mercurial-exp-framing-0005\r\n
360 s> accept: application/mercurial-exp-framing-0006\r\n
361 361 s> user-agent: test\r\n
362 362 s> host: $LOCALIP:$HGPORT\r\n (glob)
363 363 s> \r\n
364 364 s> makefile('rb', None)
365 365 s> HTTP/1.1 404 Not Found\r\n
366 366 s> Server: testing stub value\r\n
367 367 s> Date: $HTTP_DATE$\r\n
368 368 s> Content-Type: text/plain\r\n
369 369 s> Content-Length: 42\r\n
370 370 s> \r\n
371 371 s> unknown wire protocol command: badcommand\n
372 372
373 373 debugreflect isn't enabled by default
374 374
375 375 $ sendhttpraw << EOF
376 376 > httprequest POST api/$HTTPV2/ro/debugreflect
377 377 > user-agent: test
378 378 > EOF
379 379 using raw connection to peer
380 380 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
381 381 s> Accept-Encoding: identity\r\n
382 382 s> user-agent: test\r\n
383 383 s> host: $LOCALIP:$HGPORT\r\n (glob)
384 384 s> \r\n
385 385 s> makefile('rb', None)
386 386 s> HTTP/1.1 404 Not Found\r\n
387 387 s> Server: testing stub value\r\n
388 388 s> Date: $HTTP_DATE$\r\n
389 389 s> Content-Type: text/plain\r\n
390 390 s> Content-Length: 34\r\n
391 391 s> \r\n
392 392 s> debugreflect service not available
393 393
394 394 Restart server to get debugreflect endpoint
395 395
396 396 $ killdaemons.py
397 397 $ cat > server/.hg/hgrc << EOF
398 398 > [experimental]
399 399 > web.apiserver = true
400 400 > web.api.debugreflect = true
401 401 > web.api.http-v2 = true
402 402 > [web]
403 403 > push_ssl = false
404 404 > allow-push = *
405 405 > EOF
406 406
407 407 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
408 408 $ cat hg.pid > $DAEMON_PIDS
409 409
410 410 Command frames can be reflected via debugreflect
411 411
412 412 $ sendhttpraw << EOF
413 413 > httprequest POST api/$HTTPV2/ro/debugreflect
414 414 > accept: $MEDIATYPE
415 415 > content-type: $MEDIATYPE
416 416 > user-agent: test
417 417 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
418 418 > EOF
419 419 using raw connection to peer
420 420 s> POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
421 421 s> Accept-Encoding: identity\r\n
422 s> accept: application/mercurial-exp-framing-0005\r\n
423 s> content-type: application/mercurial-exp-framing-0005\r\n
422 s> accept: application/mercurial-exp-framing-0006\r\n
423 s> content-type: application/mercurial-exp-framing-0006\r\n
424 424 s> user-agent: test\r\n
425 425 s> content-length: 47\r\n
426 426 s> host: $LOCALIP:$HGPORT\r\n (glob)
427 427 s> \r\n
428 428 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
429 429 s> makefile('rb', None)
430 430 s> HTTP/1.1 200 OK\r\n
431 431 s> Server: testing stub value\r\n
432 432 s> Date: $HTTP_DATE$\r\n
433 433 s> Content-Type: text/plain\r\n
434 434 s> Content-Length: 223\r\n
435 435 s> \r\n
436 436 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
437 437 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "redirect": null, "requestid": 1}]\n
438 438 s> received: <no frame>\n
439 439 s> {"action": "noop"}
440 440
441 441 Multiple requests to regular command URL are not allowed
442 442
443 443 $ sendhttpraw << EOF
444 444 > httprequest POST api/$HTTPV2/ro/customreadonly
445 445 > accept: $MEDIATYPE
446 446 > content-type: $MEDIATYPE
447 447 > user-agent: test
448 448 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
449 449 > EOF
450 450 using raw connection to peer
451 451 s> POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
452 452 s> Accept-Encoding: identity\r\n
453 s> accept: application/mercurial-exp-framing-0005\r\n
454 s> content-type: application/mercurial-exp-framing-0005\r\n
453 s> accept: application/mercurial-exp-framing-0006\r\n
454 s> content-type: application/mercurial-exp-framing-0006\r\n
455 455 s> user-agent: test\r\n
456 456 s> content-length: 29\r\n
457 457 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 458 s> \r\n
459 459 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
460 460 s> makefile('rb', None)
461 461 s> HTTP/1.1 200 OK\r\n
462 462 s> Server: testing stub value\r\n
463 463 s> Date: $HTTP_DATE$\r\n
464 s> Content-Type: application/mercurial-exp-framing-0005\r\n
464 s> Content-Type: application/mercurial-exp-framing-0006\r\n
465 465 s> Transfer-Encoding: chunked\r\n
466 466 s> \r\n
467 467 s> 13\r\n
468 468 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
469 469 s> \r\n
470 470 s> 27\r\n
471 471 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
472 472 s> \r\n
473 473 s> 8\r\n
474 474 s> \x00\x00\x00\x01\x00\x02\x002
475 475 s> \r\n
476 476 s> 0\r\n
477 477 s> \r\n
478 478
479 479 Multiple requests to "multirequest" URL are allowed
480 480
481 481 $ sendhttpraw << EOF
482 482 > httprequest POST api/$HTTPV2/ro/multirequest
483 483 > accept: $MEDIATYPE
484 484 > content-type: $MEDIATYPE
485 485 > user-agent: test
486 486 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
487 487 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
488 488 > EOF
489 489 using raw connection to peer
490 490 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
491 491 s> Accept-Encoding: identity\r\n
492 492 s> *\r\n (glob)
493 493 s> *\r\n (glob)
494 494 s> user-agent: test\r\n
495 495 s> content-length: 58\r\n
496 496 s> host: $LOCALIP:$HGPORT\r\n (glob)
497 497 s> \r\n
498 498 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
499 499 s> makefile('rb', None)
500 500 s> HTTP/1.1 200 OK\r\n
501 501 s> Server: testing stub value\r\n
502 502 s> Date: $HTTP_DATE$\r\n
503 s> Content-Type: application/mercurial-exp-framing-0005\r\n
503 s> Content-Type: application/mercurial-exp-framing-0006\r\n
504 504 s> Transfer-Encoding: chunked\r\n
505 505 s> \r\n
506 506 s> 13\r\n
507 507 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
508 508 s> \r\n
509 509 s> 27\r\n
510 510 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
511 511 s> \r\n
512 512 s> 8\r\n
513 513 s> \x00\x00\x00\x01\x00\x02\x002
514 514 s> \r\n
515 515 s> 13\r\n
516 516 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
517 517 s> \r\n
518 518 s> 27\r\n
519 519 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
520 520 s> \r\n
521 521 s> 8\r\n
522 522 s> \x00\x00\x00\x03\x00\x02\x002
523 523 s> \r\n
524 524 s> 0\r\n
525 525 s> \r\n
526 526
527 527 Interleaved requests to "multirequest" are processed
528 528
529 529 $ sendhttpraw << EOF
530 530 > httprequest POST api/$HTTPV2/ro/multirequest
531 531 > accept: $MEDIATYPE
532 532 > content-type: $MEDIATYPE
533 533 > user-agent: test
534 534 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
535 535 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
536 536 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
537 537 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
538 538 > EOF
539 539 using raw connection to peer
540 540 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
541 541 s> Accept-Encoding: identity\r\n
542 s> accept: application/mercurial-exp-framing-0005\r\n
543 s> content-type: application/mercurial-exp-framing-0005\r\n
542 s> accept: application/mercurial-exp-framing-0006\r\n
543 s> content-type: application/mercurial-exp-framing-0006\r\n
544 544 s> user-agent: test\r\n
545 545 s> content-length: 115\r\n
546 546 s> host: $LOCALIP:$HGPORT\r\n (glob)
547 547 s> \r\n
548 548 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
549 549 s> makefile('rb', None)
550 550 s> HTTP/1.1 200 OK\r\n
551 551 s> Server: testing stub value\r\n
552 552 s> Date: $HTTP_DATE$\r\n
553 s> Content-Type: application/mercurial-exp-framing-0005\r\n
553 s> Content-Type: application/mercurial-exp-framing-0006\r\n
554 554 s> Transfer-Encoding: chunked\r\n
555 555 s> \r\n
556 556 s> 13\r\n
557 557 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
558 558 s> \r\n
559 559 s> 28\r\n
560 560 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
561 561 s> \r\n
562 562 s> 8\r\n
563 563 s> \x00\x00\x00\x03\x00\x02\x002
564 564 s> \r\n
565 565 s> 13\r\n
566 566 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
567 567 s> \r\n
568 568 s> 9\r\n
569 569 s> \x01\x00\x00\x01\x00\x02\x001\xa0
570 570 s> \r\n
571 571 s> 8\r\n
572 572 s> \x00\x00\x00\x01\x00\x02\x002
573 573 s> \r\n
574 574 s> 0\r\n
575 575 s> \r\n
576 576
577 577 Restart server to disable read-write access
578 578
579 579 $ killdaemons.py
580 580 $ cat > server/.hg/hgrc << EOF
581 581 > [experimental]
582 582 > web.apiserver = true
583 583 > web.api.debugreflect = true
584 584 > web.api.http-v2 = true
585 585 > [web]
586 586 > push_ssl = false
587 587 > EOF
588 588
589 589 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
590 590 $ cat hg.pid > $DAEMON_PIDS
591 591
592 592 Attempting to run a read-write command via multirequest on read-only URL is not allowed
593 593
594 594 $ sendhttpraw << EOF
595 595 > httprequest POST api/$HTTPV2/ro/multirequest
596 596 > accept: $MEDIATYPE
597 597 > content-type: $MEDIATYPE
598 598 > user-agent: test
599 599 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
600 600 > EOF
601 601 using raw connection to peer
602 602 s> POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
603 603 s> Accept-Encoding: identity\r\n
604 s> accept: application/mercurial-exp-framing-0005\r\n
605 s> content-type: application/mercurial-exp-framing-0005\r\n
604 s> accept: application/mercurial-exp-framing-0006\r\n
605 s> content-type: application/mercurial-exp-framing-0006\r\n
606 606 s> user-agent: test\r\n
607 607 s> content-length: 22\r\n
608 608 s> host: $LOCALIP:$HGPORT\r\n (glob)
609 609 s> \r\n
610 610 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
611 611 s> makefile('rb', None)
612 612 s> HTTP/1.1 403 Forbidden\r\n
613 613 s> Server: testing stub value\r\n
614 614 s> Date: $HTTP_DATE$\r\n
615 615 s> Content-Type: text/plain\r\n
616 616 s> Content-Length: 52\r\n
617 617 s> \r\n
618 618 s> insufficient permissions to execute command: pushkey
619 619
620 Defining an invalid content encoding results in warning
621
622 $ hg --config experimental.httppeer.v2-encoder-order=identity,badencoder --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
623 > command heads
624 > EOF
625 creating http peer for wire protocol version 2
626 sending heads command
627 wire protocol version 2 encoder referenced in config (badencoder) is not known; ignoring
628 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
629 s> Accept-Encoding: identity\r\n
630 s> accept: application/mercurial-exp-framing-0006\r\n
631 s> content-type: application/mercurial-exp-framing-0006\r\n
632 s> content-length: 56\r\n
633 s> host: $LOCALIP:$HGPORT\r\n (glob)
634 s> user-agent: Mercurial debugwireproto\r\n
635 s> \r\n
636 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
637 s> makefile('rb', None)
638 s> HTTP/1.1 200 OK\r\n
639 s> Server: testing stub value\r\n
640 s> Date: $HTTP_DATE$\r\n
641 s> Content-Type: application/mercurial-exp-framing-0006\r\n
642 s> Transfer-Encoding: chunked\r\n
643 s> \r\n
644 s> 13\r\n
645 s> \x0b\x00\x00\x01\x00\x02\x011
646 s> \xa1FstatusBok
647 s> \r\n
648 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
649 s> 1e\r\n
650 s> \x16\x00\x00\x01\x00\x02\x001
651 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
652 s> \r\n
653 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
654 s> 8\r\n
655 s> \x00\x00\x00\x01\x00\x02\x002
656 s> \r\n
657 s> 0\r\n
658 s> \r\n
659 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
660 response: [
661 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
662 ]
663 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
664
665 #if zstd
666
667 $ hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF
668 > command heads
669 > EOF
670 creating http peer for wire protocol version 2
671 sending heads command
672 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
673 s> Accept-Encoding: identity\r\n
674 s> accept: application/mercurial-exp-framing-0006\r\n
675 s> content-type: application/mercurial-exp-framing-0006\r\n
676 s> content-length: 70\r\n
677 s> host: $LOCALIP:$HGPORT\r\n (glob)
678 s> user-agent: Mercurial debugwireproto\r\n
679 s> \r\n
680 s> *\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x83Hzstd-8mbDzlibHidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
681 s> makefile('rb', None)
682 s> HTTP/1.1 200 OK\r\n
683 s> Server: testing stub value\r\n
684 s> Date: $HTTP_DATE$\r\n
685 s> Content-Type: application/mercurial-exp-framing-0006\r\n
686 s> Transfer-Encoding: chunked\r\n
687 s> \r\n
688 s> 13\r\n
689 s> \x0b\x00\x00\x01\x00\x02\x011
690 s> \xa1FstatusBok
691 s> \r\n
692 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
693 s> 1e\r\n
694 s> \x16\x00\x00\x01\x00\x02\x001
695 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
696 s> \r\n
697 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
698 s> 8\r\n
699 s> \x00\x00\x00\x01\x00\x02\x002
700 s> \r\n
701 s> 0\r\n
702 s> \r\n
703 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
704 response: [
705 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
706 ]
707 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
708
709 #endif
710
620 711 $ cat error.log
@@ -1,754 +1,754
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 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
222 222
223 223 Same thing, but with "httprequest" command
224 224
225 225 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
226 226 > httprequest GET ?cmd=listkeys
227 227 > user-agent: test
228 228 > x-hgarg-1: namespace=namespaces
229 229 > EOF
230 230 using raw connection to peer
231 231 s> GET /?cmd=listkeys HTTP/1.1\r\n
232 232 s> Accept-Encoding: identity\r\n
233 233 s> user-agent: test\r\n
234 234 s> x-hgarg-1: namespace=namespaces\r\n
235 235 s> host: $LOCALIP:$HGPORT\r\n (glob)
236 236 s> \r\n
237 237 s> makefile('rb', None)
238 238 s> HTTP/1.1 200 Script output follows\r\n
239 239 s> Server: testing stub value\r\n
240 240 s> Date: $HTTP_DATE$\r\n
241 241 s> Content-Type: application/mercurial-0.1\r\n
242 242 s> Content-Length: 30\r\n
243 243 s> \r\n
244 244 s> bookmarks\t\n
245 245 s> namespaces\t\n
246 246 s> phases\t
247 247
248 248 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
249 249
250 250 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
251 251 > command heads
252 252 > EOF
253 253 s> GET /?cmd=capabilities HTTP/1.1\r\n
254 254 s> Accept-Encoding: identity\r\n
255 255 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
256 256 s> x-hgproto-1: cbor\r\n
257 257 s> x-hgupgrade-1: exp-http-v2-0002\r\n
258 258 s> accept: application/mercurial-0.1\r\n
259 259 s> host: $LOCALIP:$HGPORT\r\n (glob)
260 260 s> user-agent: Mercurial debugwireproto\r\n
261 261 s> \r\n
262 262 s> makefile('rb', None)
263 263 s> HTTP/1.1 200 Script output follows\r\n
264 264 s> Server: testing stub value\r\n
265 265 s> Date: $HTTP_DATE$\r\n
266 266 s> Content-Type: application/mercurial-0.1\r\n
267 267 s> Content-Length: *\r\n (glob)
268 268 s> \r\n
269 269 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
270 270 sending heads command
271 271 s> GET /?cmd=heads HTTP/1.1\r\n
272 272 s> Accept-Encoding: identity\r\n
273 273 s> vary: X-HgProto-1\r\n
274 274 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
275 275 s> accept: application/mercurial-0.1\r\n
276 276 s> host: $LOCALIP:$HGPORT\r\n (glob)
277 277 s> user-agent: Mercurial debugwireproto\r\n
278 278 s> \r\n
279 279 s> makefile('rb', None)
280 280 s> HTTP/1.1 200 Script output follows\r\n
281 281 s> Server: testing stub value\r\n
282 282 s> Date: $HTTP_DATE$\r\n
283 283 s> Content-Type: application/mercurial-0.1\r\n
284 284 s> Content-Length: 41\r\n
285 285 s> \r\n
286 286 s> 0000000000000000000000000000000000000000\n
287 287 response: [
288 288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
289 289 ]
290 290 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
291 291
292 292 $ killdaemons.py
293 293 $ enablehttpv2 empty
294 294 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
295 295 $ cat hg.pid > $DAEMON_PIDS
296 296
297 297 Client with HTTPv2 enabled automatically upgrades if the server supports it
298 298
299 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
299 $ hg --config experimental.httppeer.advertise-v2=true --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
300 300 > command heads
301 301 > EOF
302 302 s> GET /?cmd=capabilities HTTP/1.1\r\n
303 303 s> Accept-Encoding: identity\r\n
304 304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
305 305 s> x-hgproto-1: cbor\r\n
306 306 s> x-hgupgrade-1: exp-http-v2-0002\r\n
307 307 s> accept: application/mercurial-0.1\r\n
308 308 s> host: $LOCALIP:$HGPORT\r\n (glob)
309 309 s> user-agent: Mercurial debugwireproto\r\n
310 310 s> \r\n
311 311 s> makefile('rb', None)
312 312 s> HTTP/1.1 200 OK\r\n
313 313 s> Server: testing stub value\r\n
314 314 s> Date: $HTTP_DATE$\r\n
315 315 s> Content-Type: application/mercurial-cbor\r\n
316 316 s> Content-Length: *\r\n (glob)
317 317 s> \r\n
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-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
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
319 319 sending heads command
320 320 s> POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
321 321 s> Accept-Encoding: identity\r\n
322 s> accept: application/mercurial-exp-framing-0005\r\n
323 s> content-type: application/mercurial-exp-framing-0005\r\n
324 s> content-length: 20\r\n
322 s> accept: application/mercurial-exp-framing-0006\r\n
323 s> content-type: application/mercurial-exp-framing-0006\r\n
324 s> content-length: 56\r\n
325 325 s> host: $LOCALIP:$HGPORT\r\n (glob)
326 326 s> user-agent: Mercurial debugwireproto\r\n
327 327 s> \r\n
328 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
328 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads
329 329 s> makefile('rb', None)
330 330 s> HTTP/1.1 200 OK\r\n
331 331 s> Server: testing stub value\r\n
332 332 s> Date: $HTTP_DATE$\r\n
333 s> Content-Type: application/mercurial-exp-framing-0005\r\n
333 s> Content-Type: application/mercurial-exp-framing-0006\r\n
334 334 s> Transfer-Encoding: chunked\r\n
335 335 s> \r\n
336 336 s> 13\r\n
337 337 s> \x0b\x00\x00\x01\x00\x02\x011
338 338 s> \xa1FstatusBok
339 339 s> \r\n
340 340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
341 341 s> 1e\r\n
342 342 s> \x16\x00\x00\x01\x00\x02\x001
343 343 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
344 344 s> \r\n
345 345 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
346 346 s> 8\r\n
347 347 s> \x00\x00\x00\x01\x00\x02\x002
348 348 s> \r\n
349 349 s> 0\r\n
350 350 s> \r\n
351 351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
352 352 response: [
353 353 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
354 354 ]
355 355 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
356 356
357 357 $ killdaemons.py
358 358
359 359 HTTP client follows HTTP redirect on handshake to new repo
360 360
361 361 $ cd $TESTTMP
362 362
363 363 $ hg init redirector
364 364 $ hg init redirected
365 365 $ cd redirected
366 366 $ touch foo
367 367 $ hg -q commit -A -m initial
368 368 $ cd ..
369 369
370 370 $ cat > paths.conf << EOF
371 371 > [paths]
372 372 > / = $TESTTMP/*
373 373 > EOF
374 374
375 375 $ cat > redirectext.py << EOF
376 376 > from mercurial import extensions, wireprotoserver
377 377 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
378 378 > path = req.advertisedurl[len(req.advertisedbaseurl):]
379 379 > if not path.startswith(b'/redirector'):
380 380 > return orig(repo, req, res, proto, cmd)
381 381 > relpath = path[len(b'/redirector'):]
382 382 > res.status = b'301 Redirect'
383 383 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
384 384 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
385 385 > newurl = newurl[0:newurl.index(b'?')]
386 386 > res.headers[b'Location'] = newurl
387 387 > res.headers[b'Content-Type'] = b'text/plain'
388 388 > res.setbodybytes(b'redirected')
389 389 > return True
390 390 >
391 391 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
392 392 > EOF
393 393
394 394 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
395 395 > --config server.compressionengines=zlib \
396 396 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
397 397 $ cat hg.pid > $DAEMON_PIDS
398 398
399 399 Verify our HTTP 301 is served properly
400 400
401 401 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
402 402 > httprequest GET /redirector?cmd=capabilities
403 403 > user-agent: test
404 404 > EOF
405 405 using raw connection to peer
406 406 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
407 407 s> Accept-Encoding: identity\r\n
408 408 s> user-agent: test\r\n
409 409 s> host: $LOCALIP:$HGPORT\r\n (glob)
410 410 s> \r\n
411 411 s> makefile('rb', None)
412 412 s> HTTP/1.1 301 Redirect\r\n
413 413 s> Server: testing stub value\r\n
414 414 s> Date: $HTTP_DATE$\r\n
415 415 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
416 416 s> Content-Type: text/plain\r\n
417 417 s> Content-Length: 10\r\n
418 418 s> \r\n
419 419 s> redirected
420 420 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
421 421 s> Accept-Encoding: identity\r\n
422 422 s> user-agent: test\r\n
423 423 s> host: $LOCALIP:$HGPORT\r\n (glob)
424 424 s> \r\n
425 425 s> makefile('rb', None)
426 426 s> HTTP/1.1 200 Script output follows\r\n
427 427 s> Server: testing stub value\r\n
428 428 s> Date: $HTTP_DATE$\r\n
429 429 s> Content-Type: application/mercurial-0.1\r\n
430 430 s> Content-Length: 467\r\n
431 431 s> \r\n
432 432 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
433 433
434 434 Test with the HTTP peer
435 435
436 436 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
437 437 > command heads
438 438 > EOF
439 439 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
440 440 s> Accept-Encoding: identity\r\n
441 441 s> accept: application/mercurial-0.1\r\n
442 442 s> host: $LOCALIP:$HGPORT\r\n (glob)
443 443 s> user-agent: Mercurial debugwireproto\r\n
444 444 s> \r\n
445 445 s> makefile('rb', None)
446 446 s> HTTP/1.1 301 Redirect\r\n
447 447 s> Server: testing stub value\r\n
448 448 s> Date: $HTTP_DATE$\r\n
449 449 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
450 450 s> Content-Type: text/plain\r\n
451 451 s> Content-Length: 10\r\n
452 452 s> \r\n
453 453 s> redirected
454 454 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
455 455 s> Accept-Encoding: identity\r\n
456 456 s> accept: application/mercurial-0.1\r\n
457 457 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 458 s> user-agent: Mercurial debugwireproto\r\n
459 459 s> \r\n
460 460 s> makefile('rb', None)
461 461 s> HTTP/1.1 200 Script output follows\r\n
462 462 s> Server: testing stub value\r\n
463 463 s> Date: $HTTP_DATE$\r\n
464 464 s> Content-Type: application/mercurial-0.1\r\n
465 465 s> Content-Length: 467\r\n
466 466 s> \r\n
467 467 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
468 468 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
469 469 sending heads command
470 470 s> GET /redirected?cmd=heads HTTP/1.1\r\n
471 471 s> Accept-Encoding: identity\r\n
472 472 s> vary: X-HgProto-1\r\n
473 473 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
474 474 s> accept: application/mercurial-0.1\r\n
475 475 s> host: $LOCALIP:$HGPORT\r\n (glob)
476 476 s> user-agent: Mercurial debugwireproto\r\n
477 477 s> \r\n
478 478 s> makefile('rb', None)
479 479 s> HTTP/1.1 200 Script output follows\r\n
480 480 s> Server: testing stub value\r\n
481 481 s> Date: $HTTP_DATE$\r\n
482 482 s> Content-Type: application/mercurial-0.1\r\n
483 483 s> Content-Length: 41\r\n
484 484 s> \r\n
485 485 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
486 486 response: [
487 487 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
488 488 ]
489 489 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
490 490
491 491 $ killdaemons.py
492 492
493 493 Now test a variation where we strip the query string from the redirect URL.
494 494 (SCM Manager apparently did this and clients would recover from it)
495 495
496 496 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
497 497 > --config server.compressionengines=zlib \
498 498 > --config testing.redirectqs=false \
499 499 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
500 500 $ cat hg.pid > $DAEMON_PIDS
501 501
502 502 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
503 503 > httprequest GET /redirector?cmd=capabilities
504 504 > user-agent: test
505 505 > EOF
506 506 using raw connection to peer
507 507 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
508 508 s> Accept-Encoding: identity\r\n
509 509 s> user-agent: test\r\n
510 510 s> host: $LOCALIP:$HGPORT\r\n (glob)
511 511 s> \r\n
512 512 s> makefile('rb', None)
513 513 s> HTTP/1.1 301 Redirect\r\n
514 514 s> Server: testing stub value\r\n
515 515 s> Date: $HTTP_DATE$\r\n
516 516 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
517 517 s> Content-Type: text/plain\r\n
518 518 s> Content-Length: 10\r\n
519 519 s> \r\n
520 520 s> redirected
521 521 s> GET /redirected HTTP/1.1\r\n
522 522 s> Accept-Encoding: identity\r\n
523 523 s> user-agent: test\r\n
524 524 s> host: $LOCALIP:$HGPORT\r\n (glob)
525 525 s> \r\n
526 526 s> makefile('rb', None)
527 527 s> HTTP/1.1 200 Script output follows\r\n
528 528 s> Server: testing stub value\r\n
529 529 s> Date: $HTTP_DATE$\r\n
530 530 s> ETag: W/"*"\r\n (glob)
531 531 s> Content-Type: text/html; charset=ascii\r\n
532 532 s> Transfer-Encoding: chunked\r\n
533 533 s> \r\n
534 534 s> 414\r\n
535 535 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
536 536 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
537 537 s> <head>\n
538 538 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
539 539 s> <meta name="robots" content="index, nofollow" />\n
540 540 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
541 541 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
542 542 s> \n
543 543 s> <title>redirected: log</title>\n
544 544 s> <link rel="alternate" type="application/atom+xml"\n
545 545 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
546 546 s> <link rel="alternate" type="application/rss+xml"\n
547 547 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
548 548 s> </head>\n
549 549 s> <body>\n
550 550 s> \n
551 551 s> <div class="container">\n
552 552 s> <div class="menu">\n
553 553 s> <div class="logo">\n
554 554 s> <a href="https://mercurial-scm.org/">\n
555 555 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
556 556 s> </div>\n
557 557 s> <ul>\n
558 558 s> <li class="active">log</li>\n
559 559 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
560 560 s> <li><a href="/redirected/tags">tags</a></li>\n
561 561 s> <li><a href="
562 562 s> \r\n
563 563 s> 810\r\n
564 564 s> /redirected/bookmarks">bookmarks</a></li>\n
565 565 s> <li><a href="/redirected/branches">branches</a></li>\n
566 566 s> </ul>\n
567 567 s> <ul>\n
568 568 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
569 569 s> <li><a href="/redirected/file/tip">browse</a></li>\n
570 570 s> </ul>\n
571 571 s> <ul>\n
572 572 s> \n
573 573 s> </ul>\n
574 574 s> <ul>\n
575 575 s> <li><a href="/redirected/help">help</a></li>\n
576 576 s> </ul>\n
577 577 s> <div class="atom-logo">\n
578 578 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
579 579 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
580 580 s> </a>\n
581 581 s> </div>\n
582 582 s> </div>\n
583 583 s> \n
584 584 s> <div class="main">\n
585 585 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
586 586 s> <h3>log</h3>\n
587 587 s> \n
588 588 s> \n
589 589 s> <form class="search" action="/redirected/log">\n
590 590 s> \n
591 591 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
592 592 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
593 593 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
594 594 s> </form>\n
595 595 s> \n
596 596 s> <div class="navigate">\n
597 597 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
598 598 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
599 599 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
600 600 s> </div>\n
601 601 s> \n
602 602 s> <table class="bigtable">\n
603 603 s> <thead>\n
604 604 s> <tr>\n
605 605 s> <th class="age">age</th>\n
606 606 s> <th class="author">author</th>\n
607 607 s> <th class="description">description</th>\n
608 608 s> </tr>\n
609 609 s> </thead>\n
610 610 s> <tbody class="stripes2">\n
611 611 s> <tr>\n
612 612 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
613 613 s> <td class="author">test</td>\n
614 614 s> <td class="description">\n
615 615 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
616 616 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
617 617 s> </td>\n
618 618 s> </tr>\n
619 619 s> \n
620 620 s> </tbody>\n
621 621 s> </table>\n
622 622 s> \n
623 623 s> <div class="navigate">\n
624 624 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
625 625 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
626 626 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
627 627 s> </div>\n
628 628 s> \n
629 629 s> <script type="text/javascript">\n
630 630 s> ajaxScrollInit(\n
631 631 s> \'/redirected/shortlog/%next%\',\n
632 632 s> \'\', <!-- NEXTHASH\n
633 633 s> function (htmlText) {
634 634 s> \r\n
635 635 s> 14a\r\n
636 636 s> \n
637 637 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
638 638 s> return m ? m[1] : null;\n
639 639 s> },\n
640 640 s> \'.bigtable > tbody\',\n
641 641 s> \'<tr class="%class%">\\\n
642 642 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
643 643 s> </tr>\'\n
644 644 s> );\n
645 645 s> </script>\n
646 646 s> \n
647 647 s> </div>\n
648 648 s> </div>\n
649 649 s> \n
650 650 s> \n
651 651 s> \n
652 652 s> </body>\n
653 653 s> </html>\n
654 654 s> \n
655 655 s> \r\n
656 656 s> 0\r\n
657 657 s> \r\n
658 658
659 659 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
660 660 > command heads
661 661 > EOF
662 662 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
663 663 s> Accept-Encoding: identity\r\n
664 664 s> accept: application/mercurial-0.1\r\n
665 665 s> host: $LOCALIP:$HGPORT\r\n (glob)
666 666 s> user-agent: Mercurial debugwireproto\r\n
667 667 s> \r\n
668 668 s> makefile('rb', None)
669 669 s> HTTP/1.1 301 Redirect\r\n
670 670 s> Server: testing stub value\r\n
671 671 s> Date: $HTTP_DATE$\r\n
672 672 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
673 673 s> Content-Type: text/plain\r\n
674 674 s> Content-Length: 10\r\n
675 675 s> \r\n
676 676 s> redirected
677 677 s> GET /redirected HTTP/1.1\r\n
678 678 s> Accept-Encoding: identity\r\n
679 679 s> accept: application/mercurial-0.1\r\n
680 680 s> host: $LOCALIP:$HGPORT\r\n (glob)
681 681 s> user-agent: Mercurial debugwireproto\r\n
682 682 s> \r\n
683 683 s> makefile('rb', None)
684 684 s> HTTP/1.1 200 Script output follows\r\n
685 685 s> Server: testing stub value\r\n
686 686 s> Date: $HTTP_DATE$\r\n
687 687 s> ETag: W/"*"\r\n (glob)
688 688 s> Content-Type: text/html; charset=ascii\r\n
689 689 s> Transfer-Encoding: chunked\r\n
690 690 s> \r\n
691 691 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
692 692 s> 414\r\n
693 693 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
694 694 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
695 695 s> <head>\n
696 696 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
697 697 s> <meta name="robots" content="index, nofollow" />\n
698 698 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
699 699 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
700 700 s> \n
701 701 s> <title>redirected: log</title>\n
702 702 s> <link rel="alternate" type="application/atom+xml"\n
703 703 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
704 704 s> <link rel="alternate" type="application/rss+xml"\n
705 705 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
706 706 s> </head>\n
707 707 s> <body>\n
708 708 s> \n
709 709 s> <div class="container">\n
710 710 s> <div class="menu">\n
711 711 s> <div class="logo">\n
712 712 s> <a href="https://mercurial-scm.org/">\n
713 713 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
714 714 s> </div>\n
715 715 s> <ul>\n
716 716 s> <li class="active">log</li>\n
717 717 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
718 718 s> <li><a href="/redirected/tags">tags</a
719 719 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
720 720 s> Accept-Encoding: identity\r\n
721 721 s> accept: application/mercurial-0.1\r\n
722 722 s> host: $LOCALIP:$HGPORT\r\n (glob)
723 723 s> user-agent: Mercurial debugwireproto\r\n
724 724 s> \r\n
725 725 s> makefile('rb', None)
726 726 s> HTTP/1.1 200 Script output follows\r\n
727 727 s> Server: testing stub value\r\n
728 728 s> Date: $HTTP_DATE$\r\n
729 729 s> Content-Type: application/mercurial-0.1\r\n
730 730 s> Content-Length: 467\r\n
731 731 s> \r\n
732 732 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
733 733 s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
734 734 sending heads command
735 735 s> GET /redirected?cmd=heads HTTP/1.1\r\n
736 736 s> Accept-Encoding: identity\r\n
737 737 s> vary: X-HgProto-1\r\n
738 738 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
739 739 s> accept: application/mercurial-0.1\r\n
740 740 s> host: $LOCALIP:$HGPORT\r\n (glob)
741 741 s> user-agent: Mercurial debugwireproto\r\n
742 742 s> \r\n
743 743 s> makefile('rb', None)
744 744 s> HTTP/1.1 200 Script output follows\r\n
745 745 s> Server: testing stub value\r\n
746 746 s> Date: $HTTP_DATE$\r\n
747 747 s> Content-Type: application/mercurial-0.1\r\n
748 748 s> Content-Length: 41\r\n
749 749 s> \r\n
750 750 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
751 751 response: [
752 752 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
753 753 ]
754 754 (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
@@ -1,422 +1,422
1 1 $ . $TESTDIR/wireprotohelpers.sh
2 2 $ cat >> $HGRCPATH << EOF
3 3 > [extensions]
4 4 > blackbox =
5 5 > [blackbox]
6 6 > track = simplecache
7 7 > EOF
8 8 $ hg init server
9 9 $ enablehttpv2 server
10 10 $ cd server
11 11 $ cat >> .hg/hgrc << EOF
12 12 > [extensions]
13 13 > simplecache = $TESTDIR/wireprotosimplecache.py
14 14 > EOF
15 15
16 16 $ echo a0 > a
17 17 $ echo b0 > b
18 18 $ hg -q commit -A -m 'commit 0'
19 19 $ echo a1 > a
20 20 $ hg commit -m 'commit 1'
21 21 $ echo b1 > b
22 22 $ hg commit -m 'commit 2'
23 23 $ echo a2 > a
24 24 $ echo b2 > b
25 25 $ hg commit -m 'commit 3'
26 26
27 27 $ hg log -G -T '{rev}:{node} {desc}'
28 28 @ 3:50590a86f3ff5d1e9a1624a7a6957884565cc8e8 commit 3
29 29 |
30 30 o 2:4d01eda50c6ac5f7e89cbe1880143a32f559c302 commit 2
31 31 |
32 32 o 1:4432d83626e8a98655f062ec1f2a43b07f7fbbb0 commit 1
33 33 |
34 34 o 0:3390ef850073fbc2f0dfff2244342c8e9229013a commit 0
35 35
36 36
37 37 $ hg --debug debugindex -m
38 38 rev linkrev nodeid p1 p2
39 39 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
40 40 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
41 41 2 2 a8853dafacfca6fc807055a660d8b835141a3bb4 a988fb43583e871d1ed5750ee074c6d840bbbfc8 0000000000000000000000000000000000000000
42 42 3 3 3fe11dfbb13645782b0addafbe75a87c210ffddc a8853dafacfca6fc807055a660d8b835141a3bb4 0000000000000000000000000000000000000000
43 43
44 44 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
45 45 $ cat hg.pid > $DAEMON_PIDS
46 46
47 47 Performing the same request should result in same result, with 2nd response
48 48 coming from cache.
49 49
50 50 $ sendhttpv2peer << EOF
51 51 > command manifestdata
52 52 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
53 53 > tree eval:b''
54 54 > fields eval:[b'parents']
55 55 > EOF
56 56 creating http peer for wire protocol version 2
57 57 sending manifestdata command
58 58 response: gen[
59 59 {
60 60 b'totalitems': 1
61 61 },
62 62 {
63 63 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
64 64 b'parents': [
65 65 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
66 66 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
67 67 ]
68 68 }
69 69 ]
70 70
71 71 $ sendhttpv2peer << EOF
72 72 > command manifestdata
73 73 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
74 74 > tree eval:b''
75 75 > fields eval:[b'parents']
76 76 > EOF
77 77 creating http peer for wire protocol version 2
78 78 sending manifestdata command
79 79 response: gen[
80 80 {
81 81 b'totalitems': 1
82 82 },
83 83 {
84 84 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
85 85 b'parents': [
86 86 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
87 87 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
88 88 ]
89 89 }
90 90 ]
91 91
92 92 Sending different request doesn't yield cache hit.
93 93
94 94 $ sendhttpv2peer << EOF
95 95 > command manifestdata
96 96 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41', b'\xa9\x88\xfb\x43\x58\x3e\x87\x1d\x1e\xd5\x75\x0e\xe0\x74\xc6\xd8\x40\xbb\xbf\xc8']
97 97 > tree eval:b''
98 98 > fields eval:[b'parents']
99 99 > EOF
100 100 creating http peer for wire protocol version 2
101 101 sending manifestdata command
102 102 response: gen[
103 103 {
104 104 b'totalitems': 2
105 105 },
106 106 {
107 107 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
108 108 b'parents': [
109 109 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
110 110 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
111 111 ]
112 112 },
113 113 {
114 114 b'node': b'\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
115 115 b'parents': [
116 116 b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
117 117 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
118 118 ]
119 119 }
120 120 ]
121 121
122 122 $ cat .hg/blackbox.log
123 123 *> cacher constructed for manifestdata (glob)
124 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
125 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
124 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
125 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
126 126 *> cacher constructed for manifestdata (glob)
127 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
127 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
128 128 *> cacher constructed for manifestdata (glob)
129 *> cache miss for 6ed2f740a1cdd12c9e99c4f27695543143c26a11 (glob)
130 *> storing cache entry for 6ed2f740a1cdd12c9e99c4f27695543143c26a11 (glob)
129 *> cache miss for 1cf89363ec234c6b92d5961281eaa5713e7493f9 (glob)
130 *> storing cache entry for 1cf89363ec234c6b92d5961281eaa5713e7493f9 (glob)
131 131
132 132 $ cat error.log
133 133
134 134 $ killdaemons.py
135 135 $ rm .hg/blackbox.log
136 136
137 137 Try with object caching mode
138 138
139 139 $ cat >> .hg/hgrc << EOF
140 140 > [simplecache]
141 141 > cacheobjects = true
142 142 > EOF
143 143
144 144 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
145 145 $ cat hg.pid > $DAEMON_PIDS
146 146
147 147 $ sendhttpv2peer << EOF
148 148 > command manifestdata
149 149 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
150 150 > tree eval:b''
151 151 > fields eval:[b'parents']
152 152 > EOF
153 153 creating http peer for wire protocol version 2
154 154 sending manifestdata command
155 155 response: gen[
156 156 {
157 157 b'totalitems': 1
158 158 },
159 159 {
160 160 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
161 161 b'parents': [
162 162 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
163 163 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
164 164 ]
165 165 }
166 166 ]
167 167
168 168 $ sendhttpv2peer << EOF
169 169 > command manifestdata
170 170 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
171 171 > tree eval:b''
172 172 > fields eval:[b'parents']
173 173 > EOF
174 174 creating http peer for wire protocol version 2
175 175 sending manifestdata command
176 176 response: gen[
177 177 {
178 178 b'totalitems': 1
179 179 },
180 180 {
181 181 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
182 182 b'parents': [
183 183 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
184 184 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
185 185 ]
186 186 }
187 187 ]
188 188
189 189 $ cat .hg/blackbox.log
190 190 *> cacher constructed for manifestdata (glob)
191 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
192 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
191 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
192 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
193 193 *> cacher constructed for manifestdata (glob)
194 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
194 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
195 195
196 196 $ cat error.log
197 197
198 198 $ killdaemons.py
199 199 $ rm .hg/blackbox.log
200 200
201 201 A non-cacheable command does not instantiate cacher
202 202
203 203 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
204 204 $ cat hg.pid > $DAEMON_PIDS
205 205 $ sendhttpv2peer << EOF
206 206 > command capabilities
207 207 > EOF
208 208 creating http peer for wire protocol version 2
209 209 sending capabilities command
210 210 response: gen[
211 211 {
212 212 b'commands': {
213 213 b'branchmap': {
214 214 b'args': {},
215 215 b'permissions': [
216 216 b'pull'
217 217 ]
218 218 },
219 219 b'capabilities': {
220 220 b'args': {},
221 221 b'permissions': [
222 222 b'pull'
223 223 ]
224 224 },
225 225 b'changesetdata': {
226 226 b'args': {
227 227 b'fields': {
228 228 b'default': set([]),
229 229 b'required': False,
230 230 b'type': b'set',
231 231 b'validvalues': set([
232 232 b'bookmarks',
233 233 b'parents',
234 234 b'phase',
235 235 b'revision'
236 236 ])
237 237 },
238 238 b'noderange': {
239 239 b'default': None,
240 240 b'required': False,
241 241 b'type': b'list'
242 242 },
243 243 b'nodes': {
244 244 b'default': None,
245 245 b'required': False,
246 246 b'type': b'list'
247 247 },
248 248 b'nodesdepth': {
249 249 b'default': None,
250 250 b'required': False,
251 251 b'type': b'int'
252 252 }
253 253 },
254 254 b'permissions': [
255 255 b'pull'
256 256 ]
257 257 },
258 258 b'filedata': {
259 259 b'args': {
260 260 b'fields': {
261 261 b'default': set([]),
262 262 b'required': False,
263 263 b'type': b'set',
264 264 b'validvalues': set([
265 265 b'parents',
266 266 b'revision'
267 267 ])
268 268 },
269 269 b'haveparents': {
270 270 b'default': False,
271 271 b'required': False,
272 272 b'type': b'bool'
273 273 },
274 274 b'nodes': {
275 275 b'required': True,
276 276 b'type': b'list'
277 277 },
278 278 b'path': {
279 279 b'required': True,
280 280 b'type': b'bytes'
281 281 }
282 282 },
283 283 b'permissions': [
284 284 b'pull'
285 285 ]
286 286 },
287 287 b'heads': {
288 288 b'args': {
289 289 b'publiconly': {
290 290 b'default': False,
291 291 b'required': False,
292 292 b'type': b'bool'
293 293 }
294 294 },
295 295 b'permissions': [
296 296 b'pull'
297 297 ]
298 298 },
299 299 b'known': {
300 300 b'args': {
301 301 b'nodes': {
302 302 b'default': [],
303 303 b'required': False,
304 304 b'type': b'list'
305 305 }
306 306 },
307 307 b'permissions': [
308 308 b'pull'
309 309 ]
310 310 },
311 311 b'listkeys': {
312 312 b'args': {
313 313 b'namespace': {
314 314 b'required': True,
315 315 b'type': b'bytes'
316 316 }
317 317 },
318 318 b'permissions': [
319 319 b'pull'
320 320 ]
321 321 },
322 322 b'lookup': {
323 323 b'args': {
324 324 b'key': {
325 325 b'required': True,
326 326 b'type': b'bytes'
327 327 }
328 328 },
329 329 b'permissions': [
330 330 b'pull'
331 331 ]
332 332 },
333 333 b'manifestdata': {
334 334 b'args': {
335 335 b'fields': {
336 336 b'default': set([]),
337 337 b'required': False,
338 338 b'type': b'set',
339 339 b'validvalues': set([
340 340 b'parents',
341 341 b'revision'
342 342 ])
343 343 },
344 344 b'haveparents': {
345 345 b'default': False,
346 346 b'required': False,
347 347 b'type': b'bool'
348 348 },
349 349 b'nodes': {
350 350 b'required': True,
351 351 b'type': b'list'
352 352 },
353 353 b'tree': {
354 354 b'required': True,
355 355 b'type': b'bytes'
356 356 }
357 357 },
358 358 b'permissions': [
359 359 b'pull'
360 360 ]
361 361 },
362 362 b'pushkey': {
363 363 b'args': {
364 364 b'key': {
365 365 b'required': True,
366 366 b'type': b'bytes'
367 367 },
368 368 b'namespace': {
369 369 b'required': True,
370 370 b'type': b'bytes'
371 371 },
372 372 b'new': {
373 373 b'required': True,
374 374 b'type': b'bytes'
375 375 },
376 376 b'old': {
377 377 b'required': True,
378 378 b'type': b'bytes'
379 379 }
380 380 },
381 381 b'permissions': [
382 382 b'push'
383 383 ]
384 384 }
385 385 },
386 386 b'framingmediatypes': [
387 b'application/mercurial-exp-framing-0005'
387 b'application/mercurial-exp-framing-0006'
388 388 ],
389 389 b'pathfilterprefixes': set([
390 390 b'path:',
391 391 b'rootfilesin:'
392 392 ]),
393 393 b'rawrepoformats': [
394 394 b'generaldelta',
395 395 b'revlogv1'
396 396 ]
397 397 }
398 398 ]
399 399
400 400 $ test -f .hg/blackbox.log
401 401 [1]
402 402
403 403 An error is not cached
404 404
405 405 $ sendhttpv2peer << EOF
406 406 > command manifestdata
407 407 > nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa']
408 408 > tree eval:b''
409 409 > fields eval:[b'parents']
410 410 > EOF
411 411 creating http peer for wire protocol version 2
412 412 sending manifestdata command
413 413 abort: unknown node: \xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa! (esc)
414 414 [255]
415 415
416 416 $ cat .hg/blackbox.log
417 417 *> cacher constructed for manifestdata (glob)
418 *> cache miss for 9d1bb421d99e913d45f2d099aa49728514292dd2 (glob)
418 *> cache miss for 904560928eb95650358f0829d9399b256822eb26 (glob)
419 419 *> cacher exiting due to error (glob)
420 420
421 421 $ killdaemons.py
422 422 $ rm .hg/blackbox.log
@@ -1,663 +1,663
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 {
151 151 b'apibase': b'api/',
152 152 b'apis': {},
153 153 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
154 154 }
155 155 ]
156 156
157 157 Restart server to enable HTTPv2
158 158
159 159 $ killdaemons.py
160 160 $ enablehttpv2 server
161 161 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
162 162 $ cat hg.pid > $DAEMON_PIDS
163 163
164 164 Only requested API services are returned
165 165
166 166 $ sendhttpraw << EOF
167 167 > httprequest GET ?cmd=capabilities
168 168 > user-agent: test
169 169 > x-hgupgrade-1: foo bar
170 170 > x-hgproto-1: cbor
171 171 > EOF
172 172 using raw connection to peer
173 173 s> GET /?cmd=capabilities HTTP/1.1\r\n
174 174 s> Accept-Encoding: identity\r\n
175 175 s> user-agent: test\r\n
176 176 s> x-hgproto-1: cbor\r\n
177 177 s> x-hgupgrade-1: foo bar\r\n
178 178 s> host: $LOCALIP:$HGPORT\r\n (glob)
179 179 s> \r\n
180 180 s> makefile('rb', None)
181 181 s> HTTP/1.1 200 OK\r\n
182 182 s> Server: testing stub value\r\n
183 183 s> Date: $HTTP_DATE$\r\n
184 184 s> Content-Type: application/mercurial-cbor\r\n
185 185 s> Content-Length: *\r\n (glob)
186 186 s> \r\n
187 187 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
188 188 cbor> [
189 189 {
190 190 b'apibase': b'api/',
191 191 b'apis': {},
192 192 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
193 193 }
194 194 ]
195 195
196 196 Request for HTTPv2 service returns information about it
197 197
198 198 $ sendhttpraw << EOF
199 199 > httprequest GET ?cmd=capabilities
200 200 > user-agent: test
201 201 > x-hgupgrade-1: exp-http-v2-0002 foo bar
202 202 > x-hgproto-1: cbor
203 203 > EOF
204 204 using raw connection to peer
205 205 s> GET /?cmd=capabilities HTTP/1.1\r\n
206 206 s> Accept-Encoding: identity\r\n
207 207 s> user-agent: test\r\n
208 208 s> x-hgproto-1: cbor\r\n
209 209 s> x-hgupgrade-1: exp-http-v2-0002 foo bar\r\n
210 210 s> host: $LOCALIP:$HGPORT\r\n (glob)
211 211 s> \r\n
212 212 s> makefile('rb', None)
213 213 s> HTTP/1.1 200 OK\r\n
214 214 s> Server: testing stub value\r\n
215 215 s> Date: $HTTP_DATE$\r\n
216 216 s> Content-Type: application/mercurial-cbor\r\n
217 217 s> Content-Length: *\r\n (glob)
218 218 s> \r\n
219 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-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
219 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
220 220 cbor> [
221 221 {
222 222 b'apibase': b'api/',
223 223 b'apis': {
224 224 b'exp-http-v2-0002': {
225 225 b'commands': {
226 226 b'branchmap': {
227 227 b'args': {},
228 228 b'permissions': [
229 229 b'pull'
230 230 ]
231 231 },
232 232 b'capabilities': {
233 233 b'args': {},
234 234 b'permissions': [
235 235 b'pull'
236 236 ]
237 237 },
238 238 b'changesetdata': {
239 239 b'args': {
240 240 b'fields': {
241 241 b'default': set([]),
242 242 b'required': False,
243 243 b'type': b'set',
244 244 b'validvalues': set([
245 245 b'bookmarks',
246 246 b'parents',
247 247 b'phase',
248 248 b'revision'
249 249 ])
250 250 },
251 251 b'noderange': {
252 252 b'default': None,
253 253 b'required': False,
254 254 b'type': b'list'
255 255 },
256 256 b'nodes': {
257 257 b'default': None,
258 258 b'required': False,
259 259 b'type': b'list'
260 260 },
261 261 b'nodesdepth': {
262 262 b'default': None,
263 263 b'required': False,
264 264 b'type': b'int'
265 265 }
266 266 },
267 267 b'permissions': [
268 268 b'pull'
269 269 ]
270 270 },
271 271 b'filedata': {
272 272 b'args': {
273 273 b'fields': {
274 274 b'default': set([]),
275 275 b'required': False,
276 276 b'type': b'set',
277 277 b'validvalues': set([
278 278 b'parents',
279 279 b'revision'
280 280 ])
281 281 },
282 282 b'haveparents': {
283 283 b'default': False,
284 284 b'required': False,
285 285 b'type': b'bool'
286 286 },
287 287 b'nodes': {
288 288 b'required': True,
289 289 b'type': b'list'
290 290 },
291 291 b'path': {
292 292 b'required': True,
293 293 b'type': b'bytes'
294 294 }
295 295 },
296 296 b'permissions': [
297 297 b'pull'
298 298 ]
299 299 },
300 300 b'heads': {
301 301 b'args': {
302 302 b'publiconly': {
303 303 b'default': False,
304 304 b'required': False,
305 305 b'type': b'bool'
306 306 }
307 307 },
308 308 b'permissions': [
309 309 b'pull'
310 310 ]
311 311 },
312 312 b'known': {
313 313 b'args': {
314 314 b'nodes': {
315 315 b'default': [],
316 316 b'required': False,
317 317 b'type': b'list'
318 318 }
319 319 },
320 320 b'permissions': [
321 321 b'pull'
322 322 ]
323 323 },
324 324 b'listkeys': {
325 325 b'args': {
326 326 b'namespace': {
327 327 b'required': True,
328 328 b'type': b'bytes'
329 329 }
330 330 },
331 331 b'permissions': [
332 332 b'pull'
333 333 ]
334 334 },
335 335 b'lookup': {
336 336 b'args': {
337 337 b'key': {
338 338 b'required': True,
339 339 b'type': b'bytes'
340 340 }
341 341 },
342 342 b'permissions': [
343 343 b'pull'
344 344 ]
345 345 },
346 346 b'manifestdata': {
347 347 b'args': {
348 348 b'fields': {
349 349 b'default': set([]),
350 350 b'required': False,
351 351 b'type': b'set',
352 352 b'validvalues': set([
353 353 b'parents',
354 354 b'revision'
355 355 ])
356 356 },
357 357 b'haveparents': {
358 358 b'default': False,
359 359 b'required': False,
360 360 b'type': b'bool'
361 361 },
362 362 b'nodes': {
363 363 b'required': True,
364 364 b'type': b'list'
365 365 },
366 366 b'tree': {
367 367 b'required': True,
368 368 b'type': b'bytes'
369 369 }
370 370 },
371 371 b'permissions': [
372 372 b'pull'
373 373 ]
374 374 },
375 375 b'pushkey': {
376 376 b'args': {
377 377 b'key': {
378 378 b'required': True,
379 379 b'type': b'bytes'
380 380 },
381 381 b'namespace': {
382 382 b'required': True,
383 383 b'type': b'bytes'
384 384 },
385 385 b'new': {
386 386 b'required': True,
387 387 b'type': b'bytes'
388 388 },
389 389 b'old': {
390 390 b'required': True,
391 391 b'type': b'bytes'
392 392 }
393 393 },
394 394 b'permissions': [
395 395 b'push'
396 396 ]
397 397 }
398 398 },
399 399 b'framingmediatypes': [
400 b'application/mercurial-exp-framing-0005'
400 b'application/mercurial-exp-framing-0006'
401 401 ],
402 402 b'pathfilterprefixes': set([
403 403 b'path:',
404 404 b'rootfilesin:'
405 405 ]),
406 406 b'rawrepoformats': [
407 407 b'generaldelta',
408 408 b'revlogv1'
409 409 ]
410 410 }
411 411 },
412 412 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
413 413 }
414 414 ]
415 415
416 416 capabilities command returns expected info
417 417
418 418 $ sendhttpv2peerhandshake << EOF
419 419 > command capabilities
420 420 > EOF
421 421 creating http peer for wire protocol version 2
422 422 s> GET /?cmd=capabilities HTTP/1.1\r\n
423 423 s> Accept-Encoding: identity\r\n
424 424 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
425 425 s> x-hgproto-1: cbor\r\n
426 426 s> x-hgupgrade-1: exp-http-v2-0002\r\n
427 427 s> accept: application/mercurial-0.1\r\n
428 428 s> host: $LOCALIP:$HGPORT\r\n (glob)
429 429 s> user-agent: Mercurial debugwireproto\r\n
430 430 s> \r\n
431 431 s> makefile('rb', None)
432 432 s> HTTP/1.1 200 OK\r\n
433 433 s> Server: testing stub value\r\n
434 434 s> Date: $HTTP_DATE$\r\n
435 435 s> Content-Type: application/mercurial-cbor\r\n
436 436 s> Content-Length: *\r\n (glob)
437 437 s> \r\n
438 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-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
438 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
439 439 sending capabilities command
440 440 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
441 441 s> Accept-Encoding: identity\r\n
442 s> accept: application/mercurial-exp-framing-0005\r\n
443 s> content-type: application/mercurial-exp-framing-0005\r\n
444 s> content-length: 27\r\n
442 s> accept: application/mercurial-exp-framing-0006\r\n
443 s> content-type: application/mercurial-exp-framing-0006\r\n
444 s> content-length: 63\r\n
445 445 s> host: $LOCALIP:$HGPORT\r\n (glob)
446 446 s> user-agent: Mercurial debugwireproto\r\n
447 447 s> \r\n
448 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
448 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x13\x00\x00\x01\x00\x01\x00\x11\xa1DnameLcapabilities
449 449 s> makefile('rb', None)
450 450 s> HTTP/1.1 200 OK\r\n
451 451 s> Server: testing stub value\r\n
452 452 s> Date: $HTTP_DATE$\r\n
453 s> Content-Type: application/mercurial-exp-framing-0005\r\n
453 s> Content-Type: application/mercurial-exp-framing-0006\r\n
454 454 s> Transfer-Encoding: chunked\r\n
455 455 s> \r\n
456 456 s> 13\r\n
457 457 s> \x0b\x00\x00\x01\x00\x02\x011
458 458 s> \xa1FstatusBok
459 459 s> \r\n
460 460 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
461 461 s> 508\r\n
462 462 s> \x00\x05\x00\x01\x00\x02\x001
463 s> \xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
463 s> \xa4Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
464 464 s> \r\n
465 465 received frame(size=1280; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
466 466 s> 8\r\n
467 467 s> \x00\x00\x00\x01\x00\x02\x002
468 468 s> \r\n
469 469 s> 0\r\n
470 470 s> \r\n
471 471 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
472 472 response: gen[
473 473 {
474 474 b'commands': {
475 475 b'branchmap': {
476 476 b'args': {},
477 477 b'permissions': [
478 478 b'pull'
479 479 ]
480 480 },
481 481 b'capabilities': {
482 482 b'args': {},
483 483 b'permissions': [
484 484 b'pull'
485 485 ]
486 486 },
487 487 b'changesetdata': {
488 488 b'args': {
489 489 b'fields': {
490 490 b'default': set([]),
491 491 b'required': False,
492 492 b'type': b'set',
493 493 b'validvalues': set([
494 494 b'bookmarks',
495 495 b'parents',
496 496 b'phase',
497 497 b'revision'
498 498 ])
499 499 },
500 500 b'noderange': {
501 501 b'default': None,
502 502 b'required': False,
503 503 b'type': b'list'
504 504 },
505 505 b'nodes': {
506 506 b'default': None,
507 507 b'required': False,
508 508 b'type': b'list'
509 509 },
510 510 b'nodesdepth': {
511 511 b'default': None,
512 512 b'required': False,
513 513 b'type': b'int'
514 514 }
515 515 },
516 516 b'permissions': [
517 517 b'pull'
518 518 ]
519 519 },
520 520 b'filedata': {
521 521 b'args': {
522 522 b'fields': {
523 523 b'default': set([]),
524 524 b'required': False,
525 525 b'type': b'set',
526 526 b'validvalues': set([
527 527 b'parents',
528 528 b'revision'
529 529 ])
530 530 },
531 531 b'haveparents': {
532 532 b'default': False,
533 533 b'required': False,
534 534 b'type': b'bool'
535 535 },
536 536 b'nodes': {
537 537 b'required': True,
538 538 b'type': b'list'
539 539 },
540 540 b'path': {
541 541 b'required': True,
542 542 b'type': b'bytes'
543 543 }
544 544 },
545 545 b'permissions': [
546 546 b'pull'
547 547 ]
548 548 },
549 549 b'heads': {
550 550 b'args': {
551 551 b'publiconly': {
552 552 b'default': False,
553 553 b'required': False,
554 554 b'type': b'bool'
555 555 }
556 556 },
557 557 b'permissions': [
558 558 b'pull'
559 559 ]
560 560 },
561 561 b'known': {
562 562 b'args': {
563 563 b'nodes': {
564 564 b'default': [],
565 565 b'required': False,
566 566 b'type': b'list'
567 567 }
568 568 },
569 569 b'permissions': [
570 570 b'pull'
571 571 ]
572 572 },
573 573 b'listkeys': {
574 574 b'args': {
575 575 b'namespace': {
576 576 b'required': True,
577 577 b'type': b'bytes'
578 578 }
579 579 },
580 580 b'permissions': [
581 581 b'pull'
582 582 ]
583 583 },
584 584 b'lookup': {
585 585 b'args': {
586 586 b'key': {
587 587 b'required': True,
588 588 b'type': b'bytes'
589 589 }
590 590 },
591 591 b'permissions': [
592 592 b'pull'
593 593 ]
594 594 },
595 595 b'manifestdata': {
596 596 b'args': {
597 597 b'fields': {
598 598 b'default': set([]),
599 599 b'required': False,
600 600 b'type': b'set',
601 601 b'validvalues': set([
602 602 b'parents',
603 603 b'revision'
604 604 ])
605 605 },
606 606 b'haveparents': {
607 607 b'default': False,
608 608 b'required': False,
609 609 b'type': b'bool'
610 610 },
611 611 b'nodes': {
612 612 b'required': True,
613 613 b'type': b'list'
614 614 },
615 615 b'tree': {
616 616 b'required': True,
617 617 b'type': b'bytes'
618 618 }
619 619 },
620 620 b'permissions': [
621 621 b'pull'
622 622 ]
623 623 },
624 624 b'pushkey': {
625 625 b'args': {
626 626 b'key': {
627 627 b'required': True,
628 628 b'type': b'bytes'
629 629 },
630 630 b'namespace': {
631 631 b'required': True,
632 632 b'type': b'bytes'
633 633 },
634 634 b'new': {
635 635 b'required': True,
636 636 b'type': b'bytes'
637 637 },
638 638 b'old': {
639 639 b'required': True,
640 640 b'type': b'bytes'
641 641 }
642 642 },
643 643 b'permissions': [
644 644 b'push'
645 645 ]
646 646 }
647 647 },
648 648 b'framingmediatypes': [
649 b'application/mercurial-exp-framing-0005'
649 b'application/mercurial-exp-framing-0006'
650 650 ],
651 651 b'pathfilterprefixes': set([
652 652 b'path:',
653 653 b'rootfilesin:'
654 654 ]),
655 655 b'rawrepoformats': [
656 656 b'generaldelta',
657 657 b'revlogv1'
658 658 ]
659 659 }
660 660 ]
661 661 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
662 662
663 663 $ cat error.log
@@ -1,1290 +1,1290
1 1 $ . $TESTDIR/wireprotohelpers.sh
2 2
3 3 $ cat >> $HGRCPATH << EOF
4 4 > [extensions]
5 5 > blackbox =
6 6 > [blackbox]
7 7 > track = simplecache
8 8 > EOF
9 9
10 10 $ hg init server
11 11 $ enablehttpv2 server
12 12 $ cd server
13 13 $ cat >> .hg/hgrc << EOF
14 14 > [server]
15 15 > compressionengines = zlib
16 16 > [extensions]
17 17 > simplecache = $TESTDIR/wireprotosimplecache.py
18 18 > [simplecache]
19 19 > cacheapi = true
20 20 > EOF
21 21
22 22 $ echo a0 > a
23 23 $ echo b0 > b
24 24 $ hg -q commit -A -m 'commit 0'
25 25 $ echo a1 > a
26 26 $ hg commit -m 'commit 1'
27 27
28 28 $ hg --debug debugindex -m
29 29 rev linkrev nodeid p1 p2
30 30 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
31 31 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000
32 32
33 33 $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log
34 34 $ cat hg.pid > $DAEMON_PIDS
35 35
36 36 $ cat > redirects.py << EOF
37 37 > [
38 38 > {
39 39 > b'name': b'target-a',
40 40 > b'protocol': b'http',
41 41 > b'snirequired': False,
42 42 > b'tlsversions': [b'1.2', b'1.3'],
43 43 > b'uris': [b'http://example.com/'],
44 44 > },
45 45 > ]
46 46 > EOF
47 47
48 48 Redirect targets advertised when configured
49 49
50 50 $ sendhttpv2peerhandshake << EOF
51 51 > command capabilities
52 52 > EOF
53 53 creating http peer for wire protocol version 2
54 54 s> GET /?cmd=capabilities HTTP/1.1\r\n
55 55 s> Accept-Encoding: identity\r\n
56 56 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
57 57 s> x-hgproto-1: cbor\r\n
58 58 s> x-hgupgrade-1: exp-http-v2-0002\r\n
59 59 s> accept: application/mercurial-0.1\r\n
60 60 s> host: $LOCALIP:$HGPORT\r\n (glob)
61 61 s> user-agent: Mercurial debugwireproto\r\n
62 62 s> \r\n
63 63 s> makefile('rb', None)
64 64 s> HTTP/1.1 200 OK\r\n
65 65 s> Server: testing stub value\r\n
66 66 s> Date: $HTTP_DATE$\r\n
67 67 s> Content-Type: application/mercurial-cbor\r\n
68 68 s> Content-Length: 1930\r\n
69 69 s> \r\n
70 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
70 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
71 71 (remote redirect target target-a is compatible)
72 72 sending capabilities command
73 73 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
74 74 s> Accept-Encoding: identity\r\n
75 s> accept: application/mercurial-exp-framing-0005\r\n
76 s> content-type: application/mercurial-exp-framing-0005\r\n
77 s> content-length: 75\r\n
75 s> accept: application/mercurial-exp-framing-0006\r\n
76 s> content-type: application/mercurial-exp-framing-0006\r\n
77 s> content-length: 111\r\n
78 78 s> host: $LOCALIP:$HGPORT\r\n (glob)
79 79 s> user-agent: Mercurial debugwireproto\r\n
80 80 s> \r\n
81 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
81 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
82 82 s> makefile('rb', None)
83 83 s> HTTP/1.1 200 OK\r\n
84 84 s> Server: testing stub value\r\n
85 85 s> Date: $HTTP_DATE$\r\n
86 s> Content-Type: application/mercurial-exp-framing-0005\r\n
86 s> Content-Type: application/mercurial-exp-framing-0006\r\n
87 87 s> Transfer-Encoding: chunked\r\n
88 88 s> \r\n
89 89 s> 13\r\n
90 90 s> \x0b\x00\x00\x01\x00\x02\x011
91 91 s> \xa1FstatusBok
92 92 s> \r\n
93 93 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
94 94 s> 588\r\n
95 95 s> \x80\x05\x00\x01\x00\x02\x001
96 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
96 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/
97 97 s> \r\n
98 98 received frame(size=1408; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
99 99 s> 8\r\n
100 100 s> \x00\x00\x00\x01\x00\x02\x002
101 101 s> \r\n
102 102 s> 0\r\n
103 103 s> \r\n
104 104 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
105 105 response: gen[
106 106 {
107 107 b'commands': {
108 108 b'branchmap': {
109 109 b'args': {},
110 110 b'permissions': [
111 111 b'pull'
112 112 ]
113 113 },
114 114 b'capabilities': {
115 115 b'args': {},
116 116 b'permissions': [
117 117 b'pull'
118 118 ]
119 119 },
120 120 b'changesetdata': {
121 121 b'args': {
122 122 b'fields': {
123 123 b'default': set([]),
124 124 b'required': False,
125 125 b'type': b'set',
126 126 b'validvalues': set([
127 127 b'bookmarks',
128 128 b'parents',
129 129 b'phase',
130 130 b'revision'
131 131 ])
132 132 },
133 133 b'noderange': {
134 134 b'default': None,
135 135 b'required': False,
136 136 b'type': b'list'
137 137 },
138 138 b'nodes': {
139 139 b'default': None,
140 140 b'required': False,
141 141 b'type': b'list'
142 142 },
143 143 b'nodesdepth': {
144 144 b'default': None,
145 145 b'required': False,
146 146 b'type': b'int'
147 147 }
148 148 },
149 149 b'permissions': [
150 150 b'pull'
151 151 ]
152 152 },
153 153 b'filedata': {
154 154 b'args': {
155 155 b'fields': {
156 156 b'default': set([]),
157 157 b'required': False,
158 158 b'type': b'set',
159 159 b'validvalues': set([
160 160 b'parents',
161 161 b'revision'
162 162 ])
163 163 },
164 164 b'haveparents': {
165 165 b'default': False,
166 166 b'required': False,
167 167 b'type': b'bool'
168 168 },
169 169 b'nodes': {
170 170 b'required': True,
171 171 b'type': b'list'
172 172 },
173 173 b'path': {
174 174 b'required': True,
175 175 b'type': b'bytes'
176 176 }
177 177 },
178 178 b'permissions': [
179 179 b'pull'
180 180 ]
181 181 },
182 182 b'heads': {
183 183 b'args': {
184 184 b'publiconly': {
185 185 b'default': False,
186 186 b'required': False,
187 187 b'type': b'bool'
188 188 }
189 189 },
190 190 b'permissions': [
191 191 b'pull'
192 192 ]
193 193 },
194 194 b'known': {
195 195 b'args': {
196 196 b'nodes': {
197 197 b'default': [],
198 198 b'required': False,
199 199 b'type': b'list'
200 200 }
201 201 },
202 202 b'permissions': [
203 203 b'pull'
204 204 ]
205 205 },
206 206 b'listkeys': {
207 207 b'args': {
208 208 b'namespace': {
209 209 b'required': True,
210 210 b'type': b'bytes'
211 211 }
212 212 },
213 213 b'permissions': [
214 214 b'pull'
215 215 ]
216 216 },
217 217 b'lookup': {
218 218 b'args': {
219 219 b'key': {
220 220 b'required': True,
221 221 b'type': b'bytes'
222 222 }
223 223 },
224 224 b'permissions': [
225 225 b'pull'
226 226 ]
227 227 },
228 228 b'manifestdata': {
229 229 b'args': {
230 230 b'fields': {
231 231 b'default': set([]),
232 232 b'required': False,
233 233 b'type': b'set',
234 234 b'validvalues': set([
235 235 b'parents',
236 236 b'revision'
237 237 ])
238 238 },
239 239 b'haveparents': {
240 240 b'default': False,
241 241 b'required': False,
242 242 b'type': b'bool'
243 243 },
244 244 b'nodes': {
245 245 b'required': True,
246 246 b'type': b'list'
247 247 },
248 248 b'tree': {
249 249 b'required': True,
250 250 b'type': b'bytes'
251 251 }
252 252 },
253 253 b'permissions': [
254 254 b'pull'
255 255 ]
256 256 },
257 257 b'pushkey': {
258 258 b'args': {
259 259 b'key': {
260 260 b'required': True,
261 261 b'type': b'bytes'
262 262 },
263 263 b'namespace': {
264 264 b'required': True,
265 265 b'type': b'bytes'
266 266 },
267 267 b'new': {
268 268 b'required': True,
269 269 b'type': b'bytes'
270 270 },
271 271 b'old': {
272 272 b'required': True,
273 273 b'type': b'bytes'
274 274 }
275 275 },
276 276 b'permissions': [
277 277 b'push'
278 278 ]
279 279 }
280 280 },
281 281 b'framingmediatypes': [
282 b'application/mercurial-exp-framing-0005'
282 b'application/mercurial-exp-framing-0006'
283 283 ],
284 284 b'pathfilterprefixes': set([
285 285 b'path:',
286 286 b'rootfilesin:'
287 287 ]),
288 288 b'rawrepoformats': [
289 289 b'generaldelta',
290 290 b'revlogv1'
291 291 ],
292 292 b'redirect': {
293 293 b'hashes': [
294 294 b'sha256',
295 295 b'sha1'
296 296 ],
297 297 b'targets': [
298 298 {
299 299 b'name': b'target-a',
300 300 b'protocol': b'http',
301 301 b'snirequired': False,
302 302 b'tlsversions': [
303 303 b'1.2',
304 304 b'1.3'
305 305 ],
306 306 b'uris': [
307 307 b'http://example.com/'
308 308 ]
309 309 }
310 310 ]
311 311 }
312 312 }
313 313 ]
314 314 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
315 315
316 316 Unknown protocol is filtered from compatible targets
317 317
318 318 $ cat > redirects.py << EOF
319 319 > [
320 320 > {
321 321 > b'name': b'target-a',
322 322 > b'protocol': b'http',
323 323 > b'uris': [b'http://example.com/'],
324 324 > },
325 325 > {
326 326 > b'name': b'target-b',
327 327 > b'protocol': b'unknown',
328 328 > b'uris': [b'unknown://example.com/'],
329 329 > },
330 330 > ]
331 331 > EOF
332 332
333 333 $ sendhttpv2peerhandshake << EOF
334 334 > command capabilities
335 335 > EOF
336 336 creating http peer for wire protocol version 2
337 337 s> GET /?cmd=capabilities HTTP/1.1\r\n
338 338 s> Accept-Encoding: identity\r\n
339 339 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
340 340 s> x-hgproto-1: cbor\r\n
341 341 s> x-hgupgrade-1: exp-http-v2-0002\r\n
342 342 s> accept: application/mercurial-0.1\r\n
343 343 s> host: $LOCALIP:$HGPORT\r\n (glob)
344 344 s> user-agent: Mercurial debugwireproto\r\n
345 345 s> \r\n
346 346 s> makefile('rb', None)
347 347 s> HTTP/1.1 200 OK\r\n
348 348 s> Server: testing stub value\r\n
349 349 s> Date: $HTTP_DATE$\r\n
350 350 s> Content-Type: application/mercurial-cbor\r\n
351 351 s> Content-Length: 1957\r\n
352 352 s> \r\n
353 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
353 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
354 354 (remote redirect target target-a is compatible)
355 355 (remote redirect target target-b uses unsupported protocol: unknown)
356 356 sending capabilities command
357 357 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
358 358 s> Accept-Encoding: identity\r\n
359 s> accept: application/mercurial-exp-framing-0005\r\n
360 s> content-type: application/mercurial-exp-framing-0005\r\n
361 s> content-length: 75\r\n
359 s> accept: application/mercurial-exp-framing-0006\r\n
360 s> content-type: application/mercurial-exp-framing-0006\r\n
361 s> content-length: 111\r\n
362 362 s> host: $LOCALIP:$HGPORT\r\n (glob)
363 363 s> user-agent: Mercurial debugwireproto\r\n
364 364 s> \r\n
365 s> C\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
365 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a
366 366 s> makefile('rb', None)
367 367 s> HTTP/1.1 200 OK\r\n
368 368 s> Server: testing stub value\r\n
369 369 s> Date: $HTTP_DATE$\r\n
370 s> Content-Type: application/mercurial-exp-framing-0005\r\n
370 s> Content-Type: application/mercurial-exp-framing-0006\r\n
371 371 s> Transfer-Encoding: chunked\r\n
372 372 s> \r\n
373 373 s> 13\r\n
374 374 s> \x0b\x00\x00\x01\x00\x02\x011
375 375 s> \xa1FstatusBok
376 376 s> \r\n
377 377 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
378 378 s> 5a3\r\n
379 379 s> \x9b\x05\x00\x01\x00\x02\x001
380 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
380 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/
381 381 s> \r\n
382 382 received frame(size=1435; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
383 383 s> 8\r\n
384 384 s> \x00\x00\x00\x01\x00\x02\x002
385 385 s> \r\n
386 386 s> 0\r\n
387 387 s> \r\n
388 388 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
389 389 response: gen[
390 390 {
391 391 b'commands': {
392 392 b'branchmap': {
393 393 b'args': {},
394 394 b'permissions': [
395 395 b'pull'
396 396 ]
397 397 },
398 398 b'capabilities': {
399 399 b'args': {},
400 400 b'permissions': [
401 401 b'pull'
402 402 ]
403 403 },
404 404 b'changesetdata': {
405 405 b'args': {
406 406 b'fields': {
407 407 b'default': set([]),
408 408 b'required': False,
409 409 b'type': b'set',
410 410 b'validvalues': set([
411 411 b'bookmarks',
412 412 b'parents',
413 413 b'phase',
414 414 b'revision'
415 415 ])
416 416 },
417 417 b'noderange': {
418 418 b'default': None,
419 419 b'required': False,
420 420 b'type': b'list'
421 421 },
422 422 b'nodes': {
423 423 b'default': None,
424 424 b'required': False,
425 425 b'type': b'list'
426 426 },
427 427 b'nodesdepth': {
428 428 b'default': None,
429 429 b'required': False,
430 430 b'type': b'int'
431 431 }
432 432 },
433 433 b'permissions': [
434 434 b'pull'
435 435 ]
436 436 },
437 437 b'filedata': {
438 438 b'args': {
439 439 b'fields': {
440 440 b'default': set([]),
441 441 b'required': False,
442 442 b'type': b'set',
443 443 b'validvalues': set([
444 444 b'parents',
445 445 b'revision'
446 446 ])
447 447 },
448 448 b'haveparents': {
449 449 b'default': False,
450 450 b'required': False,
451 451 b'type': b'bool'
452 452 },
453 453 b'nodes': {
454 454 b'required': True,
455 455 b'type': b'list'
456 456 },
457 457 b'path': {
458 458 b'required': True,
459 459 b'type': b'bytes'
460 460 }
461 461 },
462 462 b'permissions': [
463 463 b'pull'
464 464 ]
465 465 },
466 466 b'heads': {
467 467 b'args': {
468 468 b'publiconly': {
469 469 b'default': False,
470 470 b'required': False,
471 471 b'type': b'bool'
472 472 }
473 473 },
474 474 b'permissions': [
475 475 b'pull'
476 476 ]
477 477 },
478 478 b'known': {
479 479 b'args': {
480 480 b'nodes': {
481 481 b'default': [],
482 482 b'required': False,
483 483 b'type': b'list'
484 484 }
485 485 },
486 486 b'permissions': [
487 487 b'pull'
488 488 ]
489 489 },
490 490 b'listkeys': {
491 491 b'args': {
492 492 b'namespace': {
493 493 b'required': True,
494 494 b'type': b'bytes'
495 495 }
496 496 },
497 497 b'permissions': [
498 498 b'pull'
499 499 ]
500 500 },
501 501 b'lookup': {
502 502 b'args': {
503 503 b'key': {
504 504 b'required': True,
505 505 b'type': b'bytes'
506 506 }
507 507 },
508 508 b'permissions': [
509 509 b'pull'
510 510 ]
511 511 },
512 512 b'manifestdata': {
513 513 b'args': {
514 514 b'fields': {
515 515 b'default': set([]),
516 516 b'required': False,
517 517 b'type': b'set',
518 518 b'validvalues': set([
519 519 b'parents',
520 520 b'revision'
521 521 ])
522 522 },
523 523 b'haveparents': {
524 524 b'default': False,
525 525 b'required': False,
526 526 b'type': b'bool'
527 527 },
528 528 b'nodes': {
529 529 b'required': True,
530 530 b'type': b'list'
531 531 },
532 532 b'tree': {
533 533 b'required': True,
534 534 b'type': b'bytes'
535 535 }
536 536 },
537 537 b'permissions': [
538 538 b'pull'
539 539 ]
540 540 },
541 541 b'pushkey': {
542 542 b'args': {
543 543 b'key': {
544 544 b'required': True,
545 545 b'type': b'bytes'
546 546 },
547 547 b'namespace': {
548 548 b'required': True,
549 549 b'type': b'bytes'
550 550 },
551 551 b'new': {
552 552 b'required': True,
553 553 b'type': b'bytes'
554 554 },
555 555 b'old': {
556 556 b'required': True,
557 557 b'type': b'bytes'
558 558 }
559 559 },
560 560 b'permissions': [
561 561 b'push'
562 562 ]
563 563 }
564 564 },
565 565 b'framingmediatypes': [
566 b'application/mercurial-exp-framing-0005'
566 b'application/mercurial-exp-framing-0006'
567 567 ],
568 568 b'pathfilterprefixes': set([
569 569 b'path:',
570 570 b'rootfilesin:'
571 571 ]),
572 572 b'rawrepoformats': [
573 573 b'generaldelta',
574 574 b'revlogv1'
575 575 ],
576 576 b'redirect': {
577 577 b'hashes': [
578 578 b'sha256',
579 579 b'sha1'
580 580 ],
581 581 b'targets': [
582 582 {
583 583 b'name': b'target-a',
584 584 b'protocol': b'http',
585 585 b'uris': [
586 586 b'http://example.com/'
587 587 ]
588 588 },
589 589 {
590 590 b'name': b'target-b',
591 591 b'protocol': b'unknown',
592 592 b'uris': [
593 593 b'unknown://example.com/'
594 594 ]
595 595 }
596 596 ]
597 597 }
598 598 }
599 599 ]
600 600 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
601 601
602 602 Missing SNI support filters targets that require SNI
603 603
604 604 $ cat > nosni.py << EOF
605 605 > from mercurial import sslutil
606 606 > sslutil.hassni = False
607 607 > EOF
608 608 $ cat >> $HGRCPATH << EOF
609 609 > [extensions]
610 610 > nosni=`pwd`/nosni.py
611 611 > EOF
612 612
613 613 $ cat > redirects.py << EOF
614 614 > [
615 615 > {
616 616 > b'name': b'target-bad-tls',
617 617 > b'protocol': b'https',
618 618 > b'uris': [b'https://example.com/'],
619 619 > b'snirequired': True,
620 620 > },
621 621 > ]
622 622 > EOF
623 623
624 624 $ sendhttpv2peerhandshake << EOF
625 625 > command capabilities
626 626 > EOF
627 627 creating http peer for wire protocol version 2
628 628 s> GET /?cmd=capabilities HTTP/1.1\r\n
629 629 s> Accept-Encoding: identity\r\n
630 630 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
631 631 s> x-hgproto-1: cbor\r\n
632 632 s> x-hgupgrade-1: exp-http-v2-0002\r\n
633 633 s> accept: application/mercurial-0.1\r\n
634 634 s> host: $LOCALIP:$HGPORT\r\n (glob)
635 635 s> user-agent: Mercurial debugwireproto\r\n
636 636 s> \r\n
637 637 s> makefile('rb', None)
638 638 s> HTTP/1.1 200 OK\r\n
639 639 s> Server: testing stub value\r\n
640 640 s> Date: $HTTP_DATE$\r\n
641 641 s> Content-Type: application/mercurial-cbor\r\n
642 642 s> Content-Length: 1917\r\n
643 643 s> \r\n
644 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
644 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
645 645 (redirect target target-bad-tls requires SNI, which is unsupported)
646 646 sending capabilities command
647 647 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
648 648 s> Accept-Encoding: identity\r\n
649 s> accept: application/mercurial-exp-framing-0005\r\n
650 s> content-type: application/mercurial-exp-framing-0005\r\n
651 s> content-length: 66\r\n
649 s> accept: application/mercurial-exp-framing-0006\r\n
650 s> content-type: application/mercurial-exp-framing-0006\r\n
651 s> content-length: 102\r\n
652 652 s> host: $LOCALIP:$HGPORT\r\n (glob)
653 653 s> user-agent: Mercurial debugwireproto\r\n
654 654 s> \r\n
655 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
655 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
656 656 s> makefile('rb', None)
657 657 s> HTTP/1.1 200 OK\r\n
658 658 s> Server: testing stub value\r\n
659 659 s> Date: $HTTP_DATE$\r\n
660 s> Content-Type: application/mercurial-exp-framing-0005\r\n
660 s> Content-Type: application/mercurial-exp-framing-0006\r\n
661 661 s> Transfer-Encoding: chunked\r\n
662 662 s> \r\n
663 663 s> 13\r\n
664 664 s> \x0b\x00\x00\x01\x00\x02\x011
665 665 s> \xa1FstatusBok
666 666 s> \r\n
667 667 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
668 668 s> 57b\r\n
669 669 s> s\x05\x00\x01\x00\x02\x001
670 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
670 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/
671 671 s> \r\n
672 672 received frame(size=1395; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
673 673 s> 8\r\n
674 674 s> \x00\x00\x00\x01\x00\x02\x002
675 675 s> \r\n
676 676 s> 0\r\n
677 677 s> \r\n
678 678 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
679 679 response: gen[
680 680 {
681 681 b'commands': {
682 682 b'branchmap': {
683 683 b'args': {},
684 684 b'permissions': [
685 685 b'pull'
686 686 ]
687 687 },
688 688 b'capabilities': {
689 689 b'args': {},
690 690 b'permissions': [
691 691 b'pull'
692 692 ]
693 693 },
694 694 b'changesetdata': {
695 695 b'args': {
696 696 b'fields': {
697 697 b'default': set([]),
698 698 b'required': False,
699 699 b'type': b'set',
700 700 b'validvalues': set([
701 701 b'bookmarks',
702 702 b'parents',
703 703 b'phase',
704 704 b'revision'
705 705 ])
706 706 },
707 707 b'noderange': {
708 708 b'default': None,
709 709 b'required': False,
710 710 b'type': b'list'
711 711 },
712 712 b'nodes': {
713 713 b'default': None,
714 714 b'required': False,
715 715 b'type': b'list'
716 716 },
717 717 b'nodesdepth': {
718 718 b'default': None,
719 719 b'required': False,
720 720 b'type': b'int'
721 721 }
722 722 },
723 723 b'permissions': [
724 724 b'pull'
725 725 ]
726 726 },
727 727 b'filedata': {
728 728 b'args': {
729 729 b'fields': {
730 730 b'default': set([]),
731 731 b'required': False,
732 732 b'type': b'set',
733 733 b'validvalues': set([
734 734 b'parents',
735 735 b'revision'
736 736 ])
737 737 },
738 738 b'haveparents': {
739 739 b'default': False,
740 740 b'required': False,
741 741 b'type': b'bool'
742 742 },
743 743 b'nodes': {
744 744 b'required': True,
745 745 b'type': b'list'
746 746 },
747 747 b'path': {
748 748 b'required': True,
749 749 b'type': b'bytes'
750 750 }
751 751 },
752 752 b'permissions': [
753 753 b'pull'
754 754 ]
755 755 },
756 756 b'heads': {
757 757 b'args': {
758 758 b'publiconly': {
759 759 b'default': False,
760 760 b'required': False,
761 761 b'type': b'bool'
762 762 }
763 763 },
764 764 b'permissions': [
765 765 b'pull'
766 766 ]
767 767 },
768 768 b'known': {
769 769 b'args': {
770 770 b'nodes': {
771 771 b'default': [],
772 772 b'required': False,
773 773 b'type': b'list'
774 774 }
775 775 },
776 776 b'permissions': [
777 777 b'pull'
778 778 ]
779 779 },
780 780 b'listkeys': {
781 781 b'args': {
782 782 b'namespace': {
783 783 b'required': True,
784 784 b'type': b'bytes'
785 785 }
786 786 },
787 787 b'permissions': [
788 788 b'pull'
789 789 ]
790 790 },
791 791 b'lookup': {
792 792 b'args': {
793 793 b'key': {
794 794 b'required': True,
795 795 b'type': b'bytes'
796 796 }
797 797 },
798 798 b'permissions': [
799 799 b'pull'
800 800 ]
801 801 },
802 802 b'manifestdata': {
803 803 b'args': {
804 804 b'fields': {
805 805 b'default': set([]),
806 806 b'required': False,
807 807 b'type': b'set',
808 808 b'validvalues': set([
809 809 b'parents',
810 810 b'revision'
811 811 ])
812 812 },
813 813 b'haveparents': {
814 814 b'default': False,
815 815 b'required': False,
816 816 b'type': b'bool'
817 817 },
818 818 b'nodes': {
819 819 b'required': True,
820 820 b'type': b'list'
821 821 },
822 822 b'tree': {
823 823 b'required': True,
824 824 b'type': b'bytes'
825 825 }
826 826 },
827 827 b'permissions': [
828 828 b'pull'
829 829 ]
830 830 },
831 831 b'pushkey': {
832 832 b'args': {
833 833 b'key': {
834 834 b'required': True,
835 835 b'type': b'bytes'
836 836 },
837 837 b'namespace': {
838 838 b'required': True,
839 839 b'type': b'bytes'
840 840 },
841 841 b'new': {
842 842 b'required': True,
843 843 b'type': b'bytes'
844 844 },
845 845 b'old': {
846 846 b'required': True,
847 847 b'type': b'bytes'
848 848 }
849 849 },
850 850 b'permissions': [
851 851 b'push'
852 852 ]
853 853 }
854 854 },
855 855 b'framingmediatypes': [
856 b'application/mercurial-exp-framing-0005'
856 b'application/mercurial-exp-framing-0006'
857 857 ],
858 858 b'pathfilterprefixes': set([
859 859 b'path:',
860 860 b'rootfilesin:'
861 861 ]),
862 862 b'rawrepoformats': [
863 863 b'generaldelta',
864 864 b'revlogv1'
865 865 ],
866 866 b'redirect': {
867 867 b'hashes': [
868 868 b'sha256',
869 869 b'sha1'
870 870 ],
871 871 b'targets': [
872 872 {
873 873 b'name': b'target-bad-tls',
874 874 b'protocol': b'https',
875 875 b'snirequired': True,
876 876 b'uris': [
877 877 b'https://example.com/'
878 878 ]
879 879 }
880 880 ]
881 881 }
882 882 }
883 883 ]
884 884 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
885 885
886 886 $ cat >> $HGRCPATH << EOF
887 887 > [extensions]
888 888 > nosni=!
889 889 > EOF
890 890
891 891 Unknown tls value is filtered from compatible targets
892 892
893 893 $ cat > redirects.py << EOF
894 894 > [
895 895 > {
896 896 > b'name': b'target-bad-tls',
897 897 > b'protocol': b'https',
898 898 > b'uris': [b'https://example.com/'],
899 899 > b'tlsversions': [b'42', b'39'],
900 900 > },
901 901 > ]
902 902 > EOF
903 903
904 904 $ sendhttpv2peerhandshake << EOF
905 905 > command capabilities
906 906 > EOF
907 907 creating http peer for wire protocol version 2
908 908 s> GET /?cmd=capabilities HTTP/1.1\r\n
909 909 s> Accept-Encoding: identity\r\n
910 910 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
911 911 s> x-hgproto-1: cbor\r\n
912 912 s> x-hgupgrade-1: exp-http-v2-0002\r\n
913 913 s> accept: application/mercurial-0.1\r\n
914 914 s> host: $LOCALIP:$HGPORT\r\n (glob)
915 915 s> user-agent: Mercurial debugwireproto\r\n
916 916 s> \r\n
917 917 s> makefile('rb', None)
918 918 s> HTTP/1.1 200 OK\r\n
919 919 s> Server: testing stub value\r\n
920 920 s> Date: $HTTP_DATE$\r\n
921 921 s> Content-Type: application/mercurial-cbor\r\n
922 922 s> Content-Length: 1923\r\n
923 923 s> \r\n
924 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
924 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
925 925 (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42)
926 926 sending capabilities command
927 927 s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
928 928 s> Accept-Encoding: identity\r\n
929 s> accept: application/mercurial-exp-framing-0005\r\n
930 s> content-type: application/mercurial-exp-framing-0005\r\n
931 s> content-length: 66\r\n
929 s> accept: application/mercurial-exp-framing-0006\r\n
930 s> content-type: application/mercurial-exp-framing-0006\r\n
931 s> content-length: 102\r\n
932 932 s> host: $LOCALIP:$HGPORT\r\n (glob)
933 933 s> user-agent: Mercurial debugwireproto\r\n
934 934 s> \r\n
935 s> :\x00\x00\x01\x00\x01\x01\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
935 s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80
936 936 s> makefile('rb', None)
937 937 s> HTTP/1.1 200 OK\r\n
938 938 s> Server: testing stub value\r\n
939 939 s> Date: $HTTP_DATE$\r\n
940 s> Content-Type: application/mercurial-exp-framing-0005\r\n
940 s> Content-Type: application/mercurial-exp-framing-0006\r\n
941 941 s> Transfer-Encoding: chunked\r\n
942 942 s> \r\n
943 943 s> 13\r\n
944 944 s> \x0b\x00\x00\x01\x00\x02\x011
945 945 s> \xa1FstatusBok
946 946 s> \r\n
947 947 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
948 948 s> 581\r\n
949 949 s> y\x05\x00\x01\x00\x02\x001
950 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
950 s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Hredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/
951 951 s> \r\n
952 952 received frame(size=1401; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
953 953 s> 8\r\n
954 954 s> \x00\x00\x00\x01\x00\x02\x002
955 955 s> \r\n
956 956 s> 0\r\n
957 957 s> \r\n
958 958 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
959 959 response: gen[
960 960 {
961 961 b'commands': {
962 962 b'branchmap': {
963 963 b'args': {},
964 964 b'permissions': [
965 965 b'pull'
966 966 ]
967 967 },
968 968 b'capabilities': {
969 969 b'args': {},
970 970 b'permissions': [
971 971 b'pull'
972 972 ]
973 973 },
974 974 b'changesetdata': {
975 975 b'args': {
976 976 b'fields': {
977 977 b'default': set([]),
978 978 b'required': False,
979 979 b'type': b'set',
980 980 b'validvalues': set([
981 981 b'bookmarks',
982 982 b'parents',
983 983 b'phase',
984 984 b'revision'
985 985 ])
986 986 },
987 987 b'noderange': {
988 988 b'default': None,
989 989 b'required': False,
990 990 b'type': b'list'
991 991 },
992 992 b'nodes': {
993 993 b'default': None,
994 994 b'required': False,
995 995 b'type': b'list'
996 996 },
997 997 b'nodesdepth': {
998 998 b'default': None,
999 999 b'required': False,
1000 1000 b'type': b'int'
1001 1001 }
1002 1002 },
1003 1003 b'permissions': [
1004 1004 b'pull'
1005 1005 ]
1006 1006 },
1007 1007 b'filedata': {
1008 1008 b'args': {
1009 1009 b'fields': {
1010 1010 b'default': set([]),
1011 1011 b'required': False,
1012 1012 b'type': b'set',
1013 1013 b'validvalues': set([
1014 1014 b'parents',
1015 1015 b'revision'
1016 1016 ])
1017 1017 },
1018 1018 b'haveparents': {
1019 1019 b'default': False,
1020 1020 b'required': False,
1021 1021 b'type': b'bool'
1022 1022 },
1023 1023 b'nodes': {
1024 1024 b'required': True,
1025 1025 b'type': b'list'
1026 1026 },
1027 1027 b'path': {
1028 1028 b'required': True,
1029 1029 b'type': b'bytes'
1030 1030 }
1031 1031 },
1032 1032 b'permissions': [
1033 1033 b'pull'
1034 1034 ]
1035 1035 },
1036 1036 b'heads': {
1037 1037 b'args': {
1038 1038 b'publiconly': {
1039 1039 b'default': False,
1040 1040 b'required': False,
1041 1041 b'type': b'bool'
1042 1042 }
1043 1043 },
1044 1044 b'permissions': [
1045 1045 b'pull'
1046 1046 ]
1047 1047 },
1048 1048 b'known': {
1049 1049 b'args': {
1050 1050 b'nodes': {
1051 1051 b'default': [],
1052 1052 b'required': False,
1053 1053 b'type': b'list'
1054 1054 }
1055 1055 },
1056 1056 b'permissions': [
1057 1057 b'pull'
1058 1058 ]
1059 1059 },
1060 1060 b'listkeys': {
1061 1061 b'args': {
1062 1062 b'namespace': {
1063 1063 b'required': True,
1064 1064 b'type': b'bytes'
1065 1065 }
1066 1066 },
1067 1067 b'permissions': [
1068 1068 b'pull'
1069 1069 ]
1070 1070 },
1071 1071 b'lookup': {
1072 1072 b'args': {
1073 1073 b'key': {
1074 1074 b'required': True,
1075 1075 b'type': b'bytes'
1076 1076 }
1077 1077 },
1078 1078 b'permissions': [
1079 1079 b'pull'
1080 1080 ]
1081 1081 },
1082 1082 b'manifestdata': {
1083 1083 b'args': {
1084 1084 b'fields': {
1085 1085 b'default': set([]),
1086 1086 b'required': False,
1087 1087 b'type': b'set',
1088 1088 b'validvalues': set([
1089 1089 b'parents',
1090 1090 b'revision'
1091 1091 ])
1092 1092 },
1093 1093 b'haveparents': {
1094 1094 b'default': False,
1095 1095 b'required': False,
1096 1096 b'type': b'bool'
1097 1097 },
1098 1098 b'nodes': {
1099 1099 b'required': True,
1100 1100 b'type': b'list'
1101 1101 },
1102 1102 b'tree': {
1103 1103 b'required': True,
1104 1104 b'type': b'bytes'
1105 1105 }
1106 1106 },
1107 1107 b'permissions': [
1108 1108 b'pull'
1109 1109 ]
1110 1110 },
1111 1111 b'pushkey': {
1112 1112 b'args': {
1113 1113 b'key': {
1114 1114 b'required': True,
1115 1115 b'type': b'bytes'
1116 1116 },
1117 1117 b'namespace': {
1118 1118 b'required': True,
1119 1119 b'type': b'bytes'
1120 1120 },
1121 1121 b'new': {
1122 1122 b'required': True,
1123 1123 b'type': b'bytes'
1124 1124 },
1125 1125 b'old': {
1126 1126 b'required': True,
1127 1127 b'type': b'bytes'
1128 1128 }
1129 1129 },
1130 1130 b'permissions': [
1131 1131 b'push'
1132 1132 ]
1133 1133 }
1134 1134 },
1135 1135 b'framingmediatypes': [
1136 b'application/mercurial-exp-framing-0005'
1136 b'application/mercurial-exp-framing-0006'
1137 1137 ],
1138 1138 b'pathfilterprefixes': set([
1139 1139 b'path:',
1140 1140 b'rootfilesin:'
1141 1141 ]),
1142 1142 b'rawrepoformats': [
1143 1143 b'generaldelta',
1144 1144 b'revlogv1'
1145 1145 ],
1146 1146 b'redirect': {
1147 1147 b'hashes': [
1148 1148 b'sha256',
1149 1149 b'sha1'
1150 1150 ],
1151 1151 b'targets': [
1152 1152 {
1153 1153 b'name': b'target-bad-tls',
1154 1154 b'protocol': b'https',
1155 1155 b'tlsversions': [
1156 1156 b'42',
1157 1157 b'39'
1158 1158 ],
1159 1159 b'uris': [
1160 1160 b'https://example.com/'
1161 1161 ]
1162 1162 }
1163 1163 ]
1164 1164 }
1165 1165 }
1166 1166 ]
1167 1167 (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob)
1168 1168
1169 1169 Set up the server to issue content redirects to its built-in API server.
1170 1170
1171 1171 $ cat > redirects.py << EOF
1172 1172 > [
1173 1173 > {
1174 1174 > b'name': b'local',
1175 1175 > b'protocol': b'http',
1176 1176 > b'uris': [b'http://example.com/'],
1177 1177 > },
1178 1178 > ]
1179 1179 > EOF
1180 1180
1181 1181 Request to eventual cache URL should return 404 (validating the cache server works)
1182 1182
1183 1183 $ sendhttpraw << EOF
1184 1184 > httprequest GET api/simplecache/missingkey
1185 1185 > user-agent: test
1186 1186 > EOF
1187 1187 using raw connection to peer
1188 1188 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1189 1189 s> Accept-Encoding: identity\r\n
1190 1190 s> user-agent: test\r\n
1191 1191 s> host: $LOCALIP:$HGPORT\r\n (glob)
1192 1192 s> \r\n
1193 1193 s> makefile('rb', None)
1194 1194 s> HTTP/1.1 404 Not Found\r\n
1195 1195 s> Server: testing stub value\r\n
1196 1196 s> Date: $HTTP_DATE$\r\n
1197 1197 s> Content-Type: text/plain\r\n
1198 1198 s> Content-Length: 22\r\n
1199 1199 s> \r\n
1200 1200 s> key not found in cache
1201 1201
1202 1202 Send a cacheable request
1203 1203
1204 1204 $ sendhttpv2peer << EOF
1205 1205 > command manifestdata
1206 1206 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1207 1207 > tree eval:b''
1208 1208 > fields eval:[b'parents']
1209 1209 > EOF
1210 1210 creating http peer for wire protocol version 2
1211 1211 sending manifestdata command
1212 1212 response: gen[
1213 1213 {
1214 1214 b'totalitems': 1
1215 1215 },
1216 1216 {
1217 1217 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1218 1218 b'parents': [
1219 1219 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1220 1220 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1221 1221 ]
1222 1222 }
1223 1223 ]
1224 1224
1225 1225 Cached entry should be available on server
1226 1226
1227 1227 $ sendhttpraw << EOF
1228 > httprequest GET api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0
1228 > httprequest GET api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca
1229 1229 > user-agent: test
1230 1230 > EOF
1231 1231 using raw connection to peer
1232 s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n
1232 s> GET /api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca HTTP/1.1\r\n
1233 1233 s> Accept-Encoding: identity\r\n
1234 1234 s> user-agent: test\r\n
1235 1235 s> host: $LOCALIP:$HGPORT\r\n (glob)
1236 1236 s> \r\n
1237 1237 s> makefile('rb', None)
1238 1238 s> HTTP/1.1 200 OK\r\n
1239 1239 s> Server: testing stub value\r\n
1240 1240 s> Date: $HTTP_DATE$\r\n
1241 1241 s> Content-Type: application/mercurial-cbor\r\n
1242 1242 s> Content-Length: 91\r\n
1243 1243 s> \r\n
1244 1244 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1245 1245 cbor> [
1246 1246 {
1247 1247 b'totalitems': 1
1248 1248 },
1249 1249 {
1250 1250 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1251 1251 b'parents': [
1252 1252 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1253 1253 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1254 1254 ]
1255 1255 }
1256 1256 ]
1257 1257
1258 1258 2nd request should result in content redirect response
1259 1259
1260 1260 $ sendhttpv2peer << EOF
1261 1261 > command manifestdata
1262 1262 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1263 1263 > tree eval:b''
1264 1264 > fields eval:[b'parents']
1265 1265 > EOF
1266 1266 creating http peer for wire protocol version 2
1267 1267 sending manifestdata command
1268 1268 response: gen[
1269 1269 {
1270 1270 b'totalitems': 1
1271 1271 },
1272 1272 {
1273 1273 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1274 1274 b'parents': [
1275 1275 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1276 1276 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1277 1277 ]
1278 1278 }
1279 1279 ]
1280 1280
1281 1281 $ cat error.log
1282 1282 $ killdaemons.py
1283 1283
1284 1284 $ cat .hg/blackbox.log
1285 1285 *> cacher constructed for manifestdata (glob)
1286 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1287 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1286 *> cache miss for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1287 *> storing cache entry for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1288 1288 *> cacher constructed for manifestdata (glob)
1289 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1290 *> sending content redirect for c045a581599d58608efd3d93d8129841f2af04a0 to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1289 *> cache hit for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
1290 *> sending content redirect for 64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca to http://*:$HGPORT/api/simplecache/64b3162af49ea3c88e8ce2785e03ed7b88a2d6ca (glob)
@@ -1,69 +1,72
1 1 HTTPV2=exp-http-v2-0002
2 MEDIATYPE=application/mercurial-exp-framing-0005
2 MEDIATYPE=application/mercurial-exp-framing-0006
3 3
4 4 sendhttpraw() {
5 5 hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
6 6 }
7 7
8 8 sendhttpv2peer() {
9 hg debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
9 hg --config experimental.httppeer.v2-encoder-order=identity debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
10 10 }
11 11
12 12 sendhttpv2peerverbose() {
13 hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
13 hg --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
14 14 }
15 15
16 16 sendhttpv2peerhandshake() {
17 hg --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/
17 hg --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/
18 18 }
19 19
20 20 cat > dummycommands.py << EOF
21 21 from mercurial import (
22 22 wireprototypes,
23 23 wireprotov1server,
24 24 wireprotov2server,
25 25 )
26 26
27 27 @wireprotov1server.wireprotocommand(b'customreadonly', permission=b'pull')
28 28 def customreadonlyv1(repo, proto):
29 29 return wireprototypes.bytesresponse(b'customreadonly bytes response')
30 30
31 31 @wireprotov2server.wireprotocommand(b'customreadonly', permission=b'pull')
32 32 def customreadonlyv2(repo, proto):
33 33 yield b'customreadonly bytes response'
34 34
35 35 @wireprotov1server.wireprotocommand(b'customreadwrite', permission=b'push')
36 36 def customreadwrite(repo, proto):
37 37 return wireprototypes.bytesresponse(b'customreadwrite bytes response')
38 38
39 39 @wireprotov2server.wireprotocommand(b'customreadwrite', permission=b'push')
40 40 def customreadwritev2(repo, proto):
41 41 yield b'customreadwrite bytes response'
42 42 EOF
43 43
44 44 cat >> $HGRCPATH << EOF
45 45 [extensions]
46 46 drawdag = $TESTDIR/drawdag.py
47 47 EOF
48 48
49 49 enabledummycommands() {
50 50 cat >> $HGRCPATH << EOF
51 51 [extensions]
52 52 dummycommands = $TESTTMP/dummycommands.py
53 53 EOF
54 54 }
55 55
56 56 enablehttpv2() {
57 57 cat >> $1/.hg/hgrc << EOF
58 58 [experimental]
59 59 web.apiserver = true
60 60 web.api.http-v2 = true
61 61 EOF
62 62 }
63 63
64 64 enablehttpv2client() {
65 65 cat >> $HGRCPATH << EOF
66 66 [experimental]
67 67 httppeer.advertise-v2 = true
68 # So tests are in plain text. Also, zstd isn't available in all installs,
69 # which would make tests non-deterministic.
70 httppeer.v2-encoder-order = identity
68 71 EOF
69 72 }
General Comments 0
You need to be logged in to leave comments. Login now