##// END OF EJS Templates
lookup: add option to disambiguate prefix within revset...
Martin von Zweigbergk -
r38878:503f9364 default
parent child Browse files
Show More
@@ -0,0 +1,37 b''
1 $ hg init repo
2 $ cd repo
3
4 $ echo 0 > a
5 $ hg ci -qAm 0
6 $ for i in 5 8 14 43; do
7 > hg up -q 0
8 > echo $i > a
9 > hg ci -qm $i
10 > done
11 $ cat <<EOF >> .hg/hgrc
12 > [alias]
13 > l = log -T '{rev}:{shortest(node,1)}\n'
14 > EOF
15
16 $ hg l
17 4:7ba5d
18 3:7ba57
19 2:72
20 1:9
21 0:b
22 $ cat <<EOF >> .hg/hgrc
23 > [experimental]
24 > revisions.disambiguatewithin=:3
25 > EOF
26 9 was unambiguous and still is
27 $ hg l -r 9
28 1:9
29 7 was ambiguous and still is
30 $ hg l -r 7
31 abort: 00changelog.i@7: ambiguous identifier!
32 [255]
33 7b is no longer ambiguous
34 $ hg l -r 7b
35 3:7ba57
36
37 $ cd ..
@@ -1,1389 +1,1392 b''
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import functools
11 11 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in sorted(configtable.items()):
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = "extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config='warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31 class configitem(object):
32 32 """represent a known config item
33 33
34 34 :section: the official config section where to find this item,
35 35 :name: the official name within the section,
36 36 :default: default value for this item,
37 37 :alias: optional list of tuples as alternatives,
38 38 :generic: this is a generic definition, match name using regular expression.
39 39 """
40 40
41 41 def __init__(self, section, name, default=None, alias=(),
42 42 generic=False, priority=0):
43 43 self.section = section
44 44 self.name = name
45 45 self.default = default
46 46 self.alias = list(alias)
47 47 self.generic = generic
48 48 self.priority = priority
49 49 self._re = None
50 50 if generic:
51 51 self._re = re.compile(self.name)
52 52
53 53 class itemregister(dict):
54 54 """A specialized dictionary that can handle wild-card selection"""
55 55
56 56 def __init__(self):
57 57 super(itemregister, self).__init__()
58 58 self._generics = set()
59 59
60 60 def update(self, other):
61 61 super(itemregister, self).update(other)
62 62 self._generics.update(other._generics)
63 63
64 64 def __setitem__(self, key, item):
65 65 super(itemregister, self).__setitem__(key, item)
66 66 if item.generic:
67 67 self._generics.add(item)
68 68
69 69 def get(self, key):
70 70 baseitem = super(itemregister, self).get(key)
71 71 if baseitem is not None and not baseitem.generic:
72 72 return baseitem
73 73
74 74 # search for a matching generic item
75 75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 76 for item in generics:
77 77 # we use 'match' instead of 'search' to make the matching simpler
78 78 # for people unfamiliar with regular expression. Having the match
79 79 # rooted to the start of the string will produce less surprising
80 80 # result for user writing simple regex for sub-attribute.
81 81 #
82 82 # For example using "color\..*" match produces an unsurprising
83 83 # result, while using search could suddenly match apparently
84 84 # unrelated configuration that happens to contains "color."
85 85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 86 # some match to avoid the need to prefix most pattern with "^".
87 87 # The "^" seems more error prone.
88 88 if item._re.match(key):
89 89 return item
90 90
91 91 return None
92 92
93 93 coreitems = {}
94 94
95 95 def _register(configtable, *args, **kwargs):
96 96 item = configitem(*args, **kwargs)
97 97 section = configtable.setdefault(item.section, itemregister())
98 98 if item.name in section:
99 99 msg = "duplicated config item registration for '%s.%s'"
100 100 raise error.ProgrammingError(msg % (item.section, item.name))
101 101 section[item.name] = item
102 102
103 103 # special value for case where the default is derived from other values
104 104 dynamicdefault = object()
105 105
106 106 # Registering actual config items
107 107
108 108 def getitemregister(configtable):
109 109 f = functools.partial(_register, configtable)
110 110 # export pseudo enum as configitem.*
111 111 f.dynamicdefault = dynamicdefault
112 112 return f
113 113
114 114 coreconfigitem = getitemregister(coreitems)
115 115
116 116 coreconfigitem('alias', '.*',
117 117 default=dynamicdefault,
118 118 generic=True,
119 119 )
120 120 coreconfigitem('annotate', 'nodates',
121 121 default=False,
122 122 )
123 123 coreconfigitem('annotate', 'showfunc',
124 124 default=False,
125 125 )
126 126 coreconfigitem('annotate', 'unified',
127 127 default=None,
128 128 )
129 129 coreconfigitem('annotate', 'git',
130 130 default=False,
131 131 )
132 132 coreconfigitem('annotate', 'ignorews',
133 133 default=False,
134 134 )
135 135 coreconfigitem('annotate', 'ignorewsamount',
136 136 default=False,
137 137 )
138 138 coreconfigitem('annotate', 'ignoreblanklines',
139 139 default=False,
140 140 )
141 141 coreconfigitem('annotate', 'ignorewseol',
142 142 default=False,
143 143 )
144 144 coreconfigitem('annotate', 'nobinary',
145 145 default=False,
146 146 )
147 147 coreconfigitem('annotate', 'noprefix',
148 148 default=False,
149 149 )
150 150 coreconfigitem('annotate', 'word-diff',
151 151 default=False,
152 152 )
153 153 coreconfigitem('auth', 'cookiefile',
154 154 default=None,
155 155 )
156 156 # bookmarks.pushing: internal hack for discovery
157 157 coreconfigitem('bookmarks', 'pushing',
158 158 default=list,
159 159 )
160 160 # bundle.mainreporoot: internal hack for bundlerepo
161 161 coreconfigitem('bundle', 'mainreporoot',
162 162 default='',
163 163 )
164 164 # bundle.reorder: experimental config
165 165 coreconfigitem('bundle', 'reorder',
166 166 default='auto',
167 167 )
168 168 coreconfigitem('censor', 'policy',
169 169 default='abort',
170 170 )
171 171 coreconfigitem('chgserver', 'idletimeout',
172 172 default=3600,
173 173 )
174 174 coreconfigitem('chgserver', 'skiphash',
175 175 default=False,
176 176 )
177 177 coreconfigitem('cmdserver', 'log',
178 178 default=None,
179 179 )
180 180 coreconfigitem('color', '.*',
181 181 default=None,
182 182 generic=True,
183 183 )
184 184 coreconfigitem('color', 'mode',
185 185 default='auto',
186 186 )
187 187 coreconfigitem('color', 'pagermode',
188 188 default=dynamicdefault,
189 189 )
190 190 coreconfigitem('commands', 'grep.all-files',
191 191 default=False,
192 192 )
193 193 coreconfigitem('commands', 'resolve.confirm',
194 194 default=False,
195 195 )
196 196 coreconfigitem('commands', 'show.aliasprefix',
197 197 default=list,
198 198 )
199 199 coreconfigitem('commands', 'status.relative',
200 200 default=False,
201 201 )
202 202 coreconfigitem('commands', 'status.skipstates',
203 203 default=[],
204 204 )
205 205 coreconfigitem('commands', 'status.terse',
206 206 default='',
207 207 )
208 208 coreconfigitem('commands', 'status.verbose',
209 209 default=False,
210 210 )
211 211 coreconfigitem('commands', 'update.check',
212 212 default=None,
213 213 )
214 214 coreconfigitem('commands', 'update.requiredest',
215 215 default=False,
216 216 )
217 217 coreconfigitem('committemplate', '.*',
218 218 default=None,
219 219 generic=True,
220 220 )
221 221 coreconfigitem('convert', 'bzr.saverev',
222 222 default=True,
223 223 )
224 224 coreconfigitem('convert', 'cvsps.cache',
225 225 default=True,
226 226 )
227 227 coreconfigitem('convert', 'cvsps.fuzz',
228 228 default=60,
229 229 )
230 230 coreconfigitem('convert', 'cvsps.logencoding',
231 231 default=None,
232 232 )
233 233 coreconfigitem('convert', 'cvsps.mergefrom',
234 234 default=None,
235 235 )
236 236 coreconfigitem('convert', 'cvsps.mergeto',
237 237 default=None,
238 238 )
239 239 coreconfigitem('convert', 'git.committeractions',
240 240 default=lambda: ['messagedifferent'],
241 241 )
242 242 coreconfigitem('convert', 'git.extrakeys',
243 243 default=list,
244 244 )
245 245 coreconfigitem('convert', 'git.findcopiesharder',
246 246 default=False,
247 247 )
248 248 coreconfigitem('convert', 'git.remoteprefix',
249 249 default='remote',
250 250 )
251 251 coreconfigitem('convert', 'git.renamelimit',
252 252 default=400,
253 253 )
254 254 coreconfigitem('convert', 'git.saverev',
255 255 default=True,
256 256 )
257 257 coreconfigitem('convert', 'git.similarity',
258 258 default=50,
259 259 )
260 260 coreconfigitem('convert', 'git.skipsubmodules',
261 261 default=False,
262 262 )
263 263 coreconfigitem('convert', 'hg.clonebranches',
264 264 default=False,
265 265 )
266 266 coreconfigitem('convert', 'hg.ignoreerrors',
267 267 default=False,
268 268 )
269 269 coreconfigitem('convert', 'hg.revs',
270 270 default=None,
271 271 )
272 272 coreconfigitem('convert', 'hg.saverev',
273 273 default=False,
274 274 )
275 275 coreconfigitem('convert', 'hg.sourcename',
276 276 default=None,
277 277 )
278 278 coreconfigitem('convert', 'hg.startrev',
279 279 default=None,
280 280 )
281 281 coreconfigitem('convert', 'hg.tagsbranch',
282 282 default='default',
283 283 )
284 284 coreconfigitem('convert', 'hg.usebranchnames',
285 285 default=True,
286 286 )
287 287 coreconfigitem('convert', 'ignoreancestorcheck',
288 288 default=False,
289 289 )
290 290 coreconfigitem('convert', 'localtimezone',
291 291 default=False,
292 292 )
293 293 coreconfigitem('convert', 'p4.encoding',
294 294 default=dynamicdefault,
295 295 )
296 296 coreconfigitem('convert', 'p4.startrev',
297 297 default=0,
298 298 )
299 299 coreconfigitem('convert', 'skiptags',
300 300 default=False,
301 301 )
302 302 coreconfigitem('convert', 'svn.debugsvnlog',
303 303 default=True,
304 304 )
305 305 coreconfigitem('convert', 'svn.trunk',
306 306 default=None,
307 307 )
308 308 coreconfigitem('convert', 'svn.tags',
309 309 default=None,
310 310 )
311 311 coreconfigitem('convert', 'svn.branches',
312 312 default=None,
313 313 )
314 314 coreconfigitem('convert', 'svn.startrev',
315 315 default=0,
316 316 )
317 317 coreconfigitem('debug', 'dirstate.delaywrite',
318 318 default=0,
319 319 )
320 320 coreconfigitem('defaults', '.*',
321 321 default=None,
322 322 generic=True,
323 323 )
324 324 coreconfigitem('devel', 'all-warnings',
325 325 default=False,
326 326 )
327 327 coreconfigitem('devel', 'bundle2.debug',
328 328 default=False,
329 329 )
330 330 coreconfigitem('devel', 'cache-vfs',
331 331 default=None,
332 332 )
333 333 coreconfigitem('devel', 'check-locks',
334 334 default=False,
335 335 )
336 336 coreconfigitem('devel', 'check-relroot',
337 337 default=False,
338 338 )
339 339 coreconfigitem('devel', 'default-date',
340 340 default=None,
341 341 )
342 342 coreconfigitem('devel', 'deprec-warn',
343 343 default=False,
344 344 )
345 345 coreconfigitem('devel', 'disableloaddefaultcerts',
346 346 default=False,
347 347 )
348 348 coreconfigitem('devel', 'warn-empty-changegroup',
349 349 default=False,
350 350 )
351 351 coreconfigitem('devel', 'legacy.exchange',
352 352 default=list,
353 353 )
354 354 coreconfigitem('devel', 'servercafile',
355 355 default='',
356 356 )
357 357 coreconfigitem('devel', 'serverexactprotocol',
358 358 default='',
359 359 )
360 360 coreconfigitem('devel', 'serverrequirecert',
361 361 default=False,
362 362 )
363 363 coreconfigitem('devel', 'strip-obsmarkers',
364 364 default=True,
365 365 )
366 366 coreconfigitem('devel', 'warn-config',
367 367 default=None,
368 368 )
369 369 coreconfigitem('devel', 'warn-config-default',
370 370 default=None,
371 371 )
372 372 coreconfigitem('devel', 'user.obsmarker',
373 373 default=None,
374 374 )
375 375 coreconfigitem('devel', 'warn-config-unknown',
376 376 default=None,
377 377 )
378 378 coreconfigitem('devel', 'debug.extensions',
379 379 default=False,
380 380 )
381 381 coreconfigitem('devel', 'debug.peer-request',
382 382 default=False,
383 383 )
384 384 coreconfigitem('diff', 'nodates',
385 385 default=False,
386 386 )
387 387 coreconfigitem('diff', 'showfunc',
388 388 default=False,
389 389 )
390 390 coreconfigitem('diff', 'unified',
391 391 default=None,
392 392 )
393 393 coreconfigitem('diff', 'git',
394 394 default=False,
395 395 )
396 396 coreconfigitem('diff', 'ignorews',
397 397 default=False,
398 398 )
399 399 coreconfigitem('diff', 'ignorewsamount',
400 400 default=False,
401 401 )
402 402 coreconfigitem('diff', 'ignoreblanklines',
403 403 default=False,
404 404 )
405 405 coreconfigitem('diff', 'ignorewseol',
406 406 default=False,
407 407 )
408 408 coreconfigitem('diff', 'nobinary',
409 409 default=False,
410 410 )
411 411 coreconfigitem('diff', 'noprefix',
412 412 default=False,
413 413 )
414 414 coreconfigitem('diff', 'word-diff',
415 415 default=False,
416 416 )
417 417 coreconfigitem('email', 'bcc',
418 418 default=None,
419 419 )
420 420 coreconfigitem('email', 'cc',
421 421 default=None,
422 422 )
423 423 coreconfigitem('email', 'charsets',
424 424 default=list,
425 425 )
426 426 coreconfigitem('email', 'from',
427 427 default=None,
428 428 )
429 429 coreconfigitem('email', 'method',
430 430 default='smtp',
431 431 )
432 432 coreconfigitem('email', 'reply-to',
433 433 default=None,
434 434 )
435 435 coreconfigitem('email', 'to',
436 436 default=None,
437 437 )
438 438 coreconfigitem('experimental', 'archivemetatemplate',
439 439 default=dynamicdefault,
440 440 )
441 441 coreconfigitem('experimental', 'bundle-phases',
442 442 default=False,
443 443 )
444 444 coreconfigitem('experimental', 'bundle2-advertise',
445 445 default=True,
446 446 )
447 447 coreconfigitem('experimental', 'bundle2-output-capture',
448 448 default=False,
449 449 )
450 450 coreconfigitem('experimental', 'bundle2.pushback',
451 451 default=False,
452 452 )
453 453 coreconfigitem('experimental', 'bundle2.stream',
454 454 default=False,
455 455 )
456 456 coreconfigitem('experimental', 'bundle2lazylocking',
457 457 default=False,
458 458 )
459 459 coreconfigitem('experimental', 'bundlecomplevel',
460 460 default=None,
461 461 )
462 462 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
463 463 default=None,
464 464 )
465 465 coreconfigitem('experimental', 'bundlecomplevel.gzip',
466 466 default=None,
467 467 )
468 468 coreconfigitem('experimental', 'bundlecomplevel.none',
469 469 default=None,
470 470 )
471 471 coreconfigitem('experimental', 'bundlecomplevel.zstd',
472 472 default=None,
473 473 )
474 474 coreconfigitem('experimental', 'changegroup3',
475 475 default=False,
476 476 )
477 477 coreconfigitem('experimental', 'clientcompressionengines',
478 478 default=list,
479 479 )
480 480 coreconfigitem('experimental', 'copytrace',
481 481 default='on',
482 482 )
483 483 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
484 484 default=100,
485 485 )
486 486 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
487 487 default=100,
488 488 )
489 489 coreconfigitem('experimental', 'crecordtest',
490 490 default=None,
491 491 )
492 492 coreconfigitem('experimental', 'directaccess',
493 493 default=False,
494 494 )
495 495 coreconfigitem('experimental', 'directaccess.revnums',
496 496 default=False,
497 497 )
498 498 coreconfigitem('experimental', 'editortmpinhg',
499 499 default=False,
500 500 )
501 501 coreconfigitem('experimental', 'evolution',
502 502 default=list,
503 503 )
504 504 coreconfigitem('experimental', 'evolution.allowdivergence',
505 505 default=False,
506 506 alias=[('experimental', 'allowdivergence')]
507 507 )
508 508 coreconfigitem('experimental', 'evolution.allowunstable',
509 509 default=None,
510 510 )
511 511 coreconfigitem('experimental', 'evolution.createmarkers',
512 512 default=None,
513 513 )
514 514 coreconfigitem('experimental', 'evolution.effect-flags',
515 515 default=True,
516 516 alias=[('experimental', 'effect-flags')]
517 517 )
518 518 coreconfigitem('experimental', 'evolution.exchange',
519 519 default=None,
520 520 )
521 521 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
522 522 default=False,
523 523 )
524 524 coreconfigitem('experimental', 'evolution.report-instabilities',
525 525 default=True,
526 526 )
527 527 coreconfigitem('experimental', 'evolution.track-operation',
528 528 default=True,
529 529 )
530 530 coreconfigitem('experimental', 'maxdeltachainspan',
531 531 default=-1,
532 532 )
533 533 coreconfigitem('experimental', 'mergetempdirprefix',
534 534 default=None,
535 535 )
536 536 coreconfigitem('experimental', 'mmapindexthreshold',
537 537 default=None,
538 538 )
539 539 coreconfigitem('experimental', 'nonnormalparanoidcheck',
540 540 default=False,
541 541 )
542 542 coreconfigitem('experimental', 'exportableenviron',
543 543 default=list,
544 544 )
545 545 coreconfigitem('experimental', 'extendedheader.index',
546 546 default=None,
547 547 )
548 548 coreconfigitem('experimental', 'extendedheader.similarity',
549 549 default=False,
550 550 )
551 551 coreconfigitem('experimental', 'format.compression',
552 552 default='zlib',
553 553 )
554 554 coreconfigitem('experimental', 'graphshorten',
555 555 default=False,
556 556 )
557 557 coreconfigitem('experimental', 'graphstyle.parent',
558 558 default=dynamicdefault,
559 559 )
560 560 coreconfigitem('experimental', 'graphstyle.missing',
561 561 default=dynamicdefault,
562 562 )
563 563 coreconfigitem('experimental', 'graphstyle.grandparent',
564 564 default=dynamicdefault,
565 565 )
566 566 coreconfigitem('experimental', 'hook-track-tags',
567 567 default=False,
568 568 )
569 569 coreconfigitem('experimental', 'httppeer.advertise-v2',
570 570 default=False,
571 571 )
572 572 coreconfigitem('experimental', 'httppostargs',
573 573 default=False,
574 574 )
575 575 coreconfigitem('experimental', 'mergedriver',
576 576 default=None,
577 577 )
578 578 coreconfigitem('experimental', 'nointerrupt', default=False)
579 579 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
580 580
581 581 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
582 582 default=False,
583 583 )
584 584 coreconfigitem('experimental', 'remotenames',
585 585 default=False,
586 586 )
587 587 coreconfigitem('experimental', 'removeemptydirs',
588 588 default=True,
589 589 )
590 590 coreconfigitem('experimental', 'revlogv2',
591 591 default=None,
592 592 )
593 coreconfigitem('experimental', 'revisions.disambiguatewithin',
594 default=None,
595 )
593 596 coreconfigitem('experimental', 'single-head-per-branch',
594 597 default=False,
595 598 )
596 599 coreconfigitem('experimental', 'sshserver.support-v2',
597 600 default=False,
598 601 )
599 602 coreconfigitem('experimental', 'spacemovesdown',
600 603 default=False,
601 604 )
602 605 coreconfigitem('experimental', 'sparse-read',
603 606 default=False,
604 607 )
605 608 coreconfigitem('experimental', 'sparse-read.density-threshold',
606 609 default=0.50,
607 610 )
608 611 coreconfigitem('experimental', 'sparse-read.min-gap-size',
609 612 default='65K',
610 613 )
611 614 coreconfigitem('experimental', 'treemanifest',
612 615 default=False,
613 616 )
614 617 coreconfigitem('experimental', 'update.atomic-file',
615 618 default=False,
616 619 )
617 620 coreconfigitem('experimental', 'sshpeer.advertise-v2',
618 621 default=False,
619 622 )
620 623 coreconfigitem('experimental', 'web.apiserver',
621 624 default=False,
622 625 )
623 626 coreconfigitem('experimental', 'web.api.http-v2',
624 627 default=False,
625 628 )
626 629 coreconfigitem('experimental', 'web.api.debugreflect',
627 630 default=False,
628 631 )
629 632 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
630 633 default=False,
631 634 )
632 635 coreconfigitem('experimental', 'xdiff',
633 636 default=False,
634 637 )
635 638 coreconfigitem('extensions', '.*',
636 639 default=None,
637 640 generic=True,
638 641 )
639 642 coreconfigitem('extdata', '.*',
640 643 default=None,
641 644 generic=True,
642 645 )
643 646 coreconfigitem('format', 'chunkcachesize',
644 647 default=None,
645 648 )
646 649 coreconfigitem('format', 'dotencode',
647 650 default=True,
648 651 )
649 652 coreconfigitem('format', 'generaldelta',
650 653 default=False,
651 654 )
652 655 coreconfigitem('format', 'manifestcachesize',
653 656 default=None,
654 657 )
655 658 coreconfigitem('format', 'maxchainlen',
656 659 default=None,
657 660 )
658 661 coreconfigitem('format', 'obsstore-version',
659 662 default=None,
660 663 )
661 664 coreconfigitem('format', 'sparse-revlog',
662 665 default=False,
663 666 )
664 667 coreconfigitem('format', 'usefncache',
665 668 default=True,
666 669 )
667 670 coreconfigitem('format', 'usegeneraldelta',
668 671 default=True,
669 672 )
670 673 coreconfigitem('format', 'usestore',
671 674 default=True,
672 675 )
673 676 coreconfigitem('fsmonitor', 'warn_when_unused',
674 677 default=True,
675 678 )
676 679 coreconfigitem('fsmonitor', 'warn_update_file_count',
677 680 default=50000,
678 681 )
679 682 coreconfigitem('hooks', '.*',
680 683 default=dynamicdefault,
681 684 generic=True,
682 685 )
683 686 coreconfigitem('hgweb-paths', '.*',
684 687 default=list,
685 688 generic=True,
686 689 )
687 690 coreconfigitem('hostfingerprints', '.*',
688 691 default=list,
689 692 generic=True,
690 693 )
691 694 coreconfigitem('hostsecurity', 'ciphers',
692 695 default=None,
693 696 )
694 697 coreconfigitem('hostsecurity', 'disabletls10warning',
695 698 default=False,
696 699 )
697 700 coreconfigitem('hostsecurity', 'minimumprotocol',
698 701 default=dynamicdefault,
699 702 )
700 703 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
701 704 default=dynamicdefault,
702 705 generic=True,
703 706 )
704 707 coreconfigitem('hostsecurity', '.*:ciphers$',
705 708 default=dynamicdefault,
706 709 generic=True,
707 710 )
708 711 coreconfigitem('hostsecurity', '.*:fingerprints$',
709 712 default=list,
710 713 generic=True,
711 714 )
712 715 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
713 716 default=None,
714 717 generic=True,
715 718 )
716 719
717 720 coreconfigitem('http_proxy', 'always',
718 721 default=False,
719 722 )
720 723 coreconfigitem('http_proxy', 'host',
721 724 default=None,
722 725 )
723 726 coreconfigitem('http_proxy', 'no',
724 727 default=list,
725 728 )
726 729 coreconfigitem('http_proxy', 'passwd',
727 730 default=None,
728 731 )
729 732 coreconfigitem('http_proxy', 'user',
730 733 default=None,
731 734 )
732 735 coreconfigitem('logtoprocess', 'commandexception',
733 736 default=None,
734 737 )
735 738 coreconfigitem('logtoprocess', 'commandfinish',
736 739 default=None,
737 740 )
738 741 coreconfigitem('logtoprocess', 'command',
739 742 default=None,
740 743 )
741 744 coreconfigitem('logtoprocess', 'develwarn',
742 745 default=None,
743 746 )
744 747 coreconfigitem('logtoprocess', 'uiblocked',
745 748 default=None,
746 749 )
747 750 coreconfigitem('merge', 'checkunknown',
748 751 default='abort',
749 752 )
750 753 coreconfigitem('merge', 'checkignored',
751 754 default='abort',
752 755 )
753 756 coreconfigitem('experimental', 'merge.checkpathconflicts',
754 757 default=False,
755 758 )
756 759 coreconfigitem('merge', 'followcopies',
757 760 default=True,
758 761 )
759 762 coreconfigitem('merge', 'on-failure',
760 763 default='continue',
761 764 )
762 765 coreconfigitem('merge', 'preferancestor',
763 766 default=lambda: ['*'],
764 767 )
765 768 coreconfigitem('merge-tools', '.*',
766 769 default=None,
767 770 generic=True,
768 771 )
769 772 coreconfigitem('merge-tools', br'.*\.args$',
770 773 default="$local $base $other",
771 774 generic=True,
772 775 priority=-1,
773 776 )
774 777 coreconfigitem('merge-tools', br'.*\.binary$',
775 778 default=False,
776 779 generic=True,
777 780 priority=-1,
778 781 )
779 782 coreconfigitem('merge-tools', br'.*\.check$',
780 783 default=list,
781 784 generic=True,
782 785 priority=-1,
783 786 )
784 787 coreconfigitem('merge-tools', br'.*\.checkchanged$',
785 788 default=False,
786 789 generic=True,
787 790 priority=-1,
788 791 )
789 792 coreconfigitem('merge-tools', br'.*\.executable$',
790 793 default=dynamicdefault,
791 794 generic=True,
792 795 priority=-1,
793 796 )
794 797 coreconfigitem('merge-tools', br'.*\.fixeol$',
795 798 default=False,
796 799 generic=True,
797 800 priority=-1,
798 801 )
799 802 coreconfigitem('merge-tools', br'.*\.gui$',
800 803 default=False,
801 804 generic=True,
802 805 priority=-1,
803 806 )
804 807 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
805 808 default='basic',
806 809 generic=True,
807 810 priority=-1,
808 811 )
809 812 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
810 813 default=dynamicdefault, # take from ui.mergemarkertemplate
811 814 generic=True,
812 815 priority=-1,
813 816 )
814 817 coreconfigitem('merge-tools', br'.*\.priority$',
815 818 default=0,
816 819 generic=True,
817 820 priority=-1,
818 821 )
819 822 coreconfigitem('merge-tools', br'.*\.premerge$',
820 823 default=dynamicdefault,
821 824 generic=True,
822 825 priority=-1,
823 826 )
824 827 coreconfigitem('merge-tools', br'.*\.symlink$',
825 828 default=False,
826 829 generic=True,
827 830 priority=-1,
828 831 )
829 832 coreconfigitem('pager', 'attend-.*',
830 833 default=dynamicdefault,
831 834 generic=True,
832 835 )
833 836 coreconfigitem('pager', 'ignore',
834 837 default=list,
835 838 )
836 839 coreconfigitem('pager', 'pager',
837 840 default=dynamicdefault,
838 841 )
839 842 coreconfigitem('patch', 'eol',
840 843 default='strict',
841 844 )
842 845 coreconfigitem('patch', 'fuzz',
843 846 default=2,
844 847 )
845 848 coreconfigitem('paths', 'default',
846 849 default=None,
847 850 )
848 851 coreconfigitem('paths', 'default-push',
849 852 default=None,
850 853 )
851 854 coreconfigitem('paths', '.*',
852 855 default=None,
853 856 generic=True,
854 857 )
855 858 coreconfigitem('phases', 'checksubrepos',
856 859 default='follow',
857 860 )
858 861 coreconfigitem('phases', 'new-commit',
859 862 default='draft',
860 863 )
861 864 coreconfigitem('phases', 'publish',
862 865 default=True,
863 866 )
864 867 coreconfigitem('profiling', 'enabled',
865 868 default=False,
866 869 )
867 870 coreconfigitem('profiling', 'format',
868 871 default='text',
869 872 )
870 873 coreconfigitem('profiling', 'freq',
871 874 default=1000,
872 875 )
873 876 coreconfigitem('profiling', 'limit',
874 877 default=30,
875 878 )
876 879 coreconfigitem('profiling', 'nested',
877 880 default=0,
878 881 )
879 882 coreconfigitem('profiling', 'output',
880 883 default=None,
881 884 )
882 885 coreconfigitem('profiling', 'showmax',
883 886 default=0.999,
884 887 )
885 888 coreconfigitem('profiling', 'showmin',
886 889 default=dynamicdefault,
887 890 )
888 891 coreconfigitem('profiling', 'sort',
889 892 default='inlinetime',
890 893 )
891 894 coreconfigitem('profiling', 'statformat',
892 895 default='hotpath',
893 896 )
894 897 coreconfigitem('profiling', 'time-track',
895 898 default='cpu',
896 899 )
897 900 coreconfigitem('profiling', 'type',
898 901 default='stat',
899 902 )
900 903 coreconfigitem('progress', 'assume-tty',
901 904 default=False,
902 905 )
903 906 coreconfigitem('progress', 'changedelay',
904 907 default=1,
905 908 )
906 909 coreconfigitem('progress', 'clear-complete',
907 910 default=True,
908 911 )
909 912 coreconfigitem('progress', 'debug',
910 913 default=False,
911 914 )
912 915 coreconfigitem('progress', 'delay',
913 916 default=3,
914 917 )
915 918 coreconfigitem('progress', 'disable',
916 919 default=False,
917 920 )
918 921 coreconfigitem('progress', 'estimateinterval',
919 922 default=60.0,
920 923 )
921 924 coreconfigitem('progress', 'format',
922 925 default=lambda: ['topic', 'bar', 'number', 'estimate'],
923 926 )
924 927 coreconfigitem('progress', 'refresh',
925 928 default=0.1,
926 929 )
927 930 coreconfigitem('progress', 'width',
928 931 default=dynamicdefault,
929 932 )
930 933 coreconfigitem('push', 'pushvars.server',
931 934 default=False,
932 935 )
933 936 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
934 937 default=True,
935 938 alias=[('format', 'aggressivemergedeltas')],
936 939 )
937 940 coreconfigitem('experimental', 'resolve.mark-check',
938 941 default=None,
939 942 )
940 943 coreconfigitem('server', 'bookmarks-pushkey-compat',
941 944 default=True,
942 945 )
943 946 coreconfigitem('server', 'bundle1',
944 947 default=True,
945 948 )
946 949 coreconfigitem('server', 'bundle1gd',
947 950 default=None,
948 951 )
949 952 coreconfigitem('server', 'bundle1.pull',
950 953 default=None,
951 954 )
952 955 coreconfigitem('server', 'bundle1gd.pull',
953 956 default=None,
954 957 )
955 958 coreconfigitem('server', 'bundle1.push',
956 959 default=None,
957 960 )
958 961 coreconfigitem('server', 'bundle1gd.push',
959 962 default=None,
960 963 )
961 964 coreconfigitem('server', 'compressionengines',
962 965 default=list,
963 966 )
964 967 coreconfigitem('server', 'concurrent-push-mode',
965 968 default='strict',
966 969 )
967 970 coreconfigitem('server', 'disablefullbundle',
968 971 default=False,
969 972 )
970 973 coreconfigitem('server', 'maxhttpheaderlen',
971 974 default=1024,
972 975 )
973 976 coreconfigitem('server', 'pullbundle',
974 977 default=False,
975 978 )
976 979 coreconfigitem('server', 'preferuncompressed',
977 980 default=False,
978 981 )
979 982 coreconfigitem('server', 'streamunbundle',
980 983 default=False,
981 984 )
982 985 coreconfigitem('server', 'uncompressed',
983 986 default=True,
984 987 )
985 988 coreconfigitem('server', 'uncompressedallowsecret',
986 989 default=False,
987 990 )
988 991 coreconfigitem('server', 'validate',
989 992 default=False,
990 993 )
991 994 coreconfigitem('server', 'zliblevel',
992 995 default=-1,
993 996 )
994 997 coreconfigitem('server', 'zstdlevel',
995 998 default=3,
996 999 )
997 1000 coreconfigitem('share', 'pool',
998 1001 default=None,
999 1002 )
1000 1003 coreconfigitem('share', 'poolnaming',
1001 1004 default='identity',
1002 1005 )
1003 1006 coreconfigitem('smtp', 'host',
1004 1007 default=None,
1005 1008 )
1006 1009 coreconfigitem('smtp', 'local_hostname',
1007 1010 default=None,
1008 1011 )
1009 1012 coreconfigitem('smtp', 'password',
1010 1013 default=None,
1011 1014 )
1012 1015 coreconfigitem('smtp', 'port',
1013 1016 default=dynamicdefault,
1014 1017 )
1015 1018 coreconfigitem('smtp', 'tls',
1016 1019 default='none',
1017 1020 )
1018 1021 coreconfigitem('smtp', 'username',
1019 1022 default=None,
1020 1023 )
1021 1024 coreconfigitem('sparse', 'missingwarning',
1022 1025 default=True,
1023 1026 )
1024 1027 coreconfigitem('subrepos', 'allowed',
1025 1028 default=dynamicdefault, # to make backporting simpler
1026 1029 )
1027 1030 coreconfigitem('subrepos', 'hg:allowed',
1028 1031 default=dynamicdefault,
1029 1032 )
1030 1033 coreconfigitem('subrepos', 'git:allowed',
1031 1034 default=dynamicdefault,
1032 1035 )
1033 1036 coreconfigitem('subrepos', 'svn:allowed',
1034 1037 default=dynamicdefault,
1035 1038 )
1036 1039 coreconfigitem('templates', '.*',
1037 1040 default=None,
1038 1041 generic=True,
1039 1042 )
1040 1043 coreconfigitem('trusted', 'groups',
1041 1044 default=list,
1042 1045 )
1043 1046 coreconfigitem('trusted', 'users',
1044 1047 default=list,
1045 1048 )
1046 1049 coreconfigitem('ui', '_usedassubrepo',
1047 1050 default=False,
1048 1051 )
1049 1052 coreconfigitem('ui', 'allowemptycommit',
1050 1053 default=False,
1051 1054 )
1052 1055 coreconfigitem('ui', 'archivemeta',
1053 1056 default=True,
1054 1057 )
1055 1058 coreconfigitem('ui', 'askusername',
1056 1059 default=False,
1057 1060 )
1058 1061 coreconfigitem('ui', 'clonebundlefallback',
1059 1062 default=False,
1060 1063 )
1061 1064 coreconfigitem('ui', 'clonebundleprefers',
1062 1065 default=list,
1063 1066 )
1064 1067 coreconfigitem('ui', 'clonebundles',
1065 1068 default=True,
1066 1069 )
1067 1070 coreconfigitem('ui', 'color',
1068 1071 default='auto',
1069 1072 )
1070 1073 coreconfigitem('ui', 'commitsubrepos',
1071 1074 default=False,
1072 1075 )
1073 1076 coreconfigitem('ui', 'debug',
1074 1077 default=False,
1075 1078 )
1076 1079 coreconfigitem('ui', 'debugger',
1077 1080 default=None,
1078 1081 )
1079 1082 coreconfigitem('ui', 'editor',
1080 1083 default=dynamicdefault,
1081 1084 )
1082 1085 coreconfigitem('ui', 'fallbackencoding',
1083 1086 default=None,
1084 1087 )
1085 1088 coreconfigitem('ui', 'forcecwd',
1086 1089 default=None,
1087 1090 )
1088 1091 coreconfigitem('ui', 'forcemerge',
1089 1092 default=None,
1090 1093 )
1091 1094 coreconfigitem('ui', 'formatdebug',
1092 1095 default=False,
1093 1096 )
1094 1097 coreconfigitem('ui', 'formatjson',
1095 1098 default=False,
1096 1099 )
1097 1100 coreconfigitem('ui', 'formatted',
1098 1101 default=None,
1099 1102 )
1100 1103 coreconfigitem('ui', 'graphnodetemplate',
1101 1104 default=None,
1102 1105 )
1103 1106 coreconfigitem('ui', 'history-editing-backup',
1104 1107 default=True,
1105 1108 )
1106 1109 coreconfigitem('ui', 'interactive',
1107 1110 default=None,
1108 1111 )
1109 1112 coreconfigitem('ui', 'interface',
1110 1113 default=None,
1111 1114 )
1112 1115 coreconfigitem('ui', 'interface.chunkselector',
1113 1116 default=None,
1114 1117 )
1115 1118 coreconfigitem('ui', 'large-file-limit',
1116 1119 default=10000000,
1117 1120 )
1118 1121 coreconfigitem('ui', 'logblockedtimes',
1119 1122 default=False,
1120 1123 )
1121 1124 coreconfigitem('ui', 'logtemplate',
1122 1125 default=None,
1123 1126 )
1124 1127 coreconfigitem('ui', 'merge',
1125 1128 default=None,
1126 1129 )
1127 1130 coreconfigitem('ui', 'mergemarkers',
1128 1131 default='basic',
1129 1132 )
1130 1133 coreconfigitem('ui', 'mergemarkertemplate',
1131 1134 default=('{node|short} '
1132 1135 '{ifeq(tags, "tip", "", '
1133 1136 'ifeq(tags, "", "", "{tags} "))}'
1134 1137 '{if(bookmarks, "{bookmarks} ")}'
1135 1138 '{ifeq(branch, "default", "", "{branch} ")}'
1136 1139 '- {author|user}: {desc|firstline}')
1137 1140 )
1138 1141 coreconfigitem('ui', 'nontty',
1139 1142 default=False,
1140 1143 )
1141 1144 coreconfigitem('ui', 'origbackuppath',
1142 1145 default=None,
1143 1146 )
1144 1147 coreconfigitem('ui', 'paginate',
1145 1148 default=True,
1146 1149 )
1147 1150 coreconfigitem('ui', 'patch',
1148 1151 default=None,
1149 1152 )
1150 1153 coreconfigitem('ui', 'portablefilenames',
1151 1154 default='warn',
1152 1155 )
1153 1156 coreconfigitem('ui', 'promptecho',
1154 1157 default=False,
1155 1158 )
1156 1159 coreconfigitem('ui', 'quiet',
1157 1160 default=False,
1158 1161 )
1159 1162 coreconfigitem('ui', 'quietbookmarkmove',
1160 1163 default=False,
1161 1164 )
1162 1165 coreconfigitem('ui', 'remotecmd',
1163 1166 default='hg',
1164 1167 )
1165 1168 coreconfigitem('ui', 'report_untrusted',
1166 1169 default=True,
1167 1170 )
1168 1171 coreconfigitem('ui', 'rollback',
1169 1172 default=True,
1170 1173 )
1171 1174 coreconfigitem('ui', 'signal-safe-lock',
1172 1175 default=True,
1173 1176 )
1174 1177 coreconfigitem('ui', 'slash',
1175 1178 default=False,
1176 1179 )
1177 1180 coreconfigitem('ui', 'ssh',
1178 1181 default='ssh',
1179 1182 )
1180 1183 coreconfigitem('ui', 'ssherrorhint',
1181 1184 default=None,
1182 1185 )
1183 1186 coreconfigitem('ui', 'statuscopies',
1184 1187 default=False,
1185 1188 )
1186 1189 coreconfigitem('ui', 'strict',
1187 1190 default=False,
1188 1191 )
1189 1192 coreconfigitem('ui', 'style',
1190 1193 default='',
1191 1194 )
1192 1195 coreconfigitem('ui', 'supportcontact',
1193 1196 default=None,
1194 1197 )
1195 1198 coreconfigitem('ui', 'textwidth',
1196 1199 default=78,
1197 1200 )
1198 1201 coreconfigitem('ui', 'timeout',
1199 1202 default='600',
1200 1203 )
1201 1204 coreconfigitem('ui', 'timeout.warn',
1202 1205 default=0,
1203 1206 )
1204 1207 coreconfigitem('ui', 'traceback',
1205 1208 default=False,
1206 1209 )
1207 1210 coreconfigitem('ui', 'tweakdefaults',
1208 1211 default=False,
1209 1212 )
1210 1213 coreconfigitem('ui', 'username',
1211 1214 alias=[('ui', 'user')]
1212 1215 )
1213 1216 coreconfigitem('ui', 'verbose',
1214 1217 default=False,
1215 1218 )
1216 1219 coreconfigitem('verify', 'skipflags',
1217 1220 default=None,
1218 1221 )
1219 1222 coreconfigitem('web', 'allowbz2',
1220 1223 default=False,
1221 1224 )
1222 1225 coreconfigitem('web', 'allowgz',
1223 1226 default=False,
1224 1227 )
1225 1228 coreconfigitem('web', 'allow-pull',
1226 1229 alias=[('web', 'allowpull')],
1227 1230 default=True,
1228 1231 )
1229 1232 coreconfigitem('web', 'allow-push',
1230 1233 alias=[('web', 'allow_push')],
1231 1234 default=list,
1232 1235 )
1233 1236 coreconfigitem('web', 'allowzip',
1234 1237 default=False,
1235 1238 )
1236 1239 coreconfigitem('web', 'archivesubrepos',
1237 1240 default=False,
1238 1241 )
1239 1242 coreconfigitem('web', 'cache',
1240 1243 default=True,
1241 1244 )
1242 1245 coreconfigitem('web', 'contact',
1243 1246 default=None,
1244 1247 )
1245 1248 coreconfigitem('web', 'deny_push',
1246 1249 default=list,
1247 1250 )
1248 1251 coreconfigitem('web', 'guessmime',
1249 1252 default=False,
1250 1253 )
1251 1254 coreconfigitem('web', 'hidden',
1252 1255 default=False,
1253 1256 )
1254 1257 coreconfigitem('web', 'labels',
1255 1258 default=list,
1256 1259 )
1257 1260 coreconfigitem('web', 'logoimg',
1258 1261 default='hglogo.png',
1259 1262 )
1260 1263 coreconfigitem('web', 'logourl',
1261 1264 default='https://mercurial-scm.org/',
1262 1265 )
1263 1266 coreconfigitem('web', 'accesslog',
1264 1267 default='-',
1265 1268 )
1266 1269 coreconfigitem('web', 'address',
1267 1270 default='',
1268 1271 )
1269 1272 coreconfigitem('web', 'allow-archive',
1270 1273 alias=[('web', 'allow_archive')],
1271 1274 default=list,
1272 1275 )
1273 1276 coreconfigitem('web', 'allow_read',
1274 1277 default=list,
1275 1278 )
1276 1279 coreconfigitem('web', 'baseurl',
1277 1280 default=None,
1278 1281 )
1279 1282 coreconfigitem('web', 'cacerts',
1280 1283 default=None,
1281 1284 )
1282 1285 coreconfigitem('web', 'certificate',
1283 1286 default=None,
1284 1287 )
1285 1288 coreconfigitem('web', 'collapse',
1286 1289 default=False,
1287 1290 )
1288 1291 coreconfigitem('web', 'csp',
1289 1292 default=None,
1290 1293 )
1291 1294 coreconfigitem('web', 'deny_read',
1292 1295 default=list,
1293 1296 )
1294 1297 coreconfigitem('web', 'descend',
1295 1298 default=True,
1296 1299 )
1297 1300 coreconfigitem('web', 'description',
1298 1301 default="",
1299 1302 )
1300 1303 coreconfigitem('web', 'encoding',
1301 1304 default=lambda: encoding.encoding,
1302 1305 )
1303 1306 coreconfigitem('web', 'errorlog',
1304 1307 default='-',
1305 1308 )
1306 1309 coreconfigitem('web', 'ipv6',
1307 1310 default=False,
1308 1311 )
1309 1312 coreconfigitem('web', 'maxchanges',
1310 1313 default=10,
1311 1314 )
1312 1315 coreconfigitem('web', 'maxfiles',
1313 1316 default=10,
1314 1317 )
1315 1318 coreconfigitem('web', 'maxshortchanges',
1316 1319 default=60,
1317 1320 )
1318 1321 coreconfigitem('web', 'motd',
1319 1322 default='',
1320 1323 )
1321 1324 coreconfigitem('web', 'name',
1322 1325 default=dynamicdefault,
1323 1326 )
1324 1327 coreconfigitem('web', 'port',
1325 1328 default=8000,
1326 1329 )
1327 1330 coreconfigitem('web', 'prefix',
1328 1331 default='',
1329 1332 )
1330 1333 coreconfigitem('web', 'push_ssl',
1331 1334 default=True,
1332 1335 )
1333 1336 coreconfigitem('web', 'refreshinterval',
1334 1337 default=20,
1335 1338 )
1336 1339 coreconfigitem('web', 'server-header',
1337 1340 default=None,
1338 1341 )
1339 1342 coreconfigitem('web', 'staticurl',
1340 1343 default=None,
1341 1344 )
1342 1345 coreconfigitem('web', 'stripes',
1343 1346 default=1,
1344 1347 )
1345 1348 coreconfigitem('web', 'style',
1346 1349 default='paper',
1347 1350 )
1348 1351 coreconfigitem('web', 'templates',
1349 1352 default=None,
1350 1353 )
1351 1354 coreconfigitem('web', 'view',
1352 1355 default='served',
1353 1356 )
1354 1357 coreconfigitem('worker', 'backgroundclose',
1355 1358 default=dynamicdefault,
1356 1359 )
1357 1360 # Windows defaults to a limit of 512 open files. A buffer of 128
1358 1361 # should give us enough headway.
1359 1362 coreconfigitem('worker', 'backgroundclosemaxqueue',
1360 1363 default=384,
1361 1364 )
1362 1365 coreconfigitem('worker', 'backgroundcloseminfilecount',
1363 1366 default=2048,
1364 1367 )
1365 1368 coreconfigitem('worker', 'backgroundclosethreadcount',
1366 1369 default=4,
1367 1370 )
1368 1371 coreconfigitem('worker', 'enabled',
1369 1372 default=True,
1370 1373 )
1371 1374 coreconfigitem('worker', 'numcpus',
1372 1375 default=None,
1373 1376 )
1374 1377
1375 1378 # Rebase related configuration moved to core because other extension are doing
1376 1379 # strange things. For example, shelve import the extensions to reuse some bit
1377 1380 # without formally loading it.
1378 1381 coreconfigitem('commands', 'rebase.requiredest',
1379 1382 default=False,
1380 1383 )
1381 1384 coreconfigitem('experimental', 'rebaseskipobsolete',
1382 1385 default=True,
1383 1386 )
1384 1387 coreconfigitem('rebase', 'singletransaction',
1385 1388 default=False,
1386 1389 )
1387 1390 coreconfigitem('rebase', 'experimental.inmemory',
1388 1391 default=False,
1389 1392 )
@@ -1,1701 +1,1718 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import glob
12 12 import hashlib
13 13 import os
14 14 import re
15 15 import socket
16 16 import subprocess
17 17 import weakref
18 18
19 19 from .i18n import _
20 20 from .node import (
21 21 bin,
22 22 hex,
23 23 nullid,
24 24 short,
25 25 wdirid,
26 26 wdirrev,
27 27 )
28 28
29 29 from . import (
30 30 encoding,
31 31 error,
32 32 match as matchmod,
33 33 obsolete,
34 34 obsutil,
35 35 pathutil,
36 36 phases,
37 37 pycompat,
38 38 revsetlang,
39 39 similar,
40 40 url,
41 41 util,
42 42 vfs,
43 43 )
44 44
45 45 from .utils import (
46 46 procutil,
47 47 stringutil,
48 48 )
49 49
50 50 if pycompat.iswindows:
51 51 from . import scmwindows as scmplatform
52 52 else:
53 53 from . import scmposix as scmplatform
54 54
55 55 termsize = scmplatform.termsize
56 56
57 57 class status(tuple):
58 58 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
59 59 and 'ignored' properties are only relevant to the working copy.
60 60 '''
61 61
62 62 __slots__ = ()
63 63
64 64 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
65 65 clean):
66 66 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
67 67 ignored, clean))
68 68
69 69 @property
70 70 def modified(self):
71 71 '''files that have been modified'''
72 72 return self[0]
73 73
74 74 @property
75 75 def added(self):
76 76 '''files that have been added'''
77 77 return self[1]
78 78
79 79 @property
80 80 def removed(self):
81 81 '''files that have been removed'''
82 82 return self[2]
83 83
84 84 @property
85 85 def deleted(self):
86 86 '''files that are in the dirstate, but have been deleted from the
87 87 working copy (aka "missing")
88 88 '''
89 89 return self[3]
90 90
91 91 @property
92 92 def unknown(self):
93 93 '''files not in the dirstate that are not ignored'''
94 94 return self[4]
95 95
96 96 @property
97 97 def ignored(self):
98 98 '''files not in the dirstate that are ignored (by _dirignore())'''
99 99 return self[5]
100 100
101 101 @property
102 102 def clean(self):
103 103 '''files that have not been modified'''
104 104 return self[6]
105 105
106 106 def __repr__(self, *args, **kwargs):
107 107 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
108 108 r'unknown=%s, ignored=%s, clean=%s>') %
109 109 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
110 110
111 111 def itersubrepos(ctx1, ctx2):
112 112 """find subrepos in ctx1 or ctx2"""
113 113 # Create a (subpath, ctx) mapping where we prefer subpaths from
114 114 # ctx1. The subpaths from ctx2 are important when the .hgsub file
115 115 # has been modified (in ctx2) but not yet committed (in ctx1).
116 116 subpaths = dict.fromkeys(ctx2.substate, ctx2)
117 117 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
118 118
119 119 missing = set()
120 120
121 121 for subpath in ctx2.substate:
122 122 if subpath not in ctx1.substate:
123 123 del subpaths[subpath]
124 124 missing.add(subpath)
125 125
126 126 for subpath, ctx in sorted(subpaths.iteritems()):
127 127 yield subpath, ctx.sub(subpath)
128 128
129 129 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
130 130 # status and diff will have an accurate result when it does
131 131 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
132 132 # against itself.
133 133 for subpath in missing:
134 134 yield subpath, ctx2.nullsub(subpath, ctx1)
135 135
136 136 def nochangesfound(ui, repo, excluded=None):
137 137 '''Report no changes for push/pull, excluded is None or a list of
138 138 nodes excluded from the push/pull.
139 139 '''
140 140 secretlist = []
141 141 if excluded:
142 142 for n in excluded:
143 143 ctx = repo[n]
144 144 if ctx.phase() >= phases.secret and not ctx.extinct():
145 145 secretlist.append(n)
146 146
147 147 if secretlist:
148 148 ui.status(_("no changes found (ignored %d secret changesets)\n")
149 149 % len(secretlist))
150 150 else:
151 151 ui.status(_("no changes found\n"))
152 152
153 153 def callcatch(ui, func):
154 154 """call func() with global exception handling
155 155
156 156 return func() if no exception happens. otherwise do some error handling
157 157 and return an exit code accordingly. does not handle all exceptions.
158 158 """
159 159 try:
160 160 try:
161 161 return func()
162 162 except: # re-raises
163 163 ui.traceback()
164 164 raise
165 165 # Global exception handling, alphabetically
166 166 # Mercurial-specific first, followed by built-in and library exceptions
167 167 except error.LockHeld as inst:
168 168 if inst.errno == errno.ETIMEDOUT:
169 169 reason = _('timed out waiting for lock held by %r') % inst.locker
170 170 else:
171 171 reason = _('lock held by %r') % inst.locker
172 172 ui.error(_("abort: %s: %s\n") % (
173 173 inst.desc or stringutil.forcebytestr(inst.filename), reason))
174 174 if not inst.locker:
175 175 ui.error(_("(lock might be very busy)\n"))
176 176 except error.LockUnavailable as inst:
177 177 ui.error(_("abort: could not lock %s: %s\n") %
178 178 (inst.desc or stringutil.forcebytestr(inst.filename),
179 179 encoding.strtolocal(inst.strerror)))
180 180 except error.OutOfBandError as inst:
181 181 if inst.args:
182 182 msg = _("abort: remote error:\n")
183 183 else:
184 184 msg = _("abort: remote error\n")
185 185 ui.error(msg)
186 186 if inst.args:
187 187 ui.error(''.join(inst.args))
188 188 if inst.hint:
189 189 ui.error('(%s)\n' % inst.hint)
190 190 except error.RepoError as inst:
191 191 ui.error(_("abort: %s!\n") % inst)
192 192 if inst.hint:
193 193 ui.error(_("(%s)\n") % inst.hint)
194 194 except error.ResponseError as inst:
195 195 ui.error(_("abort: %s") % inst.args[0])
196 196 msg = inst.args[1]
197 197 if isinstance(msg, type(u'')):
198 198 msg = pycompat.sysbytes(msg)
199 199 if not isinstance(msg, bytes):
200 200 ui.error(" %r\n" % (msg,))
201 201 elif not msg:
202 202 ui.error(_(" empty string\n"))
203 203 else:
204 204 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
205 205 except error.CensoredNodeError as inst:
206 206 ui.error(_("abort: file censored %s!\n") % inst)
207 207 except error.RevlogError as inst:
208 208 ui.error(_("abort: %s!\n") % inst)
209 209 except error.InterventionRequired as inst:
210 210 ui.error("%s\n" % inst)
211 211 if inst.hint:
212 212 ui.error(_("(%s)\n") % inst.hint)
213 213 return 1
214 214 except error.WdirUnsupported:
215 215 ui.error(_("abort: working directory revision cannot be specified\n"))
216 216 except error.Abort as inst:
217 217 ui.error(_("abort: %s\n") % inst)
218 218 if inst.hint:
219 219 ui.error(_("(%s)\n") % inst.hint)
220 220 except ImportError as inst:
221 221 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
222 222 m = stringutil.forcebytestr(inst).split()[-1]
223 223 if m in "mpatch bdiff".split():
224 224 ui.error(_("(did you forget to compile extensions?)\n"))
225 225 elif m in "zlib".split():
226 226 ui.error(_("(is your Python install correct?)\n"))
227 227 except IOError as inst:
228 228 if util.safehasattr(inst, "code"):
229 229 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
230 230 elif util.safehasattr(inst, "reason"):
231 231 try: # usually it is in the form (errno, strerror)
232 232 reason = inst.reason.args[1]
233 233 except (AttributeError, IndexError):
234 234 # it might be anything, for example a string
235 235 reason = inst.reason
236 236 if isinstance(reason, pycompat.unicode):
237 237 # SSLError of Python 2.7.9 contains a unicode
238 238 reason = encoding.unitolocal(reason)
239 239 ui.error(_("abort: error: %s\n") % reason)
240 240 elif (util.safehasattr(inst, "args")
241 241 and inst.args and inst.args[0] == errno.EPIPE):
242 242 pass
243 243 elif getattr(inst, "strerror", None):
244 244 if getattr(inst, "filename", None):
245 245 ui.error(_("abort: %s: %s\n") % (
246 246 encoding.strtolocal(inst.strerror),
247 247 stringutil.forcebytestr(inst.filename)))
248 248 else:
249 249 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
250 250 else:
251 251 raise
252 252 except OSError as inst:
253 253 if getattr(inst, "filename", None) is not None:
254 254 ui.error(_("abort: %s: '%s'\n") % (
255 255 encoding.strtolocal(inst.strerror),
256 256 stringutil.forcebytestr(inst.filename)))
257 257 else:
258 258 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
259 259 except MemoryError:
260 260 ui.error(_("abort: out of memory\n"))
261 261 except SystemExit as inst:
262 262 # Commands shouldn't sys.exit directly, but give a return code.
263 263 # Just in case catch this and and pass exit code to caller.
264 264 return inst.code
265 265 except socket.error as inst:
266 266 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
267 267
268 268 return -1
269 269
270 270 def checknewlabel(repo, lbl, kind):
271 271 # Do not use the "kind" parameter in ui output.
272 272 # It makes strings difficult to translate.
273 273 if lbl in ['tip', '.', 'null']:
274 274 raise error.Abort(_("the name '%s' is reserved") % lbl)
275 275 for c in (':', '\0', '\n', '\r'):
276 276 if c in lbl:
277 277 raise error.Abort(
278 278 _("%r cannot be used in a name") % pycompat.bytestr(c))
279 279 try:
280 280 int(lbl)
281 281 raise error.Abort(_("cannot use an integer as a name"))
282 282 except ValueError:
283 283 pass
284 284 if lbl.strip() != lbl:
285 285 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
286 286
287 287 def checkfilename(f):
288 288 '''Check that the filename f is an acceptable filename for a tracked file'''
289 289 if '\r' in f or '\n' in f:
290 290 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
291 291 % pycompat.bytestr(f))
292 292
293 293 def checkportable(ui, f):
294 294 '''Check if filename f is portable and warn or abort depending on config'''
295 295 checkfilename(f)
296 296 abort, warn = checkportabilityalert(ui)
297 297 if abort or warn:
298 298 msg = util.checkwinfilename(f)
299 299 if msg:
300 300 msg = "%s: %s" % (msg, procutil.shellquote(f))
301 301 if abort:
302 302 raise error.Abort(msg)
303 303 ui.warn(_("warning: %s\n") % msg)
304 304
305 305 def checkportabilityalert(ui):
306 306 '''check if the user's config requests nothing, a warning, or abort for
307 307 non-portable filenames'''
308 308 val = ui.config('ui', 'portablefilenames')
309 309 lval = val.lower()
310 310 bval = stringutil.parsebool(val)
311 311 abort = pycompat.iswindows or lval == 'abort'
312 312 warn = bval or lval == 'warn'
313 313 if bval is None and not (warn or abort or lval == 'ignore'):
314 314 raise error.ConfigError(
315 315 _("ui.portablefilenames value is invalid ('%s')") % val)
316 316 return abort, warn
317 317
318 318 class casecollisionauditor(object):
319 319 def __init__(self, ui, abort, dirstate):
320 320 self._ui = ui
321 321 self._abort = abort
322 322 allfiles = '\0'.join(dirstate._map)
323 323 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
324 324 self._dirstate = dirstate
325 325 # The purpose of _newfiles is so that we don't complain about
326 326 # case collisions if someone were to call this object with the
327 327 # same filename twice.
328 328 self._newfiles = set()
329 329
330 330 def __call__(self, f):
331 331 if f in self._newfiles:
332 332 return
333 333 fl = encoding.lower(f)
334 334 if fl in self._loweredfiles and f not in self._dirstate:
335 335 msg = _('possible case-folding collision for %s') % f
336 336 if self._abort:
337 337 raise error.Abort(msg)
338 338 self._ui.warn(_("warning: %s\n") % msg)
339 339 self._loweredfiles.add(fl)
340 340 self._newfiles.add(f)
341 341
342 342 def filteredhash(repo, maxrev):
343 343 """build hash of filtered revisions in the current repoview.
344 344
345 345 Multiple caches perform up-to-date validation by checking that the
346 346 tiprev and tipnode stored in the cache file match the current repository.
347 347 However, this is not sufficient for validating repoviews because the set
348 348 of revisions in the view may change without the repository tiprev and
349 349 tipnode changing.
350 350
351 351 This function hashes all the revs filtered from the view and returns
352 352 that SHA-1 digest.
353 353 """
354 354 cl = repo.changelog
355 355 if not cl.filteredrevs:
356 356 return None
357 357 key = None
358 358 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
359 359 if revs:
360 360 s = hashlib.sha1()
361 361 for rev in revs:
362 362 s.update('%d;' % rev)
363 363 key = s.digest()
364 364 return key
365 365
366 366 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
367 367 '''yield every hg repository under path, always recursively.
368 368 The recurse flag will only control recursion into repo working dirs'''
369 369 def errhandler(err):
370 370 if err.filename == path:
371 371 raise err
372 372 samestat = getattr(os.path, 'samestat', None)
373 373 if followsym and samestat is not None:
374 374 def adddir(dirlst, dirname):
375 375 dirstat = os.stat(dirname)
376 376 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
377 377 if not match:
378 378 dirlst.append(dirstat)
379 379 return not match
380 380 else:
381 381 followsym = False
382 382
383 383 if (seen_dirs is None) and followsym:
384 384 seen_dirs = []
385 385 adddir(seen_dirs, path)
386 386 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
387 387 dirs.sort()
388 388 if '.hg' in dirs:
389 389 yield root # found a repository
390 390 qroot = os.path.join(root, '.hg', 'patches')
391 391 if os.path.isdir(os.path.join(qroot, '.hg')):
392 392 yield qroot # we have a patch queue repo here
393 393 if recurse:
394 394 # avoid recursing inside the .hg directory
395 395 dirs.remove('.hg')
396 396 else:
397 397 dirs[:] = [] # don't descend further
398 398 elif followsym:
399 399 newdirs = []
400 400 for d in dirs:
401 401 fname = os.path.join(root, d)
402 402 if adddir(seen_dirs, fname):
403 403 if os.path.islink(fname):
404 404 for hgname in walkrepos(fname, True, seen_dirs):
405 405 yield hgname
406 406 else:
407 407 newdirs.append(d)
408 408 dirs[:] = newdirs
409 409
410 410 def binnode(ctx):
411 411 """Return binary node id for a given basectx"""
412 412 node = ctx.node()
413 413 if node is None:
414 414 return wdirid
415 415 return node
416 416
417 417 def intrev(ctx):
418 418 """Return integer for a given basectx that can be used in comparison or
419 419 arithmetic operation"""
420 420 rev = ctx.rev()
421 421 if rev is None:
422 422 return wdirrev
423 423 return rev
424 424
425 425 def formatchangeid(ctx):
426 426 """Format changectx as '{rev}:{node|formatnode}', which is the default
427 427 template provided by logcmdutil.changesettemplater"""
428 428 repo = ctx.repo()
429 429 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
430 430
431 431 def formatrevnode(ui, rev, node):
432 432 """Format given revision and node depending on the current verbosity"""
433 433 if ui.debugflag:
434 434 hexfunc = hex
435 435 else:
436 436 hexfunc = short
437 437 return '%d:%s' % (rev, hexfunc(node))
438 438
439 439 def resolvehexnodeidprefix(repo, prefix):
440 # Uses unfiltered repo because it's faster when prefix is ambiguous/
441 # This matches the shortesthexnodeidprefix() function below.
442 node = repo.unfiltered().changelog._partialmatch(prefix)
440 try:
441 # Uses unfiltered repo because it's faster when prefix is ambiguous/
442 # This matches the shortesthexnodeidprefix() function below.
443 node = repo.unfiltered().changelog._partialmatch(prefix)
444 except error.AmbiguousPrefixLookupError:
445 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
446 if revset:
447 # Clear config to avoid infinite recursion
448 configoverrides = {('experimental',
449 'revisions.disambiguatewithin'): None}
450 with repo.ui.configoverride(configoverrides):
451 revs = repo.anyrevs([revset], user=True)
452 matches = []
453 for rev in revs:
454 node = repo.changelog.node(rev)
455 if hex(node).startswith(prefix):
456 matches.append(node)
457 if len(matches) == 1:
458 return matches[0]
459 raise
443 460 if node is None:
444 461 return
445 462 repo.changelog.rev(node) # make sure node isn't filtered
446 463 return node
447 464
448 465 def shortesthexnodeidprefix(repo, node, minlength=1):
449 466 """Find the shortest unambiguous prefix that matches hexnode."""
450 467 # _partialmatch() of filtered changelog could take O(len(repo)) time,
451 468 # which would be unacceptably slow. so we look for hash collision in
452 469 # unfiltered space, which means some hashes may be slightly longer.
453 470 cl = repo.unfiltered().changelog
454 471
455 472 def isrev(prefix):
456 473 try:
457 474 i = int(prefix)
458 475 # if we are a pure int, then starting with zero will not be
459 476 # confused as a rev; or, obviously, if the int is larger
460 477 # than the value of the tip rev
461 478 if prefix[0:1] == b'0' or i > len(cl):
462 479 return False
463 480 return True
464 481 except ValueError:
465 482 return False
466 483
467 484 def disambiguate(prefix):
468 485 """Disambiguate against revnums."""
469 486 hexnode = hex(node)
470 487 for length in range(len(prefix), len(hexnode) + 1):
471 488 prefix = hexnode[:length]
472 489 if not isrev(prefix):
473 490 return prefix
474 491
475 492 try:
476 493 return disambiguate(cl.shortest(node, minlength))
477 494 except error.LookupError:
478 495 raise error.RepoLookupError()
479 496
480 497 def isrevsymbol(repo, symbol):
481 498 """Checks if a symbol exists in the repo.
482 499
483 500 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
484 501 symbol is an ambiguous nodeid prefix.
485 502 """
486 503 try:
487 504 revsymbol(repo, symbol)
488 505 return True
489 506 except error.RepoLookupError:
490 507 return False
491 508
492 509 def revsymbol(repo, symbol):
493 510 """Returns a context given a single revision symbol (as string).
494 511
495 512 This is similar to revsingle(), but accepts only a single revision symbol,
496 513 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
497 514 not "max(public())".
498 515 """
499 516 if not isinstance(symbol, bytes):
500 517 msg = ("symbol (%s of type %s) was not a string, did you mean "
501 518 "repo[symbol]?" % (symbol, type(symbol)))
502 519 raise error.ProgrammingError(msg)
503 520 try:
504 521 if symbol in ('.', 'tip', 'null'):
505 522 return repo[symbol]
506 523
507 524 try:
508 525 r = int(symbol)
509 526 if '%d' % r != symbol:
510 527 raise ValueError
511 528 l = len(repo.changelog)
512 529 if r < 0:
513 530 r += l
514 531 if r < 0 or r >= l and r != wdirrev:
515 532 raise ValueError
516 533 return repo[r]
517 534 except error.FilteredIndexError:
518 535 raise
519 536 except (ValueError, OverflowError, IndexError):
520 537 pass
521 538
522 539 if len(symbol) == 40:
523 540 try:
524 541 node = bin(symbol)
525 542 rev = repo.changelog.rev(node)
526 543 return repo[rev]
527 544 except error.FilteredLookupError:
528 545 raise
529 546 except (TypeError, LookupError):
530 547 pass
531 548
532 549 # look up bookmarks through the name interface
533 550 try:
534 551 node = repo.names.singlenode(repo, symbol)
535 552 rev = repo.changelog.rev(node)
536 553 return repo[rev]
537 554 except KeyError:
538 555 pass
539 556
540 557 node = resolvehexnodeidprefix(repo, symbol)
541 558 if node is not None:
542 559 rev = repo.changelog.rev(node)
543 560 return repo[rev]
544 561
545 562 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
546 563
547 564 except error.WdirUnsupported:
548 565 return repo[None]
549 566 except (error.FilteredIndexError, error.FilteredLookupError,
550 567 error.FilteredRepoLookupError):
551 568 raise _filterederror(repo, symbol)
552 569
553 570 def _filterederror(repo, changeid):
554 571 """build an exception to be raised about a filtered changeid
555 572
556 573 This is extracted in a function to help extensions (eg: evolve) to
557 574 experiment with various message variants."""
558 575 if repo.filtername.startswith('visible'):
559 576
560 577 # Check if the changeset is obsolete
561 578 unfilteredrepo = repo.unfiltered()
562 579 ctx = revsymbol(unfilteredrepo, changeid)
563 580
564 581 # If the changeset is obsolete, enrich the message with the reason
565 582 # that made this changeset not visible
566 583 if ctx.obsolete():
567 584 msg = obsutil._getfilteredreason(repo, changeid, ctx)
568 585 else:
569 586 msg = _("hidden revision '%s'") % changeid
570 587
571 588 hint = _('use --hidden to access hidden revisions')
572 589
573 590 return error.FilteredRepoLookupError(msg, hint=hint)
574 591 msg = _("filtered revision '%s' (not in '%s' subset)")
575 592 msg %= (changeid, repo.filtername)
576 593 return error.FilteredRepoLookupError(msg)
577 594
578 595 def revsingle(repo, revspec, default='.', localalias=None):
579 596 if not revspec and revspec != 0:
580 597 return repo[default]
581 598
582 599 l = revrange(repo, [revspec], localalias=localalias)
583 600 if not l:
584 601 raise error.Abort(_('empty revision set'))
585 602 return repo[l.last()]
586 603
587 604 def _pairspec(revspec):
588 605 tree = revsetlang.parse(revspec)
589 606 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
590 607
591 608 def revpair(repo, revs):
592 609 if not revs:
593 610 return repo['.'], repo[None]
594 611
595 612 l = revrange(repo, revs)
596 613
597 614 if not l:
598 615 first = second = None
599 616 elif l.isascending():
600 617 first = l.min()
601 618 second = l.max()
602 619 elif l.isdescending():
603 620 first = l.max()
604 621 second = l.min()
605 622 else:
606 623 first = l.first()
607 624 second = l.last()
608 625
609 626 if first is None:
610 627 raise error.Abort(_('empty revision range'))
611 628 if (first == second and len(revs) >= 2
612 629 and not all(revrange(repo, [r]) for r in revs)):
613 630 raise error.Abort(_('empty revision on one side of range'))
614 631
615 632 # if top-level is range expression, the result must always be a pair
616 633 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
617 634 return repo[first], repo[None]
618 635
619 636 return repo[first], repo[second]
620 637
621 638 def revrange(repo, specs, localalias=None):
622 639 """Execute 1 to many revsets and return the union.
623 640
624 641 This is the preferred mechanism for executing revsets using user-specified
625 642 config options, such as revset aliases.
626 643
627 644 The revsets specified by ``specs`` will be executed via a chained ``OR``
628 645 expression. If ``specs`` is empty, an empty result is returned.
629 646
630 647 ``specs`` can contain integers, in which case they are assumed to be
631 648 revision numbers.
632 649
633 650 It is assumed the revsets are already formatted. If you have arguments
634 651 that need to be expanded in the revset, call ``revsetlang.formatspec()``
635 652 and pass the result as an element of ``specs``.
636 653
637 654 Specifying a single revset is allowed.
638 655
639 656 Returns a ``revset.abstractsmartset`` which is a list-like interface over
640 657 integer revisions.
641 658 """
642 659 allspecs = []
643 660 for spec in specs:
644 661 if isinstance(spec, int):
645 662 spec = revsetlang.formatspec('rev(%d)', spec)
646 663 allspecs.append(spec)
647 664 return repo.anyrevs(allspecs, user=True, localalias=localalias)
648 665
649 666 def meaningfulparents(repo, ctx):
650 667 """Return list of meaningful (or all if debug) parentrevs for rev.
651 668
652 669 For merges (two non-nullrev revisions) both parents are meaningful.
653 670 Otherwise the first parent revision is considered meaningful if it
654 671 is not the preceding revision.
655 672 """
656 673 parents = ctx.parents()
657 674 if len(parents) > 1:
658 675 return parents
659 676 if repo.ui.debugflag:
660 677 return [parents[0], repo['null']]
661 678 if parents[0].rev() >= intrev(ctx) - 1:
662 679 return []
663 680 return parents
664 681
665 682 def expandpats(pats):
666 683 '''Expand bare globs when running on windows.
667 684 On posix we assume it already has already been done by sh.'''
668 685 if not util.expandglobs:
669 686 return list(pats)
670 687 ret = []
671 688 for kindpat in pats:
672 689 kind, pat = matchmod._patsplit(kindpat, None)
673 690 if kind is None:
674 691 try:
675 692 globbed = glob.glob(pat)
676 693 except re.error:
677 694 globbed = [pat]
678 695 if globbed:
679 696 ret.extend(globbed)
680 697 continue
681 698 ret.append(kindpat)
682 699 return ret
683 700
684 701 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
685 702 badfn=None):
686 703 '''Return a matcher and the patterns that were used.
687 704 The matcher will warn about bad matches, unless an alternate badfn callback
688 705 is provided.'''
689 706 if pats == ("",):
690 707 pats = []
691 708 if opts is None:
692 709 opts = {}
693 710 if not globbed and default == 'relpath':
694 711 pats = expandpats(pats or [])
695 712
696 713 def bad(f, msg):
697 714 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
698 715
699 716 if badfn is None:
700 717 badfn = bad
701 718
702 719 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
703 720 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
704 721
705 722 if m.always():
706 723 pats = []
707 724 return m, pats
708 725
709 726 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
710 727 badfn=None):
711 728 '''Return a matcher that will warn about bad matches.'''
712 729 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
713 730
714 731 def matchall(repo):
715 732 '''Return a matcher that will efficiently match everything.'''
716 733 return matchmod.always(repo.root, repo.getcwd())
717 734
718 735 def matchfiles(repo, files, badfn=None):
719 736 '''Return a matcher that will efficiently match exactly these files.'''
720 737 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
721 738
722 739 def parsefollowlinespattern(repo, rev, pat, msg):
723 740 """Return a file name from `pat` pattern suitable for usage in followlines
724 741 logic.
725 742 """
726 743 if not matchmod.patkind(pat):
727 744 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
728 745 else:
729 746 ctx = repo[rev]
730 747 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
731 748 files = [f for f in ctx if m(f)]
732 749 if len(files) != 1:
733 750 raise error.ParseError(msg)
734 751 return files[0]
735 752
736 753 def origpath(ui, repo, filepath):
737 754 '''customize where .orig files are created
738 755
739 756 Fetch user defined path from config file: [ui] origbackuppath = <path>
740 757 Fall back to default (filepath with .orig suffix) if not specified
741 758 '''
742 759 origbackuppath = ui.config('ui', 'origbackuppath')
743 760 if not origbackuppath:
744 761 return filepath + ".orig"
745 762
746 763 # Convert filepath from an absolute path into a path inside the repo.
747 764 filepathfromroot = util.normpath(os.path.relpath(filepath,
748 765 start=repo.root))
749 766
750 767 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
751 768 origbackupdir = origvfs.dirname(filepathfromroot)
752 769 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
753 770 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
754 771
755 772 # Remove any files that conflict with the backup file's path
756 773 for f in reversed(list(util.finddirs(filepathfromroot))):
757 774 if origvfs.isfileorlink(f):
758 775 ui.note(_('removing conflicting file: %s\n')
759 776 % origvfs.join(f))
760 777 origvfs.unlink(f)
761 778 break
762 779
763 780 origvfs.makedirs(origbackupdir)
764 781
765 782 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
766 783 ui.note(_('removing conflicting directory: %s\n')
767 784 % origvfs.join(filepathfromroot))
768 785 origvfs.rmtree(filepathfromroot, forcibly=True)
769 786
770 787 return origvfs.join(filepathfromroot)
771 788
772 789 class _containsnode(object):
773 790 """proxy __contains__(node) to container.__contains__ which accepts revs"""
774 791
775 792 def __init__(self, repo, revcontainer):
776 793 self._torev = repo.changelog.rev
777 794 self._revcontains = revcontainer.__contains__
778 795
779 796 def __contains__(self, node):
780 797 return self._revcontains(self._torev(node))
781 798
782 799 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
783 800 fixphase=False, targetphase=None, backup=True):
784 801 """do common cleanups when old nodes are replaced by new nodes
785 802
786 803 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
787 804 (we might also want to move working directory parent in the future)
788 805
789 806 By default, bookmark moves are calculated automatically from 'replacements',
790 807 but 'moves' can be used to override that. Also, 'moves' may include
791 808 additional bookmark moves that should not have associated obsmarkers.
792 809
793 810 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
794 811 have replacements. operation is a string, like "rebase".
795 812
796 813 metadata is dictionary containing metadata to be stored in obsmarker if
797 814 obsolescence is enabled.
798 815 """
799 816 assert fixphase or targetphase is None
800 817 if not replacements and not moves:
801 818 return
802 819
803 820 # translate mapping's other forms
804 821 if not util.safehasattr(replacements, 'items'):
805 822 replacements = {n: () for n in replacements}
806 823
807 824 # Calculate bookmark movements
808 825 if moves is None:
809 826 moves = {}
810 827 # Unfiltered repo is needed since nodes in replacements might be hidden.
811 828 unfi = repo.unfiltered()
812 829 for oldnode, newnodes in replacements.items():
813 830 if oldnode in moves:
814 831 continue
815 832 if len(newnodes) > 1:
816 833 # usually a split, take the one with biggest rev number
817 834 newnode = next(unfi.set('max(%ln)', newnodes)).node()
818 835 elif len(newnodes) == 0:
819 836 # move bookmark backwards
820 837 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
821 838 list(replacements)))
822 839 if roots:
823 840 newnode = roots[0].node()
824 841 else:
825 842 newnode = nullid
826 843 else:
827 844 newnode = newnodes[0]
828 845 moves[oldnode] = newnode
829 846
830 847 allnewnodes = [n for ns in replacements.values() for n in ns]
831 848 toretract = {}
832 849 toadvance = {}
833 850 if fixphase:
834 851 precursors = {}
835 852 for oldnode, newnodes in replacements.items():
836 853 for newnode in newnodes:
837 854 precursors.setdefault(newnode, []).append(oldnode)
838 855
839 856 allnewnodes.sort(key=lambda n: unfi[n].rev())
840 857 newphases = {}
841 858 def phase(ctx):
842 859 return newphases.get(ctx.node(), ctx.phase())
843 860 for newnode in allnewnodes:
844 861 ctx = unfi[newnode]
845 862 parentphase = max(phase(p) for p in ctx.parents())
846 863 if targetphase is None:
847 864 oldphase = max(unfi[oldnode].phase()
848 865 for oldnode in precursors[newnode])
849 866 newphase = max(oldphase, parentphase)
850 867 else:
851 868 newphase = max(targetphase, parentphase)
852 869 newphases[newnode] = newphase
853 870 if newphase > ctx.phase():
854 871 toretract.setdefault(newphase, []).append(newnode)
855 872 elif newphase < ctx.phase():
856 873 toadvance.setdefault(newphase, []).append(newnode)
857 874
858 875 with repo.transaction('cleanup') as tr:
859 876 # Move bookmarks
860 877 bmarks = repo._bookmarks
861 878 bmarkchanges = []
862 879 for oldnode, newnode in moves.items():
863 880 oldbmarks = repo.nodebookmarks(oldnode)
864 881 if not oldbmarks:
865 882 continue
866 883 from . import bookmarks # avoid import cycle
867 884 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
868 885 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
869 886 hex(oldnode), hex(newnode)))
870 887 # Delete divergent bookmarks being parents of related newnodes
871 888 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
872 889 allnewnodes, newnode, oldnode)
873 890 deletenodes = _containsnode(repo, deleterevs)
874 891 for name in oldbmarks:
875 892 bmarkchanges.append((name, newnode))
876 893 for b in bookmarks.divergent2delete(repo, deletenodes, name):
877 894 bmarkchanges.append((b, None))
878 895
879 896 if bmarkchanges:
880 897 bmarks.applychanges(repo, tr, bmarkchanges)
881 898
882 899 for phase, nodes in toretract.items():
883 900 phases.retractboundary(repo, tr, phase, nodes)
884 901 for phase, nodes in toadvance.items():
885 902 phases.advanceboundary(repo, tr, phase, nodes)
886 903
887 904 # Obsolete or strip nodes
888 905 if obsolete.isenabled(repo, obsolete.createmarkersopt):
889 906 # If a node is already obsoleted, and we want to obsolete it
890 907 # without a successor, skip that obssolete request since it's
891 908 # unnecessary. That's the "if s or not isobs(n)" check below.
892 909 # Also sort the node in topology order, that might be useful for
893 910 # some obsstore logic.
894 911 # NOTE: the filtering and sorting might belong to createmarkers.
895 912 isobs = unfi.obsstore.successors.__contains__
896 913 torev = unfi.changelog.rev
897 914 sortfunc = lambda ns: torev(ns[0])
898 915 rels = [(unfi[n], tuple(unfi[m] for m in s))
899 916 for n, s in sorted(replacements.items(), key=sortfunc)
900 917 if s or not isobs(n)]
901 918 if rels:
902 919 obsolete.createmarkers(repo, rels, operation=operation,
903 920 metadata=metadata)
904 921 else:
905 922 from . import repair # avoid import cycle
906 923 tostrip = list(replacements)
907 924 if tostrip:
908 925 repair.delayedstrip(repo.ui, repo, tostrip, operation,
909 926 backup=backup)
910 927
911 928 def addremove(repo, matcher, prefix, opts=None):
912 929 if opts is None:
913 930 opts = {}
914 931 m = matcher
915 932 dry_run = opts.get('dry_run')
916 933 try:
917 934 similarity = float(opts.get('similarity') or 0)
918 935 except ValueError:
919 936 raise error.Abort(_('similarity must be a number'))
920 937 if similarity < 0 or similarity > 100:
921 938 raise error.Abort(_('similarity must be between 0 and 100'))
922 939 similarity /= 100.0
923 940
924 941 ret = 0
925 942 join = lambda f: os.path.join(prefix, f)
926 943
927 944 wctx = repo[None]
928 945 for subpath in sorted(wctx.substate):
929 946 submatch = matchmod.subdirmatcher(subpath, m)
930 947 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
931 948 sub = wctx.sub(subpath)
932 949 try:
933 950 if sub.addremove(submatch, prefix, opts):
934 951 ret = 1
935 952 except error.LookupError:
936 953 repo.ui.status(_("skipping missing subrepository: %s\n")
937 954 % join(subpath))
938 955
939 956 rejected = []
940 957 def badfn(f, msg):
941 958 if f in m.files():
942 959 m.bad(f, msg)
943 960 rejected.append(f)
944 961
945 962 badmatch = matchmod.badmatch(m, badfn)
946 963 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
947 964 badmatch)
948 965
949 966 unknownset = set(unknown + forgotten)
950 967 toprint = unknownset.copy()
951 968 toprint.update(deleted)
952 969 for abs in sorted(toprint):
953 970 if repo.ui.verbose or not m.exact(abs):
954 971 if abs in unknownset:
955 972 status = _('adding %s\n') % m.uipath(abs)
956 973 else:
957 974 status = _('removing %s\n') % m.uipath(abs)
958 975 repo.ui.status(status)
959 976
960 977 renames = _findrenames(repo, m, added + unknown, removed + deleted,
961 978 similarity)
962 979
963 980 if not dry_run:
964 981 _markchanges(repo, unknown + forgotten, deleted, renames)
965 982
966 983 for f in rejected:
967 984 if f in m.files():
968 985 return 1
969 986 return ret
970 987
971 988 def marktouched(repo, files, similarity=0.0):
972 989 '''Assert that files have somehow been operated upon. files are relative to
973 990 the repo root.'''
974 991 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
975 992 rejected = []
976 993
977 994 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
978 995
979 996 if repo.ui.verbose:
980 997 unknownset = set(unknown + forgotten)
981 998 toprint = unknownset.copy()
982 999 toprint.update(deleted)
983 1000 for abs in sorted(toprint):
984 1001 if abs in unknownset:
985 1002 status = _('adding %s\n') % abs
986 1003 else:
987 1004 status = _('removing %s\n') % abs
988 1005 repo.ui.status(status)
989 1006
990 1007 renames = _findrenames(repo, m, added + unknown, removed + deleted,
991 1008 similarity)
992 1009
993 1010 _markchanges(repo, unknown + forgotten, deleted, renames)
994 1011
995 1012 for f in rejected:
996 1013 if f in m.files():
997 1014 return 1
998 1015 return 0
999 1016
1000 1017 def _interestingfiles(repo, matcher):
1001 1018 '''Walk dirstate with matcher, looking for files that addremove would care
1002 1019 about.
1003 1020
1004 1021 This is different from dirstate.status because it doesn't care about
1005 1022 whether files are modified or clean.'''
1006 1023 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1007 1024 audit_path = pathutil.pathauditor(repo.root, cached=True)
1008 1025
1009 1026 ctx = repo[None]
1010 1027 dirstate = repo.dirstate
1011 1028 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1012 1029 unknown=True, ignored=False, full=False)
1013 1030 for abs, st in walkresults.iteritems():
1014 1031 dstate = dirstate[abs]
1015 1032 if dstate == '?' and audit_path.check(abs):
1016 1033 unknown.append(abs)
1017 1034 elif dstate != 'r' and not st:
1018 1035 deleted.append(abs)
1019 1036 elif dstate == 'r' and st:
1020 1037 forgotten.append(abs)
1021 1038 # for finding renames
1022 1039 elif dstate == 'r' and not st:
1023 1040 removed.append(abs)
1024 1041 elif dstate == 'a':
1025 1042 added.append(abs)
1026 1043
1027 1044 return added, unknown, deleted, removed, forgotten
1028 1045
1029 1046 def _findrenames(repo, matcher, added, removed, similarity):
1030 1047 '''Find renames from removed files to added ones.'''
1031 1048 renames = {}
1032 1049 if similarity > 0:
1033 1050 for old, new, score in similar.findrenames(repo, added, removed,
1034 1051 similarity):
1035 1052 if (repo.ui.verbose or not matcher.exact(old)
1036 1053 or not matcher.exact(new)):
1037 1054 repo.ui.status(_('recording removal of %s as rename to %s '
1038 1055 '(%d%% similar)\n') %
1039 1056 (matcher.rel(old), matcher.rel(new),
1040 1057 score * 100))
1041 1058 renames[new] = old
1042 1059 return renames
1043 1060
1044 1061 def _markchanges(repo, unknown, deleted, renames):
1045 1062 '''Marks the files in unknown as added, the files in deleted as removed,
1046 1063 and the files in renames as copied.'''
1047 1064 wctx = repo[None]
1048 1065 with repo.wlock():
1049 1066 wctx.forget(deleted)
1050 1067 wctx.add(unknown)
1051 1068 for new, old in renames.iteritems():
1052 1069 wctx.copy(old, new)
1053 1070
1054 1071 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1055 1072 """Update the dirstate to reflect the intent of copying src to dst. For
1056 1073 different reasons it might not end with dst being marked as copied from src.
1057 1074 """
1058 1075 origsrc = repo.dirstate.copied(src) or src
1059 1076 if dst == origsrc: # copying back a copy?
1060 1077 if repo.dirstate[dst] not in 'mn' and not dryrun:
1061 1078 repo.dirstate.normallookup(dst)
1062 1079 else:
1063 1080 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1064 1081 if not ui.quiet:
1065 1082 ui.warn(_("%s has not been committed yet, so no copy "
1066 1083 "data will be stored for %s.\n")
1067 1084 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1068 1085 if repo.dirstate[dst] in '?r' and not dryrun:
1069 1086 wctx.add([dst])
1070 1087 elif not dryrun:
1071 1088 wctx.copy(origsrc, dst)
1072 1089
1073 1090 def readrequires(opener, supported):
1074 1091 '''Reads and parses .hg/requires and checks if all entries found
1075 1092 are in the list of supported features.'''
1076 1093 requirements = set(opener.read("requires").splitlines())
1077 1094 missings = []
1078 1095 for r in requirements:
1079 1096 if r not in supported:
1080 1097 if not r or not r[0:1].isalnum():
1081 1098 raise error.RequirementError(_(".hg/requires file is corrupt"))
1082 1099 missings.append(r)
1083 1100 missings.sort()
1084 1101 if missings:
1085 1102 raise error.RequirementError(
1086 1103 _("repository requires features unknown to this Mercurial: %s")
1087 1104 % " ".join(missings),
1088 1105 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1089 1106 " for more information"))
1090 1107 return requirements
1091 1108
1092 1109 def writerequires(opener, requirements):
1093 1110 with opener('requires', 'w') as fp:
1094 1111 for r in sorted(requirements):
1095 1112 fp.write("%s\n" % r)
1096 1113
1097 1114 class filecachesubentry(object):
1098 1115 def __init__(self, path, stat):
1099 1116 self.path = path
1100 1117 self.cachestat = None
1101 1118 self._cacheable = None
1102 1119
1103 1120 if stat:
1104 1121 self.cachestat = filecachesubentry.stat(self.path)
1105 1122
1106 1123 if self.cachestat:
1107 1124 self._cacheable = self.cachestat.cacheable()
1108 1125 else:
1109 1126 # None means we don't know yet
1110 1127 self._cacheable = None
1111 1128
1112 1129 def refresh(self):
1113 1130 if self.cacheable():
1114 1131 self.cachestat = filecachesubentry.stat(self.path)
1115 1132
1116 1133 def cacheable(self):
1117 1134 if self._cacheable is not None:
1118 1135 return self._cacheable
1119 1136
1120 1137 # we don't know yet, assume it is for now
1121 1138 return True
1122 1139
1123 1140 def changed(self):
1124 1141 # no point in going further if we can't cache it
1125 1142 if not self.cacheable():
1126 1143 return True
1127 1144
1128 1145 newstat = filecachesubentry.stat(self.path)
1129 1146
1130 1147 # we may not know if it's cacheable yet, check again now
1131 1148 if newstat and self._cacheable is None:
1132 1149 self._cacheable = newstat.cacheable()
1133 1150
1134 1151 # check again
1135 1152 if not self._cacheable:
1136 1153 return True
1137 1154
1138 1155 if self.cachestat != newstat:
1139 1156 self.cachestat = newstat
1140 1157 return True
1141 1158 else:
1142 1159 return False
1143 1160
1144 1161 @staticmethod
1145 1162 def stat(path):
1146 1163 try:
1147 1164 return util.cachestat(path)
1148 1165 except OSError as e:
1149 1166 if e.errno != errno.ENOENT:
1150 1167 raise
1151 1168
1152 1169 class filecacheentry(object):
1153 1170 def __init__(self, paths, stat=True):
1154 1171 self._entries = []
1155 1172 for path in paths:
1156 1173 self._entries.append(filecachesubentry(path, stat))
1157 1174
1158 1175 def changed(self):
1159 1176 '''true if any entry has changed'''
1160 1177 for entry in self._entries:
1161 1178 if entry.changed():
1162 1179 return True
1163 1180 return False
1164 1181
1165 1182 def refresh(self):
1166 1183 for entry in self._entries:
1167 1184 entry.refresh()
1168 1185
1169 1186 class filecache(object):
1170 1187 """A property like decorator that tracks files under .hg/ for updates.
1171 1188
1172 1189 On first access, the files defined as arguments are stat()ed and the
1173 1190 results cached. The decorated function is called. The results are stashed
1174 1191 away in a ``_filecache`` dict on the object whose method is decorated.
1175 1192
1176 1193 On subsequent access, the cached result is returned.
1177 1194
1178 1195 On external property set operations, stat() calls are performed and the new
1179 1196 value is cached.
1180 1197
1181 1198 On property delete operations, cached data is removed.
1182 1199
1183 1200 When using the property API, cached data is always returned, if available:
1184 1201 no stat() is performed to check if the file has changed and if the function
1185 1202 needs to be called to reflect file changes.
1186 1203
1187 1204 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1188 1205 can populate an entry before the property's getter is called. In this case,
1189 1206 entries in ``_filecache`` will be used during property operations,
1190 1207 if available. If the underlying file changes, it is up to external callers
1191 1208 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1192 1209 method result as well as possibly calling ``del obj._filecache[attr]`` to
1193 1210 remove the ``filecacheentry``.
1194 1211 """
1195 1212
1196 1213 def __init__(self, *paths):
1197 1214 self.paths = paths
1198 1215
1199 1216 def join(self, obj, fname):
1200 1217 """Used to compute the runtime path of a cached file.
1201 1218
1202 1219 Users should subclass filecache and provide their own version of this
1203 1220 function to call the appropriate join function on 'obj' (an instance
1204 1221 of the class that its member function was decorated).
1205 1222 """
1206 1223 raise NotImplementedError
1207 1224
1208 1225 def __call__(self, func):
1209 1226 self.func = func
1210 1227 self.sname = func.__name__
1211 1228 self.name = pycompat.sysbytes(self.sname)
1212 1229 return self
1213 1230
1214 1231 def __get__(self, obj, type=None):
1215 1232 # if accessed on the class, return the descriptor itself.
1216 1233 if obj is None:
1217 1234 return self
1218 1235 # do we need to check if the file changed?
1219 1236 if self.sname in obj.__dict__:
1220 1237 assert self.name in obj._filecache, self.name
1221 1238 return obj.__dict__[self.sname]
1222 1239
1223 1240 entry = obj._filecache.get(self.name)
1224 1241
1225 1242 if entry:
1226 1243 if entry.changed():
1227 1244 entry.obj = self.func(obj)
1228 1245 else:
1229 1246 paths = [self.join(obj, path) for path in self.paths]
1230 1247
1231 1248 # We stat -before- creating the object so our cache doesn't lie if
1232 1249 # a writer modified between the time we read and stat
1233 1250 entry = filecacheentry(paths, True)
1234 1251 entry.obj = self.func(obj)
1235 1252
1236 1253 obj._filecache[self.name] = entry
1237 1254
1238 1255 obj.__dict__[self.sname] = entry.obj
1239 1256 return entry.obj
1240 1257
1241 1258 def __set__(self, obj, value):
1242 1259 if self.name not in obj._filecache:
1243 1260 # we add an entry for the missing value because X in __dict__
1244 1261 # implies X in _filecache
1245 1262 paths = [self.join(obj, path) for path in self.paths]
1246 1263 ce = filecacheentry(paths, False)
1247 1264 obj._filecache[self.name] = ce
1248 1265 else:
1249 1266 ce = obj._filecache[self.name]
1250 1267
1251 1268 ce.obj = value # update cached copy
1252 1269 obj.__dict__[self.sname] = value # update copy returned by obj.x
1253 1270
1254 1271 def __delete__(self, obj):
1255 1272 try:
1256 1273 del obj.__dict__[self.sname]
1257 1274 except KeyError:
1258 1275 raise AttributeError(self.sname)
1259 1276
1260 1277 def extdatasource(repo, source):
1261 1278 """Gather a map of rev -> value dict from the specified source
1262 1279
1263 1280 A source spec is treated as a URL, with a special case shell: type
1264 1281 for parsing the output from a shell command.
1265 1282
1266 1283 The data is parsed as a series of newline-separated records where
1267 1284 each record is a revision specifier optionally followed by a space
1268 1285 and a freeform string value. If the revision is known locally, it
1269 1286 is converted to a rev, otherwise the record is skipped.
1270 1287
1271 1288 Note that both key and value are treated as UTF-8 and converted to
1272 1289 the local encoding. This allows uniformity between local and
1273 1290 remote data sources.
1274 1291 """
1275 1292
1276 1293 spec = repo.ui.config("extdata", source)
1277 1294 if not spec:
1278 1295 raise error.Abort(_("unknown extdata source '%s'") % source)
1279 1296
1280 1297 data = {}
1281 1298 src = proc = None
1282 1299 try:
1283 1300 if spec.startswith("shell:"):
1284 1301 # external commands should be run relative to the repo root
1285 1302 cmd = spec[6:]
1286 1303 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1287 1304 close_fds=procutil.closefds,
1288 1305 stdout=subprocess.PIPE, cwd=repo.root)
1289 1306 src = proc.stdout
1290 1307 else:
1291 1308 # treat as a URL or file
1292 1309 src = url.open(repo.ui, spec)
1293 1310 for l in src:
1294 1311 if " " in l:
1295 1312 k, v = l.strip().split(" ", 1)
1296 1313 else:
1297 1314 k, v = l.strip(), ""
1298 1315
1299 1316 k = encoding.tolocal(k)
1300 1317 try:
1301 1318 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1302 1319 except (error.LookupError, error.RepoLookupError):
1303 1320 pass # we ignore data for nodes that don't exist locally
1304 1321 finally:
1305 1322 if proc:
1306 1323 proc.communicate()
1307 1324 if src:
1308 1325 src.close()
1309 1326 if proc and proc.returncode != 0:
1310 1327 raise error.Abort(_("extdata command '%s' failed: %s")
1311 1328 % (cmd, procutil.explainexit(proc.returncode)))
1312 1329
1313 1330 return data
1314 1331
1315 1332 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1316 1333 if lock is None:
1317 1334 raise error.LockInheritanceContractViolation(
1318 1335 'lock can only be inherited while held')
1319 1336 if environ is None:
1320 1337 environ = {}
1321 1338 with lock.inherit() as locker:
1322 1339 environ[envvar] = locker
1323 1340 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1324 1341
1325 1342 def wlocksub(repo, cmd, *args, **kwargs):
1326 1343 """run cmd as a subprocess that allows inheriting repo's wlock
1327 1344
1328 1345 This can only be called while the wlock is held. This takes all the
1329 1346 arguments that ui.system does, and returns the exit code of the
1330 1347 subprocess."""
1331 1348 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1332 1349 **kwargs)
1333 1350
1334 1351 class progress(object):
1335 1352 def __init__(self, ui, topic, unit="", total=None):
1336 1353 self.ui = ui
1337 1354 self.pos = 0
1338 1355 self.topic = topic
1339 1356 self.unit = unit
1340 1357 self.total = total
1341 1358
1342 1359 def __enter__(self):
1343 1360 return self
1344 1361
1345 1362 def __exit__(self, exc_type, exc_value, exc_tb):
1346 1363 self.complete()
1347 1364
1348 1365 def update(self, pos, item="", total=None):
1349 1366 assert pos is not None
1350 1367 if total:
1351 1368 self.total = total
1352 1369 self.pos = pos
1353 1370 self._print(item)
1354 1371
1355 1372 def increment(self, step=1, item="", total=None):
1356 1373 self.update(self.pos + step, item, total)
1357 1374
1358 1375 def complete(self):
1359 1376 self.ui.progress(self.topic, None)
1360 1377
1361 1378 def _print(self, item):
1362 1379 self.ui.progress(self.topic, self.pos, item, self.unit,
1363 1380 self.total)
1364 1381
1365 1382 def gdinitconfig(ui):
1366 1383 """helper function to know if a repo should be created as general delta
1367 1384 """
1368 1385 # experimental config: format.generaldelta
1369 1386 return (ui.configbool('format', 'generaldelta')
1370 1387 or ui.configbool('format', 'usegeneraldelta')
1371 1388 or ui.configbool('format', 'sparse-revlog'))
1372 1389
1373 1390 def gddeltaconfig(ui):
1374 1391 """helper function to know if incoming delta should be optimised
1375 1392 """
1376 1393 # experimental config: format.generaldelta
1377 1394 return ui.configbool('format', 'generaldelta')
1378 1395
1379 1396 class simplekeyvaluefile(object):
1380 1397 """A simple file with key=value lines
1381 1398
1382 1399 Keys must be alphanumerics and start with a letter, values must not
1383 1400 contain '\n' characters"""
1384 1401 firstlinekey = '__firstline'
1385 1402
1386 1403 def __init__(self, vfs, path, keys=None):
1387 1404 self.vfs = vfs
1388 1405 self.path = path
1389 1406
1390 1407 def read(self, firstlinenonkeyval=False):
1391 1408 """Read the contents of a simple key-value file
1392 1409
1393 1410 'firstlinenonkeyval' indicates whether the first line of file should
1394 1411 be treated as a key-value pair or reuturned fully under the
1395 1412 __firstline key."""
1396 1413 lines = self.vfs.readlines(self.path)
1397 1414 d = {}
1398 1415 if firstlinenonkeyval:
1399 1416 if not lines:
1400 1417 e = _("empty simplekeyvalue file")
1401 1418 raise error.CorruptedState(e)
1402 1419 # we don't want to include '\n' in the __firstline
1403 1420 d[self.firstlinekey] = lines[0][:-1]
1404 1421 del lines[0]
1405 1422
1406 1423 try:
1407 1424 # the 'if line.strip()' part prevents us from failing on empty
1408 1425 # lines which only contain '\n' therefore are not skipped
1409 1426 # by 'if line'
1410 1427 updatedict = dict(line[:-1].split('=', 1) for line in lines
1411 1428 if line.strip())
1412 1429 if self.firstlinekey in updatedict:
1413 1430 e = _("%r can't be used as a key")
1414 1431 raise error.CorruptedState(e % self.firstlinekey)
1415 1432 d.update(updatedict)
1416 1433 except ValueError as e:
1417 1434 raise error.CorruptedState(str(e))
1418 1435 return d
1419 1436
1420 1437 def write(self, data, firstline=None):
1421 1438 """Write key=>value mapping to a file
1422 1439 data is a dict. Keys must be alphanumerical and start with a letter.
1423 1440 Values must not contain newline characters.
1424 1441
1425 1442 If 'firstline' is not None, it is written to file before
1426 1443 everything else, as it is, not in a key=value form"""
1427 1444 lines = []
1428 1445 if firstline is not None:
1429 1446 lines.append('%s\n' % firstline)
1430 1447
1431 1448 for k, v in data.items():
1432 1449 if k == self.firstlinekey:
1433 1450 e = "key name '%s' is reserved" % self.firstlinekey
1434 1451 raise error.ProgrammingError(e)
1435 1452 if not k[0:1].isalpha():
1436 1453 e = "keys must start with a letter in a key-value file"
1437 1454 raise error.ProgrammingError(e)
1438 1455 if not k.isalnum():
1439 1456 e = "invalid key name in a simple key-value file"
1440 1457 raise error.ProgrammingError(e)
1441 1458 if '\n' in v:
1442 1459 e = "invalid value in a simple key-value file"
1443 1460 raise error.ProgrammingError(e)
1444 1461 lines.append("%s=%s\n" % (k, v))
1445 1462 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1446 1463 fp.write(''.join(lines))
1447 1464
1448 1465 _reportobsoletedsource = [
1449 1466 'debugobsolete',
1450 1467 'pull',
1451 1468 'push',
1452 1469 'serve',
1453 1470 'unbundle',
1454 1471 ]
1455 1472
1456 1473 _reportnewcssource = [
1457 1474 'pull',
1458 1475 'unbundle',
1459 1476 ]
1460 1477
1461 1478 def prefetchfiles(repo, revs, match):
1462 1479 """Invokes the registered file prefetch functions, allowing extensions to
1463 1480 ensure the corresponding files are available locally, before the command
1464 1481 uses them."""
1465 1482 if match:
1466 1483 # The command itself will complain about files that don't exist, so
1467 1484 # don't duplicate the message.
1468 1485 match = matchmod.badmatch(match, lambda fn, msg: None)
1469 1486 else:
1470 1487 match = matchall(repo)
1471 1488
1472 1489 fileprefetchhooks(repo, revs, match)
1473 1490
1474 1491 # a list of (repo, revs, match) prefetch functions
1475 1492 fileprefetchhooks = util.hooks()
1476 1493
1477 1494 # A marker that tells the evolve extension to suppress its own reporting
1478 1495 _reportstroubledchangesets = True
1479 1496
1480 1497 def registersummarycallback(repo, otr, txnname=''):
1481 1498 """register a callback to issue a summary after the transaction is closed
1482 1499 """
1483 1500 def txmatch(sources):
1484 1501 return any(txnname.startswith(source) for source in sources)
1485 1502
1486 1503 categories = []
1487 1504
1488 1505 def reportsummary(func):
1489 1506 """decorator for report callbacks."""
1490 1507 # The repoview life cycle is shorter than the one of the actual
1491 1508 # underlying repository. So the filtered object can die before the
1492 1509 # weakref is used leading to troubles. We keep a reference to the
1493 1510 # unfiltered object and restore the filtering when retrieving the
1494 1511 # repository through the weakref.
1495 1512 filtername = repo.filtername
1496 1513 reporef = weakref.ref(repo.unfiltered())
1497 1514 def wrapped(tr):
1498 1515 repo = reporef()
1499 1516 if filtername:
1500 1517 repo = repo.filtered(filtername)
1501 1518 func(repo, tr)
1502 1519 newcat = '%02i-txnreport' % len(categories)
1503 1520 otr.addpostclose(newcat, wrapped)
1504 1521 categories.append(newcat)
1505 1522 return wrapped
1506 1523
1507 1524 if txmatch(_reportobsoletedsource):
1508 1525 @reportsummary
1509 1526 def reportobsoleted(repo, tr):
1510 1527 obsoleted = obsutil.getobsoleted(repo, tr)
1511 1528 if obsoleted:
1512 1529 repo.ui.status(_('obsoleted %i changesets\n')
1513 1530 % len(obsoleted))
1514 1531
1515 1532 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1516 1533 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1517 1534 instabilitytypes = [
1518 1535 ('orphan', 'orphan'),
1519 1536 ('phase-divergent', 'phasedivergent'),
1520 1537 ('content-divergent', 'contentdivergent'),
1521 1538 ]
1522 1539
1523 1540 def getinstabilitycounts(repo):
1524 1541 filtered = repo.changelog.filteredrevs
1525 1542 counts = {}
1526 1543 for instability, revset in instabilitytypes:
1527 1544 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1528 1545 filtered)
1529 1546 return counts
1530 1547
1531 1548 oldinstabilitycounts = getinstabilitycounts(repo)
1532 1549 @reportsummary
1533 1550 def reportnewinstabilities(repo, tr):
1534 1551 newinstabilitycounts = getinstabilitycounts(repo)
1535 1552 for instability, revset in instabilitytypes:
1536 1553 delta = (newinstabilitycounts[instability] -
1537 1554 oldinstabilitycounts[instability])
1538 1555 msg = getinstabilitymessage(delta, instability)
1539 1556 if msg:
1540 1557 repo.ui.warn(msg)
1541 1558
1542 1559 if txmatch(_reportnewcssource):
1543 1560 @reportsummary
1544 1561 def reportnewcs(repo, tr):
1545 1562 """Report the range of new revisions pulled/unbundled."""
1546 1563 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1547 1564 if not newrevs:
1548 1565 return
1549 1566
1550 1567 # Compute the bounds of new revisions' range, excluding obsoletes.
1551 1568 unfi = repo.unfiltered()
1552 1569 revs = unfi.revs('%ld and not obsolete()', newrevs)
1553 1570 if not revs:
1554 1571 # Got only obsoletes.
1555 1572 return
1556 1573 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1557 1574
1558 1575 if minrev == maxrev:
1559 1576 revrange = minrev
1560 1577 else:
1561 1578 revrange = '%s:%s' % (minrev, maxrev)
1562 1579 repo.ui.status(_('new changesets %s\n') % revrange)
1563 1580
1564 1581 @reportsummary
1565 1582 def reportphasechanges(repo, tr):
1566 1583 """Report statistics of phase changes for changesets pre-existing
1567 1584 pull/unbundle.
1568 1585 """
1569 1586 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1570 1587 phasetracking = tr.changes.get('phases', {})
1571 1588 if not phasetracking:
1572 1589 return
1573 1590 published = [
1574 1591 rev for rev, (old, new) in phasetracking.iteritems()
1575 1592 if new == phases.public and rev not in newrevs
1576 1593 ]
1577 1594 if not published:
1578 1595 return
1579 1596 repo.ui.status(_('%d local changesets published\n')
1580 1597 % len(published))
1581 1598
1582 1599 def getinstabilitymessage(delta, instability):
1583 1600 """function to return the message to show warning about new instabilities
1584 1601
1585 1602 exists as a separate function so that extension can wrap to show more
1586 1603 information like how to fix instabilities"""
1587 1604 if delta > 0:
1588 1605 return _('%i new %s changesets\n') % (delta, instability)
1589 1606
1590 1607 def nodesummaries(repo, nodes, maxnumnodes=4):
1591 1608 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1592 1609 return ' '.join(short(h) for h in nodes)
1593 1610 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1594 1611 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1595 1612
1596 1613 def enforcesinglehead(repo, tr, desc):
1597 1614 """check that no named branch has multiple heads"""
1598 1615 if desc in ('strip', 'repair'):
1599 1616 # skip the logic during strip
1600 1617 return
1601 1618 visible = repo.filtered('visible')
1602 1619 # possible improvement: we could restrict the check to affected branch
1603 1620 for name, heads in visible.branchmap().iteritems():
1604 1621 if len(heads) > 1:
1605 1622 msg = _('rejecting multiple heads on branch "%s"')
1606 1623 msg %= name
1607 1624 hint = _('%d heads: %s')
1608 1625 hint %= (len(heads), nodesummaries(repo, heads))
1609 1626 raise error.Abort(msg, hint=hint)
1610 1627
1611 1628 def wrapconvertsink(sink):
1612 1629 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1613 1630 before it is used, whether or not the convert extension was formally loaded.
1614 1631 """
1615 1632 return sink
1616 1633
1617 1634 def unhidehashlikerevs(repo, specs, hiddentype):
1618 1635 """parse the user specs and unhide changesets whose hash or revision number
1619 1636 is passed.
1620 1637
1621 1638 hiddentype can be: 1) 'warn': warn while unhiding changesets
1622 1639 2) 'nowarn': don't warn while unhiding changesets
1623 1640
1624 1641 returns a repo object with the required changesets unhidden
1625 1642 """
1626 1643 if not repo.filtername or not repo.ui.configbool('experimental',
1627 1644 'directaccess'):
1628 1645 return repo
1629 1646
1630 1647 if repo.filtername not in ('visible', 'visible-hidden'):
1631 1648 return repo
1632 1649
1633 1650 symbols = set()
1634 1651 for spec in specs:
1635 1652 try:
1636 1653 tree = revsetlang.parse(spec)
1637 1654 except error.ParseError: # will be reported by scmutil.revrange()
1638 1655 continue
1639 1656
1640 1657 symbols.update(revsetlang.gethashlikesymbols(tree))
1641 1658
1642 1659 if not symbols:
1643 1660 return repo
1644 1661
1645 1662 revs = _getrevsfromsymbols(repo, symbols)
1646 1663
1647 1664 if not revs:
1648 1665 return repo
1649 1666
1650 1667 if hiddentype == 'warn':
1651 1668 unfi = repo.unfiltered()
1652 1669 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1653 1670 repo.ui.warn(_("warning: accessing hidden changesets for write "
1654 1671 "operation: %s\n") % revstr)
1655 1672
1656 1673 # we have to use new filtername to separate branch/tags cache until we can
1657 1674 # disbale these cache when revisions are dynamically pinned.
1658 1675 return repo.filtered('visible-hidden', revs)
1659 1676
1660 1677 def _getrevsfromsymbols(repo, symbols):
1661 1678 """parse the list of symbols and returns a set of revision numbers of hidden
1662 1679 changesets present in symbols"""
1663 1680 revs = set()
1664 1681 unfi = repo.unfiltered()
1665 1682 unficl = unfi.changelog
1666 1683 cl = repo.changelog
1667 1684 tiprev = len(unficl)
1668 1685 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1669 1686 for s in symbols:
1670 1687 try:
1671 1688 n = int(s)
1672 1689 if n <= tiprev:
1673 1690 if not allowrevnums:
1674 1691 continue
1675 1692 else:
1676 1693 if n not in cl:
1677 1694 revs.add(n)
1678 1695 continue
1679 1696 except ValueError:
1680 1697 pass
1681 1698
1682 1699 try:
1683 1700 s = resolvehexnodeidprefix(unfi, s)
1684 1701 except (error.LookupError, error.WdirUnsupported):
1685 1702 s = None
1686 1703
1687 1704 if s is not None:
1688 1705 rev = unficl.rev(s)
1689 1706 if rev not in cl:
1690 1707 revs.add(rev)
1691 1708
1692 1709 return revs
1693 1710
1694 1711 def bookmarkrevs(repo, mark):
1695 1712 """
1696 1713 Select revisions reachable by a given bookmark
1697 1714 """
1698 1715 return repo.revs("ancestors(bookmark(%s)) - "
1699 1716 "ancestors(head() and not bookmark(%s)) - "
1700 1717 "ancestors(bookmark() and not bookmark(%s))",
1701 1718 mark, mark, mark)
General Comments 0
You need to be logged in to leave comments. Login now