##// END OF EJS Templates
http: drop custom http client logic...
Augie Fackler -
r36444:23d12524 default
parent child Browse files
Show More
@@ -1,1308 +1,1302
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=None,
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('auth', 'cookiefile',
151 151 default=None,
152 152 )
153 153 # bookmarks.pushing: internal hack for discovery
154 154 coreconfigitem('bookmarks', 'pushing',
155 155 default=list,
156 156 )
157 157 # bundle.mainreporoot: internal hack for bundlerepo
158 158 coreconfigitem('bundle', 'mainreporoot',
159 159 default='',
160 160 )
161 161 # bundle.reorder: experimental config
162 162 coreconfigitem('bundle', 'reorder',
163 163 default='auto',
164 164 )
165 165 coreconfigitem('censor', 'policy',
166 166 default='abort',
167 167 )
168 168 coreconfigitem('chgserver', 'idletimeout',
169 169 default=3600,
170 170 )
171 171 coreconfigitem('chgserver', 'skiphash',
172 172 default=False,
173 173 )
174 174 coreconfigitem('cmdserver', 'log',
175 175 default=None,
176 176 )
177 177 coreconfigitem('color', '.*',
178 178 default=None,
179 179 generic=True,
180 180 )
181 181 coreconfigitem('color', 'mode',
182 182 default='auto',
183 183 )
184 184 coreconfigitem('color', 'pagermode',
185 185 default=dynamicdefault,
186 186 )
187 187 coreconfigitem('commands', 'show.aliasprefix',
188 188 default=list,
189 189 )
190 190 coreconfigitem('commands', 'status.relative',
191 191 default=False,
192 192 )
193 193 coreconfigitem('commands', 'status.skipstates',
194 194 default=[],
195 195 )
196 196 coreconfigitem('commands', 'status.verbose',
197 197 default=False,
198 198 )
199 199 coreconfigitem('commands', 'update.check',
200 200 default=None,
201 201 # Deprecated, remove after 4.4 release
202 202 alias=[('experimental', 'updatecheck')]
203 203 )
204 204 coreconfigitem('commands', 'update.requiredest',
205 205 default=False,
206 206 )
207 207 coreconfigitem('committemplate', '.*',
208 208 default=None,
209 209 generic=True,
210 210 )
211 211 coreconfigitem('convert', 'cvsps.cache',
212 212 default=True,
213 213 )
214 214 coreconfigitem('convert', 'cvsps.fuzz',
215 215 default=60,
216 216 )
217 217 coreconfigitem('convert', 'cvsps.logencoding',
218 218 default=None,
219 219 )
220 220 coreconfigitem('convert', 'cvsps.mergefrom',
221 221 default=None,
222 222 )
223 223 coreconfigitem('convert', 'cvsps.mergeto',
224 224 default=None,
225 225 )
226 226 coreconfigitem('convert', 'git.committeractions',
227 227 default=lambda: ['messagedifferent'],
228 228 )
229 229 coreconfigitem('convert', 'git.extrakeys',
230 230 default=list,
231 231 )
232 232 coreconfigitem('convert', 'git.findcopiesharder',
233 233 default=False,
234 234 )
235 235 coreconfigitem('convert', 'git.remoteprefix',
236 236 default='remote',
237 237 )
238 238 coreconfigitem('convert', 'git.renamelimit',
239 239 default=400,
240 240 )
241 241 coreconfigitem('convert', 'git.saverev',
242 242 default=True,
243 243 )
244 244 coreconfigitem('convert', 'git.similarity',
245 245 default=50,
246 246 )
247 247 coreconfigitem('convert', 'git.skipsubmodules',
248 248 default=False,
249 249 )
250 250 coreconfigitem('convert', 'hg.clonebranches',
251 251 default=False,
252 252 )
253 253 coreconfigitem('convert', 'hg.ignoreerrors',
254 254 default=False,
255 255 )
256 256 coreconfigitem('convert', 'hg.revs',
257 257 default=None,
258 258 )
259 259 coreconfigitem('convert', 'hg.saverev',
260 260 default=False,
261 261 )
262 262 coreconfigitem('convert', 'hg.sourcename',
263 263 default=None,
264 264 )
265 265 coreconfigitem('convert', 'hg.startrev',
266 266 default=None,
267 267 )
268 268 coreconfigitem('convert', 'hg.tagsbranch',
269 269 default='default',
270 270 )
271 271 coreconfigitem('convert', 'hg.usebranchnames',
272 272 default=True,
273 273 )
274 274 coreconfigitem('convert', 'ignoreancestorcheck',
275 275 default=False,
276 276 )
277 277 coreconfigitem('convert', 'localtimezone',
278 278 default=False,
279 279 )
280 280 coreconfigitem('convert', 'p4.encoding',
281 281 default=dynamicdefault,
282 282 )
283 283 coreconfigitem('convert', 'p4.startrev',
284 284 default=0,
285 285 )
286 286 coreconfigitem('convert', 'skiptags',
287 287 default=False,
288 288 )
289 289 coreconfigitem('convert', 'svn.debugsvnlog',
290 290 default=True,
291 291 )
292 292 coreconfigitem('convert', 'svn.trunk',
293 293 default=None,
294 294 )
295 295 coreconfigitem('convert', 'svn.tags',
296 296 default=None,
297 297 )
298 298 coreconfigitem('convert', 'svn.branches',
299 299 default=None,
300 300 )
301 301 coreconfigitem('convert', 'svn.startrev',
302 302 default=0,
303 303 )
304 304 coreconfigitem('debug', 'dirstate.delaywrite',
305 305 default=0,
306 306 )
307 307 coreconfigitem('defaults', '.*',
308 308 default=None,
309 309 generic=True,
310 310 )
311 311 coreconfigitem('devel', 'all-warnings',
312 312 default=False,
313 313 )
314 314 coreconfigitem('devel', 'bundle2.debug',
315 315 default=False,
316 316 )
317 317 coreconfigitem('devel', 'cache-vfs',
318 318 default=None,
319 319 )
320 320 coreconfigitem('devel', 'check-locks',
321 321 default=False,
322 322 )
323 323 coreconfigitem('devel', 'check-relroot',
324 324 default=False,
325 325 )
326 326 coreconfigitem('devel', 'default-date',
327 327 default=None,
328 328 )
329 329 coreconfigitem('devel', 'deprec-warn',
330 330 default=False,
331 331 )
332 332 coreconfigitem('devel', 'disableloaddefaultcerts',
333 333 default=False,
334 334 )
335 335 coreconfigitem('devel', 'warn-empty-changegroup',
336 336 default=False,
337 337 )
338 338 coreconfigitem('devel', 'legacy.exchange',
339 339 default=list,
340 340 )
341 341 coreconfigitem('devel', 'servercafile',
342 342 default='',
343 343 )
344 344 coreconfigitem('devel', 'serverexactprotocol',
345 345 default='',
346 346 )
347 347 coreconfigitem('devel', 'serverrequirecert',
348 348 default=False,
349 349 )
350 350 coreconfigitem('devel', 'strip-obsmarkers',
351 351 default=True,
352 352 )
353 353 coreconfigitem('devel', 'warn-config',
354 354 default=None,
355 355 )
356 356 coreconfigitem('devel', 'warn-config-default',
357 357 default=None,
358 358 )
359 359 coreconfigitem('devel', 'user.obsmarker',
360 360 default=None,
361 361 )
362 362 coreconfigitem('devel', 'warn-config-unknown',
363 363 default=None,
364 364 )
365 365 coreconfigitem('devel', 'debug.peer-request',
366 366 default=False,
367 367 )
368 368 coreconfigitem('diff', 'nodates',
369 369 default=False,
370 370 )
371 371 coreconfigitem('diff', 'showfunc',
372 372 default=False,
373 373 )
374 374 coreconfigitem('diff', 'unified',
375 375 default=None,
376 376 )
377 377 coreconfigitem('diff', 'git',
378 378 default=False,
379 379 )
380 380 coreconfigitem('diff', 'ignorews',
381 381 default=False,
382 382 )
383 383 coreconfigitem('diff', 'ignorewsamount',
384 384 default=False,
385 385 )
386 386 coreconfigitem('diff', 'ignoreblanklines',
387 387 default=False,
388 388 )
389 389 coreconfigitem('diff', 'ignorewseol',
390 390 default=False,
391 391 )
392 392 coreconfigitem('diff', 'nobinary',
393 393 default=False,
394 394 )
395 395 coreconfigitem('diff', 'noprefix',
396 396 default=False,
397 397 )
398 398 coreconfigitem('email', 'bcc',
399 399 default=None,
400 400 )
401 401 coreconfigitem('email', 'cc',
402 402 default=None,
403 403 )
404 404 coreconfigitem('email', 'charsets',
405 405 default=list,
406 406 )
407 407 coreconfigitem('email', 'from',
408 408 default=None,
409 409 )
410 410 coreconfigitem('email', 'method',
411 411 default='smtp',
412 412 )
413 413 coreconfigitem('email', 'reply-to',
414 414 default=None,
415 415 )
416 416 coreconfigitem('email', 'to',
417 417 default=None,
418 418 )
419 419 coreconfigitem('experimental', 'archivemetatemplate',
420 420 default=dynamicdefault,
421 421 )
422 422 coreconfigitem('experimental', 'bundle-phases',
423 423 default=False,
424 424 )
425 425 coreconfigitem('experimental', 'bundle2-advertise',
426 426 default=True,
427 427 )
428 428 coreconfigitem('experimental', 'bundle2-output-capture',
429 429 default=False,
430 430 )
431 431 coreconfigitem('experimental', 'bundle2.pushback',
432 432 default=False,
433 433 )
434 434 coreconfigitem('experimental', 'bundle2.stream',
435 435 default=False,
436 436 )
437 437 coreconfigitem('experimental', 'bundle2lazylocking',
438 438 default=False,
439 439 )
440 440 coreconfigitem('experimental', 'bundlecomplevel',
441 441 default=None,
442 442 )
443 443 coreconfigitem('experimental', 'changegroup3',
444 444 default=False,
445 445 )
446 446 coreconfigitem('experimental', 'clientcompressionengines',
447 447 default=list,
448 448 )
449 449 coreconfigitem('experimental', 'copytrace',
450 450 default='on',
451 451 )
452 452 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
453 453 default=100,
454 454 )
455 455 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
456 456 default=100,
457 457 )
458 458 coreconfigitem('experimental', 'crecordtest',
459 459 default=None,
460 460 )
461 461 coreconfigitem('experimental', 'directaccess',
462 462 default=False,
463 463 )
464 464 coreconfigitem('experimental', 'directaccess.revnums',
465 465 default=False,
466 466 )
467 467 coreconfigitem('experimental', 'editortmpinhg',
468 468 default=False,
469 469 )
470 470 coreconfigitem('experimental', 'evolution',
471 471 default=list,
472 472 )
473 473 coreconfigitem('experimental', 'evolution.allowdivergence',
474 474 default=False,
475 475 alias=[('experimental', 'allowdivergence')]
476 476 )
477 477 coreconfigitem('experimental', 'evolution.allowunstable',
478 478 default=None,
479 479 )
480 480 coreconfigitem('experimental', 'evolution.createmarkers',
481 481 default=None,
482 482 )
483 483 coreconfigitem('experimental', 'evolution.effect-flags',
484 484 default=True,
485 485 alias=[('experimental', 'effect-flags')]
486 486 )
487 487 coreconfigitem('experimental', 'evolution.exchange',
488 488 default=None,
489 489 )
490 490 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
491 491 default=False,
492 492 )
493 493 coreconfigitem('experimental', 'evolution.report-instabilities',
494 494 default=True,
495 495 )
496 496 coreconfigitem('experimental', 'evolution.track-operation',
497 497 default=True,
498 498 )
499 499 coreconfigitem('experimental', 'worddiff',
500 500 default=False,
501 501 )
502 502 coreconfigitem('experimental', 'maxdeltachainspan',
503 503 default=-1,
504 504 )
505 505 coreconfigitem('experimental', 'mmapindexthreshold',
506 506 default=None,
507 507 )
508 508 coreconfigitem('experimental', 'nonnormalparanoidcheck',
509 509 default=False,
510 510 )
511 511 coreconfigitem('experimental', 'exportableenviron',
512 512 default=list,
513 513 )
514 514 coreconfigitem('experimental', 'extendedheader.index',
515 515 default=None,
516 516 )
517 517 coreconfigitem('experimental', 'extendedheader.similarity',
518 518 default=False,
519 519 )
520 520 coreconfigitem('experimental', 'format.compression',
521 521 default='zlib',
522 522 )
523 523 coreconfigitem('experimental', 'graphshorten',
524 524 default=False,
525 525 )
526 526 coreconfigitem('experimental', 'graphstyle.parent',
527 527 default=dynamicdefault,
528 528 )
529 529 coreconfigitem('experimental', 'graphstyle.missing',
530 530 default=dynamicdefault,
531 531 )
532 532 coreconfigitem('experimental', 'graphstyle.grandparent',
533 533 default=dynamicdefault,
534 534 )
535 535 coreconfigitem('experimental', 'hook-track-tags',
536 536 default=False,
537 537 )
538 538 coreconfigitem('experimental', 'httppostargs',
539 539 default=False,
540 540 )
541 541 coreconfigitem('experimental', 'mergedriver',
542 542 default=None,
543 543 )
544 544 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
545 545 default=False,
546 546 )
547 547 coreconfigitem('experimental', 'remotenames',
548 548 default=False,
549 549 )
550 550 coreconfigitem('experimental', 'revlogv2',
551 551 default=None,
552 552 )
553 553 coreconfigitem('experimental', 'single-head-per-branch',
554 554 default=False,
555 555 )
556 556 coreconfigitem('experimental', 'sshserver.support-v2',
557 557 default=False,
558 558 )
559 559 coreconfigitem('experimental', 'spacemovesdown',
560 560 default=False,
561 561 )
562 562 coreconfigitem('experimental', 'sparse-read',
563 563 default=False,
564 564 )
565 565 coreconfigitem('experimental', 'sparse-read.density-threshold',
566 566 default=0.25,
567 567 )
568 568 coreconfigitem('experimental', 'sparse-read.min-gap-size',
569 569 default='256K',
570 570 )
571 571 coreconfigitem('experimental', 'treemanifest',
572 572 default=False,
573 573 )
574 574 coreconfigitem('experimental', 'update.atomic-file',
575 575 default=False,
576 576 )
577 577 coreconfigitem('experimental', 'sshpeer.advertise-v2',
578 578 default=False,
579 579 )
580 580 coreconfigitem('extensions', '.*',
581 581 default=None,
582 582 generic=True,
583 583 )
584 584 coreconfigitem('extdata', '.*',
585 585 default=None,
586 586 generic=True,
587 587 )
588 588 coreconfigitem('format', 'aggressivemergedeltas',
589 589 default=False,
590 590 )
591 591 coreconfigitem('format', 'chunkcachesize',
592 592 default=None,
593 593 )
594 594 coreconfigitem('format', 'dotencode',
595 595 default=True,
596 596 )
597 597 coreconfigitem('format', 'generaldelta',
598 598 default=False,
599 599 )
600 600 coreconfigitem('format', 'manifestcachesize',
601 601 default=None,
602 602 )
603 603 coreconfigitem('format', 'maxchainlen',
604 604 default=None,
605 605 )
606 606 coreconfigitem('format', 'obsstore-version',
607 607 default=None,
608 608 )
609 609 coreconfigitem('format', 'usefncache',
610 610 default=True,
611 611 )
612 612 coreconfigitem('format', 'usegeneraldelta',
613 613 default=True,
614 614 )
615 615 coreconfigitem('format', 'usestore',
616 616 default=True,
617 617 )
618 618 coreconfigitem('fsmonitor', 'warn_when_unused',
619 619 default=True,
620 620 )
621 621 coreconfigitem('fsmonitor', 'warn_update_file_count',
622 622 default=50000,
623 623 )
624 624 coreconfigitem('hooks', '.*',
625 625 default=dynamicdefault,
626 626 generic=True,
627 627 )
628 628 coreconfigitem('hgweb-paths', '.*',
629 629 default=list,
630 630 generic=True,
631 631 )
632 632 coreconfigitem('hostfingerprints', '.*',
633 633 default=list,
634 634 generic=True,
635 635 )
636 636 coreconfigitem('hostsecurity', 'ciphers',
637 637 default=None,
638 638 )
639 639 coreconfigitem('hostsecurity', 'disabletls10warning',
640 640 default=False,
641 641 )
642 642 coreconfigitem('hostsecurity', 'minimumprotocol',
643 643 default=dynamicdefault,
644 644 )
645 645 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
646 646 default=dynamicdefault,
647 647 generic=True,
648 648 )
649 649 coreconfigitem('hostsecurity', '.*:ciphers$',
650 650 default=dynamicdefault,
651 651 generic=True,
652 652 )
653 653 coreconfigitem('hostsecurity', '.*:fingerprints$',
654 654 default=list,
655 655 generic=True,
656 656 )
657 657 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
658 658 default=None,
659 659 generic=True,
660 660 )
661 661
662 662 coreconfigitem('http_proxy', 'always',
663 663 default=False,
664 664 )
665 665 coreconfigitem('http_proxy', 'host',
666 666 default=None,
667 667 )
668 668 coreconfigitem('http_proxy', 'no',
669 669 default=list,
670 670 )
671 671 coreconfigitem('http_proxy', 'passwd',
672 672 default=None,
673 673 )
674 674 coreconfigitem('http_proxy', 'user',
675 675 default=None,
676 676 )
677 677 coreconfigitem('logtoprocess', 'commandexception',
678 678 default=None,
679 679 )
680 680 coreconfigitem('logtoprocess', 'commandfinish',
681 681 default=None,
682 682 )
683 683 coreconfigitem('logtoprocess', 'command',
684 684 default=None,
685 685 )
686 686 coreconfigitem('logtoprocess', 'develwarn',
687 687 default=None,
688 688 )
689 689 coreconfigitem('logtoprocess', 'uiblocked',
690 690 default=None,
691 691 )
692 692 coreconfigitem('merge', 'checkunknown',
693 693 default='abort',
694 694 )
695 695 coreconfigitem('merge', 'checkignored',
696 696 default='abort',
697 697 )
698 698 coreconfigitem('experimental', 'merge.checkpathconflicts',
699 699 default=False,
700 700 )
701 701 coreconfigitem('merge', 'followcopies',
702 702 default=True,
703 703 )
704 704 coreconfigitem('merge', 'on-failure',
705 705 default='continue',
706 706 )
707 707 coreconfigitem('merge', 'preferancestor',
708 708 default=lambda: ['*'],
709 709 )
710 710 coreconfigitem('merge-tools', '.*',
711 711 default=None,
712 712 generic=True,
713 713 )
714 714 coreconfigitem('merge-tools', br'.*\.args$',
715 715 default="$local $base $other",
716 716 generic=True,
717 717 priority=-1,
718 718 )
719 719 coreconfigitem('merge-tools', br'.*\.binary$',
720 720 default=False,
721 721 generic=True,
722 722 priority=-1,
723 723 )
724 724 coreconfigitem('merge-tools', br'.*\.check$',
725 725 default=list,
726 726 generic=True,
727 727 priority=-1,
728 728 )
729 729 coreconfigitem('merge-tools', br'.*\.checkchanged$',
730 730 default=False,
731 731 generic=True,
732 732 priority=-1,
733 733 )
734 734 coreconfigitem('merge-tools', br'.*\.executable$',
735 735 default=dynamicdefault,
736 736 generic=True,
737 737 priority=-1,
738 738 )
739 739 coreconfigitem('merge-tools', br'.*\.fixeol$',
740 740 default=False,
741 741 generic=True,
742 742 priority=-1,
743 743 )
744 744 coreconfigitem('merge-tools', br'.*\.gui$',
745 745 default=False,
746 746 generic=True,
747 747 priority=-1,
748 748 )
749 749 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
750 750 default='basic',
751 751 generic=True,
752 752 priority=-1,
753 753 )
754 754 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
755 755 default=dynamicdefault, # take from ui.mergemarkertemplate
756 756 generic=True,
757 757 priority=-1,
758 758 )
759 759 coreconfigitem('merge-tools', br'.*\.priority$',
760 760 default=0,
761 761 generic=True,
762 762 priority=-1,
763 763 )
764 764 coreconfigitem('merge-tools', br'.*\.premerge$',
765 765 default=dynamicdefault,
766 766 generic=True,
767 767 priority=-1,
768 768 )
769 769 coreconfigitem('merge-tools', br'.*\.symlink$',
770 770 default=False,
771 771 generic=True,
772 772 priority=-1,
773 773 )
774 774 coreconfigitem('pager', 'attend-.*',
775 775 default=dynamicdefault,
776 776 generic=True,
777 777 )
778 778 coreconfigitem('pager', 'ignore',
779 779 default=list,
780 780 )
781 781 coreconfigitem('pager', 'pager',
782 782 default=dynamicdefault,
783 783 )
784 784 coreconfigitem('patch', 'eol',
785 785 default='strict',
786 786 )
787 787 coreconfigitem('patch', 'fuzz',
788 788 default=2,
789 789 )
790 790 coreconfigitem('paths', 'default',
791 791 default=None,
792 792 )
793 793 coreconfigitem('paths', 'default-push',
794 794 default=None,
795 795 )
796 796 coreconfigitem('paths', '.*',
797 797 default=None,
798 798 generic=True,
799 799 )
800 800 coreconfigitem('phases', 'checksubrepos',
801 801 default='follow',
802 802 )
803 803 coreconfigitem('phases', 'new-commit',
804 804 default='draft',
805 805 )
806 806 coreconfigitem('phases', 'publish',
807 807 default=True,
808 808 )
809 809 coreconfigitem('profiling', 'enabled',
810 810 default=False,
811 811 )
812 812 coreconfigitem('profiling', 'format',
813 813 default='text',
814 814 )
815 815 coreconfigitem('profiling', 'freq',
816 816 default=1000,
817 817 )
818 818 coreconfigitem('profiling', 'limit',
819 819 default=30,
820 820 )
821 821 coreconfigitem('profiling', 'nested',
822 822 default=0,
823 823 )
824 824 coreconfigitem('profiling', 'output',
825 825 default=None,
826 826 )
827 827 coreconfigitem('profiling', 'showmax',
828 828 default=0.999,
829 829 )
830 830 coreconfigitem('profiling', 'showmin',
831 831 default=dynamicdefault,
832 832 )
833 833 coreconfigitem('profiling', 'sort',
834 834 default='inlinetime',
835 835 )
836 836 coreconfigitem('profiling', 'statformat',
837 837 default='hotpath',
838 838 )
839 839 coreconfigitem('profiling', 'type',
840 840 default='stat',
841 841 )
842 842 coreconfigitem('progress', 'assume-tty',
843 843 default=False,
844 844 )
845 845 coreconfigitem('progress', 'changedelay',
846 846 default=1,
847 847 )
848 848 coreconfigitem('progress', 'clear-complete',
849 849 default=True,
850 850 )
851 851 coreconfigitem('progress', 'debug',
852 852 default=False,
853 853 )
854 854 coreconfigitem('progress', 'delay',
855 855 default=3,
856 856 )
857 857 coreconfigitem('progress', 'disable',
858 858 default=False,
859 859 )
860 860 coreconfigitem('progress', 'estimateinterval',
861 861 default=60.0,
862 862 )
863 863 coreconfigitem('progress', 'format',
864 864 default=lambda: ['topic', 'bar', 'number', 'estimate'],
865 865 )
866 866 coreconfigitem('progress', 'refresh',
867 867 default=0.1,
868 868 )
869 869 coreconfigitem('progress', 'width',
870 870 default=dynamicdefault,
871 871 )
872 872 coreconfigitem('push', 'pushvars.server',
873 873 default=False,
874 874 )
875 875 coreconfigitem('server', 'bookmarks-pushkey-compat',
876 876 default=True,
877 877 )
878 878 coreconfigitem('server', 'bundle1',
879 879 default=True,
880 880 )
881 881 coreconfigitem('server', 'bundle1gd',
882 882 default=None,
883 883 )
884 884 coreconfigitem('server', 'bundle1.pull',
885 885 default=None,
886 886 )
887 887 coreconfigitem('server', 'bundle1gd.pull',
888 888 default=None,
889 889 )
890 890 coreconfigitem('server', 'bundle1.push',
891 891 default=None,
892 892 )
893 893 coreconfigitem('server', 'bundle1gd.push',
894 894 default=None,
895 895 )
896 896 coreconfigitem('server', 'compressionengines',
897 897 default=list,
898 898 )
899 899 coreconfigitem('server', 'concurrent-push-mode',
900 900 default='strict',
901 901 )
902 902 coreconfigitem('server', 'disablefullbundle',
903 903 default=False,
904 904 )
905 905 coreconfigitem('server', 'maxhttpheaderlen',
906 906 default=1024,
907 907 )
908 908 coreconfigitem('server', 'preferuncompressed',
909 909 default=False,
910 910 )
911 911 coreconfigitem('server', 'uncompressed',
912 912 default=True,
913 913 )
914 914 coreconfigitem('server', 'uncompressedallowsecret',
915 915 default=False,
916 916 )
917 917 coreconfigitem('server', 'validate',
918 918 default=False,
919 919 )
920 920 coreconfigitem('server', 'zliblevel',
921 921 default=-1,
922 922 )
923 923 coreconfigitem('share', 'pool',
924 924 default=None,
925 925 )
926 926 coreconfigitem('share', 'poolnaming',
927 927 default='identity',
928 928 )
929 929 coreconfigitem('smtp', 'host',
930 930 default=None,
931 931 )
932 932 coreconfigitem('smtp', 'local_hostname',
933 933 default=None,
934 934 )
935 935 coreconfigitem('smtp', 'password',
936 936 default=None,
937 937 )
938 938 coreconfigitem('smtp', 'port',
939 939 default=dynamicdefault,
940 940 )
941 941 coreconfigitem('smtp', 'tls',
942 942 default='none',
943 943 )
944 944 coreconfigitem('smtp', 'username',
945 945 default=None,
946 946 )
947 947 coreconfigitem('sparse', 'missingwarning',
948 948 default=True,
949 949 )
950 950 coreconfigitem('subrepos', 'allowed',
951 951 default=dynamicdefault, # to make backporting simpler
952 952 )
953 953 coreconfigitem('subrepos', 'hg:allowed',
954 954 default=dynamicdefault,
955 955 )
956 956 coreconfigitem('subrepos', 'git:allowed',
957 957 default=dynamicdefault,
958 958 )
959 959 coreconfigitem('subrepos', 'svn:allowed',
960 960 default=dynamicdefault,
961 961 )
962 962 coreconfigitem('templates', '.*',
963 963 default=None,
964 964 generic=True,
965 965 )
966 966 coreconfigitem('trusted', 'groups',
967 967 default=list,
968 968 )
969 969 coreconfigitem('trusted', 'users',
970 970 default=list,
971 971 )
972 972 coreconfigitem('ui', '_usedassubrepo',
973 973 default=False,
974 974 )
975 975 coreconfigitem('ui', 'allowemptycommit',
976 976 default=False,
977 977 )
978 978 coreconfigitem('ui', 'archivemeta',
979 979 default=True,
980 980 )
981 981 coreconfigitem('ui', 'askusername',
982 982 default=False,
983 983 )
984 984 coreconfigitem('ui', 'clonebundlefallback',
985 985 default=False,
986 986 )
987 987 coreconfigitem('ui', 'clonebundleprefers',
988 988 default=list,
989 989 )
990 990 coreconfigitem('ui', 'clonebundles',
991 991 default=True,
992 992 )
993 993 coreconfigitem('ui', 'color',
994 994 default='auto',
995 995 )
996 996 coreconfigitem('ui', 'commitsubrepos',
997 997 default=False,
998 998 )
999 999 coreconfigitem('ui', 'debug',
1000 1000 default=False,
1001 1001 )
1002 1002 coreconfigitem('ui', 'debugger',
1003 1003 default=None,
1004 1004 )
1005 1005 coreconfigitem('ui', 'editor',
1006 1006 default=dynamicdefault,
1007 1007 )
1008 1008 coreconfigitem('ui', 'fallbackencoding',
1009 1009 default=None,
1010 1010 )
1011 1011 coreconfigitem('ui', 'forcecwd',
1012 1012 default=None,
1013 1013 )
1014 1014 coreconfigitem('ui', 'forcemerge',
1015 1015 default=None,
1016 1016 )
1017 1017 coreconfigitem('ui', 'formatdebug',
1018 1018 default=False,
1019 1019 )
1020 1020 coreconfigitem('ui', 'formatjson',
1021 1021 default=False,
1022 1022 )
1023 1023 coreconfigitem('ui', 'formatted',
1024 1024 default=None,
1025 1025 )
1026 1026 coreconfigitem('ui', 'graphnodetemplate',
1027 1027 default=None,
1028 1028 )
1029 coreconfigitem('ui', 'http2debuglevel',
1030 default=None,
1031 )
1032 1029 coreconfigitem('ui', 'interactive',
1033 1030 default=None,
1034 1031 )
1035 1032 coreconfigitem('ui', 'interface',
1036 1033 default=None,
1037 1034 )
1038 1035 coreconfigitem('ui', 'interface.chunkselector',
1039 1036 default=None,
1040 1037 )
1041 1038 coreconfigitem('ui', 'logblockedtimes',
1042 1039 default=False,
1043 1040 )
1044 1041 coreconfigitem('ui', 'logtemplate',
1045 1042 default=None,
1046 1043 )
1047 1044 coreconfigitem('ui', 'merge',
1048 1045 default=None,
1049 1046 )
1050 1047 coreconfigitem('ui', 'mergemarkers',
1051 1048 default='basic',
1052 1049 )
1053 1050 coreconfigitem('ui', 'mergemarkertemplate',
1054 1051 default=('{node|short} '
1055 1052 '{ifeq(tags, "tip", "", '
1056 1053 'ifeq(tags, "", "", "{tags} "))}'
1057 1054 '{if(bookmarks, "{bookmarks} ")}'
1058 1055 '{ifeq(branch, "default", "", "{branch} ")}'
1059 1056 '- {author|user}: {desc|firstline}')
1060 1057 )
1061 1058 coreconfigitem('ui', 'nontty',
1062 1059 default=False,
1063 1060 )
1064 1061 coreconfigitem('ui', 'origbackuppath',
1065 1062 default=None,
1066 1063 )
1067 1064 coreconfigitem('ui', 'paginate',
1068 1065 default=True,
1069 1066 )
1070 1067 coreconfigitem('ui', 'patch',
1071 1068 default=None,
1072 1069 )
1073 1070 coreconfigitem('ui', 'portablefilenames',
1074 1071 default='warn',
1075 1072 )
1076 1073 coreconfigitem('ui', 'promptecho',
1077 1074 default=False,
1078 1075 )
1079 1076 coreconfigitem('ui', 'quiet',
1080 1077 default=False,
1081 1078 )
1082 1079 coreconfigitem('ui', 'quietbookmarkmove',
1083 1080 default=False,
1084 1081 )
1085 1082 coreconfigitem('ui', 'remotecmd',
1086 1083 default='hg',
1087 1084 )
1088 1085 coreconfigitem('ui', 'report_untrusted',
1089 1086 default=True,
1090 1087 )
1091 1088 coreconfigitem('ui', 'rollback',
1092 1089 default=True,
1093 1090 )
1094 1091 coreconfigitem('ui', 'slash',
1095 1092 default=False,
1096 1093 )
1097 1094 coreconfigitem('ui', 'ssh',
1098 1095 default='ssh',
1099 1096 )
1100 1097 coreconfigitem('ui', 'ssherrorhint',
1101 1098 default=None,
1102 1099 )
1103 1100 coreconfigitem('ui', 'statuscopies',
1104 1101 default=False,
1105 1102 )
1106 1103 coreconfigitem('ui', 'strict',
1107 1104 default=False,
1108 1105 )
1109 1106 coreconfigitem('ui', 'style',
1110 1107 default='',
1111 1108 )
1112 1109 coreconfigitem('ui', 'supportcontact',
1113 1110 default=None,
1114 1111 )
1115 1112 coreconfigitem('ui', 'textwidth',
1116 1113 default=78,
1117 1114 )
1118 1115 coreconfigitem('ui', 'timeout',
1119 1116 default='600',
1120 1117 )
1121 1118 coreconfigitem('ui', 'timeout.warn',
1122 1119 default=0,
1123 1120 )
1124 1121 coreconfigitem('ui', 'traceback',
1125 1122 default=False,
1126 1123 )
1127 1124 coreconfigitem('ui', 'tweakdefaults',
1128 1125 default=False,
1129 1126 )
1130 coreconfigitem('ui', 'usehttp2',
1131 default=False,
1132 )
1133 1127 coreconfigitem('ui', 'username',
1134 1128 alias=[('ui', 'user')]
1135 1129 )
1136 1130 coreconfigitem('ui', 'verbose',
1137 1131 default=False,
1138 1132 )
1139 1133 coreconfigitem('verify', 'skipflags',
1140 1134 default=None,
1141 1135 )
1142 1136 coreconfigitem('web', 'allowbz2',
1143 1137 default=False,
1144 1138 )
1145 1139 coreconfigitem('web', 'allowgz',
1146 1140 default=False,
1147 1141 )
1148 1142 coreconfigitem('web', 'allow-pull',
1149 1143 alias=[('web', 'allowpull')],
1150 1144 default=True,
1151 1145 )
1152 1146 coreconfigitem('web', 'allow-push',
1153 1147 alias=[('web', 'allow_push')],
1154 1148 default=list,
1155 1149 )
1156 1150 coreconfigitem('web', 'allowzip',
1157 1151 default=False,
1158 1152 )
1159 1153 coreconfigitem('web', 'archivesubrepos',
1160 1154 default=False,
1161 1155 )
1162 1156 coreconfigitem('web', 'cache',
1163 1157 default=True,
1164 1158 )
1165 1159 coreconfigitem('web', 'contact',
1166 1160 default=None,
1167 1161 )
1168 1162 coreconfigitem('web', 'deny_push',
1169 1163 default=list,
1170 1164 )
1171 1165 coreconfigitem('web', 'guessmime',
1172 1166 default=False,
1173 1167 )
1174 1168 coreconfigitem('web', 'hidden',
1175 1169 default=False,
1176 1170 )
1177 1171 coreconfigitem('web', 'labels',
1178 1172 default=list,
1179 1173 )
1180 1174 coreconfigitem('web', 'logoimg',
1181 1175 default='hglogo.png',
1182 1176 )
1183 1177 coreconfigitem('web', 'logourl',
1184 1178 default='https://mercurial-scm.org/',
1185 1179 )
1186 1180 coreconfigitem('web', 'accesslog',
1187 1181 default='-',
1188 1182 )
1189 1183 coreconfigitem('web', 'address',
1190 1184 default='',
1191 1185 )
1192 1186 coreconfigitem('web', 'allow_archive',
1193 1187 default=list,
1194 1188 )
1195 1189 coreconfigitem('web', 'allow_read',
1196 1190 default=list,
1197 1191 )
1198 1192 coreconfigitem('web', 'baseurl',
1199 1193 default=None,
1200 1194 )
1201 1195 coreconfigitem('web', 'cacerts',
1202 1196 default=None,
1203 1197 )
1204 1198 coreconfigitem('web', 'certificate',
1205 1199 default=None,
1206 1200 )
1207 1201 coreconfigitem('web', 'collapse',
1208 1202 default=False,
1209 1203 )
1210 1204 coreconfigitem('web', 'csp',
1211 1205 default=None,
1212 1206 )
1213 1207 coreconfigitem('web', 'deny_read',
1214 1208 default=list,
1215 1209 )
1216 1210 coreconfigitem('web', 'descend',
1217 1211 default=True,
1218 1212 )
1219 1213 coreconfigitem('web', 'description',
1220 1214 default="",
1221 1215 )
1222 1216 coreconfigitem('web', 'encoding',
1223 1217 default=lambda: encoding.encoding,
1224 1218 )
1225 1219 coreconfigitem('web', 'errorlog',
1226 1220 default='-',
1227 1221 )
1228 1222 coreconfigitem('web', 'ipv6',
1229 1223 default=False,
1230 1224 )
1231 1225 coreconfigitem('web', 'maxchanges',
1232 1226 default=10,
1233 1227 )
1234 1228 coreconfigitem('web', 'maxfiles',
1235 1229 default=10,
1236 1230 )
1237 1231 coreconfigitem('web', 'maxshortchanges',
1238 1232 default=60,
1239 1233 )
1240 1234 coreconfigitem('web', 'motd',
1241 1235 default='',
1242 1236 )
1243 1237 coreconfigitem('web', 'name',
1244 1238 default=dynamicdefault,
1245 1239 )
1246 1240 coreconfigitem('web', 'port',
1247 1241 default=8000,
1248 1242 )
1249 1243 coreconfigitem('web', 'prefix',
1250 1244 default='',
1251 1245 )
1252 1246 coreconfigitem('web', 'push_ssl',
1253 1247 default=True,
1254 1248 )
1255 1249 coreconfigitem('web', 'refreshinterval',
1256 1250 default=20,
1257 1251 )
1258 1252 coreconfigitem('web', 'staticurl',
1259 1253 default=None,
1260 1254 )
1261 1255 coreconfigitem('web', 'stripes',
1262 1256 default=1,
1263 1257 )
1264 1258 coreconfigitem('web', 'style',
1265 1259 default='paper',
1266 1260 )
1267 1261 coreconfigitem('web', 'templates',
1268 1262 default=None,
1269 1263 )
1270 1264 coreconfigitem('web', 'view',
1271 1265 default='served',
1272 1266 )
1273 1267 coreconfigitem('worker', 'backgroundclose',
1274 1268 default=dynamicdefault,
1275 1269 )
1276 1270 # Windows defaults to a limit of 512 open files. A buffer of 128
1277 1271 # should give us enough headway.
1278 1272 coreconfigitem('worker', 'backgroundclosemaxqueue',
1279 1273 default=384,
1280 1274 )
1281 1275 coreconfigitem('worker', 'backgroundcloseminfilecount',
1282 1276 default=2048,
1283 1277 )
1284 1278 coreconfigitem('worker', 'backgroundclosethreadcount',
1285 1279 default=4,
1286 1280 )
1287 1281 coreconfigitem('worker', 'enabled',
1288 1282 default=True,
1289 1283 )
1290 1284 coreconfigitem('worker', 'numcpus',
1291 1285 default=None,
1292 1286 )
1293 1287
1294 1288 # Rebase related configuration moved to core because other extension are doing
1295 1289 # strange things. For example, shelve import the extensions to reuse some bit
1296 1290 # without formally loading it.
1297 1291 coreconfigitem('commands', 'rebase.requiredest',
1298 1292 default=False,
1299 1293 )
1300 1294 coreconfigitem('experimental', 'rebaseskipobsolete',
1301 1295 default=True,
1302 1296 )
1303 1297 coreconfigitem('rebase', 'singletransaction',
1304 1298 default=False,
1305 1299 )
1306 1300 coreconfigitem('rebase', 'experimental.inmemory',
1307 1301 default=False,
1308 1302 )
@@ -1,299 +1,107
1 1 # httpconnection.py - urllib2 handler for new http support
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 # Copyright 2011 Google, Inc.
7 7 #
8 8 # This software may be used and distributed according to the terms of the
9 9 # GNU General Public License version 2 or any later version.
10 10
11 11 from __future__ import absolute_import
12 12
13 import logging
14 13 import os
15 import socket
16 14
17 15 from .i18n import _
18 16 from . import (
19 httpclient,
20 sslutil,
21 urllibcompat,
22 17 util,
23 18 )
24 19
25 20 urlerr = util.urlerr
26 21 urlreq = util.urlreq
27 22
28 23 # moved here from url.py to avoid a cycle
29 24 class httpsendfile(object):
30 25 """This is a wrapper around the objects returned by python's "open".
31 26
32 27 Its purpose is to send file-like objects via HTTP.
33 28 It do however not define a __len__ attribute because the length
34 29 might be more than Py_ssize_t can handle.
35 30 """
36 31
37 32 def __init__(self, ui, *args, **kwargs):
38 33 self.ui = ui
39 34 self._data = open(*args, **kwargs)
40 35 self.seek = self._data.seek
41 36 self.close = self._data.close
42 37 self.write = self._data.write
43 38 self.length = os.fstat(self._data.fileno()).st_size
44 39 self._pos = 0
45 40 self._total = self.length // 1024 * 2
46 41
47 42 def read(self, *args, **kwargs):
48 43 ret = self._data.read(*args, **kwargs)
49 44 if not ret:
50 45 self.ui.progress(_('sending'), None)
51 46 return ret
52 47 self._pos += len(ret)
53 48 # We pass double the max for total because we currently have
54 49 # to send the bundle twice in the case of a server that
55 50 # requires authentication. Since we can't know until we try
56 51 # once whether authentication will be required, just lie to
57 52 # the user and maybe the push succeeds suddenly at 50%.
58 53 self.ui.progress(_('sending'), self._pos // 1024,
59 54 unit=_('kb'), total=self._total)
60 55 return ret
61 56
62 57 def __enter__(self):
63 58 return self
64 59
65 60 def __exit__(self, exc_type, exc_val, exc_tb):
66 61 self.close()
67 62
68 63 # moved here from url.py to avoid a cycle
69 64 def readauthforuri(ui, uri, user):
70 65 # Read configuration
71 66 groups = {}
72 67 for key, val in ui.configitems('auth'):
73 68 if key in ('cookiefile',):
74 69 continue
75 70
76 71 if '.' not in key:
77 72 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
78 73 continue
79 74 group, setting = key.rsplit('.', 1)
80 75 gdict = groups.setdefault(group, {})
81 76 if setting in ('username', 'cert', 'key'):
82 77 val = util.expandpath(val)
83 78 gdict[setting] = val
84 79
85 80 # Find the best match
86 81 scheme, hostpath = uri.split('://', 1)
87 82 bestuser = None
88 83 bestlen = 0
89 84 bestauth = None
90 85 for group, auth in groups.iteritems():
91 86 if user and user != auth.get('username', user):
92 87 # If a username was set in the URI, the entry username
93 88 # must either match it or be unset
94 89 continue
95 90 prefix = auth.get('prefix')
96 91 if not prefix:
97 92 continue
98 93 p = prefix.split('://', 1)
99 94 if len(p) > 1:
100 95 schemes, prefix = [p[0]], p[1]
101 96 else:
102 97 schemes = (auth.get('schemes') or 'https').split()
103 98 if (prefix == '*' or hostpath.startswith(prefix)) and \
104 99 (len(prefix) > bestlen or (len(prefix) == bestlen and \
105 100 not bestuser and 'username' in auth)) \
106 101 and scheme in schemes:
107 102 bestlen = len(prefix)
108 103 bestauth = group, auth
109 104 bestuser = auth.get('username')
110 105 if user and not bestuser:
111 106 auth['username'] = user
112 107 return bestauth
113
114 # Mercurial (at least until we can remove the old codepath) requires
115 # that the http response object be sufficiently file-like, so we
116 # provide a close() method here.
117 class HTTPResponse(httpclient.HTTPResponse):
118 def close(self):
119 pass
120
121 class HTTPConnection(httpclient.HTTPConnection):
122 response_class = HTTPResponse
123 def request(self, method, uri, body=None, headers=None):
124 if headers is None:
125 headers = {}
126 if isinstance(body, httpsendfile):
127 body.seek(0)
128 httpclient.HTTPConnection.request(self, method, uri, body=body,
129 headers=headers)
130
131
132 _configuredlogging = False
133 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
134 # Subclass BOTH of these because otherwise urllib2 "helpfully"
135 # reinserts them since it notices we don't include any subclasses of
136 # them.
137 class http2handler(urlreq.httphandler, urlreq.httpshandler):
138 def __init__(self, ui, pwmgr):
139 global _configuredlogging
140 urlreq.abstracthttphandler.__init__(self)
141 self.ui = ui
142 self.pwmgr = pwmgr
143 self._connections = {}
144 # developer config: ui.http2debuglevel
145 loglevel = ui.config('ui', 'http2debuglevel')
146 if loglevel and not _configuredlogging:
147 _configuredlogging = True
148 logger = logging.getLogger('mercurial.httpclient')
149 logger.setLevel(getattr(logging, loglevel.upper()))
150 handler = logging.StreamHandler()
151 handler.setFormatter(logging.Formatter(LOGFMT))
152 logger.addHandler(handler)
153
154 def close_all(self):
155 """Close and remove all connection objects being kept for reuse."""
156 for openconns in self._connections.values():
157 for conn in openconns:
158 conn.close()
159 self._connections = {}
160
161 # shamelessly borrowed from urllib2.AbstractHTTPHandler
162 def do_open(self, http_class, req, use_ssl):
163 """Return an addinfourl object for the request, using http_class.
164
165 http_class must implement the HTTPConnection API from httplib.
166 The addinfourl return value is a file-like object. It also
167 has methods and attributes including:
168 - info(): return a mimetools.Message object for the headers
169 - geturl(): return the original request URL
170 - code: HTTP status code
171 """
172 # If using a proxy, the host returned by get_host() is
173 # actually the proxy. On Python 2.6.1, the real destination
174 # hostname is encoded in the URI in the urllib2 request
175 # object. On Python 2.6.5, it's stored in the _tunnel_host
176 # attribute which has no accessor.
177 tunhost = getattr(req, '_tunnel_host', None)
178 host = urllibcompat.gethost(req)
179 if tunhost:
180 proxyhost = host
181 host = tunhost
182 elif req.has_proxy():
183 proxyhost = urllibcompat.gethost(req)
184 host = urllibcompat.getselector(
185 req).split('://', 1)[1].split('/', 1)[0]
186 else:
187 proxyhost = None
188
189 if proxyhost:
190 if ':' in proxyhost:
191 # Note: this means we'll explode if we try and use an
192 # IPv6 http proxy. This isn't a regression, so we
193 # won't worry about it for now.
194 proxyhost, proxyport = proxyhost.rsplit(':', 1)
195 else:
196 proxyport = 3128 # squid default
197 proxy = (proxyhost, proxyport)
198 else:
199 proxy = None
200
201 if not host:
202 raise urlerr.urlerror('no host given')
203
204 connkey = use_ssl, host, proxy
205 allconns = self._connections.get(connkey, [])
206 conns = [c for c in allconns if not c.busy()]
207 if conns:
208 h = conns[0]
209 else:
210 if allconns:
211 self.ui.debug('all connections for %s busy, making a new '
212 'one\n' % host)
213 timeout = None
214 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
215 timeout = req.timeout
216 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
217 self._connections.setdefault(connkey, []).append(h)
218
219 headers = dict(req.headers)
220 headers.update(req.unredirected_hdrs)
221 headers = dict(
222 (name.title(), val) for name, val in headers.items())
223 try:
224 path = urllibcompat.getselector(req)
225 if '://' in path:
226 path = path.split('://', 1)[1].split('/', 1)[1]
227 if path[0] != '/':
228 path = '/' + path
229 h.request(req.get_method(), path, req.data, headers)
230 r = h.getresponse()
231 except socket.error as err: # XXX what error?
232 raise urlerr.urlerror(err)
233
234 # Pick apart the HTTPResponse object to get the addinfourl
235 # object initialized properly.
236 r.recv = r.read
237
238 resp = urlreq.addinfourl(r, r.headers, urllibcompat.getfullurl(req))
239 resp.code = r.status
240 resp.msg = r.reason
241 return resp
242
243 # httplib always uses the given host/port as the socket connect
244 # target, and then allows full URIs in the request path, which it
245 # then observes and treats as a signal to do proxying instead.
246 def http_open(self, req):
247 if urllibcompat.getfullurl(req).startswith('https'):
248 return self.https_open(req)
249 def makehttpcon(*args, **kwargs):
250 k2 = dict(kwargs)
251 k2[r'use_ssl'] = False
252 return HTTPConnection(*args, **k2)
253 return self.do_open(makehttpcon, req, False)
254
255 def https_open(self, req):
256 # urllibcompat.getfullurl(req) does not contain credentials and we may
257 # need them to match the certificates.
258 url = urllibcompat.getfullurl(req)
259 user, password = self.pwmgr.find_stored_password(url)
260 res = readauthforuri(self.ui, url, user)
261 if res:
262 group, auth = res
263 self.auth = auth
264 self.ui.debug("using auth.%s.* for authentication\n" % group)
265 else:
266 self.auth = None
267 return self.do_open(self._makesslconnection, req, True)
268
269 def _makesslconnection(self, host, port=443, *args, **kwargs):
270 keyfile = None
271 certfile = None
272
273 if args: # key_file
274 keyfile = args.pop(0)
275 if args: # cert_file
276 certfile = args.pop(0)
277
278 # if the user has specified different key/cert files in
279 # hgrc, we prefer these
280 if self.auth and 'key' in self.auth and 'cert' in self.auth:
281 keyfile = self.auth['key']
282 certfile = self.auth['cert']
283
284 # let host port take precedence
285 if ':' in host and '[' not in host or ']:' in host:
286 host, port = host.rsplit(':', 1)
287 port = int(port)
288 if '[' in host:
289 host = host[1:-1]
290
291 kwargs[r'keyfile'] = keyfile
292 kwargs[r'certfile'] = certfile
293
294 con = HTTPConnection(host, port, use_ssl=True,
295 ssl_wrap_socket=sslutil.wrapsocket,
296 ssl_validator=sslutil.validatesocket,
297 ui=self.ui,
298 **kwargs)
299 return con
@@ -1,501 +1,498
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 tempfile
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 statichttprepo,
25 25 url,
26 26 util,
27 27 wireproto,
28 28 )
29 29
30 30 httplib = util.httplib
31 31 urlerr = util.urlerr
32 32 urlreq = util.urlreq
33 33
34 34 def encodevalueinheaders(value, header, limit):
35 35 """Encode a string value into multiple HTTP headers.
36 36
37 37 ``value`` will be encoded into 1 or more HTTP headers with the names
38 38 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
39 39 name + value will be at most ``limit`` bytes long.
40 40
41 41 Returns an iterable of 2-tuples consisting of header names and
42 42 values as native strings.
43 43 """
44 44 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
45 45 # not bytes. This function always takes bytes in as arguments.
46 46 fmt = pycompat.strurl(header) + r'-%s'
47 47 # Note: it is *NOT* a bug that the last bit here is a bytestring
48 48 # and not a unicode: we're just getting the encoded length anyway,
49 49 # and using an r-string to make it portable between Python 2 and 3
50 50 # doesn't work because then the \r is a literal backslash-r
51 51 # instead of a carriage return.
52 52 valuelen = limit - len(fmt % r'000') - len(': \r\n')
53 53 result = []
54 54
55 55 n = 0
56 56 for i in xrange(0, len(value), valuelen):
57 57 n += 1
58 58 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
59 59
60 60 return result
61 61
62 62 def _wraphttpresponse(resp):
63 63 """Wrap an HTTPResponse with common error handlers.
64 64
65 65 This ensures that any I/O from any consumer raises the appropriate
66 66 error and messaging.
67 67 """
68 68 origread = resp.read
69 69
70 70 class readerproxy(resp.__class__):
71 71 def read(self, size=None):
72 72 try:
73 73 return origread(size)
74 74 except httplib.IncompleteRead as e:
75 75 # e.expected is an integer if length known or None otherwise.
76 76 if e.expected:
77 77 msg = _('HTTP request error (incomplete response; '
78 78 'expected %d bytes got %d)') % (e.expected,
79 79 len(e.partial))
80 80 else:
81 81 msg = _('HTTP request error (incomplete response)')
82 82
83 83 raise error.PeerTransportError(
84 84 msg,
85 85 hint=_('this may be an intermittent network failure; '
86 86 'if the error persists, consider contacting the '
87 87 'network or server operator'))
88 88 except httplib.HTTPException as e:
89 89 raise error.PeerTransportError(
90 90 _('HTTP request error (%s)') % e,
91 91 hint=_('this may be an intermittent network failure; '
92 92 'if the error persists, consider contacting the '
93 93 'network or server operator'))
94 94
95 95 resp.__class__ = readerproxy
96 96
97 97 class _multifile(object):
98 98 def __init__(self, *fileobjs):
99 99 for f in fileobjs:
100 100 if not util.safehasattr(f, 'length'):
101 101 raise ValueError(
102 102 '_multifile only supports file objects that '
103 103 'have a length but this one does not:', type(f), f)
104 104 self._fileobjs = fileobjs
105 105 self._index = 0
106 106
107 107 @property
108 108 def length(self):
109 109 return sum(f.length for f in self._fileobjs)
110 110
111 111 def read(self, amt=None):
112 112 if amt <= 0:
113 113 return ''.join(f.read() for f in self._fileobjs)
114 114 parts = []
115 115 while amt and self._index < len(self._fileobjs):
116 116 parts.append(self._fileobjs[self._index].read(amt))
117 117 got = len(parts[-1])
118 118 if got < amt:
119 119 self._index += 1
120 120 amt -= got
121 121 return ''.join(parts)
122 122
123 123 def seek(self, offset, whence=os.SEEK_SET):
124 124 if whence != os.SEEK_SET:
125 125 raise NotImplementedError(
126 126 '_multifile does not support anything other'
127 127 ' than os.SEEK_SET for whence on seek()')
128 128 if offset != 0:
129 129 raise NotImplementedError(
130 130 '_multifile only supports seeking to start, but that '
131 131 'could be fixed if you need it')
132 132 for f in self._fileobjs:
133 133 f.seek(0)
134 134 self._index = 0
135 135
136 136 class httppeer(wireproto.wirepeer):
137 137 def __init__(self, ui, path):
138 138 self._path = path
139 139 self._caps = None
140 140 self._urlopener = None
141 141 self._requestbuilder = None
142 142 u = util.url(path)
143 143 if u.query or u.fragment:
144 144 raise error.Abort(_('unsupported URL component: "%s"') %
145 145 (u.query or u.fragment))
146 146
147 147 # urllib cannot handle URLs with embedded user or passwd
148 148 self._url, authinfo = u.authinfo()
149 149
150 150 self._ui = ui
151 151 ui.debug('using %s\n' % self._url)
152 152
153 153 self._urlopener = url.opener(ui, authinfo)
154 154 self._requestbuilder = urlreq.request
155 155
156 156 def __del__(self):
157 157 urlopener = getattr(self, '_urlopener', None)
158 158 if urlopener:
159 159 for h in urlopener.handlers:
160 160 h.close()
161 161 getattr(h, "close_all", lambda: None)()
162 162
163 163 def _openurl(self, req):
164 164 if (self._ui.debugflag
165 165 and self._ui.configbool('devel', 'debug.peer-request')):
166 166 dbg = self._ui.debug
167 167 line = 'devel-peer-request: %s\n'
168 168 dbg(line % '%s %s' % (req.get_method(), req.get_full_url()))
169 169 hgargssize = None
170 170
171 171 for header, value in sorted(req.header_items()):
172 172 if header.startswith('X-hgarg-'):
173 173 if hgargssize is None:
174 174 hgargssize = 0
175 175 hgargssize += len(value)
176 176 else:
177 177 dbg(line % ' %s %s' % (header, value))
178 178
179 179 if hgargssize is not None:
180 180 dbg(line % ' %d bytes of commands arguments in headers'
181 181 % hgargssize)
182 182
183 183 if req.has_data():
184 184 data = req.get_data()
185 185 length = getattr(data, 'length', None)
186 186 if length is None:
187 187 length = len(data)
188 188 dbg(line % ' %d bytes of data' % length)
189 189
190 190 start = util.timer()
191 191
192 192 ret = self._urlopener.open(req)
193 193 if self._ui.configbool('devel', 'debug.peer-request'):
194 194 dbg(line % ' finished in %.4f seconds (%s)'
195 195 % (util.timer() - start, ret.code))
196 196 return ret
197 197
198 198 # Begin of _basepeer interface.
199 199
200 200 @util.propertycache
201 201 def ui(self):
202 202 return self._ui
203 203
204 204 def url(self):
205 205 return self._path
206 206
207 207 def local(self):
208 208 return None
209 209
210 210 def peer(self):
211 211 return self
212 212
213 213 def canpush(self):
214 214 return True
215 215
216 216 def close(self):
217 217 pass
218 218
219 219 # End of _basepeer interface.
220 220
221 221 # Begin of _basewirepeer interface.
222 222
223 223 def capabilities(self):
224 224 # self._fetchcaps() should have been called as part of peer
225 225 # handshake. So self._caps should always be set.
226 226 assert self._caps is not None
227 227 return self._caps
228 228
229 229 # End of _basewirepeer interface.
230 230
231 231 # look up capabilities only when needed
232 232
233 233 def _fetchcaps(self):
234 234 self._caps = set(self._call('capabilities').split())
235 235
236 236 def _callstream(self, cmd, _compressible=False, **args):
237 237 args = pycompat.byteskwargs(args)
238 238 if cmd == 'pushkey':
239 239 args['data'] = ''
240 240 data = args.pop('data', None)
241 241 headers = args.pop('headers', {})
242 242
243 243 self.ui.debug("sending %s command\n" % cmd)
244 244 q = [('cmd', cmd)]
245 245 headersize = 0
246 246 varyheaders = []
247 247 # Important: don't use self.capable() here or else you end up
248 248 # with infinite recursion when trying to look up capabilities
249 249 # for the first time.
250 250 postargsok = self._caps is not None and 'httppostargs' in self._caps
251 251
252 252 # Send arguments via POST.
253 253 if postargsok and args:
254 254 strargs = urlreq.urlencode(sorted(args.items()))
255 255 if not data:
256 256 data = strargs
257 257 else:
258 258 if isinstance(data, bytes):
259 259 i = io.BytesIO(data)
260 260 i.length = len(data)
261 261 data = i
262 262 argsio = io.BytesIO(strargs)
263 263 argsio.length = len(strargs)
264 264 data = _multifile(argsio, data)
265 265 headers[r'X-HgArgs-Post'] = len(strargs)
266 266 elif args:
267 267 # Calling self.capable() can infinite loop if we are calling
268 268 # "capabilities". But that command should never accept wire
269 269 # protocol arguments. So this should never happen.
270 270 assert cmd != 'capabilities'
271 271 httpheader = self.capable('httpheader')
272 272 if httpheader:
273 273 headersize = int(httpheader.split(',', 1)[0])
274 274
275 275 # Send arguments via HTTP headers.
276 276 if headersize > 0:
277 277 # The headers can typically carry more data than the URL.
278 278 encargs = urlreq.urlencode(sorted(args.items()))
279 279 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
280 280 headersize):
281 281 headers[header] = value
282 282 varyheaders.append(header)
283 283 # Send arguments via query string (Mercurial <1.9).
284 284 else:
285 285 q += sorted(args.items())
286 286
287 287 qs = '?%s' % urlreq.urlencode(q)
288 288 cu = "%s%s" % (self._url, qs)
289 289 size = 0
290 290 if util.safehasattr(data, 'length'):
291 291 size = data.length
292 292 elif data is not None:
293 293 size = len(data)
294 if size and self.ui.configbool('ui', 'usehttp2'):
295 headers[r'Expect'] = r'100-Continue'
296 headers[r'X-HgHttp2'] = r'1'
297 294 if data is not None and r'Content-Type' not in headers:
298 295 headers[r'Content-Type'] = r'application/mercurial-0.1'
299 296
300 297 # Tell the server we accept application/mercurial-0.2 and multiple
301 298 # compression formats if the server is capable of emitting those
302 299 # payloads.
303 300 protoparams = []
304 301
305 302 mediatypes = set()
306 303 if self._caps is not None:
307 304 mt = self.capable('httpmediatype')
308 305 if mt:
309 306 protoparams.append('0.1')
310 307 mediatypes = set(mt.split(','))
311 308
312 309 if '0.2tx' in mediatypes:
313 310 protoparams.append('0.2')
314 311
315 312 if '0.2tx' in mediatypes and self.capable('compression'):
316 313 # We /could/ compare supported compression formats and prune
317 314 # non-mutually supported or error if nothing is mutually supported.
318 315 # For now, send the full list to the server and have it error.
319 316 comps = [e.wireprotosupport().name for e in
320 317 util.compengines.supportedwireengines(util.CLIENTROLE)]
321 318 protoparams.append('comp=%s' % ','.join(comps))
322 319
323 320 if protoparams:
324 321 protoheaders = encodevalueinheaders(' '.join(protoparams),
325 322 'X-HgProto',
326 323 headersize or 1024)
327 324 for header, value in protoheaders:
328 325 headers[header] = value
329 326 varyheaders.append(header)
330 327
331 328 if varyheaders:
332 329 headers[r'Vary'] = r','.join(varyheaders)
333 330
334 331 req = self._requestbuilder(pycompat.strurl(cu), data, headers)
335 332
336 333 if data is not None:
337 334 self.ui.debug("sending %d bytes\n" % size)
338 335 req.add_unredirected_header(r'Content-Length', r'%d' % size)
339 336 try:
340 337 resp = self._openurl(req)
341 338 except urlerr.httperror as inst:
342 339 if inst.code == 401:
343 340 raise error.Abort(_('authorization failed'))
344 341 raise
345 342 except httplib.HTTPException as inst:
346 343 self.ui.debug('http error while sending %s command\n' % cmd)
347 344 self.ui.traceback()
348 345 raise IOError(None, inst)
349 346
350 347 # Insert error handlers for common I/O failures.
351 348 _wraphttpresponse(resp)
352 349
353 350 # record the url we got redirected to
354 351 resp_url = pycompat.bytesurl(resp.geturl())
355 352 if resp_url.endswith(qs):
356 353 resp_url = resp_url[:-len(qs)]
357 354 if self._url.rstrip('/') != resp_url.rstrip('/'):
358 355 if not self.ui.quiet:
359 356 self.ui.warn(_('real URL is %s\n') % resp_url)
360 357 self._url = resp_url
361 358 try:
362 359 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
363 360 except AttributeError:
364 361 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
365 362
366 363 safeurl = util.hidepassword(self._url)
367 364 if proto.startswith('application/hg-error'):
368 365 raise error.OutOfBandError(resp.read())
369 366 # accept old "text/plain" and "application/hg-changegroup" for now
370 367 if not (proto.startswith('application/mercurial-') or
371 368 (proto.startswith('text/plain')
372 369 and not resp.headers.get('content-length')) or
373 370 proto.startswith('application/hg-changegroup')):
374 371 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
375 372 raise error.RepoError(
376 373 _("'%s' does not appear to be an hg repository:\n"
377 374 "---%%<--- (%s)\n%s\n---%%<---\n")
378 375 % (safeurl, proto or 'no content-type', resp.read(1024)))
379 376
380 377 if proto.startswith('application/mercurial-'):
381 378 try:
382 379 version = proto.split('-', 1)[1]
383 380 version_info = tuple([int(n) for n in version.split('.')])
384 381 except ValueError:
385 382 raise error.RepoError(_("'%s' sent a broken Content-Type "
386 383 "header (%s)") % (safeurl, proto))
387 384
388 385 # TODO consider switching to a decompression reader that uses
389 386 # generators.
390 387 if version_info == (0, 1):
391 388 if _compressible:
392 389 return util.compengines['zlib'].decompressorreader(resp)
393 390 return resp
394 391 elif version_info == (0, 2):
395 392 # application/mercurial-0.2 always identifies the compression
396 393 # engine in the payload header.
397 394 elen = struct.unpack('B', resp.read(1))[0]
398 395 ename = resp.read(elen)
399 396 engine = util.compengines.forwiretype(ename)
400 397 return engine.decompressorreader(resp)
401 398 else:
402 399 raise error.RepoError(_("'%s' uses newer protocol %s") %
403 400 (safeurl, version))
404 401
405 402 if _compressible:
406 403 return util.compengines['zlib'].decompressorreader(resp)
407 404
408 405 return resp
409 406
410 407 def _call(self, cmd, **args):
411 408 fp = self._callstream(cmd, **args)
412 409 try:
413 410 return fp.read()
414 411 finally:
415 412 # if using keepalive, allow connection to be reused
416 413 fp.close()
417 414
418 415 def _callpush(self, cmd, cg, **args):
419 416 # have to stream bundle to a temp file because we do not have
420 417 # http 1.1 chunked transfer.
421 418
422 419 types = self.capable('unbundle')
423 420 try:
424 421 types = types.split(',')
425 422 except AttributeError:
426 423 # servers older than d1b16a746db6 will send 'unbundle' as a
427 424 # boolean capability. They only support headerless/uncompressed
428 425 # bundles.
429 426 types = [""]
430 427 for x in types:
431 428 if x in bundle2.bundletypes:
432 429 type = x
433 430 break
434 431
435 432 tempname = bundle2.writebundle(self.ui, cg, None, type)
436 433 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
437 434 headers = {r'Content-Type': r'application/mercurial-0.1'}
438 435
439 436 try:
440 437 r = self._call(cmd, data=fp, headers=headers, **args)
441 438 vals = r.split('\n', 1)
442 439 if len(vals) < 2:
443 440 raise error.ResponseError(_("unexpected response:"), r)
444 441 return vals
445 442 except socket.error as err:
446 443 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
447 444 raise error.Abort(_('push failed: %s') % err.args[1])
448 445 raise error.Abort(err.args[1])
449 446 finally:
450 447 fp.close()
451 448 os.unlink(tempname)
452 449
453 450 def _calltwowaystream(self, cmd, fp, **args):
454 451 fh = None
455 452 fp_ = None
456 453 filename = None
457 454 try:
458 455 # dump bundle to disk
459 456 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
460 457 fh = os.fdopen(fd, pycompat.sysstr("wb"))
461 458 d = fp.read(4096)
462 459 while d:
463 460 fh.write(d)
464 461 d = fp.read(4096)
465 462 fh.close()
466 463 # start http push
467 464 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
468 465 headers = {r'Content-Type': r'application/mercurial-0.1'}
469 466 return self._callstream(cmd, data=fp_, headers=headers, **args)
470 467 finally:
471 468 if fp_ is not None:
472 469 fp_.close()
473 470 if fh is not None:
474 471 fh.close()
475 472 os.unlink(filename)
476 473
477 474 def _callcompressable(self, cmd, **args):
478 475 return self._callstream(cmd, _compressible=True, **args)
479 476
480 477 def _abort(self, exception):
481 478 raise exception
482 479
483 480 def instance(ui, path, create):
484 481 if create:
485 482 raise error.Abort(_('cannot create new http repository'))
486 483 try:
487 484 if path.startswith('https:') and not url.has_https:
488 485 raise error.Abort(_('Python support for SSL and HTTPS '
489 486 'is not installed'))
490 487
491 488 inst = httppeer(ui, path)
492 489 inst._fetchcaps()
493 490
494 491 return inst
495 492 except error.RepoError as httpexception:
496 493 try:
497 494 r = statichttprepo.instance(ui, "static-" + path, create)
498 495 ui.note(_('(falling back to static-http)\n'))
499 496 return r
500 497 except error.RepoError:
501 498 raise httpexception # use the original http RepoError instead
@@ -1,539 +1,531
1 1 # url.py - HTTP handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import base64
13 13 import os
14 14 import socket
15 15
16 16 from .i18n import _
17 17 from . import (
18 18 encoding,
19 19 error,
20 20 httpconnection as httpconnectionmod,
21 21 keepalive,
22 22 pycompat,
23 23 sslutil,
24 24 urllibcompat,
25 25 util,
26 26 )
27 27
28 28 httplib = util.httplib
29 29 stringio = util.stringio
30 30 urlerr = util.urlerr
31 31 urlreq = util.urlreq
32 32
33 33 def escape(s, quote=None):
34 34 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
35 35 If the optional flag quote is true, the quotation mark character (")
36 36 is also translated.
37 37
38 38 This is the same as cgi.escape in Python, but always operates on
39 39 bytes, whereas cgi.escape in Python 3 only works on unicodes.
40 40 '''
41 41 s = s.replace(b"&", b"&amp;")
42 42 s = s.replace(b"<", b"&lt;")
43 43 s = s.replace(b">", b"&gt;")
44 44 if quote:
45 45 s = s.replace(b'"', b"&quot;")
46 46 return s
47 47
48 48 class passwordmgr(object):
49 49 def __init__(self, ui, passwddb):
50 50 self.ui = ui
51 51 self.passwddb = passwddb
52 52
53 53 def add_password(self, realm, uri, user, passwd):
54 54 return self.passwddb.add_password(realm, uri, user, passwd)
55 55
56 56 def find_user_password(self, realm, authuri):
57 57 authinfo = self.passwddb.find_user_password(realm, authuri)
58 58 user, passwd = authinfo
59 59 if user and passwd:
60 60 self._writedebug(user, passwd)
61 61 return (user, passwd)
62 62
63 63 if not user or not passwd:
64 64 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
65 65 if res:
66 66 group, auth = res
67 67 user, passwd = auth.get('username'), auth.get('password')
68 68 self.ui.debug("using auth.%s.* for authentication\n" % group)
69 69 if not user or not passwd:
70 70 u = util.url(authuri)
71 71 u.query = None
72 72 if not self.ui.interactive():
73 73 raise error.Abort(_('http authorization required for %s') %
74 74 util.hidepassword(bytes(u)))
75 75
76 76 self.ui.write(_("http authorization required for %s\n") %
77 77 util.hidepassword(bytes(u)))
78 78 self.ui.write(_("realm: %s\n") % realm)
79 79 if user:
80 80 self.ui.write(_("user: %s\n") % user)
81 81 else:
82 82 user = self.ui.prompt(_("user:"), default=None)
83 83
84 84 if not passwd:
85 85 passwd = self.ui.getpass()
86 86
87 87 self.passwddb.add_password(realm, authuri, user, passwd)
88 88 self._writedebug(user, passwd)
89 89 return (user, passwd)
90 90
91 91 def _writedebug(self, user, passwd):
92 92 msg = _('http auth: user %s, password %s\n')
93 93 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
94 94
95 95 def find_stored_password(self, authuri):
96 96 return self.passwddb.find_user_password(None, authuri)
97 97
98 98 class proxyhandler(urlreq.proxyhandler):
99 99 def __init__(self, ui):
100 100 proxyurl = (ui.config("http_proxy", "host") or
101 101 encoding.environ.get('http_proxy'))
102 102 # XXX proxyauthinfo = None
103 103
104 104 if proxyurl:
105 105 # proxy can be proper url or host[:port]
106 106 if not (proxyurl.startswith('http:') or
107 107 proxyurl.startswith('https:')):
108 108 proxyurl = 'http://' + proxyurl + '/'
109 109 proxy = util.url(proxyurl)
110 110 if not proxy.user:
111 111 proxy.user = ui.config("http_proxy", "user")
112 112 proxy.passwd = ui.config("http_proxy", "passwd")
113 113
114 114 # see if we should use a proxy for this url
115 115 no_list = ["localhost", "127.0.0.1"]
116 116 no_list.extend([p.lower() for
117 117 p in ui.configlist("http_proxy", "no")])
118 118 no_list.extend([p.strip().lower() for
119 119 p in encoding.environ.get("no_proxy", '').split(',')
120 120 if p.strip()])
121 121 # "http_proxy.always" config is for running tests on localhost
122 122 if ui.configbool("http_proxy", "always"):
123 123 self.no_list = []
124 124 else:
125 125 self.no_list = no_list
126 126
127 127 proxyurl = bytes(proxy)
128 128 proxies = {'http': proxyurl, 'https': proxyurl}
129 129 ui.debug('proxying through %s\n' % util.hidepassword(proxyurl))
130 130 else:
131 131 proxies = {}
132 132
133 133 urlreq.proxyhandler.__init__(self, proxies)
134 134 self.ui = ui
135 135
136 136 def proxy_open(self, req, proxy, type_):
137 137 host = urllibcompat.gethost(req).split(':')[0]
138 138 for e in self.no_list:
139 139 if host == e:
140 140 return None
141 141 if e.startswith('*.') and host.endswith(e[2:]):
142 142 return None
143 143 if e.startswith('.') and host.endswith(e[1:]):
144 144 return None
145 145
146 146 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
147 147
148 148 def _gen_sendfile(orgsend):
149 149 def _sendfile(self, data):
150 150 # send a file
151 151 if isinstance(data, httpconnectionmod.httpsendfile):
152 152 # if auth required, some data sent twice, so rewind here
153 153 data.seek(0)
154 154 for chunk in util.filechunkiter(data):
155 155 orgsend(self, chunk)
156 156 else:
157 157 orgsend(self, data)
158 158 return _sendfile
159 159
160 160 has_https = util.safehasattr(urlreq, 'httpshandler')
161 161
162 162 class httpconnection(keepalive.HTTPConnection):
163 163 # must be able to send big bundle as stream.
164 164 send = _gen_sendfile(keepalive.HTTPConnection.send)
165 165
166 166 def getresponse(self):
167 167 proxyres = getattr(self, 'proxyres', None)
168 168 if proxyres:
169 169 if proxyres.will_close:
170 170 self.close()
171 171 self.proxyres = None
172 172 return proxyres
173 173 return keepalive.HTTPConnection.getresponse(self)
174 174
175 175 # general transaction handler to support different ways to handle
176 176 # HTTPS proxying before and after Python 2.6.3.
177 177 def _generic_start_transaction(handler, h, req):
178 178 tunnel_host = getattr(req, '_tunnel_host', None)
179 179 if tunnel_host:
180 180 if tunnel_host[:7] not in ['http://', 'https:/']:
181 181 tunnel_host = 'https://' + tunnel_host
182 182 new_tunnel = True
183 183 else:
184 184 tunnel_host = urllibcompat.getselector(req)
185 185 new_tunnel = False
186 186
187 187 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
188 188 u = util.url(tunnel_host)
189 189 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
190 190 h.realhostport = ':'.join([u.host, (u.port or '443')])
191 191 h.headers = req.headers.copy()
192 192 h.headers.update(handler.parent.addheaders)
193 193 return
194 194
195 195 h.realhostport = None
196 196 h.headers = None
197 197
198 198 def _generic_proxytunnel(self):
199 199 proxyheaders = dict(
200 200 [(x, self.headers[x]) for x in self.headers
201 201 if x.lower().startswith('proxy-')])
202 202 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
203 203 for header in proxyheaders.iteritems():
204 204 self.send('%s: %s\r\n' % header)
205 205 self.send('\r\n')
206 206
207 207 # majority of the following code is duplicated from
208 208 # httplib.HTTPConnection as there are no adequate places to
209 209 # override functions to provide the needed functionality
210 210 res = self.response_class(self.sock,
211 211 strict=self.strict,
212 212 method=self._method)
213 213
214 214 while True:
215 215 version, status, reason = res._read_status()
216 216 if status != httplib.CONTINUE:
217 217 break
218 218 # skip lines that are all whitespace
219 219 list(iter(lambda: res.fp.readline().strip(), ''))
220 220 res.status = status
221 221 res.reason = reason.strip()
222 222
223 223 if res.status == 200:
224 224 # skip lines until we find a blank line
225 225 list(iter(res.fp.readline, '\r\n'))
226 226 return True
227 227
228 228 if version == 'HTTP/1.0':
229 229 res.version = 10
230 230 elif version.startswith('HTTP/1.'):
231 231 res.version = 11
232 232 elif version == 'HTTP/0.9':
233 233 res.version = 9
234 234 else:
235 235 raise httplib.UnknownProtocol(version)
236 236
237 237 if res.version == 9:
238 238 res.length = None
239 239 res.chunked = 0
240 240 res.will_close = 1
241 241 res.msg = httplib.HTTPMessage(stringio())
242 242 return False
243 243
244 244 res.msg = httplib.HTTPMessage(res.fp)
245 245 res.msg.fp = None
246 246
247 247 # are we using the chunked-style of transfer encoding?
248 248 trenc = res.msg.getheader('transfer-encoding')
249 249 if trenc and trenc.lower() == "chunked":
250 250 res.chunked = 1
251 251 res.chunk_left = None
252 252 else:
253 253 res.chunked = 0
254 254
255 255 # will the connection close at the end of the response?
256 256 res.will_close = res._check_close()
257 257
258 258 # do we have a Content-Length?
259 259 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
260 260 # transfer-encoding is "chunked"
261 261 length = res.msg.getheader('content-length')
262 262 if length and not res.chunked:
263 263 try:
264 264 res.length = int(length)
265 265 except ValueError:
266 266 res.length = None
267 267 else:
268 268 if res.length < 0: # ignore nonsensical negative lengths
269 269 res.length = None
270 270 else:
271 271 res.length = None
272 272
273 273 # does the body have a fixed length? (of zero)
274 274 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
275 275 100 <= status < 200 or # 1xx codes
276 276 res._method == 'HEAD'):
277 277 res.length = 0
278 278
279 279 # if the connection remains open, and we aren't using chunked, and
280 280 # a content-length was not provided, then assume that the connection
281 281 # WILL close.
282 282 if (not res.will_close and
283 283 not res.chunked and
284 284 res.length is None):
285 285 res.will_close = 1
286 286
287 287 self.proxyres = res
288 288
289 289 return False
290 290
291 291 class httphandler(keepalive.HTTPHandler):
292 292 def http_open(self, req):
293 293 return self.do_open(httpconnection, req)
294 294
295 295 def _start_transaction(self, h, req):
296 296 _generic_start_transaction(self, h, req)
297 297 return keepalive.HTTPHandler._start_transaction(self, h, req)
298 298
299 299 if has_https:
300 300 class httpsconnection(httplib.HTTPConnection):
301 301 response_class = keepalive.HTTPResponse
302 302 default_port = httplib.HTTPS_PORT
303 303 # must be able to send big bundle as stream.
304 304 send = _gen_sendfile(keepalive.safesend)
305 305 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
306 306
307 307 def __init__(self, host, port=None, key_file=None, cert_file=None,
308 308 *args, **kwargs):
309 309 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
310 310 self.key_file = key_file
311 311 self.cert_file = cert_file
312 312
313 313 def connect(self):
314 314 self.sock = socket.create_connection((self.host, self.port))
315 315
316 316 host = self.host
317 317 if self.realhostport: # use CONNECT proxy
318 318 _generic_proxytunnel(self)
319 319 host = self.realhostport.rsplit(':', 1)[0]
320 320 self.sock = sslutil.wrapsocket(
321 321 self.sock, self.key_file, self.cert_file, ui=self.ui,
322 322 serverhostname=host)
323 323 sslutil.validatesocket(self.sock)
324 324
325 325 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
326 326 def __init__(self, ui):
327 327 keepalive.KeepAliveHandler.__init__(self)
328 328 urlreq.httpshandler.__init__(self)
329 329 self.ui = ui
330 330 self.pwmgr = passwordmgr(self.ui,
331 331 self.ui.httppasswordmgrdb)
332 332
333 333 def _start_transaction(self, h, req):
334 334 _generic_start_transaction(self, h, req)
335 335 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
336 336
337 337 def https_open(self, req):
338 338 # urllibcompat.getfullurl() does not contain credentials
339 339 # and we may need them to match the certificates.
340 340 url = urllibcompat.getfullurl(req)
341 341 user, password = self.pwmgr.find_stored_password(url)
342 342 res = httpconnectionmod.readauthforuri(self.ui, url, user)
343 343 if res:
344 344 group, auth = res
345 345 self.auth = auth
346 346 self.ui.debug("using auth.%s.* for authentication\n" % group)
347 347 else:
348 348 self.auth = None
349 349 return self.do_open(self._makeconnection, req)
350 350
351 351 def _makeconnection(self, host, port=None, *args, **kwargs):
352 352 keyfile = None
353 353 certfile = None
354 354
355 355 if len(args) >= 1: # key_file
356 356 keyfile = args[0]
357 357 if len(args) >= 2: # cert_file
358 358 certfile = args[1]
359 359 args = args[2:]
360 360
361 361 # if the user has specified different key/cert files in
362 362 # hgrc, we prefer these
363 363 if self.auth and 'key' in self.auth and 'cert' in self.auth:
364 364 keyfile = self.auth['key']
365 365 certfile = self.auth['cert']
366 366
367 367 conn = httpsconnection(host, port, keyfile, certfile, *args,
368 368 **kwargs)
369 369 conn.ui = self.ui
370 370 return conn
371 371
372 372 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
373 373 def __init__(self, *args, **kwargs):
374 374 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
375 375 self.retried_req = None
376 376
377 377 def reset_retry_count(self):
378 378 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
379 379 # forever. We disable reset_retry_count completely and reset in
380 380 # http_error_auth_reqed instead.
381 381 pass
382 382
383 383 def http_error_auth_reqed(self, auth_header, host, req, headers):
384 384 # Reset the retry counter once for each request.
385 385 if req is not self.retried_req:
386 386 self.retried_req = req
387 387 self.retried = 0
388 388 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
389 389 self, auth_header, host, req, headers)
390 390
391 391 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
392 392 def __init__(self, *args, **kwargs):
393 393 self.auth = None
394 394 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
395 395 self.retried_req = None
396 396
397 397 def http_request(self, request):
398 398 if self.auth:
399 399 request.add_unredirected_header(self.auth_header, self.auth)
400 400
401 401 return request
402 402
403 403 def https_request(self, request):
404 404 if self.auth:
405 405 request.add_unredirected_header(self.auth_header, self.auth)
406 406
407 407 return request
408 408
409 409 def reset_retry_count(self):
410 410 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
411 411 # forever. We disable reset_retry_count completely and reset in
412 412 # http_error_auth_reqed instead.
413 413 pass
414 414
415 415 def http_error_auth_reqed(self, auth_header, host, req, headers):
416 416 # Reset the retry counter once for each request.
417 417 if req is not self.retried_req:
418 418 self.retried_req = req
419 419 self.retried = 0
420 420 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
421 421 self, auth_header, host, req, headers)
422 422
423 423 def retry_http_basic_auth(self, host, req, realm):
424 424 user, pw = self.passwd.find_user_password(
425 425 realm, urllibcompat.getfullurl(req))
426 426 if pw is not None:
427 427 raw = "%s:%s" % (user, pw)
428 428 auth = 'Basic %s' % base64.b64encode(raw).strip()
429 429 if req.get_header(self.auth_header, None) == auth:
430 430 return None
431 431 self.auth = auth
432 432 req.add_unredirected_header(self.auth_header, auth)
433 433 return self.parent.open(req)
434 434 else:
435 435 return None
436 436
437 437 class cookiehandler(urlreq.basehandler):
438 438 def __init__(self, ui):
439 439 self.cookiejar = None
440 440
441 441 cookiefile = ui.config('auth', 'cookiefile')
442 442 if not cookiefile:
443 443 return
444 444
445 445 cookiefile = util.expandpath(cookiefile)
446 446 try:
447 447 cookiejar = util.cookielib.MozillaCookieJar(cookiefile)
448 448 cookiejar.load()
449 449 self.cookiejar = cookiejar
450 450 except util.cookielib.LoadError as e:
451 451 ui.warn(_('(error loading cookie file %s: %s; continuing without '
452 452 'cookies)\n') % (cookiefile, util.forcebytestr(e)))
453 453
454 454 def http_request(self, request):
455 455 if self.cookiejar:
456 456 self.cookiejar.add_cookie_header(request)
457 457
458 458 return request
459 459
460 460 def https_request(self, request):
461 461 if self.cookiejar:
462 462 self.cookiejar.add_cookie_header(request)
463 463
464 464 return request
465 465
466 466 handlerfuncs = []
467 467
468 468 def opener(ui, authinfo=None, useragent=None):
469 469 '''
470 470 construct an opener suitable for urllib2
471 471 authinfo will be added to the password manager
472 472 '''
473 # experimental config: ui.usehttp2
474 if ui.configbool('ui', 'usehttp2'):
475 handlers = [
476 httpconnectionmod.http2handler(
477 ui,
478 passwordmgr(ui, ui.httppasswordmgrdb))
479 ]
480 else:
481 473 handlers = [httphandler()]
482 474 if has_https:
483 475 handlers.append(httpshandler(ui))
484 476
485 477 handlers.append(proxyhandler(ui))
486 478
487 479 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
488 480 if authinfo is not None:
489 481 realm, uris, user, passwd = authinfo
490 482 saveduser, savedpass = passmgr.find_stored_password(uris[0])
491 483 if user != saveduser or passwd:
492 484 passmgr.add_password(realm, uris, user, passwd)
493 485 ui.debug('http auth: user %s, password %s\n' %
494 486 (user, passwd and '*' * len(passwd) or 'not set'))
495 487
496 488 handlers.extend((httpbasicauthhandler(passmgr),
497 489 httpdigestauthhandler(passmgr)))
498 490 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
499 491 handlers.append(cookiehandler(ui))
500 492 opener = urlreq.buildopener(*handlers)
501 493
502 494 # The user agent should should *NOT* be used by servers for e.g.
503 495 # protocol detection or feature negotiation: there are other
504 496 # facilities for that.
505 497 #
506 498 # "mercurial/proto-1.0" was the original user agent string and
507 499 # exists for backwards compatibility reasons.
508 500 #
509 501 # The "(Mercurial %s)" string contains the distribution
510 502 # name and version. Other client implementations should choose their
511 503 # own distribution name. Since servers should not be using the user
512 504 # agent string for anything, clients should be able to define whatever
513 505 # user agent they deem appropriate.
514 506 #
515 507 # The custom user agent is for lfs, because unfortunately some servers
516 508 # do look at this value.
517 509 if not useragent:
518 510 agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
519 511 opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
520 512 else:
521 513 opener.addheaders = [(r'User-agent', pycompat.sysstr(useragent))]
522 514
523 515 # This header should only be needed by wire protocol requests. But it has
524 516 # been sent on all requests since forever. We keep sending it for backwards
525 517 # compatibility reasons. Modern versions of the wire protocol use
526 518 # X-HgProto-<N> for advertising client support.
527 519 opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
528 520 return opener
529 521
530 522 def open(ui, url_, data=None):
531 523 u = util.url(url_)
532 524 if u.scheme:
533 525 u.scheme = u.scheme.lower()
534 526 url_, authinfo = u.authinfo()
535 527 else:
536 528 path = util.normpath(os.path.abspath(url_))
537 529 url_ = 'file://' + urlreq.pathname2url(path)
538 530 authinfo = None
539 531 return opener(ui, authinfo).open(url_, data)
@@ -1,1055 +1,1054
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6
7 7 import os
8 8
9 9 supportedpy = '~= 2.7'
10 10 if os.environ.get('HGALLOWPYTHON3', ''):
11 11 # Mercurial will never work on Python 3 before 3.5 due to a lack
12 12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
13 13 # due to a bug in % formatting in bytestrings.
14 14 #
15 15 # TODO: when we actually work on Python 3, use this string as the
16 16 # actual supportedpy string.
17 17 supportedpy = ','.join([
18 18 '>=2.7',
19 19 '!=3.0.*',
20 20 '!=3.1.*',
21 21 '!=3.2.*',
22 22 '!=3.3.*',
23 23 '!=3.4.*',
24 24 '!=3.6.0',
25 25 '!=3.6.1',
26 26 ])
27 27
28 28 import sys, platform
29 29 if sys.version_info[0] >= 3:
30 30 printf = eval('print')
31 31 libdir_escape = 'unicode_escape'
32 32 def sysstr(s):
33 33 return s.decode('latin-1')
34 34 else:
35 35 libdir_escape = 'string_escape'
36 36 def printf(*args, **kwargs):
37 37 f = kwargs.get('file', sys.stdout)
38 38 end = kwargs.get('end', '\n')
39 39 f.write(b' '.join(args) + end)
40 40 def sysstr(s):
41 41 return s
42 42
43 43 # Attempt to guide users to a modern pip - this means that 2.6 users
44 44 # should have a chance of getting a 4.2 release, and when we ratchet
45 45 # the version requirement forward again hopefully everyone will get
46 46 # something that works for them.
47 47 if sys.version_info < (2, 7, 0, 'final'):
48 48 pip_message = ('This may be due to an out of date pip. '
49 49 'Make sure you have pip >= 9.0.1.')
50 50 try:
51 51 import pip
52 52 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
53 53 if pip_version < (9, 0, 1) :
54 54 pip_message = (
55 55 'Your pip version is out of date, please install '
56 56 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
57 57 else:
58 58 # pip is new enough - it must be something else
59 59 pip_message = ''
60 60 except Exception:
61 61 pass
62 62 error = """
63 63 Mercurial does not support Python older than 2.7.
64 64 Python {py} detected.
65 65 {pip}
66 66 """.format(py=sys.version_info, pip=pip_message)
67 67 printf(error, file=sys.stderr)
68 68 sys.exit(1)
69 69
70 70 # We don't yet officially support Python 3. But we want to allow developers to
71 71 # hack on. Detect and disallow running on Python 3 by default. But provide a
72 72 # backdoor to enable working on Python 3.
73 73 if sys.version_info[0] != 2:
74 74 badpython = True
75 75
76 76 # Allow Python 3 from source checkouts.
77 77 if os.path.isdir('.hg'):
78 78 badpython = False
79 79
80 80 if badpython:
81 81 error = """
82 82 Mercurial only supports Python 2.7.
83 83 Python {py} detected.
84 84 Please re-run with Python 2.7.
85 85 """.format(py=sys.version_info)
86 86
87 87 printf(error, file=sys.stderr)
88 88 sys.exit(1)
89 89
90 90 # Solaris Python packaging brain damage
91 91 try:
92 92 import hashlib
93 93 sha = hashlib.sha1()
94 94 except ImportError:
95 95 try:
96 96 import sha
97 97 sha.sha # silence unused import warning
98 98 except ImportError:
99 99 raise SystemExit(
100 100 "Couldn't import standard hashlib (incomplete Python install).")
101 101
102 102 try:
103 103 import zlib
104 104 zlib.compressobj # silence unused import warning
105 105 except ImportError:
106 106 raise SystemExit(
107 107 "Couldn't import standard zlib (incomplete Python install).")
108 108
109 109 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
110 110 isironpython = False
111 111 try:
112 112 isironpython = (platform.python_implementation()
113 113 .lower().find("ironpython") != -1)
114 114 except AttributeError:
115 115 pass
116 116
117 117 if isironpython:
118 118 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
119 119 else:
120 120 try:
121 121 import bz2
122 122 bz2.BZ2Compressor # silence unused import warning
123 123 except ImportError:
124 124 raise SystemExit(
125 125 "Couldn't import standard bz2 (incomplete Python install).")
126 126
127 127 ispypy = "PyPy" in sys.version
128 128
129 129 import ctypes
130 130 import stat, subprocess, time
131 131 import re
132 132 import shutil
133 133 import tempfile
134 134 from distutils import log
135 135 # We have issues with setuptools on some platforms and builders. Until
136 136 # those are resolved, setuptools is opt-in except for platforms where
137 137 # we don't have issues.
138 138 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
139 139 if issetuptools:
140 140 from setuptools import setup
141 141 else:
142 142 from distutils.core import setup
143 143 from distutils.ccompiler import new_compiler
144 144 from distutils.core import Command, Extension
145 145 from distutils.dist import Distribution
146 146 from distutils.command.build import build
147 147 from distutils.command.build_ext import build_ext
148 148 from distutils.command.build_py import build_py
149 149 from distutils.command.build_scripts import build_scripts
150 150 from distutils.command.install import install
151 151 from distutils.command.install_lib import install_lib
152 152 from distutils.command.install_scripts import install_scripts
153 153 from distutils.spawn import spawn, find_executable
154 154 from distutils import file_util
155 155 from distutils.errors import (
156 156 CCompilerError,
157 157 DistutilsError,
158 158 DistutilsExecError,
159 159 )
160 160 from distutils.sysconfig import get_python_inc, get_config_var
161 161 from distutils.version import StrictVersion
162 162
163 163 def write_if_changed(path, content):
164 164 """Write content to a file iff the content hasn't changed."""
165 165 if os.path.exists(path):
166 166 with open(path, 'rb') as fh:
167 167 current = fh.read()
168 168 else:
169 169 current = b''
170 170
171 171 if current != content:
172 172 with open(path, 'wb') as fh:
173 173 fh.write(content)
174 174
175 175 scripts = ['hg']
176 176 if os.name == 'nt':
177 177 # We remove hg.bat if we are able to build hg.exe.
178 178 scripts.append('contrib/win32/hg.bat')
179 179
180 180 def cancompile(cc, code):
181 181 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
182 182 devnull = oldstderr = None
183 183 try:
184 184 fname = os.path.join(tmpdir, 'testcomp.c')
185 185 f = open(fname, 'w')
186 186 f.write(code)
187 187 f.close()
188 188 # Redirect stderr to /dev/null to hide any error messages
189 189 # from the compiler.
190 190 # This will have to be changed if we ever have to check
191 191 # for a function on Windows.
192 192 devnull = open('/dev/null', 'w')
193 193 oldstderr = os.dup(sys.stderr.fileno())
194 194 os.dup2(devnull.fileno(), sys.stderr.fileno())
195 195 objects = cc.compile([fname], output_dir=tmpdir)
196 196 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
197 197 return True
198 198 except Exception:
199 199 return False
200 200 finally:
201 201 if oldstderr is not None:
202 202 os.dup2(oldstderr, sys.stderr.fileno())
203 203 if devnull is not None:
204 204 devnull.close()
205 205 shutil.rmtree(tmpdir)
206 206
207 207 # simplified version of distutils.ccompiler.CCompiler.has_function
208 208 # that actually removes its temporary files.
209 209 def hasfunction(cc, funcname):
210 210 code = 'int main(void) { %s(); }\n' % funcname
211 211 return cancompile(cc, code)
212 212
213 213 def hasheader(cc, headername):
214 214 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
215 215 return cancompile(cc, code)
216 216
217 217 # py2exe needs to be installed to work
218 218 try:
219 219 import py2exe
220 220 py2exe.Distribution # silence unused import warning
221 221 py2exeloaded = True
222 222 # import py2exe's patched Distribution class
223 223 from distutils.core import Distribution
224 224 except ImportError:
225 225 py2exeloaded = False
226 226
227 227 def runcmd(cmd, env):
228 228 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
229 229 stderr=subprocess.PIPE, env=env)
230 230 out, err = p.communicate()
231 231 return p.returncode, out, err
232 232
233 233 class hgcommand(object):
234 234 def __init__(self, cmd, env):
235 235 self.cmd = cmd
236 236 self.env = env
237 237
238 238 def run(self, args):
239 239 cmd = self.cmd + args
240 240 returncode, out, err = runcmd(cmd, self.env)
241 241 err = filterhgerr(err)
242 242 if err or returncode != 0:
243 243 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
244 244 printf(err, file=sys.stderr)
245 245 return ''
246 246 return out
247 247
248 248 def filterhgerr(err):
249 249 # If root is executing setup.py, but the repository is owned by
250 250 # another user (as in "sudo python setup.py install") we will get
251 251 # trust warnings since the .hg/hgrc file is untrusted. That is
252 252 # fine, we don't want to load it anyway. Python may warn about
253 253 # a missing __init__.py in mercurial/locale, we also ignore that.
254 254 err = [e for e in err.splitlines()
255 255 if (not e.startswith(b'not trusting file')
256 256 and not e.startswith(b'warning: Not importing')
257 257 and not e.startswith(b'obsolete feature not enabled')
258 258 and not e.startswith(b'devel-warn:'))]
259 259 return b'\n'.join(b' ' + e for e in err)
260 260
261 261 def findhg():
262 262 """Try to figure out how we should invoke hg for examining the local
263 263 repository contents.
264 264
265 265 Returns an hgcommand object."""
266 266 # By default, prefer the "hg" command in the user's path. This was
267 267 # presumably the hg command that the user used to create this repository.
268 268 #
269 269 # This repository may require extensions or other settings that would not
270 270 # be enabled by running the hg script directly from this local repository.
271 271 hgenv = os.environ.copy()
272 272 # Use HGPLAIN to disable hgrc settings that would change output formatting,
273 273 # and disable localization for the same reasons.
274 274 hgenv['HGPLAIN'] = '1'
275 275 hgenv['LANGUAGE'] = 'C'
276 276 hgcmd = ['hg']
277 277 # Run a simple "hg log" command just to see if using hg from the user's
278 278 # path works and can successfully interact with this repository.
279 279 check_cmd = ['log', '-r.', '-Ttest']
280 280 try:
281 281 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
282 282 except EnvironmentError:
283 283 retcode = -1
284 284 if retcode == 0 and not filterhgerr(err):
285 285 return hgcommand(hgcmd, hgenv)
286 286
287 287 # Fall back to trying the local hg installation.
288 288 hgenv = localhgenv()
289 289 hgcmd = [sys.executable, 'hg']
290 290 try:
291 291 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
292 292 except EnvironmentError:
293 293 retcode = -1
294 294 if retcode == 0 and not filterhgerr(err):
295 295 return hgcommand(hgcmd, hgenv)
296 296
297 297 raise SystemExit('Unable to find a working hg binary to extract the '
298 298 'version from the repository tags')
299 299
300 300 def localhgenv():
301 301 """Get an environment dictionary to use for invoking or importing
302 302 mercurial from the local repository."""
303 303 # Execute hg out of this directory with a custom environment which takes
304 304 # care to not use any hgrc files and do no localization.
305 305 env = {'HGMODULEPOLICY': 'py',
306 306 'HGRCPATH': '',
307 307 'LANGUAGE': 'C',
308 308 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
309 309 if 'LD_LIBRARY_PATH' in os.environ:
310 310 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
311 311 if 'SystemRoot' in os.environ:
312 312 # SystemRoot is required by Windows to load various DLLs. See:
313 313 # https://bugs.python.org/issue13524#msg148850
314 314 env['SystemRoot'] = os.environ['SystemRoot']
315 315 return env
316 316
317 317 version = ''
318 318
319 319 if os.path.isdir('.hg'):
320 320 hg = findhg()
321 321 cmd = ['log', '-r', '.', '--template', '{tags}\n']
322 322 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
323 323 hgid = sysstr(hg.run(['id', '-i'])).strip()
324 324 if not hgid:
325 325 # Bail out if hg is having problems interacting with this repository,
326 326 # rather than falling through and producing a bogus version number.
327 327 # Continuing with an invalid version number will break extensions
328 328 # that define minimumhgversion.
329 329 raise SystemExit('Unable to determine hg version from local repository')
330 330 if numerictags: # tag(s) found
331 331 version = numerictags[-1]
332 332 if hgid.endswith('+'): # propagate the dirty status to the tag
333 333 version += '+'
334 334 else: # no tag found
335 335 ltagcmd = ['parents', '--template', '{latesttag}']
336 336 ltag = sysstr(hg.run(ltagcmd))
337 337 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
338 338 changessince = len(hg.run(changessincecmd).splitlines())
339 339 version = '%s+%s-%s' % (ltag, changessince, hgid)
340 340 if version.endswith('+'):
341 341 version += time.strftime('%Y%m%d')
342 342 elif os.path.exists('.hg_archival.txt'):
343 343 kw = dict([[t.strip() for t in l.split(':', 1)]
344 344 for l in open('.hg_archival.txt')])
345 345 if 'tag' in kw:
346 346 version = kw['tag']
347 347 elif 'latesttag' in kw:
348 348 if 'changessincelatesttag' in kw:
349 349 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
350 350 else:
351 351 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
352 352 else:
353 353 version = kw.get('node', '')[:12]
354 354
355 355 if version:
356 356 versionb = version
357 357 if not isinstance(versionb, bytes):
358 358 versionb = versionb.encode('ascii')
359 359
360 360 write_if_changed('mercurial/__version__.py', b''.join([
361 361 b'# this file is autogenerated by setup.py\n'
362 362 b'version = "%s"\n' % versionb,
363 363 ]))
364 364
365 365 try:
366 366 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
367 367 os.environ['HGMODULEPOLICY'] = 'py'
368 368 from mercurial import __version__
369 369 version = __version__.version
370 370 except ImportError:
371 371 version = 'unknown'
372 372 finally:
373 373 if oldpolicy is None:
374 374 del os.environ['HGMODULEPOLICY']
375 375 else:
376 376 os.environ['HGMODULEPOLICY'] = oldpolicy
377 377
378 378 class hgbuild(build):
379 379 # Insert hgbuildmo first so that files in mercurial/locale/ are found
380 380 # when build_py is run next.
381 381 sub_commands = [('build_mo', None)] + build.sub_commands
382 382
383 383 class hgbuildmo(build):
384 384
385 385 description = "build translations (.mo files)"
386 386
387 387 def run(self):
388 388 if not find_executable('msgfmt'):
389 389 self.warn("could not find msgfmt executable, no translations "
390 390 "will be built")
391 391 return
392 392
393 393 podir = 'i18n'
394 394 if not os.path.isdir(podir):
395 395 self.warn("could not find %s/ directory" % podir)
396 396 return
397 397
398 398 join = os.path.join
399 399 for po in os.listdir(podir):
400 400 if not po.endswith('.po'):
401 401 continue
402 402 pofile = join(podir, po)
403 403 modir = join('locale', po[:-3], 'LC_MESSAGES')
404 404 mofile = join(modir, 'hg.mo')
405 405 mobuildfile = join('mercurial', mofile)
406 406 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
407 407 if sys.platform != 'sunos5':
408 408 # msgfmt on Solaris does not know about -c
409 409 cmd.append('-c')
410 410 self.mkpath(join('mercurial', modir))
411 411 self.make_file([pofile], mobuildfile, spawn, (cmd,))
412 412
413 413
414 414 class hgdist(Distribution):
415 415 pure = False
416 416 cffi = ispypy
417 417
418 418 global_options = Distribution.global_options + \
419 419 [('pure', None, "use pure (slow) Python "
420 420 "code instead of C extensions"),
421 421 ]
422 422
423 423 def has_ext_modules(self):
424 424 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
425 425 # too late for some cases
426 426 return not self.pure and Distribution.has_ext_modules(self)
427 427
428 428 # This is ugly as a one-liner. So use a variable.
429 429 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
430 430 buildextnegops['no-zstd'] = 'zstd'
431 431
432 432 class hgbuildext(build_ext):
433 433 user_options = build_ext.user_options + [
434 434 ('zstd', None, 'compile zstd bindings [default]'),
435 435 ('no-zstd', None, 'do not compile zstd bindings'),
436 436 ]
437 437
438 438 boolean_options = build_ext.boolean_options + ['zstd']
439 439 negative_opt = buildextnegops
440 440
441 441 def initialize_options(self):
442 442 self.zstd = True
443 443 return build_ext.initialize_options(self)
444 444
445 445 def build_extensions(self):
446 446 # Filter out zstd if disabled via argument.
447 447 if not self.zstd:
448 448 self.extensions = [e for e in self.extensions
449 449 if e.name != 'mercurial.zstd']
450 450
451 451 return build_ext.build_extensions(self)
452 452
453 453 def build_extension(self, ext):
454 454 try:
455 455 build_ext.build_extension(self, ext)
456 456 except CCompilerError:
457 457 if not getattr(ext, 'optional', False):
458 458 raise
459 459 log.warn("Failed to build optional extension '%s' (skipping)",
460 460 ext.name)
461 461
462 462 class hgbuildscripts(build_scripts):
463 463 def run(self):
464 464 if os.name != 'nt' or self.distribution.pure:
465 465 return build_scripts.run(self)
466 466
467 467 exebuilt = False
468 468 try:
469 469 self.run_command('build_hgexe')
470 470 exebuilt = True
471 471 except (DistutilsError, CCompilerError):
472 472 log.warn('failed to build optional hg.exe')
473 473
474 474 if exebuilt:
475 475 # Copying hg.exe to the scripts build directory ensures it is
476 476 # installed by the install_scripts command.
477 477 hgexecommand = self.get_finalized_command('build_hgexe')
478 478 dest = os.path.join(self.build_dir, 'hg.exe')
479 479 self.mkpath(self.build_dir)
480 480 self.copy_file(hgexecommand.hgexepath, dest)
481 481
482 482 # Remove hg.bat because it is redundant with hg.exe.
483 483 self.scripts.remove('contrib/win32/hg.bat')
484 484
485 485 return build_scripts.run(self)
486 486
487 487 class hgbuildpy(build_py):
488 488 def finalize_options(self):
489 489 build_py.finalize_options(self)
490 490
491 491 if self.distribution.pure:
492 492 self.distribution.ext_modules = []
493 493 elif self.distribution.cffi:
494 494 from mercurial.cffi import (
495 495 bdiffbuild,
496 496 mpatchbuild,
497 497 )
498 498 exts = [mpatchbuild.ffi.distutils_extension(),
499 499 bdiffbuild.ffi.distutils_extension()]
500 500 # cffi modules go here
501 501 if sys.platform == 'darwin':
502 502 from mercurial.cffi import osutilbuild
503 503 exts.append(osutilbuild.ffi.distutils_extension())
504 504 self.distribution.ext_modules = exts
505 505 else:
506 506 h = os.path.join(get_python_inc(), 'Python.h')
507 507 if not os.path.exists(h):
508 508 raise SystemExit('Python headers are required to build '
509 509 'Mercurial but weren\'t found in %s' % h)
510 510
511 511 def run(self):
512 512 basepath = os.path.join(self.build_lib, 'mercurial')
513 513 self.mkpath(basepath)
514 514
515 515 if self.distribution.pure:
516 516 modulepolicy = 'py'
517 517 elif self.build_lib == '.':
518 518 # in-place build should run without rebuilding C extensions
519 519 modulepolicy = 'allow'
520 520 else:
521 521 modulepolicy = 'c'
522 522
523 523 content = b''.join([
524 524 b'# this file is autogenerated by setup.py\n',
525 525 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
526 526 ])
527 527 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
528 528 content)
529 529
530 530 build_py.run(self)
531 531
532 532 class buildhgextindex(Command):
533 533 description = 'generate prebuilt index of hgext (for frozen package)'
534 534 user_options = []
535 535 _indexfilename = 'hgext/__index__.py'
536 536
537 537 def initialize_options(self):
538 538 pass
539 539
540 540 def finalize_options(self):
541 541 pass
542 542
543 543 def run(self):
544 544 if os.path.exists(self._indexfilename):
545 545 with open(self._indexfilename, 'w') as f:
546 546 f.write('# empty\n')
547 547
548 548 # here no extension enabled, disabled() lists up everything
549 549 code = ('import pprint; from mercurial import extensions; '
550 550 'pprint.pprint(extensions.disabled())')
551 551 returncode, out, err = runcmd([sys.executable, '-c', code],
552 552 localhgenv())
553 553 if err or returncode != 0:
554 554 raise DistutilsExecError(err)
555 555
556 556 with open(self._indexfilename, 'w') as f:
557 557 f.write('# this file is autogenerated by setup.py\n')
558 558 f.write('docs = ')
559 559 f.write(out)
560 560
561 561 class buildhgexe(build_ext):
562 562 description = 'compile hg.exe from mercurial/exewrapper.c'
563 563 user_options = build_ext.user_options + [
564 564 ('long-paths-support', None, 'enable support for long paths on '
565 565 'Windows (off by default and '
566 566 'experimental)'),
567 567 ]
568 568
569 569 LONG_PATHS_MANIFEST = """
570 570 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
571 571 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
572 572 <application>
573 573 <windowsSettings
574 574 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
575 575 <ws2:longPathAware>true</ws2:longPathAware>
576 576 </windowsSettings>
577 577 </application>
578 578 </assembly>"""
579 579
580 580 def initialize_options(self):
581 581 build_ext.initialize_options(self)
582 582 self.long_paths_support = False
583 583
584 584 def build_extensions(self):
585 585 if os.name != 'nt':
586 586 return
587 587 if isinstance(self.compiler, HackedMingw32CCompiler):
588 588 self.compiler.compiler_so = self.compiler.compiler # no -mdll
589 589 self.compiler.dll_libraries = [] # no -lmsrvc90
590 590
591 591 # Different Python installs can have different Python library
592 592 # names. e.g. the official CPython distribution uses pythonXY.dll
593 593 # and MinGW uses libpythonX.Y.dll.
594 594 _kernel32 = ctypes.windll.kernel32
595 595 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
596 596 ctypes.c_void_p,
597 597 ctypes.c_ulong]
598 598 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
599 599 size = 1000
600 600 buf = ctypes.create_string_buffer(size + 1)
601 601 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
602 602 size)
603 603
604 604 if filelen > 0 and filelen != size:
605 605 dllbasename = os.path.basename(buf.value)
606 606 if not dllbasename.lower().endswith('.dll'):
607 607 raise SystemExit('Python DLL does not end with .dll: %s' %
608 608 dllbasename)
609 609 pythonlib = dllbasename[:-4]
610 610 else:
611 611 log.warn('could not determine Python DLL filename; '
612 612 'assuming pythonXY')
613 613
614 614 hv = sys.hexversion
615 615 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
616 616
617 617 log.info('using %s as Python library name' % pythonlib)
618 618 with open('mercurial/hgpythonlib.h', 'wb') as f:
619 619 f.write('/* this file is autogenerated by setup.py */\n')
620 620 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
621 621 objects = self.compiler.compile(['mercurial/exewrapper.c'],
622 622 output_dir=self.build_temp)
623 623 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
624 624 self.hgtarget = os.path.join(dir, 'hg')
625 625 self.compiler.link_executable(objects, self.hgtarget,
626 626 libraries=[],
627 627 output_dir=self.build_temp)
628 628 if self.long_paths_support:
629 629 self.addlongpathsmanifest()
630 630
631 631 def addlongpathsmanifest(self):
632 632 """Add manifest pieces so that hg.exe understands long paths
633 633
634 634 This is an EXPERIMENTAL feature, use with care.
635 635 To enable long paths support, one needs to do two things:
636 636 - build Mercurial with --long-paths-support option
637 637 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
638 638 LongPathsEnabled to have value 1.
639 639
640 640 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
641 641 it happens because Mercurial uses mt.exe circa 2008, which is not
642 642 yet aware of long paths support in the manifest (I think so at least).
643 643 This does not stop mt.exe from embedding/merging the XML properly.
644 644
645 645 Why resource #1 should be used for .exe manifests? I don't know and
646 646 wasn't able to find an explanation for mortals. But it seems to work.
647 647 """
648 648 exefname = self.compiler.executable_filename(self.hgtarget)
649 649 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
650 650 os.close(fdauto)
651 651 with open(manfname, 'w') as f:
652 652 f.write(self.LONG_PATHS_MANIFEST)
653 653 log.info("long paths manifest is written to '%s'" % manfname)
654 654 inputresource = '-inputresource:%s;#1' % exefname
655 655 outputresource = '-outputresource:%s;#1' % exefname
656 656 log.info("running mt.exe to update hg.exe's manifest in-place")
657 657 # supplying both -manifest and -inputresource to mt.exe makes
658 658 # it merge the embedded and supplied manifests in the -outputresource
659 659 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
660 660 inputresource, outputresource])
661 661 log.info("done updating hg.exe's manifest")
662 662 os.remove(manfname)
663 663
664 664 @property
665 665 def hgexepath(self):
666 666 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
667 667 return os.path.join(self.build_temp, dir, 'hg.exe')
668 668
669 669 class hginstall(install):
670 670
671 671 user_options = install.user_options + [
672 672 ('old-and-unmanageable', None,
673 673 'noop, present for eggless setuptools compat'),
674 674 ('single-version-externally-managed', None,
675 675 'noop, present for eggless setuptools compat'),
676 676 ]
677 677
678 678 # Also helps setuptools not be sad while we refuse to create eggs.
679 679 single_version_externally_managed = True
680 680
681 681 def get_sub_commands(self):
682 682 # Screen out egg related commands to prevent egg generation. But allow
683 683 # mercurial.egg-info generation, since that is part of modern
684 684 # packaging.
685 685 excl = set(['bdist_egg'])
686 686 return filter(lambda x: x not in excl, install.get_sub_commands(self))
687 687
688 688 class hginstalllib(install_lib):
689 689 '''
690 690 This is a specialization of install_lib that replaces the copy_file used
691 691 there so that it supports setting the mode of files after copying them,
692 692 instead of just preserving the mode that the files originally had. If your
693 693 system has a umask of something like 027, preserving the permissions when
694 694 copying will lead to a broken install.
695 695
696 696 Note that just passing keep_permissions=False to copy_file would be
697 697 insufficient, as it might still be applying a umask.
698 698 '''
699 699
700 700 def run(self):
701 701 realcopyfile = file_util.copy_file
702 702 def copyfileandsetmode(*args, **kwargs):
703 703 src, dst = args[0], args[1]
704 704 dst, copied = realcopyfile(*args, **kwargs)
705 705 if copied:
706 706 st = os.stat(src)
707 707 # Persist executable bit (apply it to group and other if user
708 708 # has it)
709 709 if st[stat.ST_MODE] & stat.S_IXUSR:
710 710 setmode = int('0755', 8)
711 711 else:
712 712 setmode = int('0644', 8)
713 713 m = stat.S_IMODE(st[stat.ST_MODE])
714 714 m = (m & ~int('0777', 8)) | setmode
715 715 os.chmod(dst, m)
716 716 file_util.copy_file = copyfileandsetmode
717 717 try:
718 718 install_lib.run(self)
719 719 finally:
720 720 file_util.copy_file = realcopyfile
721 721
722 722 class hginstallscripts(install_scripts):
723 723 '''
724 724 This is a specialization of install_scripts that replaces the @LIBDIR@ with
725 725 the configured directory for modules. If possible, the path is made relative
726 726 to the directory for scripts.
727 727 '''
728 728
729 729 def initialize_options(self):
730 730 install_scripts.initialize_options(self)
731 731
732 732 self.install_lib = None
733 733
734 734 def finalize_options(self):
735 735 install_scripts.finalize_options(self)
736 736 self.set_undefined_options('install',
737 737 ('install_lib', 'install_lib'))
738 738
739 739 def run(self):
740 740 install_scripts.run(self)
741 741
742 742 # It only makes sense to replace @LIBDIR@ with the install path if
743 743 # the install path is known. For wheels, the logic below calculates
744 744 # the libdir to be "../..". This is because the internal layout of a
745 745 # wheel archive looks like:
746 746 #
747 747 # mercurial-3.6.1.data/scripts/hg
748 748 # mercurial/__init__.py
749 749 #
750 750 # When installing wheels, the subdirectories of the "<pkg>.data"
751 751 # directory are translated to system local paths and files therein
752 752 # are copied in place. The mercurial/* files are installed into the
753 753 # site-packages directory. However, the site-packages directory
754 754 # isn't known until wheel install time. This means we have no clue
755 755 # at wheel generation time what the installed site-packages directory
756 756 # will be. And, wheels don't appear to provide the ability to register
757 757 # custom code to run during wheel installation. This all means that
758 758 # we can't reliably set the libdir in wheels: the default behavior
759 759 # of looking in sys.path must do.
760 760
761 761 if (os.path.splitdrive(self.install_dir)[0] !=
762 762 os.path.splitdrive(self.install_lib)[0]):
763 763 # can't make relative paths from one drive to another, so use an
764 764 # absolute path instead
765 765 libdir = self.install_lib
766 766 else:
767 767 common = os.path.commonprefix((self.install_dir, self.install_lib))
768 768 rest = self.install_dir[len(common):]
769 769 uplevel = len([n for n in os.path.split(rest) if n])
770 770
771 771 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
772 772
773 773 for outfile in self.outfiles:
774 774 with open(outfile, 'rb') as fp:
775 775 data = fp.read()
776 776
777 777 # skip binary files
778 778 if b'\0' in data:
779 779 continue
780 780
781 781 # During local installs, the shebang will be rewritten to the final
782 782 # install path. During wheel packaging, the shebang has a special
783 783 # value.
784 784 if data.startswith(b'#!python'):
785 785 log.info('not rewriting @LIBDIR@ in %s because install path '
786 786 'not known' % outfile)
787 787 continue
788 788
789 789 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
790 790 with open(outfile, 'wb') as fp:
791 791 fp.write(data)
792 792
793 793 cmdclass = {'build': hgbuild,
794 794 'build_mo': hgbuildmo,
795 795 'build_ext': hgbuildext,
796 796 'build_py': hgbuildpy,
797 797 'build_scripts': hgbuildscripts,
798 798 'build_hgextindex': buildhgextindex,
799 799 'install': hginstall,
800 800 'install_lib': hginstalllib,
801 801 'install_scripts': hginstallscripts,
802 802 'build_hgexe': buildhgexe,
803 803 }
804 804
805 805 packages = ['mercurial',
806 806 'mercurial.cext',
807 807 'mercurial.cffi',
808 808 'mercurial.hgweb',
809 'mercurial.httpclient',
810 809 'mercurial.pure',
811 810 'mercurial.thirdparty',
812 811 'mercurial.thirdparty.attr',
813 812 'hgext', 'hgext.convert', 'hgext.fsmonitor',
814 813 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
815 814 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
816 815 'hgext.zeroconf', 'hgext3rd',
817 816 'hgdemandimport']
818 817
819 818 common_depends = ['mercurial/bitmanipulation.h',
820 819 'mercurial/compat.h',
821 820 'mercurial/cext/util.h']
822 821 common_include_dirs = ['mercurial']
823 822
824 823 osutil_cflags = []
825 824 osutil_ldflags = []
826 825
827 826 # platform specific macros
828 827 for plat, func in [('bsd', 'setproctitle')]:
829 828 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
830 829 osutil_cflags.append('-DHAVE_%s' % func.upper())
831 830
832 831 for plat, macro, code in [
833 832 ('bsd|darwin', 'BSD_STATFS', '''
834 833 #include <sys/param.h>
835 834 #include <sys/mount.h>
836 835 int main() { struct statfs s; return sizeof(s.f_fstypename); }
837 836 '''),
838 837 ('linux', 'LINUX_STATFS', '''
839 838 #include <linux/magic.h>
840 839 #include <sys/vfs.h>
841 840 int main() { struct statfs s; return sizeof(s.f_type); }
842 841 '''),
843 842 ]:
844 843 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
845 844 osutil_cflags.append('-DHAVE_%s' % macro)
846 845
847 846 if sys.platform == 'darwin':
848 847 osutil_ldflags += ['-framework', 'ApplicationServices']
849 848
850 849 extmodules = [
851 850 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
852 851 include_dirs=common_include_dirs,
853 852 depends=common_depends),
854 853 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
855 854 'mercurial/cext/bdiff.c'],
856 855 include_dirs=common_include_dirs,
857 856 depends=common_depends + ['mercurial/bdiff.h']),
858 857 Extension('mercurial.cext.diffhelpers', ['mercurial/cext/diffhelpers.c'],
859 858 include_dirs=common_include_dirs,
860 859 depends=common_depends),
861 860 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
862 861 'mercurial/cext/mpatch.c'],
863 862 include_dirs=common_include_dirs,
864 863 depends=common_depends),
865 864 Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
866 865 'mercurial/cext/dirs.c',
867 866 'mercurial/cext/manifest.c',
868 867 'mercurial/cext/parsers.c',
869 868 'mercurial/cext/pathencode.c',
870 869 'mercurial/cext/revlog.c'],
871 870 include_dirs=common_include_dirs,
872 871 depends=common_depends + ['mercurial/cext/charencode.h']),
873 872 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
874 873 include_dirs=common_include_dirs,
875 874 extra_compile_args=osutil_cflags,
876 875 extra_link_args=osutil_ldflags,
877 876 depends=common_depends),
878 877 Extension('hgext.fsmonitor.pywatchman.bser',
879 878 ['hgext/fsmonitor/pywatchman/bser.c']),
880 879 ]
881 880
882 881 sys.path.insert(0, 'contrib/python-zstandard')
883 882 import setup_zstd
884 883 extmodules.append(setup_zstd.get_c_extension(name='mercurial.zstd'))
885 884
886 885 try:
887 886 from distutils import cygwinccompiler
888 887
889 888 # the -mno-cygwin option has been deprecated for years
890 889 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
891 890
892 891 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
893 892 def __init__(self, *args, **kwargs):
894 893 mingw32compilerclass.__init__(self, *args, **kwargs)
895 894 for i in 'compiler compiler_so linker_exe linker_so'.split():
896 895 try:
897 896 getattr(self, i).remove('-mno-cygwin')
898 897 except ValueError:
899 898 pass
900 899
901 900 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
902 901 except ImportError:
903 902 # the cygwinccompiler package is not available on some Python
904 903 # distributions like the ones from the optware project for Synology
905 904 # DiskStation boxes
906 905 class HackedMingw32CCompiler(object):
907 906 pass
908 907
909 908 if os.name == 'nt':
910 909 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
911 910 # extra_link_args to distutils.extensions.Extension() doesn't have any
912 911 # effect.
913 912 from distutils import msvccompiler
914 913
915 914 msvccompilerclass = msvccompiler.MSVCCompiler
916 915
917 916 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
918 917 def initialize(self):
919 918 msvccompilerclass.initialize(self)
920 919 # "warning LNK4197: export 'func' specified multiple times"
921 920 self.ldflags_shared.append('/ignore:4197')
922 921 self.ldflags_shared_debug.append('/ignore:4197')
923 922
924 923 msvccompiler.MSVCCompiler = HackedMSVCCompiler
925 924
926 925 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
927 926 'help/*.txt',
928 927 'help/internals/*.txt',
929 928 'default.d/*.rc',
930 929 'dummycert.pem']}
931 930
932 931 def ordinarypath(p):
933 932 return p and p[0] != '.' and p[-1] != '~'
934 933
935 934 for root in ('templates',):
936 935 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
937 936 curdir = curdir.split(os.sep, 1)[1]
938 937 dirs[:] = filter(ordinarypath, dirs)
939 938 for f in filter(ordinarypath, files):
940 939 f = os.path.join(curdir, f)
941 940 packagedata['mercurial'].append(f)
942 941
943 942 datafiles = []
944 943
945 944 # distutils expects version to be str/unicode. Converting it to
946 945 # unicode on Python 2 still works because it won't contain any
947 946 # non-ascii bytes and will be implicitly converted back to bytes
948 947 # when operated on.
949 948 assert isinstance(version, bytes)
950 949 setupversion = version.decode('ascii')
951 950
952 951 extra = {}
953 952
954 953 if issetuptools:
955 954 extra['python_requires'] = supportedpy
956 955 if py2exeloaded:
957 956 extra['console'] = [
958 957 {'script':'hg',
959 958 'copyright':'Copyright (C) 2005-2018 Matt Mackall and others',
960 959 'product_version':version}]
961 960 # sub command of 'build' because 'py2exe' does not handle sub_commands
962 961 build.sub_commands.insert(0, ('build_hgextindex', None))
963 962 # put dlls in sub directory so that they won't pollute PATH
964 963 extra['zipfile'] = 'lib/library.zip'
965 964
966 965 if os.name == 'nt':
967 966 # Windows binary file versions for exe/dll files must have the
968 967 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
969 968 setupversion = version.split('+', 1)[0]
970 969
971 970 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
972 971 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
973 972 if version:
974 973 version = version[0]
975 974 if sys.version_info[0] == 3:
976 975 version = version.decode('utf-8')
977 976 xcode4 = (version.startswith('Xcode') and
978 977 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
979 978 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
980 979 else:
981 980 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
982 981 # installed, but instead with only command-line tools. Assume
983 982 # that only happens on >= Lion, thus no PPC support.
984 983 xcode4 = True
985 984 xcode51 = False
986 985
987 986 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
988 987 # distutils.sysconfig
989 988 if xcode4:
990 989 os.environ['ARCHFLAGS'] = ''
991 990
992 991 # XCode 5.1 changes clang such that it now fails to compile if the
993 992 # -mno-fused-madd flag is passed, but the version of Python shipped with
994 993 # OS X 10.9 Mavericks includes this flag. This causes problems in all
995 994 # C extension modules, and a bug has been filed upstream at
996 995 # http://bugs.python.org/issue21244. We also need to patch this here
997 996 # so Mercurial can continue to compile in the meantime.
998 997 if xcode51:
999 998 cflags = get_config_var('CFLAGS')
1000 999 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1001 1000 os.environ['CFLAGS'] = (
1002 1001 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1003 1002
1004 1003 setup(name='mercurial',
1005 1004 version=setupversion,
1006 1005 author='Matt Mackall and many others',
1007 1006 author_email='mercurial@mercurial-scm.org',
1008 1007 url='https://mercurial-scm.org/',
1009 1008 download_url='https://mercurial-scm.org/release/',
1010 1009 description=('Fast scalable distributed SCM (revision control, version '
1011 1010 'control) system'),
1012 1011 long_description=('Mercurial is a distributed SCM tool written in Python.'
1013 1012 ' It is used by a number of large projects that require'
1014 1013 ' fast, reliable distributed revision control, such as '
1015 1014 'Mozilla.'),
1016 1015 license='GNU GPLv2 or any later version',
1017 1016 classifiers=[
1018 1017 'Development Status :: 6 - Mature',
1019 1018 'Environment :: Console',
1020 1019 'Intended Audience :: Developers',
1021 1020 'Intended Audience :: System Administrators',
1022 1021 'License :: OSI Approved :: GNU General Public License (GPL)',
1023 1022 'Natural Language :: Danish',
1024 1023 'Natural Language :: English',
1025 1024 'Natural Language :: German',
1026 1025 'Natural Language :: Italian',
1027 1026 'Natural Language :: Japanese',
1028 1027 'Natural Language :: Portuguese (Brazilian)',
1029 1028 'Operating System :: Microsoft :: Windows',
1030 1029 'Operating System :: OS Independent',
1031 1030 'Operating System :: POSIX',
1032 1031 'Programming Language :: C',
1033 1032 'Programming Language :: Python',
1034 1033 'Topic :: Software Development :: Version Control',
1035 1034 ],
1036 1035 scripts=scripts,
1037 1036 packages=packages,
1038 1037 ext_modules=extmodules,
1039 1038 data_files=datafiles,
1040 1039 package_data=packagedata,
1041 1040 cmdclass=cmdclass,
1042 1041 distclass=hgdist,
1043 1042 options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
1044 1043 # implicitly imported per module policy
1045 1044 # (cffi wouldn't be used as a frozen exe)
1046 1045 'mercurial.cext',
1047 1046 #'mercurial.cffi',
1048 1047 'mercurial.pure']},
1049 1048 'bdist_mpkg': {'zipdist': False,
1050 1049 'license': 'COPYING',
1051 1050 'readme': 'contrib/macosx/Readme.html',
1052 1051 'welcome': 'contrib/macosx/Welcome.html',
1053 1052 },
1054 1053 },
1055 1054 **extra)
@@ -1,55 +1,53
1 1 #require test-repo
2 2
3 3 $ . "$TESTDIR/helpers-testrepo.sh"
4 4 $ check_code="$TESTDIR"/../contrib/check-code.py
5 5 $ cd "$TESTDIR"/..
6 6
7 7 New errors are not allowed. Warnings are strongly discouraged.
8 8 (The writing "no-che?k-code" is for not skipping this file when checking.)
9 9
10 10 $ testrepohg locate \
11 11 > -X contrib/python-zstandard \
12 12 > -X hgext/fsmonitor/pywatchman \
13 13 > -X mercurial/thirdparty \
14 14 > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
15 15 Skipping i18n/polib.py it has no-che?k-code (glob)
16 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
17 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
18 16 Skipping mercurial/statprof.py it has no-che?k-code (glob)
19 17 Skipping tests/badserverext.py it has no-che?k-code (glob)
20 18
21 19 @commands in debugcommands.py should be in alphabetical order.
22 20
23 21 >>> import re
24 22 >>> commands = []
25 23 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
26 24 ... for line in fh:
27 25 ... m = re.match("^@command\('([a-z]+)", line)
28 26 ... if m:
29 27 ... commands.append(m.group(1))
30 28 >>> scommands = list(sorted(commands))
31 29 >>> for i, command in enumerate(scommands):
32 30 ... if command != commands[i]:
33 31 ... print('commands in debugcommands.py not sorted; first differing '
34 32 ... 'command is %s; expected %s' % (commands[i], command))
35 33 ... break
36 34
37 35 Prevent adding new files in the root directory accidentally.
38 36
39 37 $ testrepohg files 'glob:*'
40 38 .arcconfig
41 39 .clang-format
42 40 .editorconfig
43 41 .hgignore
44 42 .hgsigs
45 43 .hgtags
46 44 .jshintrc
47 45 CONTRIBUTING
48 46 CONTRIBUTORS
49 47 COPYING
50 48 Makefile
51 49 README.rst
52 50 hg
53 51 hgeditor
54 52 hgweb.cgi
55 53 setup.py
@@ -1,1024 +1,1022
1 1 #if windows
2 2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 3 #else
4 4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 5 #endif
6 6 $ export PYTHONPATH
7 7
8 8 typical client does not want echo-back messages, so test without it:
9 9
10 10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 11 $ mv $HGRCPATH.new $HGRCPATH
12 12
13 13 $ hg init repo
14 14 $ cd repo
15 15
16 16 >>> from __future__ import absolute_import, print_function
17 17 >>> import os
18 18 >>> import sys
19 19 >>> from hgclient import check, readchannel, runcommand
20 20 >>> @check
21 21 ... def hellomessage(server):
22 22 ... ch, data = readchannel(server)
23 23 ... print('%c, %r' % (ch, data))
24 24 ... # run an arbitrary command to make sure the next thing the server
25 25 ... # sends isn't part of the hello message
26 26 ... runcommand(server, ['id'])
27 27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
28 28 *** runcommand id
29 29 000000000000 tip
30 30
31 31 >>> from hgclient import check
32 32 >>> @check
33 33 ... def unknowncommand(server):
34 34 ... server.stdin.write('unknowncommand\n')
35 35 abort: unknown command unknowncommand
36 36
37 37 >>> from hgclient import check, readchannel, runcommand
38 38 >>> @check
39 39 ... def checkruncommand(server):
40 40 ... # hello block
41 41 ... readchannel(server)
42 42 ...
43 43 ... # no args
44 44 ... runcommand(server, [])
45 45 ...
46 46 ... # global options
47 47 ... runcommand(server, ['id', '--quiet'])
48 48 ...
49 49 ... # make sure global options don't stick through requests
50 50 ... runcommand(server, ['id'])
51 51 ...
52 52 ... # --config
53 53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
54 54 ...
55 55 ... # make sure --config doesn't stick
56 56 ... runcommand(server, ['id'])
57 57 ...
58 58 ... # negative return code should be masked
59 59 ... runcommand(server, ['id', '-runknown'])
60 60 *** runcommand
61 61 Mercurial Distributed SCM
62 62
63 63 basic commands:
64 64
65 65 add add the specified files on the next commit
66 66 annotate show changeset information by line for each file
67 67 clone make a copy of an existing repository
68 68 commit commit the specified files or all outstanding changes
69 69 diff diff repository (or selected files)
70 70 export dump the header and diffs for one or more changesets
71 71 forget forget the specified files on the next commit
72 72 init create a new repository in the given directory
73 73 log show revision history of entire repository or files
74 74 merge merge another revision into working directory
75 75 pull pull changes from the specified source
76 76 push push changes to the specified destination
77 77 remove remove the specified files on the next commit
78 78 serve start stand-alone webserver
79 79 status show changed files in the working directory
80 80 summary summarize working directory state
81 81 update update working directory (or switch revisions)
82 82
83 83 (use 'hg help' for the full list of commands or 'hg -v' for details)
84 84 *** runcommand id --quiet
85 85 000000000000
86 86 *** runcommand id
87 87 000000000000 tip
88 88 *** runcommand id --config ui.quiet=True
89 89 000000000000
90 90 *** runcommand id
91 91 000000000000 tip
92 92 *** runcommand id -runknown
93 93 abort: unknown revision 'unknown'!
94 94 [255]
95 95
96 96 >>> from hgclient import check, readchannel
97 97 >>> @check
98 98 ... def inputeof(server):
99 99 ... readchannel(server)
100 100 ... server.stdin.write('runcommand\n')
101 101 ... # close stdin while server is waiting for input
102 102 ... server.stdin.close()
103 103 ...
104 104 ... # server exits with 1 if the pipe closed while reading the command
105 105 ... print('server exit code =', server.wait())
106 106 server exit code = 1
107 107
108 108 >>> from hgclient import check, readchannel, runcommand, stringio
109 109 >>> @check
110 110 ... def serverinput(server):
111 111 ... readchannel(server)
112 112 ...
113 113 ... patch = """
114 114 ... # HG changeset patch
115 115 ... # User test
116 116 ... # Date 0 0
117 117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
118 118 ... # Parent 0000000000000000000000000000000000000000
119 119 ... 1
120 120 ...
121 121 ... diff -r 000000000000 -r c103a3dec114 a
122 122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 124 ... @@ -0,0 +1,1 @@
125 125 ... +1
126 126 ... """
127 127 ...
128 128 ... runcommand(server, ['import', '-'], input=stringio(patch))
129 129 ... runcommand(server, ['log'])
130 130 *** runcommand import -
131 131 applying patch from stdin
132 132 *** runcommand log
133 133 changeset: 0:eff892de26ec
134 134 tag: tip
135 135 user: test
136 136 date: Thu Jan 01 00:00:00 1970 +0000
137 137 summary: 1
138 138
139 139
140 140 check strict parsing of early options:
141 141
142 142 >>> import os
143 143 >>> from hgclient import check, readchannel, runcommand
144 144 >>> os.environ['HGPLAIN'] = '+strictflags'
145 145 >>> @check
146 146 ... def cwd(server):
147 147 ... readchannel(server)
148 148 ... runcommand(server, ['log', '-b', '--config=alias.log=!echo pwned',
149 149 ... 'default'])
150 150 *** runcommand log -b --config=alias.log=!echo pwned default
151 151 abort: unknown revision '--config=alias.log=!echo pwned'!
152 152 [255]
153 153
154 154 check that "histedit --commands=-" can read rules from the input channel:
155 155
156 156 >>> import cStringIO
157 157 >>> from hgclient import check, readchannel, runcommand
158 158 >>> @check
159 159 ... def serverinput(server):
160 160 ... readchannel(server)
161 161 ... rules = 'pick eff892de26ec\n'
162 162 ... runcommand(server, ['histedit', '0', '--commands=-',
163 163 ... '--config', 'extensions.histedit='],
164 164 ... input=cStringIO.StringIO(rules))
165 165 *** runcommand histedit 0 --commands=- --config extensions.histedit=
166 166
167 167 check that --cwd doesn't persist between requests:
168 168
169 169 $ mkdir foo
170 170 $ touch foo/bar
171 171 >>> from hgclient import check, readchannel, runcommand
172 172 >>> @check
173 173 ... def cwd(server):
174 174 ... readchannel(server)
175 175 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
176 176 ... runcommand(server, ['st', 'foo/bar'])
177 177 *** runcommand --cwd foo st bar
178 178 ? bar
179 179 *** runcommand st foo/bar
180 180 ? foo/bar
181 181
182 182 $ rm foo/bar
183 183
184 184
185 185 check that local configs for the cached repo aren't inherited when -R is used:
186 186
187 187 $ cat <<EOF >> .hg/hgrc
188 188 > [ui]
189 189 > foo = bar
190 190 > EOF
191 191
192 192 >>> from hgclient import check, readchannel, runcommand, sep
193 193 >>> @check
194 194 ... def localhgrc(server):
195 195 ... readchannel(server)
196 196 ...
197 197 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
198 198 ... # show it
199 199 ... runcommand(server, ['showconfig'], outfilter=sep)
200 200 ...
201 201 ... # but not for this repo
202 202 ... runcommand(server, ['init', 'foo'])
203 203 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
204 204 *** runcommand showconfig
205 205 bundle.mainreporoot=$TESTTMP/repo
206 206 devel.all-warnings=true
207 207 devel.default-date=0 0
208 208 extensions.fsmonitor= (fsmonitor !)
209 209 largefiles.usercache=$TESTTMP/.cache/largefiles
210 210 lfs.usercache=$TESTTMP/.cache/lfs
211 211 ui.slash=True
212 212 ui.interactive=False
213 213 ui.mergemarkers=detailed
214 ui.usehttp2=true (?)
215 214 ui.foo=bar
216 215 ui.nontty=true
217 216 web.address=localhost
218 217 web\.ipv6=(?:True|False) (re)
219 218 *** runcommand init foo
220 219 *** runcommand -R foo showconfig ui defaults
221 220 ui.slash=True
222 221 ui.interactive=False
223 222 ui.mergemarkers=detailed
224 ui.usehttp2=true (?)
225 223 ui.nontty=true
226 224
227 225 $ rm -R foo
228 226
229 227 #if windows
230 228 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
231 229 #else
232 230 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
233 231 #endif
234 232
235 233 $ cat <<EOF > hook.py
236 234 > from __future__ import print_function
237 235 > import sys
238 236 > def hook(**args):
239 237 > print('hook talking')
240 238 > print('now try to read something: %r' % sys.stdin.read())
241 239 > EOF
242 240
243 241 >>> from hgclient import check, readchannel, runcommand, stringio
244 242 >>> @check
245 243 ... def hookoutput(server):
246 244 ... readchannel(server)
247 245 ... runcommand(server, ['--config',
248 246 ... 'hooks.pre-identify=python:hook.hook',
249 247 ... 'id'],
250 248 ... input=stringio('some input'))
251 249 *** runcommand --config hooks.pre-identify=python:hook.hook id
252 250 eff892de26ec tip
253 251
254 252 Clean hook cached version
255 253 $ rm hook.py*
256 254 $ rm -Rf __pycache__
257 255
258 256 $ echo a >> a
259 257 >>> import os
260 258 >>> from hgclient import check, readchannel, runcommand
261 259 >>> @check
262 260 ... def outsidechanges(server):
263 261 ... readchannel(server)
264 262 ... runcommand(server, ['status'])
265 263 ... os.system('hg ci -Am2')
266 264 ... runcommand(server, ['tip'])
267 265 ... runcommand(server, ['status'])
268 266 *** runcommand status
269 267 M a
270 268 *** runcommand tip
271 269 changeset: 1:d3a0a68be6de
272 270 tag: tip
273 271 user: test
274 272 date: Thu Jan 01 00:00:00 1970 +0000
275 273 summary: 2
276 274
277 275 *** runcommand status
278 276
279 277 >>> import os
280 278 >>> from hgclient import check, readchannel, runcommand
281 279 >>> @check
282 280 ... def bookmarks(server):
283 281 ... readchannel(server)
284 282 ... runcommand(server, ['bookmarks'])
285 283 ...
286 284 ... # changes .hg/bookmarks
287 285 ... os.system('hg bookmark -i bm1')
288 286 ... os.system('hg bookmark -i bm2')
289 287 ... runcommand(server, ['bookmarks'])
290 288 ...
291 289 ... # changes .hg/bookmarks.current
292 290 ... os.system('hg upd bm1 -q')
293 291 ... runcommand(server, ['bookmarks'])
294 292 ...
295 293 ... runcommand(server, ['bookmarks', 'bm3'])
296 294 ... f = open('a', 'ab')
297 295 ... f.write('a\n')
298 296 ... f.close()
299 297 ... runcommand(server, ['commit', '-Amm'])
300 298 ... runcommand(server, ['bookmarks'])
301 299 ... print('')
302 300 *** runcommand bookmarks
303 301 no bookmarks set
304 302 *** runcommand bookmarks
305 303 bm1 1:d3a0a68be6de
306 304 bm2 1:d3a0a68be6de
307 305 *** runcommand bookmarks
308 306 * bm1 1:d3a0a68be6de
309 307 bm2 1:d3a0a68be6de
310 308 *** runcommand bookmarks bm3
311 309 *** runcommand commit -Amm
312 310 *** runcommand bookmarks
313 311 bm1 1:d3a0a68be6de
314 312 bm2 1:d3a0a68be6de
315 313 * bm3 2:aef17e88f5f0
316 314
317 315
318 316 >>> import os
319 317 >>> from hgclient import check, readchannel, runcommand
320 318 >>> @check
321 319 ... def tagscache(server):
322 320 ... readchannel(server)
323 321 ... runcommand(server, ['id', '-t', '-r', '0'])
324 322 ... os.system('hg tag -r 0 foo')
325 323 ... runcommand(server, ['id', '-t', '-r', '0'])
326 324 *** runcommand id -t -r 0
327 325
328 326 *** runcommand id -t -r 0
329 327 foo
330 328
331 329 >>> import os
332 330 >>> from hgclient import check, readchannel, runcommand
333 331 >>> @check
334 332 ... def setphase(server):
335 333 ... readchannel(server)
336 334 ... runcommand(server, ['phase', '-r', '.'])
337 335 ... os.system('hg phase -r . -p')
338 336 ... runcommand(server, ['phase', '-r', '.'])
339 337 *** runcommand phase -r .
340 338 3: draft
341 339 *** runcommand phase -r .
342 340 3: public
343 341
344 342 $ echo a >> a
345 343 >>> from hgclient import check, readchannel, runcommand
346 344 >>> @check
347 345 ... def rollback(server):
348 346 ... readchannel(server)
349 347 ... runcommand(server, ['phase', '-r', '.', '-p'])
350 348 ... runcommand(server, ['commit', '-Am.'])
351 349 ... runcommand(server, ['rollback'])
352 350 ... runcommand(server, ['phase', '-r', '.'])
353 351 ... print('')
354 352 *** runcommand phase -r . -p
355 353 no phases changed
356 354 *** runcommand commit -Am.
357 355 *** runcommand rollback
358 356 repository tip rolled back to revision 3 (undo commit)
359 357 working directory now based on revision 3
360 358 *** runcommand phase -r .
361 359 3: public
362 360
363 361
364 362 >>> import os
365 363 >>> from hgclient import check, readchannel, runcommand
366 364 >>> @check
367 365 ... def branch(server):
368 366 ... readchannel(server)
369 367 ... runcommand(server, ['branch'])
370 368 ... os.system('hg branch foo')
371 369 ... runcommand(server, ['branch'])
372 370 ... os.system('hg branch default')
373 371 *** runcommand branch
374 372 default
375 373 marked working directory as branch foo
376 374 (branches are permanent and global, did you want a bookmark?)
377 375 *** runcommand branch
378 376 foo
379 377 marked working directory as branch default
380 378 (branches are permanent and global, did you want a bookmark?)
381 379
382 380 $ touch .hgignore
383 381 >>> import os
384 382 >>> from hgclient import check, readchannel, runcommand
385 383 >>> @check
386 384 ... def hgignore(server):
387 385 ... readchannel(server)
388 386 ... runcommand(server, ['commit', '-Am.'])
389 387 ... f = open('ignored-file', 'ab')
390 388 ... f.write('')
391 389 ... f.close()
392 390 ... f = open('.hgignore', 'ab')
393 391 ... f.write('ignored-file')
394 392 ... f.close()
395 393 ... runcommand(server, ['status', '-i', '-u'])
396 394 ... print('')
397 395 *** runcommand commit -Am.
398 396 adding .hgignore
399 397 *** runcommand status -i -u
400 398 I ignored-file
401 399
402 400
403 401 cache of non-public revisions should be invalidated on repository change
404 402 (issue4855):
405 403
406 404 >>> import os
407 405 >>> from hgclient import check, readchannel, runcommand
408 406 >>> @check
409 407 ... def phasesetscacheaftercommit(server):
410 408 ... readchannel(server)
411 409 ... # load _phasecache._phaserevs and _phasesets
412 410 ... runcommand(server, ['log', '-qr', 'draft()'])
413 411 ... # create draft commits by another process
414 412 ... for i in range(5, 7):
415 413 ... f = open('a', 'ab')
416 414 ... f.seek(0, os.SEEK_END)
417 415 ... f.write('a\n')
418 416 ... f.close()
419 417 ... os.system('hg commit -Aqm%d' % i)
420 418 ... # new commits should be listed as draft revisions
421 419 ... runcommand(server, ['log', '-qr', 'draft()'])
422 420 ... print('')
423 421 *** runcommand log -qr draft()
424 422 4:7966c8e3734d
425 423 *** runcommand log -qr draft()
426 424 4:7966c8e3734d
427 425 5:41f6602d1c4f
428 426 6:10501e202c35
429 427
430 428
431 429 >>> import os
432 430 >>> from hgclient import check, readchannel, runcommand
433 431 >>> @check
434 432 ... def phasesetscacheafterstrip(server):
435 433 ... readchannel(server)
436 434 ... # load _phasecache._phaserevs and _phasesets
437 435 ... runcommand(server, ['log', '-qr', 'draft()'])
438 436 ... # strip cached revisions by another process
439 437 ... os.system('hg --config extensions.strip= strip -q 5')
440 438 ... # shouldn't abort by "unknown revision '6'"
441 439 ... runcommand(server, ['log', '-qr', 'draft()'])
442 440 ... print('')
443 441 *** runcommand log -qr draft()
444 442 4:7966c8e3734d
445 443 5:41f6602d1c4f
446 444 6:10501e202c35
447 445 *** runcommand log -qr draft()
448 446 4:7966c8e3734d
449 447
450 448
451 449 cache of phase roots should be invalidated on strip (issue3827):
452 450
453 451 >>> import os
454 452 >>> from hgclient import check, readchannel, runcommand, sep
455 453 >>> @check
456 454 ... def phasecacheafterstrip(server):
457 455 ... readchannel(server)
458 456 ...
459 457 ... # create new head, 5:731265503d86
460 458 ... runcommand(server, ['update', '-C', '0'])
461 459 ... f = open('a', 'ab')
462 460 ... f.write('a\n')
463 461 ... f.close()
464 462 ... runcommand(server, ['commit', '-Am.', 'a'])
465 463 ... runcommand(server, ['log', '-Gq'])
466 464 ...
467 465 ... # make it public; draft marker moves to 4:7966c8e3734d
468 466 ... runcommand(server, ['phase', '-p', '.'])
469 467 ... # load _phasecache.phaseroots
470 468 ... runcommand(server, ['phase', '.'], outfilter=sep)
471 469 ...
472 470 ... # strip 1::4 outside server
473 471 ... os.system('hg -q --config extensions.mq= strip 1')
474 472 ...
475 473 ... # shouldn't raise "7966c8e3734d: no node!"
476 474 ... runcommand(server, ['branches'])
477 475 *** runcommand update -C 0
478 476 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
479 477 (leaving bookmark bm3)
480 478 *** runcommand commit -Am. a
481 479 created new head
482 480 *** runcommand log -Gq
483 481 @ 5:731265503d86
484 482 |
485 483 | o 4:7966c8e3734d
486 484 | |
487 485 | o 3:b9b85890c400
488 486 | |
489 487 | o 2:aef17e88f5f0
490 488 | |
491 489 | o 1:d3a0a68be6de
492 490 |/
493 491 o 0:eff892de26ec
494 492
495 493 *** runcommand phase -p .
496 494 *** runcommand phase .
497 495 5: public
498 496 *** runcommand branches
499 497 default 1:731265503d86
500 498
501 499 in-memory cache must be reloaded if transaction is aborted. otherwise
502 500 changelog and manifest would have invalid node:
503 501
504 502 $ echo a >> a
505 503 >>> from hgclient import check, readchannel, runcommand
506 504 >>> @check
507 505 ... def txabort(server):
508 506 ... readchannel(server)
509 507 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
510 508 ... '-mfoo'])
511 509 ... runcommand(server, ['verify'])
512 510 *** runcommand commit --config hooks.pretxncommit=false -mfoo
513 511 transaction abort!
514 512 rollback completed
515 513 abort: pretxncommit hook exited with status 1
516 514 [255]
517 515 *** runcommand verify
518 516 checking changesets
519 517 checking manifests
520 518 crosschecking files in changesets and manifests
521 519 checking files
522 520 1 files, 2 changesets, 2 total revisions
523 521 $ hg revert --no-backup -aq
524 522
525 523 $ cat >> .hg/hgrc << EOF
526 524 > [experimental]
527 525 > evolution.createmarkers=True
528 526 > EOF
529 527
530 528 >>> import os
531 529 >>> from hgclient import check, readchannel, runcommand
532 530 >>> @check
533 531 ... def obsolete(server):
534 532 ... readchannel(server)
535 533 ...
536 534 ... runcommand(server, ['up', 'null'])
537 535 ... runcommand(server, ['phase', '-df', 'tip'])
538 536 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
539 537 ... if os.name == 'nt':
540 538 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
541 539 ... os.system(cmd)
542 540 ... runcommand(server, ['log', '--hidden'])
543 541 ... runcommand(server, ['log'])
544 542 *** runcommand up null
545 543 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
546 544 *** runcommand phase -df tip
547 545 obsoleted 1 changesets
548 546 *** runcommand log --hidden
549 547 changeset: 1:731265503d86
550 548 tag: tip
551 549 user: test
552 550 date: Thu Jan 01 00:00:00 1970 +0000
553 551 obsolete: pruned
554 552 summary: .
555 553
556 554 changeset: 0:eff892de26ec
557 555 bookmark: bm1
558 556 bookmark: bm2
559 557 bookmark: bm3
560 558 user: test
561 559 date: Thu Jan 01 00:00:00 1970 +0000
562 560 summary: 1
563 561
564 562 *** runcommand log
565 563 changeset: 0:eff892de26ec
566 564 bookmark: bm1
567 565 bookmark: bm2
568 566 bookmark: bm3
569 567 tag: tip
570 568 user: test
571 569 date: Thu Jan 01 00:00:00 1970 +0000
572 570 summary: 1
573 571
574 572
575 573 $ cat <<EOF >> .hg/hgrc
576 574 > [extensions]
577 575 > mq =
578 576 > EOF
579 577
580 578 >>> import os
581 579 >>> from hgclient import check, readchannel, runcommand
582 580 >>> @check
583 581 ... def mqoutsidechanges(server):
584 582 ... readchannel(server)
585 583 ...
586 584 ... # load repo.mq
587 585 ... runcommand(server, ['qapplied'])
588 586 ... os.system('hg qnew 0.diff')
589 587 ... # repo.mq should be invalidated
590 588 ... runcommand(server, ['qapplied'])
591 589 ...
592 590 ... runcommand(server, ['qpop', '--all'])
593 591 ... os.system('hg qqueue --create foo')
594 592 ... # repo.mq should be recreated to point to new queue
595 593 ... runcommand(server, ['qqueue', '--active'])
596 594 *** runcommand qapplied
597 595 *** runcommand qapplied
598 596 0.diff
599 597 *** runcommand qpop --all
600 598 popping 0.diff
601 599 patch queue now empty
602 600 *** runcommand qqueue --active
603 601 foo
604 602
605 603 $ cat <<EOF > dbgui.py
606 604 > import os
607 605 > import sys
608 606 > from mercurial import commands, registrar
609 607 > cmdtable = {}
610 608 > command = registrar.command(cmdtable)
611 609 > @command(b"debuggetpass", norepo=True)
612 610 > def debuggetpass(ui):
613 611 > ui.write("%s\\n" % ui.getpass())
614 612 > @command(b"debugprompt", norepo=True)
615 613 > def debugprompt(ui):
616 614 > ui.write("%s\\n" % ui.prompt("prompt:"))
617 615 > @command(b"debugreadstdin", norepo=True)
618 616 > def debugreadstdin(ui):
619 617 > ui.write("read: %r\n" % sys.stdin.read(1))
620 618 > @command(b"debugwritestdout", norepo=True)
621 619 > def debugwritestdout(ui):
622 620 > os.write(1, "low-level stdout fd and\n")
623 621 > sys.stdout.write("stdout should be redirected to /dev/null\n")
624 622 > sys.stdout.flush()
625 623 > EOF
626 624 $ cat <<EOF >> .hg/hgrc
627 625 > [extensions]
628 626 > dbgui = dbgui.py
629 627 > EOF
630 628
631 629 >>> from hgclient import check, readchannel, runcommand, stringio
632 630 >>> @check
633 631 ... def getpass(server):
634 632 ... readchannel(server)
635 633 ... runcommand(server, ['debuggetpass', '--config',
636 634 ... 'ui.interactive=True'],
637 635 ... input=stringio('1234\n'))
638 636 ... runcommand(server, ['debuggetpass', '--config',
639 637 ... 'ui.interactive=True'],
640 638 ... input=stringio('\n'))
641 639 ... runcommand(server, ['debuggetpass', '--config',
642 640 ... 'ui.interactive=True'],
643 641 ... input=stringio(''))
644 642 ... runcommand(server, ['debugprompt', '--config',
645 643 ... 'ui.interactive=True'],
646 644 ... input=stringio('5678\n'))
647 645 ... runcommand(server, ['debugreadstdin'])
648 646 ... runcommand(server, ['debugwritestdout'])
649 647 *** runcommand debuggetpass --config ui.interactive=True
650 648 password: 1234
651 649 *** runcommand debuggetpass --config ui.interactive=True
652 650 password:
653 651 *** runcommand debuggetpass --config ui.interactive=True
654 652 password: abort: response expected
655 653 [255]
656 654 *** runcommand debugprompt --config ui.interactive=True
657 655 prompt: 5678
658 656 *** runcommand debugreadstdin
659 657 read: ''
660 658 *** runcommand debugwritestdout
661 659
662 660
663 661 run commandserver in commandserver, which is silly but should work:
664 662
665 663 >>> from __future__ import print_function
666 664 >>> from hgclient import check, readchannel, runcommand, stringio
667 665 >>> @check
668 666 ... def nested(server):
669 667 ... print('%c, %r' % readchannel(server))
670 668 ... class nestedserver(object):
671 669 ... stdin = stringio('getencoding\n')
672 670 ... stdout = stringio()
673 671 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
674 672 ... output=nestedserver.stdout, input=nestedserver.stdin)
675 673 ... nestedserver.stdout.seek(0)
676 674 ... print('%c, %r' % readchannel(nestedserver)) # hello
677 675 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
678 676 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
679 677 *** runcommand serve --cmdserver pipe
680 678 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
681 679 r, '*' (glob)
682 680
683 681
684 682 start without repository:
685 683
686 684 $ cd ..
687 685
688 686 >>> from __future__ import print_function
689 687 >>> from hgclient import check, readchannel, runcommand
690 688 >>> @check
691 689 ... def hellomessage(server):
692 690 ... ch, data = readchannel(server)
693 691 ... print('%c, %r' % (ch, data))
694 692 ... # run an arbitrary command to make sure the next thing the server
695 693 ... # sends isn't part of the hello message
696 694 ... runcommand(server, ['id'])
697 695 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
698 696 *** runcommand id
699 697 abort: there is no Mercurial repository here (.hg not found)
700 698 [255]
701 699
702 700 >>> from hgclient import check, readchannel, runcommand
703 701 >>> @check
704 702 ... def startwithoutrepo(server):
705 703 ... readchannel(server)
706 704 ... runcommand(server, ['init', 'repo2'])
707 705 ... runcommand(server, ['id', '-R', 'repo2'])
708 706 *** runcommand init repo2
709 707 *** runcommand id -R repo2
710 708 000000000000 tip
711 709
712 710
713 711 don't fall back to cwd if invalid -R path is specified (issue4805):
714 712
715 713 $ cd repo
716 714 $ hg serve --cmdserver pipe -R ../nonexistent
717 715 abort: repository ../nonexistent not found!
718 716 [255]
719 717 $ cd ..
720 718
721 719
722 720 unix domain socket:
723 721
724 722 $ cd repo
725 723 $ hg update -q
726 724
727 725 #if unix-socket unix-permissions
728 726
729 727 >>> from __future__ import print_function
730 728 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
731 729 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
732 730 >>> def hellomessage(conn):
733 731 ... ch, data = readchannel(conn)
734 732 ... print('%c, %r' % (ch, data))
735 733 ... runcommand(conn, ['id'])
736 734 >>> check(hellomessage, server.connect)
737 735 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
738 736 *** runcommand id
739 737 eff892de26ec tip bm1/bm2/bm3
740 738 >>> def unknowncommand(conn):
741 739 ... readchannel(conn)
742 740 ... conn.stdin.write('unknowncommand\n')
743 741 >>> check(unknowncommand, server.connect) # error sent to server.log
744 742 >>> def serverinput(conn):
745 743 ... readchannel(conn)
746 744 ... patch = """
747 745 ... # HG changeset patch
748 746 ... # User test
749 747 ... # Date 0 0
750 748 ... 2
751 749 ...
752 750 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
753 751 ... --- a/a
754 752 ... +++ b/a
755 753 ... @@ -1,1 +1,2 @@
756 754 ... 1
757 755 ... +2
758 756 ... """
759 757 ... runcommand(conn, ['import', '-'], input=stringio(patch))
760 758 ... runcommand(conn, ['log', '-rtip', '-q'])
761 759 >>> check(serverinput, server.connect)
762 760 *** runcommand import -
763 761 applying patch from stdin
764 762 *** runcommand log -rtip -q
765 763 2:1ed24be7e7a0
766 764 >>> server.shutdown()
767 765
768 766 $ cat .hg/server.log
769 767 listening at .hg/server.sock
770 768 abort: unknown command unknowncommand
771 769 killed!
772 770 $ rm .hg/server.log
773 771
774 772 if server crashed before hello, traceback will be sent to 'e' channel as
775 773 last ditch:
776 774
777 775 $ cat <<EOF >> .hg/hgrc
778 776 > [cmdserver]
779 777 > log = inexistent/path.log
780 778 > EOF
781 779 >>> from __future__ import print_function
782 780 >>> from hgclient import check, readchannel, unixserver
783 781 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
784 782 >>> def earlycrash(conn):
785 783 ... while True:
786 784 ... try:
787 785 ... ch, data = readchannel(conn)
788 786 ... if not data.startswith(' '):
789 787 ... print('%c, %r' % (ch, data))
790 788 ... except EOFError:
791 789 ... break
792 790 >>> check(earlycrash, server.connect)
793 791 e, 'Traceback (most recent call last):\n'
794 792 e, "IOError: *" (glob)
795 793 >>> server.shutdown()
796 794
797 795 $ cat .hg/server.log | grep -v '^ '
798 796 listening at .hg/server.sock
799 797 Traceback (most recent call last):
800 798 IOError: * (glob)
801 799 killed!
802 800 #endif
803 801 #if no-unix-socket
804 802
805 803 $ hg serve --cmdserver unix -a .hg/server.sock
806 804 abort: unsupported platform
807 805 [255]
808 806
809 807 #endif
810 808
811 809 $ cd ..
812 810
813 811 Test that accessing to invalid changelog cache is avoided at
814 812 subsequent operations even if repo object is reused even after failure
815 813 of transaction (see 0a7610758c42 also)
816 814
817 815 "hg log" after failure of transaction is needed to detect invalid
818 816 cache in repoview: this can't detect by "hg verify" only.
819 817
820 818 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
821 819 4) are tested, because '00changelog.i' are differently changed in each
822 820 cases.
823 821
824 822 $ cat > $TESTTMP/failafterfinalize.py <<EOF
825 823 > # extension to abort transaction after finalization forcibly
826 824 > from mercurial import commands, error, extensions, lock as lockmod
827 825 > from mercurial import registrar
828 826 > cmdtable = {}
829 827 > command = registrar.command(cmdtable)
830 828 > configtable = {}
831 829 > configitem = registrar.configitem(configtable)
832 830 > configitem('failafterfinalize', 'fail',
833 831 > default=None,
834 832 > )
835 833 > def fail(tr):
836 834 > raise error.Abort('fail after finalization')
837 835 > def reposetup(ui, repo):
838 836 > class failrepo(repo.__class__):
839 837 > def commitctx(self, ctx, error=False):
840 838 > if self.ui.configbool('failafterfinalize', 'fail'):
841 839 > # 'sorted()' by ASCII code on category names causes
842 840 > # invoking 'fail' after finalization of changelog
843 841 > # using "'cl-%i' % id(self)" as category name
844 842 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
845 843 > return super(failrepo, self).commitctx(ctx, error)
846 844 > repo.__class__ = failrepo
847 845 > EOF
848 846
849 847 $ hg init repo3
850 848 $ cd repo3
851 849
852 850 $ cat <<EOF >> $HGRCPATH
853 851 > [ui]
854 852 > logtemplate = {rev} {desc|firstline} ({files})\n
855 853 >
856 854 > [extensions]
857 855 > failafterfinalize = $TESTTMP/failafterfinalize.py
858 856 > EOF
859 857
860 858 - test failure with "empty changelog"
861 859
862 860 $ echo foo > foo
863 861 $ hg add foo
864 862
865 863 (failure before finalization)
866 864
867 865 >>> from hgclient import check, readchannel, runcommand
868 866 >>> @check
869 867 ... def abort(server):
870 868 ... readchannel(server)
871 869 ... runcommand(server, ['commit',
872 870 ... '--config', 'hooks.pretxncommit=false',
873 871 ... '-mfoo'])
874 872 ... runcommand(server, ['log'])
875 873 ... runcommand(server, ['verify', '-q'])
876 874 *** runcommand commit --config hooks.pretxncommit=false -mfoo
877 875 transaction abort!
878 876 rollback completed
879 877 abort: pretxncommit hook exited with status 1
880 878 [255]
881 879 *** runcommand log
882 880 *** runcommand verify -q
883 881
884 882 (failure after finalization)
885 883
886 884 >>> from hgclient import check, readchannel, runcommand
887 885 >>> @check
888 886 ... def abort(server):
889 887 ... readchannel(server)
890 888 ... runcommand(server, ['commit',
891 889 ... '--config', 'failafterfinalize.fail=true',
892 890 ... '-mfoo'])
893 891 ... runcommand(server, ['log'])
894 892 ... runcommand(server, ['verify', '-q'])
895 893 *** runcommand commit --config failafterfinalize.fail=true -mfoo
896 894 transaction abort!
897 895 rollback completed
898 896 abort: fail after finalization
899 897 [255]
900 898 *** runcommand log
901 899 *** runcommand verify -q
902 900
903 901 - test failure with "not-empty changelog"
904 902
905 903 $ echo bar > bar
906 904 $ hg add bar
907 905 $ hg commit -mbar bar
908 906
909 907 (failure before finalization)
910 908
911 909 >>> from hgclient import check, readchannel, runcommand
912 910 >>> @check
913 911 ... def abort(server):
914 912 ... readchannel(server)
915 913 ... runcommand(server, ['commit',
916 914 ... '--config', 'hooks.pretxncommit=false',
917 915 ... '-mfoo', 'foo'])
918 916 ... runcommand(server, ['log'])
919 917 ... runcommand(server, ['verify', '-q'])
920 918 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
921 919 transaction abort!
922 920 rollback completed
923 921 abort: pretxncommit hook exited with status 1
924 922 [255]
925 923 *** runcommand log
926 924 0 bar (bar)
927 925 *** runcommand verify -q
928 926
929 927 (failure after finalization)
930 928
931 929 >>> from hgclient import check, readchannel, runcommand
932 930 >>> @check
933 931 ... def abort(server):
934 932 ... readchannel(server)
935 933 ... runcommand(server, ['commit',
936 934 ... '--config', 'failafterfinalize.fail=true',
937 935 ... '-mfoo', 'foo'])
938 936 ... runcommand(server, ['log'])
939 937 ... runcommand(server, ['verify', '-q'])
940 938 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
941 939 transaction abort!
942 940 rollback completed
943 941 abort: fail after finalization
944 942 [255]
945 943 *** runcommand log
946 944 0 bar (bar)
947 945 *** runcommand verify -q
948 946
949 947 $ cd ..
950 948
951 949 Test symlink traversal over cached audited paths:
952 950 -------------------------------------------------
953 951
954 952 #if symlink
955 953
956 954 set up symlink hell
957 955
958 956 $ mkdir merge-symlink-out
959 957 $ hg init merge-symlink
960 958 $ cd merge-symlink
961 959 $ touch base
962 960 $ hg commit -qAm base
963 961 $ ln -s ../merge-symlink-out a
964 962 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
965 963 $ hg up -q 0
966 964 $ mkdir a
967 965 $ touch a/poisoned
968 966 $ hg commit -qAm 'file a/poisoned'
969 967 $ hg log -G -T '{rev}: {desc}\n'
970 968 @ 2: file a/poisoned
971 969 |
972 970 | o 1: symlink a -> ../merge-symlink-out
973 971 |/
974 972 o 0: base
975 973
976 974
977 975 try trivial merge after update: cache of audited paths should be discarded,
978 976 and the merge should fail (issue5628)
979 977
980 978 $ hg up -q null
981 979 >>> from hgclient import check, readchannel, runcommand
982 980 >>> @check
983 981 ... def merge(server):
984 982 ... readchannel(server)
985 983 ... # audit a/poisoned as a good path
986 984 ... runcommand(server, ['up', '-qC', '2'])
987 985 ... runcommand(server, ['up', '-qC', '1'])
988 986 ... # here a is a symlink, so a/poisoned is bad
989 987 ... runcommand(server, ['merge', '2'])
990 988 *** runcommand up -qC 2
991 989 *** runcommand up -qC 1
992 990 *** runcommand merge 2
993 991 abort: path 'a/poisoned' traverses symbolic link 'a'
994 992 [255]
995 993 $ ls ../merge-symlink-out
996 994
997 995 cache of repo.auditor should be discarded, so matcher would never traverse
998 996 symlinks:
999 997
1000 998 $ hg up -qC 0
1001 999 $ touch ../merge-symlink-out/poisoned
1002 1000 >>> from hgclient import check, readchannel, runcommand
1003 1001 >>> @check
1004 1002 ... def files(server):
1005 1003 ... readchannel(server)
1006 1004 ... runcommand(server, ['up', '-qC', '2'])
1007 1005 ... # audit a/poisoned as a good path
1008 1006 ... runcommand(server, ['files', 'a/poisoned'])
1009 1007 ... runcommand(server, ['up', '-qC', '0'])
1010 1008 ... runcommand(server, ['up', '-qC', '1'])
1011 1009 ... # here 'a' is a symlink, so a/poisoned should be warned
1012 1010 ... runcommand(server, ['files', 'a/poisoned'])
1013 1011 *** runcommand up -qC 2
1014 1012 *** runcommand files a/poisoned
1015 1013 a/poisoned
1016 1014 *** runcommand up -qC 0
1017 1015 *** runcommand up -qC 1
1018 1016 *** runcommand files a/poisoned
1019 1017 abort: path 'a/poisoned' traverses symbolic link 'a'
1020 1018 [255]
1021 1019
1022 1020 $ cd ..
1023 1021
1024 1022 #endif
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (912 lines changed) Show them Hide them
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now