##// END OF EJS Templates
ui: add an uninterruptable context manager that can block SIGINT...
Augie Fackler -
r38545:313a940d default
parent child Browse files
Show More
@@ -0,0 +1,83 b''
1 Dummy extension simulating unsafe long running command
2 $ cat > sleepext.py <<EOF
3 > import time
4 > import itertools
5 >
6 > from mercurial import registrar
7 > from mercurial.i18n import _
8 >
9 > cmdtable = {}
10 > command = registrar.command(cmdtable)
11 >
12 > @command(b'sleep', [], _(b'TIME'), norepo=True)
13 > def sleep(ui, sleeptime=b"1", **opts):
14 > with ui.uninterruptable():
15 > for _i in itertools.repeat(None, int(sleeptime)):
16 > time.sleep(1)
17 > ui.warn(b"end of unsafe operation\n")
18 > ui.warn(b"%s second(s) passed\n" % sleeptime)
19 > EOF
20
21 Kludge to emulate timeout(1) which is not generally available.
22 $ cat > timeout.py <<EOF
23 > from __future__ import print_function
24 > import argparse
25 > import signal
26 > import subprocess
27 > import sys
28 > import time
29 >
30 > ap = argparse.ArgumentParser()
31 > ap.add_argument('-s', nargs=1, default='SIGTERM')
32 > ap.add_argument('duration', nargs=1, type=int)
33 > ap.add_argument('argv', nargs='*')
34 > opts = ap.parse_args()
35 > try:
36 > sig = int(opts.s[0])
37 > except ValueError:
38 > sname = opts.s[0]
39 > if not sname.startswith('SIG'):
40 > sname = 'SIG' + sname
41 > sig = getattr(signal, sname)
42 > proc = subprocess.Popen(opts.argv)
43 > time.sleep(opts.duration[0])
44 > proc.poll()
45 > if proc.returncode is None:
46 > proc.send_signal(sig)
47 > proc.wait()
48 > sys.exit(124)
49 > EOF
50
51 Set up repository
52 $ hg init repo
53 $ cd repo
54 $ cat >> $HGRCPATH << EOF
55 > [extensions]
56 > sleepext = ../sleepext.py
57 > EOF
58
59 Test ctrl-c
60 $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2
61 interrupted!
62 [124]
63
64 $ cat >> $HGRCPATH << EOF
65 > [experimental]
66 > nointerrupt = yes
67 > EOF
68
69 $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2
70 interrupted!
71 [124]
72
73 $ cat >> $HGRCPATH << EOF
74 > [experimental]
75 > nointerrupt-interactiveonly = False
76 > EOF
77
78 $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2
79 shutting down cleanly
80 press ^C again to terminate immediately (dangerous)
81 end of unsafe operation
82 interrupted!
83 [124]
@@ -1,1355 +1,1358 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('auth', 'cookiefile',
151 151 default=None,
152 152 )
153 153 # bookmarks.pushing: internal hack for discovery
154 154 coreconfigitem('bookmarks', 'pushing',
155 155 default=list,
156 156 )
157 157 # bundle.mainreporoot: internal hack for bundlerepo
158 158 coreconfigitem('bundle', 'mainreporoot',
159 159 default='',
160 160 )
161 161 # bundle.reorder: experimental config
162 162 coreconfigitem('bundle', 'reorder',
163 163 default='auto',
164 164 )
165 165 coreconfigitem('censor', 'policy',
166 166 default='abort',
167 167 )
168 168 coreconfigitem('chgserver', 'idletimeout',
169 169 default=3600,
170 170 )
171 171 coreconfigitem('chgserver', 'skiphash',
172 172 default=False,
173 173 )
174 174 coreconfigitem('cmdserver', 'log',
175 175 default=None,
176 176 )
177 177 coreconfigitem('color', '.*',
178 178 default=None,
179 179 generic=True,
180 180 )
181 181 coreconfigitem('color', 'mode',
182 182 default='auto',
183 183 )
184 184 coreconfigitem('color', 'pagermode',
185 185 default=dynamicdefault,
186 186 )
187 187 coreconfigitem('commands', 'show.aliasprefix',
188 188 default=list,
189 189 )
190 190 coreconfigitem('commands', 'status.relative',
191 191 default=False,
192 192 )
193 193 coreconfigitem('commands', 'status.skipstates',
194 194 default=[],
195 195 )
196 196 coreconfigitem('commands', 'status.terse',
197 197 default='',
198 198 )
199 199 coreconfigitem('commands', 'status.verbose',
200 200 default=False,
201 201 )
202 202 coreconfigitem('commands', 'update.check',
203 203 default=None,
204 204 )
205 205 coreconfigitem('commands', 'update.requiredest',
206 206 default=False,
207 207 )
208 208 coreconfigitem('committemplate', '.*',
209 209 default=None,
210 210 generic=True,
211 211 )
212 212 coreconfigitem('convert', 'cvsps.cache',
213 213 default=True,
214 214 )
215 215 coreconfigitem('convert', 'cvsps.fuzz',
216 216 default=60,
217 217 )
218 218 coreconfigitem('convert', 'cvsps.logencoding',
219 219 default=None,
220 220 )
221 221 coreconfigitem('convert', 'cvsps.mergefrom',
222 222 default=None,
223 223 )
224 224 coreconfigitem('convert', 'cvsps.mergeto',
225 225 default=None,
226 226 )
227 227 coreconfigitem('convert', 'git.committeractions',
228 228 default=lambda: ['messagedifferent'],
229 229 )
230 230 coreconfigitem('convert', 'git.extrakeys',
231 231 default=list,
232 232 )
233 233 coreconfigitem('convert', 'git.findcopiesharder',
234 234 default=False,
235 235 )
236 236 coreconfigitem('convert', 'git.remoteprefix',
237 237 default='remote',
238 238 )
239 239 coreconfigitem('convert', 'git.renamelimit',
240 240 default=400,
241 241 )
242 242 coreconfigitem('convert', 'git.saverev',
243 243 default=True,
244 244 )
245 245 coreconfigitem('convert', 'git.similarity',
246 246 default=50,
247 247 )
248 248 coreconfigitem('convert', 'git.skipsubmodules',
249 249 default=False,
250 250 )
251 251 coreconfigitem('convert', 'hg.clonebranches',
252 252 default=False,
253 253 )
254 254 coreconfigitem('convert', 'hg.ignoreerrors',
255 255 default=False,
256 256 )
257 257 coreconfigitem('convert', 'hg.revs',
258 258 default=None,
259 259 )
260 260 coreconfigitem('convert', 'hg.saverev',
261 261 default=False,
262 262 )
263 263 coreconfigitem('convert', 'hg.sourcename',
264 264 default=None,
265 265 )
266 266 coreconfigitem('convert', 'hg.startrev',
267 267 default=None,
268 268 )
269 269 coreconfigitem('convert', 'hg.tagsbranch',
270 270 default='default',
271 271 )
272 272 coreconfigitem('convert', 'hg.usebranchnames',
273 273 default=True,
274 274 )
275 275 coreconfigitem('convert', 'ignoreancestorcheck',
276 276 default=False,
277 277 )
278 278 coreconfigitem('convert', 'localtimezone',
279 279 default=False,
280 280 )
281 281 coreconfigitem('convert', 'p4.encoding',
282 282 default=dynamicdefault,
283 283 )
284 284 coreconfigitem('convert', 'p4.startrev',
285 285 default=0,
286 286 )
287 287 coreconfigitem('convert', 'skiptags',
288 288 default=False,
289 289 )
290 290 coreconfigitem('convert', 'svn.debugsvnlog',
291 291 default=True,
292 292 )
293 293 coreconfigitem('convert', 'svn.trunk',
294 294 default=None,
295 295 )
296 296 coreconfigitem('convert', 'svn.tags',
297 297 default=None,
298 298 )
299 299 coreconfigitem('convert', 'svn.branches',
300 300 default=None,
301 301 )
302 302 coreconfigitem('convert', 'svn.startrev',
303 303 default=0,
304 304 )
305 305 coreconfigitem('debug', 'dirstate.delaywrite',
306 306 default=0,
307 307 )
308 308 coreconfigitem('defaults', '.*',
309 309 default=None,
310 310 generic=True,
311 311 )
312 312 coreconfigitem('devel', 'all-warnings',
313 313 default=False,
314 314 )
315 315 coreconfigitem('devel', 'bundle2.debug',
316 316 default=False,
317 317 )
318 318 coreconfigitem('devel', 'cache-vfs',
319 319 default=None,
320 320 )
321 321 coreconfigitem('devel', 'check-locks',
322 322 default=False,
323 323 )
324 324 coreconfigitem('devel', 'check-relroot',
325 325 default=False,
326 326 )
327 327 coreconfigitem('devel', 'default-date',
328 328 default=None,
329 329 )
330 330 coreconfigitem('devel', 'deprec-warn',
331 331 default=False,
332 332 )
333 333 coreconfigitem('devel', 'disableloaddefaultcerts',
334 334 default=False,
335 335 )
336 336 coreconfigitem('devel', 'warn-empty-changegroup',
337 337 default=False,
338 338 )
339 339 coreconfigitem('devel', 'legacy.exchange',
340 340 default=list,
341 341 )
342 342 coreconfigitem('devel', 'servercafile',
343 343 default='',
344 344 )
345 345 coreconfigitem('devel', 'serverexactprotocol',
346 346 default='',
347 347 )
348 348 coreconfigitem('devel', 'serverrequirecert',
349 349 default=False,
350 350 )
351 351 coreconfigitem('devel', 'strip-obsmarkers',
352 352 default=True,
353 353 )
354 354 coreconfigitem('devel', 'warn-config',
355 355 default=None,
356 356 )
357 357 coreconfigitem('devel', 'warn-config-default',
358 358 default=None,
359 359 )
360 360 coreconfigitem('devel', 'user.obsmarker',
361 361 default=None,
362 362 )
363 363 coreconfigitem('devel', 'warn-config-unknown',
364 364 default=None,
365 365 )
366 366 coreconfigitem('devel', 'debug.peer-request',
367 367 default=False,
368 368 )
369 369 coreconfigitem('diff', 'nodates',
370 370 default=False,
371 371 )
372 372 coreconfigitem('diff', 'showfunc',
373 373 default=False,
374 374 )
375 375 coreconfigitem('diff', 'unified',
376 376 default=None,
377 377 )
378 378 coreconfigitem('diff', 'git',
379 379 default=False,
380 380 )
381 381 coreconfigitem('diff', 'ignorews',
382 382 default=False,
383 383 )
384 384 coreconfigitem('diff', 'ignorewsamount',
385 385 default=False,
386 386 )
387 387 coreconfigitem('diff', 'ignoreblanklines',
388 388 default=False,
389 389 )
390 390 coreconfigitem('diff', 'ignorewseol',
391 391 default=False,
392 392 )
393 393 coreconfigitem('diff', 'nobinary',
394 394 default=False,
395 395 )
396 396 coreconfigitem('diff', 'noprefix',
397 397 default=False,
398 398 )
399 399 coreconfigitem('email', 'bcc',
400 400 default=None,
401 401 )
402 402 coreconfigitem('email', 'cc',
403 403 default=None,
404 404 )
405 405 coreconfigitem('email', 'charsets',
406 406 default=list,
407 407 )
408 408 coreconfigitem('email', 'from',
409 409 default=None,
410 410 )
411 411 coreconfigitem('email', 'method',
412 412 default='smtp',
413 413 )
414 414 coreconfigitem('email', 'reply-to',
415 415 default=None,
416 416 )
417 417 coreconfigitem('email', 'to',
418 418 default=None,
419 419 )
420 420 coreconfigitem('experimental', 'archivemetatemplate',
421 421 default=dynamicdefault,
422 422 )
423 423 coreconfigitem('experimental', 'bundle-phases',
424 424 default=False,
425 425 )
426 426 coreconfigitem('experimental', 'bundle2-advertise',
427 427 default=True,
428 428 )
429 429 coreconfigitem('experimental', 'bundle2-output-capture',
430 430 default=False,
431 431 )
432 432 coreconfigitem('experimental', 'bundle2.pushback',
433 433 default=False,
434 434 )
435 435 coreconfigitem('experimental', 'bundle2.stream',
436 436 default=False,
437 437 )
438 438 coreconfigitem('experimental', 'bundle2lazylocking',
439 439 default=False,
440 440 )
441 441 coreconfigitem('experimental', 'bundlecomplevel',
442 442 default=None,
443 443 )
444 444 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
445 445 default=None,
446 446 )
447 447 coreconfigitem('experimental', 'bundlecomplevel.gzip',
448 448 default=None,
449 449 )
450 450 coreconfigitem('experimental', 'bundlecomplevel.none',
451 451 default=None,
452 452 )
453 453 coreconfigitem('experimental', 'bundlecomplevel.zstd',
454 454 default=None,
455 455 )
456 456 coreconfigitem('experimental', 'changegroup3',
457 457 default=False,
458 458 )
459 459 coreconfigitem('experimental', 'clientcompressionengines',
460 460 default=list,
461 461 )
462 462 coreconfigitem('experimental', 'copytrace',
463 463 default='on',
464 464 )
465 465 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
466 466 default=100,
467 467 )
468 468 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
469 469 default=100,
470 470 )
471 471 coreconfigitem('experimental', 'crecordtest',
472 472 default=None,
473 473 )
474 474 coreconfigitem('experimental', 'directaccess',
475 475 default=False,
476 476 )
477 477 coreconfigitem('experimental', 'directaccess.revnums',
478 478 default=False,
479 479 )
480 480 coreconfigitem('experimental', 'editortmpinhg',
481 481 default=False,
482 482 )
483 483 coreconfigitem('experimental', 'evolution',
484 484 default=list,
485 485 )
486 486 coreconfigitem('experimental', 'evolution.allowdivergence',
487 487 default=False,
488 488 alias=[('experimental', 'allowdivergence')]
489 489 )
490 490 coreconfigitem('experimental', 'evolution.allowunstable',
491 491 default=None,
492 492 )
493 493 coreconfigitem('experimental', 'evolution.createmarkers',
494 494 default=None,
495 495 )
496 496 coreconfigitem('experimental', 'evolution.effect-flags',
497 497 default=True,
498 498 alias=[('experimental', 'effect-flags')]
499 499 )
500 500 coreconfigitem('experimental', 'evolution.exchange',
501 501 default=None,
502 502 )
503 503 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
504 504 default=False,
505 505 )
506 506 coreconfigitem('experimental', 'evolution.report-instabilities',
507 507 default=True,
508 508 )
509 509 coreconfigitem('experimental', 'evolution.track-operation',
510 510 default=True,
511 511 )
512 512 coreconfigitem('experimental', 'worddiff',
513 513 default=False,
514 514 )
515 515 coreconfigitem('experimental', 'maxdeltachainspan',
516 516 default=-1,
517 517 )
518 518 coreconfigitem('experimental', 'mergetempdirprefix',
519 519 default=None,
520 520 )
521 521 coreconfigitem('experimental', 'mmapindexthreshold',
522 522 default=None,
523 523 )
524 524 coreconfigitem('experimental', 'nonnormalparanoidcheck',
525 525 default=False,
526 526 )
527 527 coreconfigitem('experimental', 'exportableenviron',
528 528 default=list,
529 529 )
530 530 coreconfigitem('experimental', 'extendedheader.index',
531 531 default=None,
532 532 )
533 533 coreconfigitem('experimental', 'extendedheader.similarity',
534 534 default=False,
535 535 )
536 536 coreconfigitem('experimental', 'format.compression',
537 537 default='zlib',
538 538 )
539 539 coreconfigitem('experimental', 'graphshorten',
540 540 default=False,
541 541 )
542 542 coreconfigitem('experimental', 'graphstyle.parent',
543 543 default=dynamicdefault,
544 544 )
545 545 coreconfigitem('experimental', 'graphstyle.missing',
546 546 default=dynamicdefault,
547 547 )
548 548 coreconfigitem('experimental', 'graphstyle.grandparent',
549 549 default=dynamicdefault,
550 550 )
551 551 coreconfigitem('experimental', 'hook-track-tags',
552 552 default=False,
553 553 )
554 554 coreconfigitem('experimental', 'httppeer.advertise-v2',
555 555 default=False,
556 556 )
557 557 coreconfigitem('experimental', 'httppostargs',
558 558 default=False,
559 559 )
560 560 coreconfigitem('experimental', 'mergedriver',
561 561 default=None,
562 562 )
563 coreconfigitem('experimental', 'nointerrupt', default=False)
564 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
565
563 566 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
564 567 default=False,
565 568 )
566 569 coreconfigitem('experimental', 'remotenames',
567 570 default=False,
568 571 )
569 572 coreconfigitem('experimental', 'removeemptydirs',
570 573 default=True,
571 574 )
572 575 coreconfigitem('experimental', 'revlogv2',
573 576 default=None,
574 577 )
575 578 coreconfigitem('experimental', 'single-head-per-branch',
576 579 default=False,
577 580 )
578 581 coreconfigitem('experimental', 'sshserver.support-v2',
579 582 default=False,
580 583 )
581 584 coreconfigitem('experimental', 'spacemovesdown',
582 585 default=False,
583 586 )
584 587 coreconfigitem('experimental', 'sparse-read',
585 588 default=False,
586 589 )
587 590 coreconfigitem('experimental', 'sparse-read.density-threshold',
588 591 default=0.25,
589 592 )
590 593 coreconfigitem('experimental', 'sparse-read.min-gap-size',
591 594 default='256K',
592 595 )
593 596 coreconfigitem('experimental', 'treemanifest',
594 597 default=False,
595 598 )
596 599 coreconfigitem('experimental', 'update.atomic-file',
597 600 default=False,
598 601 )
599 602 coreconfigitem('experimental', 'sshpeer.advertise-v2',
600 603 default=False,
601 604 )
602 605 coreconfigitem('experimental', 'web.apiserver',
603 606 default=False,
604 607 )
605 608 coreconfigitem('experimental', 'web.api.http-v2',
606 609 default=False,
607 610 )
608 611 coreconfigitem('experimental', 'web.api.debugreflect',
609 612 default=False,
610 613 )
611 614 coreconfigitem('experimental', 'xdiff',
612 615 default=False,
613 616 )
614 617 coreconfigitem('extensions', '.*',
615 618 default=None,
616 619 generic=True,
617 620 )
618 621 coreconfigitem('extdata', '.*',
619 622 default=None,
620 623 generic=True,
621 624 )
622 625 coreconfigitem('format', 'aggressivemergedeltas',
623 626 default=False,
624 627 )
625 628 coreconfigitem('format', 'chunkcachesize',
626 629 default=None,
627 630 )
628 631 coreconfigitem('format', 'dotencode',
629 632 default=True,
630 633 )
631 634 coreconfigitem('format', 'generaldelta',
632 635 default=False,
633 636 )
634 637 coreconfigitem('format', 'manifestcachesize',
635 638 default=None,
636 639 )
637 640 coreconfigitem('format', 'maxchainlen',
638 641 default=None,
639 642 )
640 643 coreconfigitem('format', 'obsstore-version',
641 644 default=None,
642 645 )
643 646 coreconfigitem('format', 'usefncache',
644 647 default=True,
645 648 )
646 649 coreconfigitem('format', 'usegeneraldelta',
647 650 default=True,
648 651 )
649 652 coreconfigitem('format', 'usestore',
650 653 default=True,
651 654 )
652 655 coreconfigitem('fsmonitor', 'warn_when_unused',
653 656 default=True,
654 657 )
655 658 coreconfigitem('fsmonitor', 'warn_update_file_count',
656 659 default=50000,
657 660 )
658 661 coreconfigitem('hooks', '.*',
659 662 default=dynamicdefault,
660 663 generic=True,
661 664 )
662 665 coreconfigitem('hgweb-paths', '.*',
663 666 default=list,
664 667 generic=True,
665 668 )
666 669 coreconfigitem('hostfingerprints', '.*',
667 670 default=list,
668 671 generic=True,
669 672 )
670 673 coreconfigitem('hostsecurity', 'ciphers',
671 674 default=None,
672 675 )
673 676 coreconfigitem('hostsecurity', 'disabletls10warning',
674 677 default=False,
675 678 )
676 679 coreconfigitem('hostsecurity', 'minimumprotocol',
677 680 default=dynamicdefault,
678 681 )
679 682 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
680 683 default=dynamicdefault,
681 684 generic=True,
682 685 )
683 686 coreconfigitem('hostsecurity', '.*:ciphers$',
684 687 default=dynamicdefault,
685 688 generic=True,
686 689 )
687 690 coreconfigitem('hostsecurity', '.*:fingerprints$',
688 691 default=list,
689 692 generic=True,
690 693 )
691 694 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
692 695 default=None,
693 696 generic=True,
694 697 )
695 698
696 699 coreconfigitem('http_proxy', 'always',
697 700 default=False,
698 701 )
699 702 coreconfigitem('http_proxy', 'host',
700 703 default=None,
701 704 )
702 705 coreconfigitem('http_proxy', 'no',
703 706 default=list,
704 707 )
705 708 coreconfigitem('http_proxy', 'passwd',
706 709 default=None,
707 710 )
708 711 coreconfigitem('http_proxy', 'user',
709 712 default=None,
710 713 )
711 714 coreconfigitem('logtoprocess', 'commandexception',
712 715 default=None,
713 716 )
714 717 coreconfigitem('logtoprocess', 'commandfinish',
715 718 default=None,
716 719 )
717 720 coreconfigitem('logtoprocess', 'command',
718 721 default=None,
719 722 )
720 723 coreconfigitem('logtoprocess', 'develwarn',
721 724 default=None,
722 725 )
723 726 coreconfigitem('logtoprocess', 'uiblocked',
724 727 default=None,
725 728 )
726 729 coreconfigitem('merge', 'checkunknown',
727 730 default='abort',
728 731 )
729 732 coreconfigitem('merge', 'checkignored',
730 733 default='abort',
731 734 )
732 735 coreconfigitem('experimental', 'merge.checkpathconflicts',
733 736 default=False,
734 737 )
735 738 coreconfigitem('merge', 'followcopies',
736 739 default=True,
737 740 )
738 741 coreconfigitem('merge', 'on-failure',
739 742 default='continue',
740 743 )
741 744 coreconfigitem('merge', 'preferancestor',
742 745 default=lambda: ['*'],
743 746 )
744 747 coreconfigitem('merge-tools', '.*',
745 748 default=None,
746 749 generic=True,
747 750 )
748 751 coreconfigitem('merge-tools', br'.*\.args$',
749 752 default="$local $base $other",
750 753 generic=True,
751 754 priority=-1,
752 755 )
753 756 coreconfigitem('merge-tools', br'.*\.binary$',
754 757 default=False,
755 758 generic=True,
756 759 priority=-1,
757 760 )
758 761 coreconfigitem('merge-tools', br'.*\.check$',
759 762 default=list,
760 763 generic=True,
761 764 priority=-1,
762 765 )
763 766 coreconfigitem('merge-tools', br'.*\.checkchanged$',
764 767 default=False,
765 768 generic=True,
766 769 priority=-1,
767 770 )
768 771 coreconfigitem('merge-tools', br'.*\.executable$',
769 772 default=dynamicdefault,
770 773 generic=True,
771 774 priority=-1,
772 775 )
773 776 coreconfigitem('merge-tools', br'.*\.fixeol$',
774 777 default=False,
775 778 generic=True,
776 779 priority=-1,
777 780 )
778 781 coreconfigitem('merge-tools', br'.*\.gui$',
779 782 default=False,
780 783 generic=True,
781 784 priority=-1,
782 785 )
783 786 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
784 787 default='basic',
785 788 generic=True,
786 789 priority=-1,
787 790 )
788 791 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
789 792 default=dynamicdefault, # take from ui.mergemarkertemplate
790 793 generic=True,
791 794 priority=-1,
792 795 )
793 796 coreconfigitem('merge-tools', br'.*\.priority$',
794 797 default=0,
795 798 generic=True,
796 799 priority=-1,
797 800 )
798 801 coreconfigitem('merge-tools', br'.*\.premerge$',
799 802 default=dynamicdefault,
800 803 generic=True,
801 804 priority=-1,
802 805 )
803 806 coreconfigitem('merge-tools', br'.*\.symlink$',
804 807 default=False,
805 808 generic=True,
806 809 priority=-1,
807 810 )
808 811 coreconfigitem('pager', 'attend-.*',
809 812 default=dynamicdefault,
810 813 generic=True,
811 814 )
812 815 coreconfigitem('pager', 'ignore',
813 816 default=list,
814 817 )
815 818 coreconfigitem('pager', 'pager',
816 819 default=dynamicdefault,
817 820 )
818 821 coreconfigitem('patch', 'eol',
819 822 default='strict',
820 823 )
821 824 coreconfigitem('patch', 'fuzz',
822 825 default=2,
823 826 )
824 827 coreconfigitem('paths', 'default',
825 828 default=None,
826 829 )
827 830 coreconfigitem('paths', 'default-push',
828 831 default=None,
829 832 )
830 833 coreconfigitem('paths', '.*',
831 834 default=None,
832 835 generic=True,
833 836 )
834 837 coreconfigitem('phases', 'checksubrepos',
835 838 default='follow',
836 839 )
837 840 coreconfigitem('phases', 'new-commit',
838 841 default='draft',
839 842 )
840 843 coreconfigitem('phases', 'publish',
841 844 default=True,
842 845 )
843 846 coreconfigitem('profiling', 'enabled',
844 847 default=False,
845 848 )
846 849 coreconfigitem('profiling', 'format',
847 850 default='text',
848 851 )
849 852 coreconfigitem('profiling', 'freq',
850 853 default=1000,
851 854 )
852 855 coreconfigitem('profiling', 'limit',
853 856 default=30,
854 857 )
855 858 coreconfigitem('profiling', 'nested',
856 859 default=0,
857 860 )
858 861 coreconfigitem('profiling', 'output',
859 862 default=None,
860 863 )
861 864 coreconfigitem('profiling', 'showmax',
862 865 default=0.999,
863 866 )
864 867 coreconfigitem('profiling', 'showmin',
865 868 default=dynamicdefault,
866 869 )
867 870 coreconfigitem('profiling', 'sort',
868 871 default='inlinetime',
869 872 )
870 873 coreconfigitem('profiling', 'statformat',
871 874 default='hotpath',
872 875 )
873 876 coreconfigitem('profiling', 'time-track',
874 877 default='cpu',
875 878 )
876 879 coreconfigitem('profiling', 'type',
877 880 default='stat',
878 881 )
879 882 coreconfigitem('progress', 'assume-tty',
880 883 default=False,
881 884 )
882 885 coreconfigitem('progress', 'changedelay',
883 886 default=1,
884 887 )
885 888 coreconfigitem('progress', 'clear-complete',
886 889 default=True,
887 890 )
888 891 coreconfigitem('progress', 'debug',
889 892 default=False,
890 893 )
891 894 coreconfigitem('progress', 'delay',
892 895 default=3,
893 896 )
894 897 coreconfigitem('progress', 'disable',
895 898 default=False,
896 899 )
897 900 coreconfigitem('progress', 'estimateinterval',
898 901 default=60.0,
899 902 )
900 903 coreconfigitem('progress', 'format',
901 904 default=lambda: ['topic', 'bar', 'number', 'estimate'],
902 905 )
903 906 coreconfigitem('progress', 'refresh',
904 907 default=0.1,
905 908 )
906 909 coreconfigitem('progress', 'width',
907 910 default=dynamicdefault,
908 911 )
909 912 coreconfigitem('push', 'pushvars.server',
910 913 default=False,
911 914 )
912 915 coreconfigitem('server', 'bookmarks-pushkey-compat',
913 916 default=True,
914 917 )
915 918 coreconfigitem('server', 'bundle1',
916 919 default=True,
917 920 )
918 921 coreconfigitem('server', 'bundle1gd',
919 922 default=None,
920 923 )
921 924 coreconfigitem('server', 'bundle1.pull',
922 925 default=None,
923 926 )
924 927 coreconfigitem('server', 'bundle1gd.pull',
925 928 default=None,
926 929 )
927 930 coreconfigitem('server', 'bundle1.push',
928 931 default=None,
929 932 )
930 933 coreconfigitem('server', 'bundle1gd.push',
931 934 default=None,
932 935 )
933 936 coreconfigitem('server', 'compressionengines',
934 937 default=list,
935 938 )
936 939 coreconfigitem('server', 'concurrent-push-mode',
937 940 default='strict',
938 941 )
939 942 coreconfigitem('server', 'disablefullbundle',
940 943 default=False,
941 944 )
942 945 coreconfigitem('server', 'maxhttpheaderlen',
943 946 default=1024,
944 947 )
945 948 coreconfigitem('server', 'pullbundle',
946 949 default=False,
947 950 )
948 951 coreconfigitem('server', 'preferuncompressed',
949 952 default=False,
950 953 )
951 954 coreconfigitem('server', 'streamunbundle',
952 955 default=False,
953 956 )
954 957 coreconfigitem('server', 'uncompressed',
955 958 default=True,
956 959 )
957 960 coreconfigitem('server', 'uncompressedallowsecret',
958 961 default=False,
959 962 )
960 963 coreconfigitem('server', 'validate',
961 964 default=False,
962 965 )
963 966 coreconfigitem('server', 'zliblevel',
964 967 default=-1,
965 968 )
966 969 coreconfigitem('server', 'zstdlevel',
967 970 default=3,
968 971 )
969 972 coreconfigitem('share', 'pool',
970 973 default=None,
971 974 )
972 975 coreconfigitem('share', 'poolnaming',
973 976 default='identity',
974 977 )
975 978 coreconfigitem('smtp', 'host',
976 979 default=None,
977 980 )
978 981 coreconfigitem('smtp', 'local_hostname',
979 982 default=None,
980 983 )
981 984 coreconfigitem('smtp', 'password',
982 985 default=None,
983 986 )
984 987 coreconfigitem('smtp', 'port',
985 988 default=dynamicdefault,
986 989 )
987 990 coreconfigitem('smtp', 'tls',
988 991 default='none',
989 992 )
990 993 coreconfigitem('smtp', 'username',
991 994 default=None,
992 995 )
993 996 coreconfigitem('sparse', 'missingwarning',
994 997 default=True,
995 998 )
996 999 coreconfigitem('subrepos', 'allowed',
997 1000 default=dynamicdefault, # to make backporting simpler
998 1001 )
999 1002 coreconfigitem('subrepos', 'hg:allowed',
1000 1003 default=dynamicdefault,
1001 1004 )
1002 1005 coreconfigitem('subrepos', 'git:allowed',
1003 1006 default=dynamicdefault,
1004 1007 )
1005 1008 coreconfigitem('subrepos', 'svn:allowed',
1006 1009 default=dynamicdefault,
1007 1010 )
1008 1011 coreconfigitem('templates', '.*',
1009 1012 default=None,
1010 1013 generic=True,
1011 1014 )
1012 1015 coreconfigitem('trusted', 'groups',
1013 1016 default=list,
1014 1017 )
1015 1018 coreconfigitem('trusted', 'users',
1016 1019 default=list,
1017 1020 )
1018 1021 coreconfigitem('ui', '_usedassubrepo',
1019 1022 default=False,
1020 1023 )
1021 1024 coreconfigitem('ui', 'allowemptycommit',
1022 1025 default=False,
1023 1026 )
1024 1027 coreconfigitem('ui', 'archivemeta',
1025 1028 default=True,
1026 1029 )
1027 1030 coreconfigitem('ui', 'askusername',
1028 1031 default=False,
1029 1032 )
1030 1033 coreconfigitem('ui', 'clonebundlefallback',
1031 1034 default=False,
1032 1035 )
1033 1036 coreconfigitem('ui', 'clonebundleprefers',
1034 1037 default=list,
1035 1038 )
1036 1039 coreconfigitem('ui', 'clonebundles',
1037 1040 default=True,
1038 1041 )
1039 1042 coreconfigitem('ui', 'color',
1040 1043 default='auto',
1041 1044 )
1042 1045 coreconfigitem('ui', 'commitsubrepos',
1043 1046 default=False,
1044 1047 )
1045 1048 coreconfigitem('ui', 'debug',
1046 1049 default=False,
1047 1050 )
1048 1051 coreconfigitem('ui', 'debugger',
1049 1052 default=None,
1050 1053 )
1051 1054 coreconfigitem('ui', 'editor',
1052 1055 default=dynamicdefault,
1053 1056 )
1054 1057 coreconfigitem('ui', 'fallbackencoding',
1055 1058 default=None,
1056 1059 )
1057 1060 coreconfigitem('ui', 'forcecwd',
1058 1061 default=None,
1059 1062 )
1060 1063 coreconfigitem('ui', 'forcemerge',
1061 1064 default=None,
1062 1065 )
1063 1066 coreconfigitem('ui', 'formatdebug',
1064 1067 default=False,
1065 1068 )
1066 1069 coreconfigitem('ui', 'formatjson',
1067 1070 default=False,
1068 1071 )
1069 1072 coreconfigitem('ui', 'formatted',
1070 1073 default=None,
1071 1074 )
1072 1075 coreconfigitem('ui', 'graphnodetemplate',
1073 1076 default=None,
1074 1077 )
1075 1078 coreconfigitem('ui', 'interactive',
1076 1079 default=None,
1077 1080 )
1078 1081 coreconfigitem('ui', 'interface',
1079 1082 default=None,
1080 1083 )
1081 1084 coreconfigitem('ui', 'interface.chunkselector',
1082 1085 default=None,
1083 1086 )
1084 1087 coreconfigitem('ui', 'logblockedtimes',
1085 1088 default=False,
1086 1089 )
1087 1090 coreconfigitem('ui', 'logtemplate',
1088 1091 default=None,
1089 1092 )
1090 1093 coreconfigitem('ui', 'merge',
1091 1094 default=None,
1092 1095 )
1093 1096 coreconfigitem('ui', 'mergemarkers',
1094 1097 default='basic',
1095 1098 )
1096 1099 coreconfigitem('ui', 'mergemarkertemplate',
1097 1100 default=('{node|short} '
1098 1101 '{ifeq(tags, "tip", "", '
1099 1102 'ifeq(tags, "", "", "{tags} "))}'
1100 1103 '{if(bookmarks, "{bookmarks} ")}'
1101 1104 '{ifeq(branch, "default", "", "{branch} ")}'
1102 1105 '- {author|user}: {desc|firstline}')
1103 1106 )
1104 1107 coreconfigitem('ui', 'nontty',
1105 1108 default=False,
1106 1109 )
1107 1110 coreconfigitem('ui', 'origbackuppath',
1108 1111 default=None,
1109 1112 )
1110 1113 coreconfigitem('ui', 'paginate',
1111 1114 default=True,
1112 1115 )
1113 1116 coreconfigitem('ui', 'patch',
1114 1117 default=None,
1115 1118 )
1116 1119 coreconfigitem('ui', 'portablefilenames',
1117 1120 default='warn',
1118 1121 )
1119 1122 coreconfigitem('ui', 'promptecho',
1120 1123 default=False,
1121 1124 )
1122 1125 coreconfigitem('ui', 'quiet',
1123 1126 default=False,
1124 1127 )
1125 1128 coreconfigitem('ui', 'quietbookmarkmove',
1126 1129 default=False,
1127 1130 )
1128 1131 coreconfigitem('ui', 'remotecmd',
1129 1132 default='hg',
1130 1133 )
1131 1134 coreconfigitem('ui', 'report_untrusted',
1132 1135 default=True,
1133 1136 )
1134 1137 coreconfigitem('ui', 'rollback',
1135 1138 default=True,
1136 1139 )
1137 1140 coreconfigitem('ui', 'signal-safe-lock',
1138 1141 default=True,
1139 1142 )
1140 1143 coreconfigitem('ui', 'slash',
1141 1144 default=False,
1142 1145 )
1143 1146 coreconfigitem('ui', 'ssh',
1144 1147 default='ssh',
1145 1148 )
1146 1149 coreconfigitem('ui', 'ssherrorhint',
1147 1150 default=None,
1148 1151 )
1149 1152 coreconfigitem('ui', 'statuscopies',
1150 1153 default=False,
1151 1154 )
1152 1155 coreconfigitem('ui', 'strict',
1153 1156 default=False,
1154 1157 )
1155 1158 coreconfigitem('ui', 'style',
1156 1159 default='',
1157 1160 )
1158 1161 coreconfigitem('ui', 'supportcontact',
1159 1162 default=None,
1160 1163 )
1161 1164 coreconfigitem('ui', 'textwidth',
1162 1165 default=78,
1163 1166 )
1164 1167 coreconfigitem('ui', 'timeout',
1165 1168 default='600',
1166 1169 )
1167 1170 coreconfigitem('ui', 'timeout.warn',
1168 1171 default=0,
1169 1172 )
1170 1173 coreconfigitem('ui', 'traceback',
1171 1174 default=False,
1172 1175 )
1173 1176 coreconfigitem('ui', 'tweakdefaults',
1174 1177 default=False,
1175 1178 )
1176 1179 coreconfigitem('ui', 'username',
1177 1180 alias=[('ui', 'user')]
1178 1181 )
1179 1182 coreconfigitem('ui', 'verbose',
1180 1183 default=False,
1181 1184 )
1182 1185 coreconfigitem('verify', 'skipflags',
1183 1186 default=None,
1184 1187 )
1185 1188 coreconfigitem('web', 'allowbz2',
1186 1189 default=False,
1187 1190 )
1188 1191 coreconfigitem('web', 'allowgz',
1189 1192 default=False,
1190 1193 )
1191 1194 coreconfigitem('web', 'allow-pull',
1192 1195 alias=[('web', 'allowpull')],
1193 1196 default=True,
1194 1197 )
1195 1198 coreconfigitem('web', 'allow-push',
1196 1199 alias=[('web', 'allow_push')],
1197 1200 default=list,
1198 1201 )
1199 1202 coreconfigitem('web', 'allowzip',
1200 1203 default=False,
1201 1204 )
1202 1205 coreconfigitem('web', 'archivesubrepos',
1203 1206 default=False,
1204 1207 )
1205 1208 coreconfigitem('web', 'cache',
1206 1209 default=True,
1207 1210 )
1208 1211 coreconfigitem('web', 'contact',
1209 1212 default=None,
1210 1213 )
1211 1214 coreconfigitem('web', 'deny_push',
1212 1215 default=list,
1213 1216 )
1214 1217 coreconfigitem('web', 'guessmime',
1215 1218 default=False,
1216 1219 )
1217 1220 coreconfigitem('web', 'hidden',
1218 1221 default=False,
1219 1222 )
1220 1223 coreconfigitem('web', 'labels',
1221 1224 default=list,
1222 1225 )
1223 1226 coreconfigitem('web', 'logoimg',
1224 1227 default='hglogo.png',
1225 1228 )
1226 1229 coreconfigitem('web', 'logourl',
1227 1230 default='https://mercurial-scm.org/',
1228 1231 )
1229 1232 coreconfigitem('web', 'accesslog',
1230 1233 default='-',
1231 1234 )
1232 1235 coreconfigitem('web', 'address',
1233 1236 default='',
1234 1237 )
1235 1238 coreconfigitem('web', 'allow-archive',
1236 1239 alias=[('web', 'allow_archive')],
1237 1240 default=list,
1238 1241 )
1239 1242 coreconfigitem('web', 'allow_read',
1240 1243 default=list,
1241 1244 )
1242 1245 coreconfigitem('web', 'baseurl',
1243 1246 default=None,
1244 1247 )
1245 1248 coreconfigitem('web', 'cacerts',
1246 1249 default=None,
1247 1250 )
1248 1251 coreconfigitem('web', 'certificate',
1249 1252 default=None,
1250 1253 )
1251 1254 coreconfigitem('web', 'collapse',
1252 1255 default=False,
1253 1256 )
1254 1257 coreconfigitem('web', 'csp',
1255 1258 default=None,
1256 1259 )
1257 1260 coreconfigitem('web', 'deny_read',
1258 1261 default=list,
1259 1262 )
1260 1263 coreconfigitem('web', 'descend',
1261 1264 default=True,
1262 1265 )
1263 1266 coreconfigitem('web', 'description',
1264 1267 default="",
1265 1268 )
1266 1269 coreconfigitem('web', 'encoding',
1267 1270 default=lambda: encoding.encoding,
1268 1271 )
1269 1272 coreconfigitem('web', 'errorlog',
1270 1273 default='-',
1271 1274 )
1272 1275 coreconfigitem('web', 'ipv6',
1273 1276 default=False,
1274 1277 )
1275 1278 coreconfigitem('web', 'maxchanges',
1276 1279 default=10,
1277 1280 )
1278 1281 coreconfigitem('web', 'maxfiles',
1279 1282 default=10,
1280 1283 )
1281 1284 coreconfigitem('web', 'maxshortchanges',
1282 1285 default=60,
1283 1286 )
1284 1287 coreconfigitem('web', 'motd',
1285 1288 default='',
1286 1289 )
1287 1290 coreconfigitem('web', 'name',
1288 1291 default=dynamicdefault,
1289 1292 )
1290 1293 coreconfigitem('web', 'port',
1291 1294 default=8000,
1292 1295 )
1293 1296 coreconfigitem('web', 'prefix',
1294 1297 default='',
1295 1298 )
1296 1299 coreconfigitem('web', 'push_ssl',
1297 1300 default=True,
1298 1301 )
1299 1302 coreconfigitem('web', 'refreshinterval',
1300 1303 default=20,
1301 1304 )
1302 1305 coreconfigitem('web', 'server-header',
1303 1306 default=None,
1304 1307 )
1305 1308 coreconfigitem('web', 'staticurl',
1306 1309 default=None,
1307 1310 )
1308 1311 coreconfigitem('web', 'stripes',
1309 1312 default=1,
1310 1313 )
1311 1314 coreconfigitem('web', 'style',
1312 1315 default='paper',
1313 1316 )
1314 1317 coreconfigitem('web', 'templates',
1315 1318 default=None,
1316 1319 )
1317 1320 coreconfigitem('web', 'view',
1318 1321 default='served',
1319 1322 )
1320 1323 coreconfigitem('worker', 'backgroundclose',
1321 1324 default=dynamicdefault,
1322 1325 )
1323 1326 # Windows defaults to a limit of 512 open files. A buffer of 128
1324 1327 # should give us enough headway.
1325 1328 coreconfigitem('worker', 'backgroundclosemaxqueue',
1326 1329 default=384,
1327 1330 )
1328 1331 coreconfigitem('worker', 'backgroundcloseminfilecount',
1329 1332 default=2048,
1330 1333 )
1331 1334 coreconfigitem('worker', 'backgroundclosethreadcount',
1332 1335 default=4,
1333 1336 )
1334 1337 coreconfigitem('worker', 'enabled',
1335 1338 default=True,
1336 1339 )
1337 1340 coreconfigitem('worker', 'numcpus',
1338 1341 default=None,
1339 1342 )
1340 1343
1341 1344 # Rebase related configuration moved to core because other extension are doing
1342 1345 # strange things. For example, shelve import the extensions to reuse some bit
1343 1346 # without formally loading it.
1344 1347 coreconfigitem('commands', 'rebase.requiredest',
1345 1348 default=False,
1346 1349 )
1347 1350 coreconfigitem('experimental', 'rebaseskipobsolete',
1348 1351 default=True,
1349 1352 )
1350 1353 coreconfigitem('rebase', 'singletransaction',
1351 1354 default=False,
1352 1355 )
1353 1356 coreconfigitem('rebase', 'experimental.inmemory',
1354 1357 default=False,
1355 1358 )
@@ -1,1873 +1,1905 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import traceback
22 22
23 23 from .i18n import _
24 24 from .node import hex
25 25
26 26 from . import (
27 27 color,
28 28 config,
29 29 configitems,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 progress,
34 34 pycompat,
35 35 rcutil,
36 36 scmutil,
37 37 util,
38 38 )
39 39 from .utils import (
40 40 dateutil,
41 41 procutil,
42 42 stringutil,
43 43 )
44 44
45 45 urlreq = util.urlreq
46 46
47 47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 49 if not c.isalnum())
50 50
51 51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 52 tweakrc = b"""
53 53 [ui]
54 54 # The rollback command is dangerous. As a rule, don't use it.
55 55 rollback = False
56 56 # Make `hg status` report copy information
57 57 statuscopies = yes
58 58 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 59 interface = curses
60 60
61 61 [commands]
62 62 # Make `hg status` emit cwd-relative paths by default.
63 63 status.relative = yes
64 64 # Refuse to perform an `hg update` that would cause a file content merge
65 65 update.check = noconflict
66 66 # Show conflicts information in `hg status`
67 67 status.verbose = True
68 68 # Collapse entire directories that contain only unknown files
69 69 status.terse = u
70 70
71 71 [diff]
72 72 git = 1
73 73 showfunc = 1
74 74 """
75 75
76 76 samplehgrcs = {
77 77 'user':
78 78 b"""# example user config (see 'hg help config' for more info)
79 79 [ui]
80 80 # name and email, e.g.
81 81 # username = Jane Doe <jdoe@example.com>
82 82 username =
83 83
84 84 # We recommend enabling tweakdefaults to get slight improvements to
85 85 # the UI over time. Make sure to set HGPLAIN in the environment when
86 86 # writing scripts!
87 87 # tweakdefaults = True
88 88
89 89 # uncomment to disable color in command output
90 90 # (see 'hg help color' for details)
91 91 # color = never
92 92
93 93 # uncomment to disable command output pagination
94 94 # (see 'hg help pager' for details)
95 95 # paginate = never
96 96
97 97 [extensions]
98 98 # uncomment these lines to enable some popular extensions
99 99 # (see 'hg help extensions' for more info)
100 100 #
101 101 # churn =
102 102 """,
103 103
104 104 'cloned':
105 105 b"""# example repository config (see 'hg help config' for more info)
106 106 [paths]
107 107 default = %s
108 108
109 109 # path aliases to other clones of this repo in URLs or filesystem paths
110 110 # (see 'hg help config.paths' for more info)
111 111 #
112 112 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
113 113 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
114 114 # my-clone = /home/jdoe/jdoes-clone
115 115
116 116 [ui]
117 117 # name and email (local to this repository, optional), e.g.
118 118 # username = Jane Doe <jdoe@example.com>
119 119 """,
120 120
121 121 'local':
122 122 b"""# example repository config (see 'hg help config' for more info)
123 123 [paths]
124 124 # path aliases to other clones of this repo in URLs or filesystem paths
125 125 # (see 'hg help config.paths' for more info)
126 126 #
127 127 # default = http://example.com/hg/example-repo
128 128 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
129 129 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
130 130 # my-clone = /home/jdoe/jdoes-clone
131 131
132 132 [ui]
133 133 # name and email (local to this repository, optional), e.g.
134 134 # username = Jane Doe <jdoe@example.com>
135 135 """,
136 136
137 137 'global':
138 138 b"""# example system-wide hg config (see 'hg help config' for more info)
139 139
140 140 [ui]
141 141 # uncomment to disable color in command output
142 142 # (see 'hg help color' for details)
143 143 # color = never
144 144
145 145 # uncomment to disable command output pagination
146 146 # (see 'hg help pager' for details)
147 147 # paginate = never
148 148
149 149 [extensions]
150 150 # uncomment these lines to enable some popular extensions
151 151 # (see 'hg help extensions' for more info)
152 152 #
153 153 # blackbox =
154 154 # churn =
155 155 """,
156 156 }
157 157
158 158 def _maybestrurl(maybebytes):
159 159 return util.rapply(pycompat.strurl, maybebytes)
160 160
161 161 def _maybebytesurl(maybestr):
162 162 return util.rapply(pycompat.bytesurl, maybestr)
163 163
164 164 class httppasswordmgrdbproxy(object):
165 165 """Delays loading urllib2 until it's needed."""
166 166 def __init__(self):
167 167 self._mgr = None
168 168
169 169 def _get_mgr(self):
170 170 if self._mgr is None:
171 171 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
172 172 return self._mgr
173 173
174 174 def add_password(self, realm, uris, user, passwd):
175 175 return self._get_mgr().add_password(
176 176 _maybestrurl(realm), _maybestrurl(uris),
177 177 _maybestrurl(user), _maybestrurl(passwd))
178 178
179 179 def find_user_password(self, realm, uri):
180 180 mgr = self._get_mgr()
181 181 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
182 182 _maybestrurl(uri)))
183 183
184 184 def _catchterm(*args):
185 185 raise error.SignalInterrupt
186 186
187 187 # unique object used to detect no default value has been provided when
188 188 # retrieving configuration value.
189 189 _unset = object()
190 190
191 191 # _reqexithandlers: callbacks run at the end of a request
192 192 _reqexithandlers = []
193 193
194 194 class ui(object):
195 195 def __init__(self, src=None):
196 196 """Create a fresh new ui object if no src given
197 197
198 198 Use uimod.ui.load() to create a ui which knows global and user configs.
199 199 In most cases, you should use ui.copy() to create a copy of an existing
200 200 ui object.
201 201 """
202 202 # _buffers: used for temporary capture of output
203 203 self._buffers = []
204 204 # 3-tuple describing how each buffer in the stack behaves.
205 205 # Values are (capture stderr, capture subprocesses, apply labels).
206 206 self._bufferstates = []
207 207 # When a buffer is active, defines whether we are expanding labels.
208 208 # This exists to prevent an extra list lookup.
209 209 self._bufferapplylabels = None
210 210 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
211 211 self._reportuntrusted = True
212 212 self._knownconfig = configitems.coreitems
213 213 self._ocfg = config.config() # overlay
214 214 self._tcfg = config.config() # trusted
215 215 self._ucfg = config.config() # untrusted
216 216 self._trustusers = set()
217 217 self._trustgroups = set()
218 218 self.callhooks = True
219 219 # Insecure server connections requested.
220 220 self.insecureconnections = False
221 221 # Blocked time
222 222 self.logblockedtimes = False
223 223 # color mode: see mercurial/color.py for possible value
224 224 self._colormode = None
225 225 self._terminfoparams = {}
226 226 self._styles = {}
227 self._uninterruptible = False
227 228
228 229 if src:
229 230 self.fout = src.fout
230 231 self.ferr = src.ferr
231 232 self.fin = src.fin
232 233 self.pageractive = src.pageractive
233 234 self._disablepager = src._disablepager
234 235 self._tweaked = src._tweaked
235 236
236 237 self._tcfg = src._tcfg.copy()
237 238 self._ucfg = src._ucfg.copy()
238 239 self._ocfg = src._ocfg.copy()
239 240 self._trustusers = src._trustusers.copy()
240 241 self._trustgroups = src._trustgroups.copy()
241 242 self.environ = src.environ
242 243 self.callhooks = src.callhooks
243 244 self.insecureconnections = src.insecureconnections
244 245 self._colormode = src._colormode
245 246 self._terminfoparams = src._terminfoparams.copy()
246 247 self._styles = src._styles.copy()
247 248
248 249 self.fixconfig()
249 250
250 251 self.httppasswordmgrdb = src.httppasswordmgrdb
251 252 self._blockedtimes = src._blockedtimes
252 253 else:
253 254 self.fout = procutil.stdout
254 255 self.ferr = procutil.stderr
255 256 self.fin = procutil.stdin
256 257 self.pageractive = False
257 258 self._disablepager = False
258 259 self._tweaked = False
259 260
260 261 # shared read-only environment
261 262 self.environ = encoding.environ
262 263
263 264 self.httppasswordmgrdb = httppasswordmgrdbproxy()
264 265 self._blockedtimes = collections.defaultdict(int)
265 266
266 267 allowed = self.configlist('experimental', 'exportableenviron')
267 268 if '*' in allowed:
268 269 self._exportableenviron = self.environ
269 270 else:
270 271 self._exportableenviron = {}
271 272 for k in allowed:
272 273 if k in self.environ:
273 274 self._exportableenviron[k] = self.environ[k]
274 275
275 276 @classmethod
276 277 def load(cls):
277 278 """Create a ui and load global and user configs"""
278 279 u = cls()
279 280 # we always trust global config files and environment variables
280 281 for t, f in rcutil.rccomponents():
281 282 if t == 'path':
282 283 u.readconfig(f, trust=True)
283 284 elif t == 'items':
284 285 sections = set()
285 286 for section, name, value, source in f:
286 287 # do not set u._ocfg
287 288 # XXX clean this up once immutable config object is a thing
288 289 u._tcfg.set(section, name, value, source)
289 290 u._ucfg.set(section, name, value, source)
290 291 sections.add(section)
291 292 for section in sections:
292 293 u.fixconfig(section=section)
293 294 else:
294 295 raise error.ProgrammingError('unknown rctype: %s' % t)
295 296 u._maybetweakdefaults()
296 297 return u
297 298
298 299 def _maybetweakdefaults(self):
299 300 if not self.configbool('ui', 'tweakdefaults'):
300 301 return
301 302 if self._tweaked or self.plain('tweakdefaults'):
302 303 return
303 304
304 305 # Note: it is SUPER IMPORTANT that you set self._tweaked to
305 306 # True *before* any calls to setconfig(), otherwise you'll get
306 307 # infinite recursion between setconfig and this method.
307 308 #
308 309 # TODO: We should extract an inner method in setconfig() to
309 310 # avoid this weirdness.
310 311 self._tweaked = True
311 312 tmpcfg = config.config()
312 313 tmpcfg.parse('<tweakdefaults>', tweakrc)
313 314 for section in tmpcfg:
314 315 for name, value in tmpcfg.items(section):
315 316 if not self.hasconfig(section, name):
316 317 self.setconfig(section, name, value, "<tweakdefaults>")
317 318
318 319 def copy(self):
319 320 return self.__class__(self)
320 321
321 322 def resetstate(self):
322 323 """Clear internal state that shouldn't persist across commands"""
323 324 if self._progbar:
324 325 self._progbar.resetstate() # reset last-print time of progress bar
325 326 self.httppasswordmgrdb = httppasswordmgrdbproxy()
326 327
327 328 @contextlib.contextmanager
328 329 def timeblockedsection(self, key):
329 330 # this is open-coded below - search for timeblockedsection to find them
330 331 starttime = util.timer()
331 332 try:
332 333 yield
333 334 finally:
334 335 self._blockedtimes[key + '_blocked'] += \
335 336 (util.timer() - starttime) * 1000
336 337
338 @contextlib.contextmanager
339 def uninterruptable(self):
340 """Mark an operation as unsafe.
341
342 Most operations on a repository are safe to interrupt, but a
343 few are risky (for example repair.strip). This context manager
344 lets you advise Mercurial that something risky is happening so
345 that control-C etc can be blocked if desired.
346 """
347 enabled = self.configbool('experimental', 'nointerrupt')
348 if (enabled and
349 self.configbool('experimental', 'nointerrupt-interactiveonly')):
350 enabled = self.interactive()
351 if self._uninterruptible or not enabled:
352 # if nointerrupt support is turned off, the process isn't
353 # interactive, or we're already in an uninterruptable
354 # block, do nothing.
355 yield
356 return
357 def warn():
358 self.warn(_("shutting down cleanly\n"))
359 self.warn(
360 _("press ^C again to terminate immediately (dangerous)\n"))
361 return True
362 with procutil.uninterruptable(warn):
363 try:
364 self._uninterruptible = True
365 yield
366 finally:
367 self._uninterruptible = False
368
337 369 def formatter(self, topic, opts):
338 370 return formatter.formatter(self, self, topic, opts)
339 371
340 372 def _trusted(self, fp, f):
341 373 st = util.fstat(fp)
342 374 if util.isowner(st):
343 375 return True
344 376
345 377 tusers, tgroups = self._trustusers, self._trustgroups
346 378 if '*' in tusers or '*' in tgroups:
347 379 return True
348 380
349 381 user = util.username(st.st_uid)
350 382 group = util.groupname(st.st_gid)
351 383 if user in tusers or group in tgroups or user == util.username():
352 384 return True
353 385
354 386 if self._reportuntrusted:
355 387 self.warn(_('not trusting file %s from untrusted '
356 388 'user %s, group %s\n') % (f, user, group))
357 389 return False
358 390
359 391 def readconfig(self, filename, root=None, trust=False,
360 392 sections=None, remap=None):
361 393 try:
362 394 fp = open(filename, u'rb')
363 395 except IOError:
364 396 if not sections: # ignore unless we were looking for something
365 397 return
366 398 raise
367 399
368 400 cfg = config.config()
369 401 trusted = sections or trust or self._trusted(fp, filename)
370 402
371 403 try:
372 404 cfg.read(filename, fp, sections=sections, remap=remap)
373 405 fp.close()
374 406 except error.ConfigError as inst:
375 407 if trusted:
376 408 raise
377 409 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
378 410
379 411 if self.plain():
380 412 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
381 413 'logtemplate', 'statuscopies', 'style',
382 414 'traceback', 'verbose'):
383 415 if k in cfg['ui']:
384 416 del cfg['ui'][k]
385 417 for k, v in cfg.items('defaults'):
386 418 del cfg['defaults'][k]
387 419 for k, v in cfg.items('commands'):
388 420 del cfg['commands'][k]
389 421 # Don't remove aliases from the configuration if in the exceptionlist
390 422 if self.plain('alias'):
391 423 for k, v in cfg.items('alias'):
392 424 del cfg['alias'][k]
393 425 if self.plain('revsetalias'):
394 426 for k, v in cfg.items('revsetalias'):
395 427 del cfg['revsetalias'][k]
396 428 if self.plain('templatealias'):
397 429 for k, v in cfg.items('templatealias'):
398 430 del cfg['templatealias'][k]
399 431
400 432 if trusted:
401 433 self._tcfg.update(cfg)
402 434 self._tcfg.update(self._ocfg)
403 435 self._ucfg.update(cfg)
404 436 self._ucfg.update(self._ocfg)
405 437
406 438 if root is None:
407 439 root = os.path.expanduser('~')
408 440 self.fixconfig(root=root)
409 441
410 442 def fixconfig(self, root=None, section=None):
411 443 if section in (None, 'paths'):
412 444 # expand vars and ~
413 445 # translate paths relative to root (or home) into absolute paths
414 446 root = root or pycompat.getcwd()
415 447 for c in self._tcfg, self._ucfg, self._ocfg:
416 448 for n, p in c.items('paths'):
417 449 # Ignore sub-options.
418 450 if ':' in n:
419 451 continue
420 452 if not p:
421 453 continue
422 454 if '%%' in p:
423 455 s = self.configsource('paths', n) or 'none'
424 456 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
425 457 % (n, p, s))
426 458 p = p.replace('%%', '%')
427 459 p = util.expandpath(p)
428 460 if not util.hasscheme(p) and not os.path.isabs(p):
429 461 p = os.path.normpath(os.path.join(root, p))
430 462 c.set("paths", n, p)
431 463
432 464 if section in (None, 'ui'):
433 465 # update ui options
434 466 self.debugflag = self.configbool('ui', 'debug')
435 467 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
436 468 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
437 469 if self.verbose and self.quiet:
438 470 self.quiet = self.verbose = False
439 471 self._reportuntrusted = self.debugflag or self.configbool("ui",
440 472 "report_untrusted")
441 473 self.tracebackflag = self.configbool('ui', 'traceback')
442 474 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
443 475
444 476 if section in (None, 'trusted'):
445 477 # update trust information
446 478 self._trustusers.update(self.configlist('trusted', 'users'))
447 479 self._trustgroups.update(self.configlist('trusted', 'groups'))
448 480
449 481 def backupconfig(self, section, item):
450 482 return (self._ocfg.backup(section, item),
451 483 self._tcfg.backup(section, item),
452 484 self._ucfg.backup(section, item),)
453 485 def restoreconfig(self, data):
454 486 self._ocfg.restore(data[0])
455 487 self._tcfg.restore(data[1])
456 488 self._ucfg.restore(data[2])
457 489
458 490 def setconfig(self, section, name, value, source=''):
459 491 for cfg in (self._ocfg, self._tcfg, self._ucfg):
460 492 cfg.set(section, name, value, source)
461 493 self.fixconfig(section=section)
462 494 self._maybetweakdefaults()
463 495
464 496 def _data(self, untrusted):
465 497 return untrusted and self._ucfg or self._tcfg
466 498
467 499 def configsource(self, section, name, untrusted=False):
468 500 return self._data(untrusted).source(section, name)
469 501
470 502 def config(self, section, name, default=_unset, untrusted=False):
471 503 """return the plain string version of a config"""
472 504 value = self._config(section, name, default=default,
473 505 untrusted=untrusted)
474 506 if value is _unset:
475 507 return None
476 508 return value
477 509
478 510 def _config(self, section, name, default=_unset, untrusted=False):
479 511 value = itemdefault = default
480 512 item = self._knownconfig.get(section, {}).get(name)
481 513 alternates = [(section, name)]
482 514
483 515 if item is not None:
484 516 alternates.extend(item.alias)
485 517 if callable(item.default):
486 518 itemdefault = item.default()
487 519 else:
488 520 itemdefault = item.default
489 521 else:
490 522 msg = ("accessing unregistered config item: '%s.%s'")
491 523 msg %= (section, name)
492 524 self.develwarn(msg, 2, 'warn-config-unknown')
493 525
494 526 if default is _unset:
495 527 if item is None:
496 528 value = default
497 529 elif item.default is configitems.dynamicdefault:
498 530 value = None
499 531 msg = "config item requires an explicit default value: '%s.%s'"
500 532 msg %= (section, name)
501 533 self.develwarn(msg, 2, 'warn-config-default')
502 534 else:
503 535 value = itemdefault
504 536 elif (item is not None
505 537 and item.default is not configitems.dynamicdefault
506 538 and default != itemdefault):
507 539 msg = ("specifying a mismatched default value for a registered "
508 540 "config item: '%s.%s' '%s'")
509 541 msg %= (section, name, pycompat.bytestr(default))
510 542 self.develwarn(msg, 2, 'warn-config-default')
511 543
512 544 for s, n in alternates:
513 545 candidate = self._data(untrusted).get(s, n, None)
514 546 if candidate is not None:
515 547 value = candidate
516 548 section = s
517 549 name = n
518 550 break
519 551
520 552 if self.debugflag and not untrusted and self._reportuntrusted:
521 553 for s, n in alternates:
522 554 uvalue = self._ucfg.get(s, n)
523 555 if uvalue is not None and uvalue != value:
524 556 self.debug("ignoring untrusted configuration option "
525 557 "%s.%s = %s\n" % (s, n, uvalue))
526 558 return value
527 559
528 560 def configsuboptions(self, section, name, default=_unset, untrusted=False):
529 561 """Get a config option and all sub-options.
530 562
531 563 Some config options have sub-options that are declared with the
532 564 format "key:opt = value". This method is used to return the main
533 565 option and all its declared sub-options.
534 566
535 567 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
536 568 is a dict of defined sub-options where keys and values are strings.
537 569 """
538 570 main = self.config(section, name, default, untrusted=untrusted)
539 571 data = self._data(untrusted)
540 572 sub = {}
541 573 prefix = '%s:' % name
542 574 for k, v in data.items(section):
543 575 if k.startswith(prefix):
544 576 sub[k[len(prefix):]] = v
545 577
546 578 if self.debugflag and not untrusted and self._reportuntrusted:
547 579 for k, v in sub.items():
548 580 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
549 581 if uvalue is not None and uvalue != v:
550 582 self.debug('ignoring untrusted configuration option '
551 583 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
552 584
553 585 return main, sub
554 586
555 587 def configpath(self, section, name, default=_unset, untrusted=False):
556 588 'get a path config item, expanded relative to repo root or config file'
557 589 v = self.config(section, name, default, untrusted)
558 590 if v is None:
559 591 return None
560 592 if not os.path.isabs(v) or "://" not in v:
561 593 src = self.configsource(section, name, untrusted)
562 594 if ':' in src:
563 595 base = os.path.dirname(src.rsplit(':')[0])
564 596 v = os.path.join(base, os.path.expanduser(v))
565 597 return v
566 598
567 599 def configbool(self, section, name, default=_unset, untrusted=False):
568 600 """parse a configuration element as a boolean
569 601
570 602 >>> u = ui(); s = b'foo'
571 603 >>> u.setconfig(s, b'true', b'yes')
572 604 >>> u.configbool(s, b'true')
573 605 True
574 606 >>> u.setconfig(s, b'false', b'no')
575 607 >>> u.configbool(s, b'false')
576 608 False
577 609 >>> u.configbool(s, b'unknown')
578 610 False
579 611 >>> u.configbool(s, b'unknown', True)
580 612 True
581 613 >>> u.setconfig(s, b'invalid', b'somevalue')
582 614 >>> u.configbool(s, b'invalid')
583 615 Traceback (most recent call last):
584 616 ...
585 617 ConfigError: foo.invalid is not a boolean ('somevalue')
586 618 """
587 619
588 620 v = self._config(section, name, default, untrusted=untrusted)
589 621 if v is None:
590 622 return v
591 623 if v is _unset:
592 624 if default is _unset:
593 625 return False
594 626 return default
595 627 if isinstance(v, bool):
596 628 return v
597 629 b = stringutil.parsebool(v)
598 630 if b is None:
599 631 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
600 632 % (section, name, v))
601 633 return b
602 634
603 635 def configwith(self, convert, section, name, default=_unset,
604 636 desc=None, untrusted=False):
605 637 """parse a configuration element with a conversion function
606 638
607 639 >>> u = ui(); s = b'foo'
608 640 >>> u.setconfig(s, b'float1', b'42')
609 641 >>> u.configwith(float, s, b'float1')
610 642 42.0
611 643 >>> u.setconfig(s, b'float2', b'-4.25')
612 644 >>> u.configwith(float, s, b'float2')
613 645 -4.25
614 646 >>> u.configwith(float, s, b'unknown', 7)
615 647 7.0
616 648 >>> u.setconfig(s, b'invalid', b'somevalue')
617 649 >>> u.configwith(float, s, b'invalid')
618 650 Traceback (most recent call last):
619 651 ...
620 652 ConfigError: foo.invalid is not a valid float ('somevalue')
621 653 >>> u.configwith(float, s, b'invalid', desc=b'womble')
622 654 Traceback (most recent call last):
623 655 ...
624 656 ConfigError: foo.invalid is not a valid womble ('somevalue')
625 657 """
626 658
627 659 v = self.config(section, name, default, untrusted)
628 660 if v is None:
629 661 return v # do not attempt to convert None
630 662 try:
631 663 return convert(v)
632 664 except (ValueError, error.ParseError):
633 665 if desc is None:
634 666 desc = pycompat.sysbytes(convert.__name__)
635 667 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
636 668 % (section, name, desc, v))
637 669
638 670 def configint(self, section, name, default=_unset, untrusted=False):
639 671 """parse a configuration element as an integer
640 672
641 673 >>> u = ui(); s = b'foo'
642 674 >>> u.setconfig(s, b'int1', b'42')
643 675 >>> u.configint(s, b'int1')
644 676 42
645 677 >>> u.setconfig(s, b'int2', b'-42')
646 678 >>> u.configint(s, b'int2')
647 679 -42
648 680 >>> u.configint(s, b'unknown', 7)
649 681 7
650 682 >>> u.setconfig(s, b'invalid', b'somevalue')
651 683 >>> u.configint(s, b'invalid')
652 684 Traceback (most recent call last):
653 685 ...
654 686 ConfigError: foo.invalid is not a valid integer ('somevalue')
655 687 """
656 688
657 689 return self.configwith(int, section, name, default, 'integer',
658 690 untrusted)
659 691
660 692 def configbytes(self, section, name, default=_unset, untrusted=False):
661 693 """parse a configuration element as a quantity in bytes
662 694
663 695 Units can be specified as b (bytes), k or kb (kilobytes), m or
664 696 mb (megabytes), g or gb (gigabytes).
665 697
666 698 >>> u = ui(); s = b'foo'
667 699 >>> u.setconfig(s, b'val1', b'42')
668 700 >>> u.configbytes(s, b'val1')
669 701 42
670 702 >>> u.setconfig(s, b'val2', b'42.5 kb')
671 703 >>> u.configbytes(s, b'val2')
672 704 43520
673 705 >>> u.configbytes(s, b'unknown', b'7 MB')
674 706 7340032
675 707 >>> u.setconfig(s, b'invalid', b'somevalue')
676 708 >>> u.configbytes(s, b'invalid')
677 709 Traceback (most recent call last):
678 710 ...
679 711 ConfigError: foo.invalid is not a byte quantity ('somevalue')
680 712 """
681 713
682 714 value = self._config(section, name, default, untrusted)
683 715 if value is _unset:
684 716 if default is _unset:
685 717 default = 0
686 718 value = default
687 719 if not isinstance(value, bytes):
688 720 return value
689 721 try:
690 722 return util.sizetoint(value)
691 723 except error.ParseError:
692 724 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
693 725 % (section, name, value))
694 726
695 727 def configlist(self, section, name, default=_unset, untrusted=False):
696 728 """parse a configuration element as a list of comma/space separated
697 729 strings
698 730
699 731 >>> u = ui(); s = b'foo'
700 732 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
701 733 >>> u.configlist(s, b'list1')
702 734 ['this', 'is', 'a small', 'test']
703 735 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
704 736 >>> u.configlist(s, b'list2')
705 737 ['this', 'is', 'a small', 'test']
706 738 """
707 739 # default is not always a list
708 740 v = self.configwith(config.parselist, section, name, default,
709 741 'list', untrusted)
710 742 if isinstance(v, bytes):
711 743 return config.parselist(v)
712 744 elif v is None:
713 745 return []
714 746 return v
715 747
716 748 def configdate(self, section, name, default=_unset, untrusted=False):
717 749 """parse a configuration element as a tuple of ints
718 750
719 751 >>> u = ui(); s = b'foo'
720 752 >>> u.setconfig(s, b'date', b'0 0')
721 753 >>> u.configdate(s, b'date')
722 754 (0, 0)
723 755 """
724 756 if self.config(section, name, default, untrusted):
725 757 return self.configwith(dateutil.parsedate, section, name, default,
726 758 'date', untrusted)
727 759 if default is _unset:
728 760 return None
729 761 return default
730 762
731 763 def hasconfig(self, section, name, untrusted=False):
732 764 return self._data(untrusted).hasitem(section, name)
733 765
734 766 def has_section(self, section, untrusted=False):
735 767 '''tell whether section exists in config.'''
736 768 return section in self._data(untrusted)
737 769
738 770 def configitems(self, section, untrusted=False, ignoresub=False):
739 771 items = self._data(untrusted).items(section)
740 772 if ignoresub:
741 773 items = [i for i in items if ':' not in i[0]]
742 774 if self.debugflag and not untrusted and self._reportuntrusted:
743 775 for k, v in self._ucfg.items(section):
744 776 if self._tcfg.get(section, k) != v:
745 777 self.debug("ignoring untrusted configuration option "
746 778 "%s.%s = %s\n" % (section, k, v))
747 779 return items
748 780
749 781 def walkconfig(self, untrusted=False):
750 782 cfg = self._data(untrusted)
751 783 for section in cfg.sections():
752 784 for name, value in self.configitems(section, untrusted):
753 785 yield section, name, value
754 786
755 787 def plain(self, feature=None):
756 788 '''is plain mode active?
757 789
758 790 Plain mode means that all configuration variables which affect
759 791 the behavior and output of Mercurial should be
760 792 ignored. Additionally, the output should be stable,
761 793 reproducible and suitable for use in scripts or applications.
762 794
763 795 The only way to trigger plain mode is by setting either the
764 796 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
765 797
766 798 The return value can either be
767 799 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
768 800 - False if feature is disabled by default and not included in HGPLAIN
769 801 - True otherwise
770 802 '''
771 803 if ('HGPLAIN' not in encoding.environ and
772 804 'HGPLAINEXCEPT' not in encoding.environ):
773 805 return False
774 806 exceptions = encoding.environ.get('HGPLAINEXCEPT',
775 807 '').strip().split(',')
776 808 # TODO: add support for HGPLAIN=+feature,-feature syntax
777 809 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
778 810 exceptions.append('strictflags')
779 811 if feature and exceptions:
780 812 return feature not in exceptions
781 813 return True
782 814
783 815 def username(self, acceptempty=False):
784 816 """Return default username to be used in commits.
785 817
786 818 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
787 819 and stop searching if one of these is set.
788 820 If not found and acceptempty is True, returns None.
789 821 If not found and ui.askusername is True, ask the user, else use
790 822 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
791 823 If no username could be found, raise an Abort error.
792 824 """
793 825 user = encoding.environ.get("HGUSER")
794 826 if user is None:
795 827 user = self.config("ui", "username")
796 828 if user is not None:
797 829 user = os.path.expandvars(user)
798 830 if user is None:
799 831 user = encoding.environ.get("EMAIL")
800 832 if user is None and acceptempty:
801 833 return user
802 834 if user is None and self.configbool("ui", "askusername"):
803 835 user = self.prompt(_("enter a commit username:"), default=None)
804 836 if user is None and not self.interactive():
805 837 try:
806 838 user = '%s@%s' % (procutil.getuser(),
807 839 encoding.strtolocal(socket.getfqdn()))
808 840 self.warn(_("no username found, using '%s' instead\n") % user)
809 841 except KeyError:
810 842 pass
811 843 if not user:
812 844 raise error.Abort(_('no username supplied'),
813 845 hint=_("use 'hg config --edit' "
814 846 'to set your username'))
815 847 if "\n" in user:
816 848 raise error.Abort(_("username %r contains a newline\n")
817 849 % pycompat.bytestr(user))
818 850 return user
819 851
820 852 def shortuser(self, user):
821 853 """Return a short representation of a user name or email address."""
822 854 if not self.verbose:
823 855 user = stringutil.shortuser(user)
824 856 return user
825 857
826 858 def expandpath(self, loc, default=None):
827 859 """Return repository location relative to cwd or from [paths]"""
828 860 try:
829 861 p = self.paths.getpath(loc)
830 862 if p:
831 863 return p.rawloc
832 864 except error.RepoError:
833 865 pass
834 866
835 867 if default:
836 868 try:
837 869 p = self.paths.getpath(default)
838 870 if p:
839 871 return p.rawloc
840 872 except error.RepoError:
841 873 pass
842 874
843 875 return loc
844 876
845 877 @util.propertycache
846 878 def paths(self):
847 879 return paths(self)
848 880
849 881 def pushbuffer(self, error=False, subproc=False, labeled=False):
850 882 """install a buffer to capture standard output of the ui object
851 883
852 884 If error is True, the error output will be captured too.
853 885
854 886 If subproc is True, output from subprocesses (typically hooks) will be
855 887 captured too.
856 888
857 889 If labeled is True, any labels associated with buffered
858 890 output will be handled. By default, this has no effect
859 891 on the output returned, but extensions and GUI tools may
860 892 handle this argument and returned styled output. If output
861 893 is being buffered so it can be captured and parsed or
862 894 processed, labeled should not be set to True.
863 895 """
864 896 self._buffers.append([])
865 897 self._bufferstates.append((error, subproc, labeled))
866 898 self._bufferapplylabels = labeled
867 899
868 900 def popbuffer(self):
869 901 '''pop the last buffer and return the buffered output'''
870 902 self._bufferstates.pop()
871 903 if self._bufferstates:
872 904 self._bufferapplylabels = self._bufferstates[-1][2]
873 905 else:
874 906 self._bufferapplylabels = None
875 907
876 908 return "".join(self._buffers.pop())
877 909
878 910 def canwritewithoutlabels(self):
879 911 '''check if write skips the label'''
880 912 if self._buffers and not self._bufferapplylabels:
881 913 return True
882 914 return self._colormode is None
883 915
884 916 def canbatchlabeledwrites(self):
885 917 '''check if write calls with labels are batchable'''
886 918 # Windows color printing is special, see ``write``.
887 919 return self._colormode != 'win32'
888 920
889 921 def write(self, *args, **opts):
890 922 '''write args to output
891 923
892 924 By default, this method simply writes to the buffer or stdout.
893 925 Color mode can be set on the UI class to have the output decorated
894 926 with color modifier before being written to stdout.
895 927
896 928 The color used is controlled by an optional keyword argument, "label".
897 929 This should be a string containing label names separated by space.
898 930 Label names take the form of "topic.type". For example, ui.debug()
899 931 issues a label of "ui.debug".
900 932
901 933 When labeling output for a specific command, a label of
902 934 "cmdname.type" is recommended. For example, status issues
903 935 a label of "status.modified" for modified files.
904 936 '''
905 937 if self._buffers:
906 938 if self._bufferapplylabels:
907 939 label = opts.get(r'label', '')
908 940 self._buffers[-1].extend(self.label(a, label) for a in args)
909 941 else:
910 942 self._buffers[-1].extend(args)
911 943 else:
912 944 self._writenobuf(*args, **opts)
913 945
914 946 def _writenobuf(self, *args, **opts):
915 947 if self._colormode == 'win32':
916 948 # windows color printing is its own can of crab, defer to
917 949 # the color module and that is it.
918 950 color.win32print(self, self._write, *args, **opts)
919 951 else:
920 952 msgs = args
921 953 if self._colormode is not None:
922 954 label = opts.get(r'label', '')
923 955 msgs = [self.label(a, label) for a in args]
924 956 self._write(*msgs, **opts)
925 957
926 958 def _write(self, *msgs, **opts):
927 959 self._progclear()
928 960 # opencode timeblockedsection because this is a critical path
929 961 starttime = util.timer()
930 962 try:
931 963 self.fout.write(''.join(msgs))
932 964 except IOError as err:
933 965 raise error.StdioError(err)
934 966 finally:
935 967 self._blockedtimes['stdio_blocked'] += \
936 968 (util.timer() - starttime) * 1000
937 969
938 970 def write_err(self, *args, **opts):
939 971 self._progclear()
940 972 if self._bufferstates and self._bufferstates[-1][0]:
941 973 self.write(*args, **opts)
942 974 elif self._colormode == 'win32':
943 975 # windows color printing is its own can of crab, defer to
944 976 # the color module and that is it.
945 977 color.win32print(self, self._write_err, *args, **opts)
946 978 else:
947 979 msgs = args
948 980 if self._colormode is not None:
949 981 label = opts.get(r'label', '')
950 982 msgs = [self.label(a, label) for a in args]
951 983 self._write_err(*msgs, **opts)
952 984
953 985 def _write_err(self, *msgs, **opts):
954 986 try:
955 987 with self.timeblockedsection('stdio'):
956 988 if not getattr(self.fout, 'closed', False):
957 989 self.fout.flush()
958 990 for a in msgs:
959 991 self.ferr.write(a)
960 992 # stderr may be buffered under win32 when redirected to files,
961 993 # including stdout.
962 994 if not getattr(self.ferr, 'closed', False):
963 995 self.ferr.flush()
964 996 except IOError as inst:
965 997 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
966 998 raise error.StdioError(inst)
967 999
968 1000 def flush(self):
969 1001 # opencode timeblockedsection because this is a critical path
970 1002 starttime = util.timer()
971 1003 try:
972 1004 try:
973 1005 self.fout.flush()
974 1006 except IOError as err:
975 1007 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
976 1008 raise error.StdioError(err)
977 1009 finally:
978 1010 try:
979 1011 self.ferr.flush()
980 1012 except IOError as err:
981 1013 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
982 1014 raise error.StdioError(err)
983 1015 finally:
984 1016 self._blockedtimes['stdio_blocked'] += \
985 1017 (util.timer() - starttime) * 1000
986 1018
987 1019 def _isatty(self, fh):
988 1020 if self.configbool('ui', 'nontty'):
989 1021 return False
990 1022 return procutil.isatty(fh)
991 1023
992 1024 def disablepager(self):
993 1025 self._disablepager = True
994 1026
995 1027 def pager(self, command):
996 1028 """Start a pager for subsequent command output.
997 1029
998 1030 Commands which produce a long stream of output should call
999 1031 this function to activate the user's preferred pagination
1000 1032 mechanism (which may be no pager). Calling this function
1001 1033 precludes any future use of interactive functionality, such as
1002 1034 prompting the user or activating curses.
1003 1035
1004 1036 Args:
1005 1037 command: The full, non-aliased name of the command. That is, "log"
1006 1038 not "history, "summary" not "summ", etc.
1007 1039 """
1008 1040 if (self._disablepager
1009 1041 or self.pageractive):
1010 1042 # how pager should do is already determined
1011 1043 return
1012 1044
1013 1045 if not command.startswith('internal-always-') and (
1014 1046 # explicit --pager=on (= 'internal-always-' prefix) should
1015 1047 # take precedence over disabling factors below
1016 1048 command in self.configlist('pager', 'ignore')
1017 1049 or not self.configbool('ui', 'paginate')
1018 1050 or not self.configbool('pager', 'attend-' + command, True)
1019 1051 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1020 1052 # formatted() will need some adjustment.
1021 1053 or not self.formatted()
1022 1054 or self.plain()
1023 1055 or self._buffers
1024 1056 # TODO: expose debugger-enabled on the UI object
1025 1057 or '--debugger' in pycompat.sysargv):
1026 1058 # We only want to paginate if the ui appears to be
1027 1059 # interactive, the user didn't say HGPLAIN or
1028 1060 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1029 1061 return
1030 1062
1031 1063 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1032 1064 if not pagercmd:
1033 1065 return
1034 1066
1035 1067 pagerenv = {}
1036 1068 for name, value in rcutil.defaultpagerenv().items():
1037 1069 if name not in encoding.environ:
1038 1070 pagerenv[name] = value
1039 1071
1040 1072 self.debug('starting pager for command %r\n' % command)
1041 1073 self.flush()
1042 1074
1043 1075 wasformatted = self.formatted()
1044 1076 if util.safehasattr(signal, "SIGPIPE"):
1045 1077 signal.signal(signal.SIGPIPE, _catchterm)
1046 1078 if self._runpager(pagercmd, pagerenv):
1047 1079 self.pageractive = True
1048 1080 # Preserve the formatted-ness of the UI. This is important
1049 1081 # because we mess with stdout, which might confuse
1050 1082 # auto-detection of things being formatted.
1051 1083 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1052 1084 self.setconfig('ui', 'interactive', False, 'pager')
1053 1085
1054 1086 # If pagermode differs from color.mode, reconfigure color now that
1055 1087 # pageractive is set.
1056 1088 cm = self._colormode
1057 1089 if cm != self.config('color', 'pagermode', cm):
1058 1090 color.setup(self)
1059 1091 else:
1060 1092 # If the pager can't be spawned in dispatch when --pager=on is
1061 1093 # given, don't try again when the command runs, to avoid a duplicate
1062 1094 # warning about a missing pager command.
1063 1095 self.disablepager()
1064 1096
1065 1097 def _runpager(self, command, env=None):
1066 1098 """Actually start the pager and set up file descriptors.
1067 1099
1068 1100 This is separate in part so that extensions (like chg) can
1069 1101 override how a pager is invoked.
1070 1102 """
1071 1103 if command == 'cat':
1072 1104 # Save ourselves some work.
1073 1105 return False
1074 1106 # If the command doesn't contain any of these characters, we
1075 1107 # assume it's a binary and exec it directly. This means for
1076 1108 # simple pager command configurations, we can degrade
1077 1109 # gracefully and tell the user about their broken pager.
1078 1110 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1079 1111
1080 1112 if pycompat.iswindows and not shell:
1081 1113 # Window's built-in `more` cannot be invoked with shell=False, but
1082 1114 # its `more.com` can. Hide this implementation detail from the
1083 1115 # user so we can also get sane bad PAGER behavior. MSYS has
1084 1116 # `more.exe`, so do a cmd.exe style resolution of the executable to
1085 1117 # determine which one to use.
1086 1118 fullcmd = procutil.findexe(command)
1087 1119 if not fullcmd:
1088 1120 self.warn(_("missing pager command '%s', skipping pager\n")
1089 1121 % command)
1090 1122 return False
1091 1123
1092 1124 command = fullcmd
1093 1125
1094 1126 try:
1095 1127 pager = subprocess.Popen(
1096 1128 command, shell=shell, bufsize=-1,
1097 1129 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1098 1130 stdout=procutil.stdout, stderr=procutil.stderr,
1099 1131 env=procutil.shellenviron(env))
1100 1132 except OSError as e:
1101 1133 if e.errno == errno.ENOENT and not shell:
1102 1134 self.warn(_("missing pager command '%s', skipping pager\n")
1103 1135 % command)
1104 1136 return False
1105 1137 raise
1106 1138
1107 1139 # back up original file descriptors
1108 1140 stdoutfd = os.dup(procutil.stdout.fileno())
1109 1141 stderrfd = os.dup(procutil.stderr.fileno())
1110 1142
1111 1143 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1112 1144 if self._isatty(procutil.stderr):
1113 1145 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1114 1146
1115 1147 @self.atexit
1116 1148 def killpager():
1117 1149 if util.safehasattr(signal, "SIGINT"):
1118 1150 signal.signal(signal.SIGINT, signal.SIG_IGN)
1119 1151 # restore original fds, closing pager.stdin copies in the process
1120 1152 os.dup2(stdoutfd, procutil.stdout.fileno())
1121 1153 os.dup2(stderrfd, procutil.stderr.fileno())
1122 1154 pager.stdin.close()
1123 1155 pager.wait()
1124 1156
1125 1157 return True
1126 1158
1127 1159 @property
1128 1160 def _exithandlers(self):
1129 1161 return _reqexithandlers
1130 1162
1131 1163 def atexit(self, func, *args, **kwargs):
1132 1164 '''register a function to run after dispatching a request
1133 1165
1134 1166 Handlers do not stay registered across request boundaries.'''
1135 1167 self._exithandlers.append((func, args, kwargs))
1136 1168 return func
1137 1169
1138 1170 def interface(self, feature):
1139 1171 """what interface to use for interactive console features?
1140 1172
1141 1173 The interface is controlled by the value of `ui.interface` but also by
1142 1174 the value of feature-specific configuration. For example:
1143 1175
1144 1176 ui.interface.histedit = text
1145 1177 ui.interface.chunkselector = curses
1146 1178
1147 1179 Here the features are "histedit" and "chunkselector".
1148 1180
1149 1181 The configuration above means that the default interfaces for commands
1150 1182 is curses, the interface for histedit is text and the interface for
1151 1183 selecting chunk is crecord (the best curses interface available).
1152 1184
1153 1185 Consider the following example:
1154 1186 ui.interface = curses
1155 1187 ui.interface.histedit = text
1156 1188
1157 1189 Then histedit will use the text interface and chunkselector will use
1158 1190 the default curses interface (crecord at the moment).
1159 1191 """
1160 1192 alldefaults = frozenset(["text", "curses"])
1161 1193
1162 1194 featureinterfaces = {
1163 1195 "chunkselector": [
1164 1196 "text",
1165 1197 "curses",
1166 1198 ]
1167 1199 }
1168 1200
1169 1201 # Feature-specific interface
1170 1202 if feature not in featureinterfaces.keys():
1171 1203 # Programming error, not user error
1172 1204 raise ValueError("Unknown feature requested %s" % feature)
1173 1205
1174 1206 availableinterfaces = frozenset(featureinterfaces[feature])
1175 1207 if alldefaults > availableinterfaces:
1176 1208 # Programming error, not user error. We need a use case to
1177 1209 # define the right thing to do here.
1178 1210 raise ValueError(
1179 1211 "Feature %s does not handle all default interfaces" %
1180 1212 feature)
1181 1213
1182 1214 if self.plain():
1183 1215 return "text"
1184 1216
1185 1217 # Default interface for all the features
1186 1218 defaultinterface = "text"
1187 1219 i = self.config("ui", "interface")
1188 1220 if i in alldefaults:
1189 1221 defaultinterface = i
1190 1222
1191 1223 choseninterface = defaultinterface
1192 1224 f = self.config("ui", "interface.%s" % feature)
1193 1225 if f in availableinterfaces:
1194 1226 choseninterface = f
1195 1227
1196 1228 if i is not None and defaultinterface != i:
1197 1229 if f is not None:
1198 1230 self.warn(_("invalid value for ui.interface: %s\n") %
1199 1231 (i,))
1200 1232 else:
1201 1233 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1202 1234 (i, choseninterface))
1203 1235 if f is not None and choseninterface != f:
1204 1236 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1205 1237 (feature, f, choseninterface))
1206 1238
1207 1239 return choseninterface
1208 1240
1209 1241 def interactive(self):
1210 1242 '''is interactive input allowed?
1211 1243
1212 1244 An interactive session is a session where input can be reasonably read
1213 1245 from `sys.stdin'. If this function returns false, any attempt to read
1214 1246 from stdin should fail with an error, unless a sensible default has been
1215 1247 specified.
1216 1248
1217 1249 Interactiveness is triggered by the value of the `ui.interactive'
1218 1250 configuration variable or - if it is unset - when `sys.stdin' points
1219 1251 to a terminal device.
1220 1252
1221 1253 This function refers to input only; for output, see `ui.formatted()'.
1222 1254 '''
1223 1255 i = self.configbool("ui", "interactive")
1224 1256 if i is None:
1225 1257 # some environments replace stdin without implementing isatty
1226 1258 # usually those are non-interactive
1227 1259 return self._isatty(self.fin)
1228 1260
1229 1261 return i
1230 1262
1231 1263 def termwidth(self):
1232 1264 '''how wide is the terminal in columns?
1233 1265 '''
1234 1266 if 'COLUMNS' in encoding.environ:
1235 1267 try:
1236 1268 return int(encoding.environ['COLUMNS'])
1237 1269 except ValueError:
1238 1270 pass
1239 1271 return scmutil.termsize(self)[0]
1240 1272
1241 1273 def formatted(self):
1242 1274 '''should formatted output be used?
1243 1275
1244 1276 It is often desirable to format the output to suite the output medium.
1245 1277 Examples of this are truncating long lines or colorizing messages.
1246 1278 However, this is not often not desirable when piping output into other
1247 1279 utilities, e.g. `grep'.
1248 1280
1249 1281 Formatted output is triggered by the value of the `ui.formatted'
1250 1282 configuration variable or - if it is unset - when `sys.stdout' points
1251 1283 to a terminal device. Please note that `ui.formatted' should be
1252 1284 considered an implementation detail; it is not intended for use outside
1253 1285 Mercurial or its extensions.
1254 1286
1255 1287 This function refers to output only; for input, see `ui.interactive()'.
1256 1288 This function always returns false when in plain mode, see `ui.plain()'.
1257 1289 '''
1258 1290 if self.plain():
1259 1291 return False
1260 1292
1261 1293 i = self.configbool("ui", "formatted")
1262 1294 if i is None:
1263 1295 # some environments replace stdout without implementing isatty
1264 1296 # usually those are non-interactive
1265 1297 return self._isatty(self.fout)
1266 1298
1267 1299 return i
1268 1300
1269 1301 def _readline(self):
1270 1302 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1271 1303 # because they have to be text streams with *no buffering*. Instead,
1272 1304 # we use rawinput() only if call_readline() will be invoked by
1273 1305 # PyOS_Readline(), so no I/O will be made at Python layer.
1274 1306 usereadline = (self._isatty(self.fin) and self._isatty(self.fout)
1275 1307 and procutil.isstdin(self.fin)
1276 1308 and procutil.isstdout(self.fout))
1277 1309 if usereadline:
1278 1310 try:
1279 1311 # magically add command line editing support, where
1280 1312 # available
1281 1313 import readline
1282 1314 # force demandimport to really load the module
1283 1315 readline.read_history_file
1284 1316 # windows sometimes raises something other than ImportError
1285 1317 except Exception:
1286 1318 usereadline = False
1287 1319
1288 1320 # prompt ' ' must exist; otherwise readline may delete entire line
1289 1321 # - http://bugs.python.org/issue12833
1290 1322 with self.timeblockedsection('stdio'):
1291 1323 if usereadline:
1292 1324 line = encoding.strtolocal(pycompat.rawinput(r' '))
1293 1325 # When stdin is in binary mode on Windows, it can cause
1294 1326 # raw_input() to emit an extra trailing carriage return
1295 1327 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1296 1328 line = line[:-1]
1297 1329 else:
1298 1330 self.fout.write(b' ')
1299 1331 self.fout.flush()
1300 1332 line = self.fin.readline()
1301 1333 if not line:
1302 1334 raise EOFError
1303 1335 line = line.rstrip(pycompat.oslinesep)
1304 1336
1305 1337 return line
1306 1338
1307 1339 def prompt(self, msg, default="y"):
1308 1340 """Prompt user with msg, read response.
1309 1341 If ui is not interactive, the default is returned.
1310 1342 """
1311 1343 if not self.interactive():
1312 1344 self.write(msg, ' ', default or '', "\n")
1313 1345 return default
1314 1346 self._writenobuf(msg, label='ui.prompt')
1315 1347 self.flush()
1316 1348 try:
1317 1349 r = self._readline()
1318 1350 if not r:
1319 1351 r = default
1320 1352 if self.configbool('ui', 'promptecho'):
1321 1353 self.write(r, "\n")
1322 1354 return r
1323 1355 except EOFError:
1324 1356 raise error.ResponseExpected()
1325 1357
1326 1358 @staticmethod
1327 1359 def extractchoices(prompt):
1328 1360 """Extract prompt message and list of choices from specified prompt.
1329 1361
1330 1362 This returns tuple "(message, choices)", and "choices" is the
1331 1363 list of tuple "(response character, text without &)".
1332 1364
1333 1365 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1334 1366 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1335 1367 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1336 1368 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1337 1369 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1338 1370 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1339 1371 """
1340 1372
1341 1373 # Sadly, the prompt string may have been built with a filename
1342 1374 # containing "$$" so let's try to find the first valid-looking
1343 1375 # prompt to start parsing. Sadly, we also can't rely on
1344 1376 # choices containing spaces, ASCII, or basically anything
1345 1377 # except an ampersand followed by a character.
1346 1378 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1347 1379 msg = m.group(1)
1348 1380 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1349 1381 def choicetuple(s):
1350 1382 ampidx = s.index('&')
1351 1383 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1352 1384 return (msg, [choicetuple(s) for s in choices])
1353 1385
1354 1386 def promptchoice(self, prompt, default=0):
1355 1387 """Prompt user with a message, read response, and ensure it matches
1356 1388 one of the provided choices. The prompt is formatted as follows:
1357 1389
1358 1390 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1359 1391
1360 1392 The index of the choice is returned. Responses are case
1361 1393 insensitive. If ui is not interactive, the default is
1362 1394 returned.
1363 1395 """
1364 1396
1365 1397 msg, choices = self.extractchoices(prompt)
1366 1398 resps = [r for r, t in choices]
1367 1399 while True:
1368 1400 r = self.prompt(msg, resps[default])
1369 1401 if r.lower() in resps:
1370 1402 return resps.index(r.lower())
1371 1403 self.write(_("unrecognized response\n"))
1372 1404
1373 1405 def getpass(self, prompt=None, default=None):
1374 1406 if not self.interactive():
1375 1407 return default
1376 1408 try:
1377 1409 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1378 1410 # disable getpass() only if explicitly specified. it's still valid
1379 1411 # to interact with tty even if fin is not a tty.
1380 1412 with self.timeblockedsection('stdio'):
1381 1413 if self.configbool('ui', 'nontty'):
1382 1414 l = self.fin.readline()
1383 1415 if not l:
1384 1416 raise EOFError
1385 1417 return l.rstrip('\n')
1386 1418 else:
1387 1419 return getpass.getpass('')
1388 1420 except EOFError:
1389 1421 raise error.ResponseExpected()
1390 1422 def status(self, *msg, **opts):
1391 1423 '''write status message to output (if ui.quiet is False)
1392 1424
1393 1425 This adds an output label of "ui.status".
1394 1426 '''
1395 1427 if not self.quiet:
1396 1428 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1397 1429 self.write(*msg, **opts)
1398 1430 def warn(self, *msg, **opts):
1399 1431 '''write warning message to output (stderr)
1400 1432
1401 1433 This adds an output label of "ui.warning".
1402 1434 '''
1403 1435 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1404 1436 self.write_err(*msg, **opts)
1405 1437 def note(self, *msg, **opts):
1406 1438 '''write note to output (if ui.verbose is True)
1407 1439
1408 1440 This adds an output label of "ui.note".
1409 1441 '''
1410 1442 if self.verbose:
1411 1443 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1412 1444 self.write(*msg, **opts)
1413 1445 def debug(self, *msg, **opts):
1414 1446 '''write debug message to output (if ui.debugflag is True)
1415 1447
1416 1448 This adds an output label of "ui.debug".
1417 1449 '''
1418 1450 if self.debugflag:
1419 1451 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1420 1452 self.write(*msg, **opts)
1421 1453
1422 1454 def edit(self, text, user, extra=None, editform=None, pending=None,
1423 1455 repopath=None, action=None):
1424 1456 if action is None:
1425 1457 self.develwarn('action is None but will soon be a required '
1426 1458 'parameter to ui.edit()')
1427 1459 extra_defaults = {
1428 1460 'prefix': 'editor',
1429 1461 'suffix': '.txt',
1430 1462 }
1431 1463 if extra is not None:
1432 1464 if extra.get('suffix') is not None:
1433 1465 self.develwarn('extra.suffix is not None but will soon be '
1434 1466 'ignored by ui.edit()')
1435 1467 extra_defaults.update(extra)
1436 1468 extra = extra_defaults
1437 1469
1438 1470 if action == 'diff':
1439 1471 suffix = '.diff'
1440 1472 elif action:
1441 1473 suffix = '.%s.hg.txt' % action
1442 1474 else:
1443 1475 suffix = extra['suffix']
1444 1476
1445 1477 rdir = None
1446 1478 if self.configbool('experimental', 'editortmpinhg'):
1447 1479 rdir = repopath
1448 1480 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1449 1481 suffix=suffix,
1450 1482 dir=rdir)
1451 1483 try:
1452 1484 f = os.fdopen(fd, r'wb')
1453 1485 f.write(util.tonativeeol(text))
1454 1486 f.close()
1455 1487
1456 1488 environ = {'HGUSER': user}
1457 1489 if 'transplant_source' in extra:
1458 1490 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1459 1491 for label in ('intermediate-source', 'source', 'rebase_source'):
1460 1492 if label in extra:
1461 1493 environ.update({'HGREVISION': extra[label]})
1462 1494 break
1463 1495 if editform:
1464 1496 environ.update({'HGEDITFORM': editform})
1465 1497 if pending:
1466 1498 environ.update({'HG_PENDING': pending})
1467 1499
1468 1500 editor = self.geteditor()
1469 1501
1470 1502 self.system("%s \"%s\"" % (editor, name),
1471 1503 environ=environ,
1472 1504 onerr=error.Abort, errprefix=_("edit failed"),
1473 1505 blockedtag='editor')
1474 1506
1475 1507 f = open(name, r'rb')
1476 1508 t = util.fromnativeeol(f.read())
1477 1509 f.close()
1478 1510 finally:
1479 1511 os.unlink(name)
1480 1512
1481 1513 return t
1482 1514
1483 1515 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1484 1516 blockedtag=None):
1485 1517 '''execute shell command with appropriate output stream. command
1486 1518 output will be redirected if fout is not stdout.
1487 1519
1488 1520 if command fails and onerr is None, return status, else raise onerr
1489 1521 object as exception.
1490 1522 '''
1491 1523 if blockedtag is None:
1492 1524 # Long cmds tend to be because of an absolute path on cmd. Keep
1493 1525 # the tail end instead
1494 1526 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1495 1527 blockedtag = 'unknown_system_' + cmdsuffix
1496 1528 out = self.fout
1497 1529 if any(s[1] for s in self._bufferstates):
1498 1530 out = self
1499 1531 with self.timeblockedsection(blockedtag):
1500 1532 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1501 1533 if rc and onerr:
1502 1534 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1503 1535 procutil.explainexit(rc))
1504 1536 if errprefix:
1505 1537 errmsg = '%s: %s' % (errprefix, errmsg)
1506 1538 raise onerr(errmsg)
1507 1539 return rc
1508 1540
1509 1541 def _runsystem(self, cmd, environ, cwd, out):
1510 1542 """actually execute the given shell command (can be overridden by
1511 1543 extensions like chg)"""
1512 1544 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1513 1545
1514 1546 def traceback(self, exc=None, force=False):
1515 1547 '''print exception traceback if traceback printing enabled or forced.
1516 1548 only to call in exception handler. returns true if traceback
1517 1549 printed.'''
1518 1550 if self.tracebackflag or force:
1519 1551 if exc is None:
1520 1552 exc = sys.exc_info()
1521 1553 cause = getattr(exc[1], 'cause', None)
1522 1554
1523 1555 if cause is not None:
1524 1556 causetb = traceback.format_tb(cause[2])
1525 1557 exctb = traceback.format_tb(exc[2])
1526 1558 exconly = traceback.format_exception_only(cause[0], cause[1])
1527 1559
1528 1560 # exclude frame where 'exc' was chained and rethrown from exctb
1529 1561 self.write_err('Traceback (most recent call last):\n',
1530 1562 ''.join(exctb[:-1]),
1531 1563 ''.join(causetb),
1532 1564 ''.join(exconly))
1533 1565 else:
1534 1566 output = traceback.format_exception(exc[0], exc[1], exc[2])
1535 1567 self.write_err(encoding.strtolocal(r''.join(output)))
1536 1568 return self.tracebackflag or force
1537 1569
1538 1570 def geteditor(self):
1539 1571 '''return editor to use'''
1540 1572 if pycompat.sysplatform == 'plan9':
1541 1573 # vi is the MIPS instruction simulator on Plan 9. We
1542 1574 # instead default to E to plumb commit messages to
1543 1575 # avoid confusion.
1544 1576 editor = 'E'
1545 1577 else:
1546 1578 editor = 'vi'
1547 1579 return (encoding.environ.get("HGEDITOR") or
1548 1580 self.config("ui", "editor", editor))
1549 1581
1550 1582 @util.propertycache
1551 1583 def _progbar(self):
1552 1584 """setup the progbar singleton to the ui object"""
1553 1585 if (self.quiet or self.debugflag
1554 1586 or self.configbool('progress', 'disable')
1555 1587 or not progress.shouldprint(self)):
1556 1588 return None
1557 1589 return getprogbar(self)
1558 1590
1559 1591 def _progclear(self):
1560 1592 """clear progress bar output if any. use it before any output"""
1561 1593 if not haveprogbar(): # nothing loaded yet
1562 1594 return
1563 1595 if self._progbar is not None and self._progbar.printed:
1564 1596 self._progbar.clear()
1565 1597
1566 1598 def progress(self, topic, pos, item="", unit="", total=None):
1567 1599 '''show a progress message
1568 1600
1569 1601 By default a textual progress bar will be displayed if an operation
1570 1602 takes too long. 'topic' is the current operation, 'item' is a
1571 1603 non-numeric marker of the current position (i.e. the currently
1572 1604 in-process file), 'pos' is the current numeric position (i.e.
1573 1605 revision, bytes, etc.), unit is a corresponding unit label,
1574 1606 and total is the highest expected pos.
1575 1607
1576 1608 Multiple nested topics may be active at a time.
1577 1609
1578 1610 All topics should be marked closed by setting pos to None at
1579 1611 termination.
1580 1612 '''
1581 1613 if self._progbar is not None:
1582 1614 self._progbar.progress(topic, pos, item=item, unit=unit,
1583 1615 total=total)
1584 1616 if pos is None or not self.configbool('progress', 'debug'):
1585 1617 return
1586 1618
1587 1619 if unit:
1588 1620 unit = ' ' + unit
1589 1621 if item:
1590 1622 item = ' ' + item
1591 1623
1592 1624 if total:
1593 1625 pct = 100.0 * pos / total
1594 1626 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1595 1627 % (topic, item, pos, total, unit, pct))
1596 1628 else:
1597 1629 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1598 1630
1599 1631 def makeprogress(self, topic, unit="", total=None):
1600 1632 '''exists only so low-level modules won't need to import scmutil'''
1601 1633 return scmutil.progress(self, topic, unit, total)
1602 1634
1603 1635 def log(self, service, *msg, **opts):
1604 1636 '''hook for logging facility extensions
1605 1637
1606 1638 service should be a readily-identifiable subsystem, which will
1607 1639 allow filtering.
1608 1640
1609 1641 *msg should be a newline-terminated format string to log, and
1610 1642 then any values to %-format into that format string.
1611 1643
1612 1644 **opts currently has no defined meanings.
1613 1645 '''
1614 1646
1615 1647 def label(self, msg, label):
1616 1648 '''style msg based on supplied label
1617 1649
1618 1650 If some color mode is enabled, this will add the necessary control
1619 1651 characters to apply such color. In addition, 'debug' color mode adds
1620 1652 markup showing which label affects a piece of text.
1621 1653
1622 1654 ui.write(s, 'label') is equivalent to
1623 1655 ui.write(ui.label(s, 'label')).
1624 1656 '''
1625 1657 if self._colormode is not None:
1626 1658 return color.colorlabel(self, msg, label)
1627 1659 return msg
1628 1660
1629 1661 def develwarn(self, msg, stacklevel=1, config=None):
1630 1662 """issue a developer warning message
1631 1663
1632 1664 Use 'stacklevel' to report the offender some layers further up in the
1633 1665 stack.
1634 1666 """
1635 1667 if not self.configbool('devel', 'all-warnings'):
1636 1668 if config is None or not self.configbool('devel', config):
1637 1669 return
1638 1670 msg = 'devel-warn: ' + msg
1639 1671 stacklevel += 1 # get in develwarn
1640 1672 if self.tracebackflag:
1641 1673 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1642 1674 self.log('develwarn', '%s at:\n%s' %
1643 1675 (msg, ''.join(util.getstackframes(stacklevel))))
1644 1676 else:
1645 1677 curframe = inspect.currentframe()
1646 1678 calframe = inspect.getouterframes(curframe, 2)
1647 1679 fname, lineno, fmsg = calframe[stacklevel][1:4]
1648 1680 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1649 1681 self.write_err('%s at: %s:%d (%s)\n'
1650 1682 % (msg, fname, lineno, fmsg))
1651 1683 self.log('develwarn', '%s at: %s:%d (%s)\n',
1652 1684 msg, fname, lineno, fmsg)
1653 1685 curframe = calframe = None # avoid cycles
1654 1686
1655 1687 def deprecwarn(self, msg, version, stacklevel=2):
1656 1688 """issue a deprecation warning
1657 1689
1658 1690 - msg: message explaining what is deprecated and how to upgrade,
1659 1691 - version: last version where the API will be supported,
1660 1692 """
1661 1693 if not (self.configbool('devel', 'all-warnings')
1662 1694 or self.configbool('devel', 'deprec-warn')):
1663 1695 return
1664 1696 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1665 1697 " update your code.)") % version
1666 1698 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1667 1699
1668 1700 def exportableenviron(self):
1669 1701 """The environment variables that are safe to export, e.g. through
1670 1702 hgweb.
1671 1703 """
1672 1704 return self._exportableenviron
1673 1705
1674 1706 @contextlib.contextmanager
1675 1707 def configoverride(self, overrides, source=""):
1676 1708 """Context manager for temporary config overrides
1677 1709 `overrides` must be a dict of the following structure:
1678 1710 {(section, name) : value}"""
1679 1711 backups = {}
1680 1712 try:
1681 1713 for (section, name), value in overrides.items():
1682 1714 backups[(section, name)] = self.backupconfig(section, name)
1683 1715 self.setconfig(section, name, value, source)
1684 1716 yield
1685 1717 finally:
1686 1718 for __, backup in backups.items():
1687 1719 self.restoreconfig(backup)
1688 1720 # just restoring ui.quiet config to the previous value is not enough
1689 1721 # as it does not update ui.quiet class member
1690 1722 if ('ui', 'quiet') in overrides:
1691 1723 self.fixconfig(section='ui')
1692 1724
1693 1725 class paths(dict):
1694 1726 """Represents a collection of paths and their configs.
1695 1727
1696 1728 Data is initially derived from ui instances and the config files they have
1697 1729 loaded.
1698 1730 """
1699 1731 def __init__(self, ui):
1700 1732 dict.__init__(self)
1701 1733
1702 1734 for name, loc in ui.configitems('paths', ignoresub=True):
1703 1735 # No location is the same as not existing.
1704 1736 if not loc:
1705 1737 continue
1706 1738 loc, sub = ui.configsuboptions('paths', name)
1707 1739 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1708 1740
1709 1741 def getpath(self, name, default=None):
1710 1742 """Return a ``path`` from a string, falling back to default.
1711 1743
1712 1744 ``name`` can be a named path or locations. Locations are filesystem
1713 1745 paths or URIs.
1714 1746
1715 1747 Returns None if ``name`` is not a registered path, a URI, or a local
1716 1748 path to a repo.
1717 1749 """
1718 1750 # Only fall back to default if no path was requested.
1719 1751 if name is None:
1720 1752 if not default:
1721 1753 default = ()
1722 1754 elif not isinstance(default, (tuple, list)):
1723 1755 default = (default,)
1724 1756 for k in default:
1725 1757 try:
1726 1758 return self[k]
1727 1759 except KeyError:
1728 1760 continue
1729 1761 return None
1730 1762
1731 1763 # Most likely empty string.
1732 1764 # This may need to raise in the future.
1733 1765 if not name:
1734 1766 return None
1735 1767
1736 1768 try:
1737 1769 return self[name]
1738 1770 except KeyError:
1739 1771 # Try to resolve as a local path or URI.
1740 1772 try:
1741 1773 # We don't pass sub-options in, so no need to pass ui instance.
1742 1774 return path(None, None, rawloc=name)
1743 1775 except ValueError:
1744 1776 raise error.RepoError(_('repository %s does not exist') %
1745 1777 name)
1746 1778
1747 1779 _pathsuboptions = {}
1748 1780
1749 1781 def pathsuboption(option, attr):
1750 1782 """Decorator used to declare a path sub-option.
1751 1783
1752 1784 Arguments are the sub-option name and the attribute it should set on
1753 1785 ``path`` instances.
1754 1786
1755 1787 The decorated function will receive as arguments a ``ui`` instance,
1756 1788 ``path`` instance, and the string value of this option from the config.
1757 1789 The function should return the value that will be set on the ``path``
1758 1790 instance.
1759 1791
1760 1792 This decorator can be used to perform additional verification of
1761 1793 sub-options and to change the type of sub-options.
1762 1794 """
1763 1795 def register(func):
1764 1796 _pathsuboptions[option] = (attr, func)
1765 1797 return func
1766 1798 return register
1767 1799
1768 1800 @pathsuboption('pushurl', 'pushloc')
1769 1801 def pushurlpathoption(ui, path, value):
1770 1802 u = util.url(value)
1771 1803 # Actually require a URL.
1772 1804 if not u.scheme:
1773 1805 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1774 1806 return None
1775 1807
1776 1808 # Don't support the #foo syntax in the push URL to declare branch to
1777 1809 # push.
1778 1810 if u.fragment:
1779 1811 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1780 1812 'ignoring)\n') % path.name)
1781 1813 u.fragment = None
1782 1814
1783 1815 return bytes(u)
1784 1816
1785 1817 @pathsuboption('pushrev', 'pushrev')
1786 1818 def pushrevpathoption(ui, path, value):
1787 1819 return value
1788 1820
1789 1821 class path(object):
1790 1822 """Represents an individual path and its configuration."""
1791 1823
1792 1824 def __init__(self, ui, name, rawloc=None, suboptions=None):
1793 1825 """Construct a path from its config options.
1794 1826
1795 1827 ``ui`` is the ``ui`` instance the path is coming from.
1796 1828 ``name`` is the symbolic name of the path.
1797 1829 ``rawloc`` is the raw location, as defined in the config.
1798 1830 ``pushloc`` is the raw locations pushes should be made to.
1799 1831
1800 1832 If ``name`` is not defined, we require that the location be a) a local
1801 1833 filesystem path with a .hg directory or b) a URL. If not,
1802 1834 ``ValueError`` is raised.
1803 1835 """
1804 1836 if not rawloc:
1805 1837 raise ValueError('rawloc must be defined')
1806 1838
1807 1839 # Locations may define branches via syntax <base>#<branch>.
1808 1840 u = util.url(rawloc)
1809 1841 branch = None
1810 1842 if u.fragment:
1811 1843 branch = u.fragment
1812 1844 u.fragment = None
1813 1845
1814 1846 self.url = u
1815 1847 self.branch = branch
1816 1848
1817 1849 self.name = name
1818 1850 self.rawloc = rawloc
1819 1851 self.loc = '%s' % u
1820 1852
1821 1853 # When given a raw location but not a symbolic name, validate the
1822 1854 # location is valid.
1823 1855 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1824 1856 raise ValueError('location is not a URL or path to a local '
1825 1857 'repo: %s' % rawloc)
1826 1858
1827 1859 suboptions = suboptions or {}
1828 1860
1829 1861 # Now process the sub-options. If a sub-option is registered, its
1830 1862 # attribute will always be present. The value will be None if there
1831 1863 # was no valid sub-option.
1832 1864 for suboption, (attr, func) in _pathsuboptions.iteritems():
1833 1865 if suboption not in suboptions:
1834 1866 setattr(self, attr, None)
1835 1867 continue
1836 1868
1837 1869 value = func(ui, self, suboptions[suboption])
1838 1870 setattr(self, attr, value)
1839 1871
1840 1872 def _isvalidlocalpath(self, path):
1841 1873 """Returns True if the given path is a potentially valid repository.
1842 1874 This is its own function so that extensions can change the definition of
1843 1875 'valid' in this case (like when pulling from a git repo into a hg
1844 1876 one)."""
1845 1877 return os.path.isdir(os.path.join(path, '.hg'))
1846 1878
1847 1879 @property
1848 1880 def suboptions(self):
1849 1881 """Return sub-options and their values for this path.
1850 1882
1851 1883 This is intended to be used for presentation purposes.
1852 1884 """
1853 1885 d = {}
1854 1886 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1855 1887 value = getattr(self, attr)
1856 1888 if value is not None:
1857 1889 d[subopt] = value
1858 1890 return d
1859 1891
1860 1892 # we instantiate one globally shared progress bar to avoid
1861 1893 # competing progress bars when multiple UI objects get created
1862 1894 _progresssingleton = None
1863 1895
1864 1896 def getprogbar(ui):
1865 1897 global _progresssingleton
1866 1898 if _progresssingleton is None:
1867 1899 # passing 'ui' object to the singleton is fishy,
1868 1900 # this is how the extension used to work but feel free to rework it.
1869 1901 _progresssingleton = progress.progbar(ui)
1870 1902 return _progresssingleton
1871 1903
1872 1904 def haveprogbar():
1873 1905 return _progresssingleton is not None
@@ -1,417 +1,450 b''
1 1 # procutil.py - utility for managing processes and executable environment
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import contextlib
13 13 import imp
14 14 import io
15 15 import os
16 16 import signal
17 17 import subprocess
18 18 import sys
19 19 import time
20 20
21 21 from ..i18n import _
22 22
23 23 from .. import (
24 24 encoding,
25 25 error,
26 26 policy,
27 27 pycompat,
28 28 )
29 29
30 30 osutil = policy.importmod(r'osutil')
31 31
32 32 stderr = pycompat.stderr
33 33 stdin = pycompat.stdin
34 34 stdout = pycompat.stdout
35 35
36 36 def isatty(fp):
37 37 try:
38 38 return fp.isatty()
39 39 except AttributeError:
40 40 return False
41 41
42 42 # glibc determines buffering on first write to stdout - if we replace a TTY
43 43 # destined stdout with a pipe destined stdout (e.g. pager), we want line
44 44 # buffering (or unbuffered, on Windows)
45 45 if isatty(stdout):
46 46 if pycompat.iswindows:
47 47 # Windows doesn't support line buffering
48 48 stdout = os.fdopen(stdout.fileno(), r'wb', 0)
49 49 else:
50 50 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
51 51
52 52 if pycompat.iswindows:
53 53 from .. import windows as platform
54 54 stdout = platform.winstdout(stdout)
55 55 else:
56 56 from .. import posix as platform
57 57
58 58 findexe = platform.findexe
59 59 _gethgcmd = platform.gethgcmd
60 60 getuser = platform.getuser
61 61 getpid = os.getpid
62 62 hidewindow = platform.hidewindow
63 63 quotecommand = platform.quotecommand
64 64 readpipe = platform.readpipe
65 65 setbinary = platform.setbinary
66 66 setsignalhandler = platform.setsignalhandler
67 67 shellquote = platform.shellquote
68 68 shellsplit = platform.shellsplit
69 69 spawndetached = platform.spawndetached
70 70 sshargs = platform.sshargs
71 71 testpid = platform.testpid
72 72
73 73 try:
74 74 setprocname = osutil.setprocname
75 75 except AttributeError:
76 76 pass
77 77 try:
78 78 unblocksignal = osutil.unblocksignal
79 79 except AttributeError:
80 80 pass
81 81
82 82 closefds = pycompat.isposix
83 83
84 84 def explainexit(code):
85 85 """return a message describing a subprocess status
86 86 (codes from kill are negative - not os.system/wait encoding)"""
87 87 if code >= 0:
88 88 return _("exited with status %d") % code
89 89 return _("killed by signal %d") % -code
90 90
91 91 class _pfile(object):
92 92 """File-like wrapper for a stream opened by subprocess.Popen()"""
93 93
94 94 def __init__(self, proc, fp):
95 95 self._proc = proc
96 96 self._fp = fp
97 97
98 98 def close(self):
99 99 # unlike os.popen(), this returns an integer in subprocess coding
100 100 self._fp.close()
101 101 return self._proc.wait()
102 102
103 103 def __iter__(self):
104 104 return iter(self._fp)
105 105
106 106 def __getattr__(self, attr):
107 107 return getattr(self._fp, attr)
108 108
109 109 def __enter__(self):
110 110 return self
111 111
112 112 def __exit__(self, exc_type, exc_value, exc_tb):
113 113 self.close()
114 114
115 115 def popen(cmd, mode='rb', bufsize=-1):
116 116 if mode == 'rb':
117 117 return _popenreader(cmd, bufsize)
118 118 elif mode == 'wb':
119 119 return _popenwriter(cmd, bufsize)
120 120 raise error.ProgrammingError('unsupported mode: %r' % mode)
121 121
122 122 def _popenreader(cmd, bufsize):
123 123 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
124 124 close_fds=closefds,
125 125 stdout=subprocess.PIPE)
126 126 return _pfile(p, p.stdout)
127 127
128 128 def _popenwriter(cmd, bufsize):
129 129 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
130 130 close_fds=closefds,
131 131 stdin=subprocess.PIPE)
132 132 return _pfile(p, p.stdin)
133 133
134 134 def popen2(cmd, env=None):
135 135 # Setting bufsize to -1 lets the system decide the buffer size.
136 136 # The default for bufsize is 0, meaning unbuffered. This leads to
137 137 # poor performance on Mac OS X: http://bugs.python.org/issue4194
138 138 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
139 139 close_fds=closefds,
140 140 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
141 141 env=env)
142 142 return p.stdin, p.stdout
143 143
144 144 def popen3(cmd, env=None):
145 145 stdin, stdout, stderr, p = popen4(cmd, env)
146 146 return stdin, stdout, stderr
147 147
148 148 def popen4(cmd, env=None, bufsize=-1):
149 149 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
150 150 close_fds=closefds,
151 151 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
152 152 stderr=subprocess.PIPE,
153 153 env=env)
154 154 return p.stdin, p.stdout, p.stderr, p
155 155
156 156 def pipefilter(s, cmd):
157 157 '''filter string S through command CMD, returning its output'''
158 158 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
159 159 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
160 160 pout, perr = p.communicate(s)
161 161 return pout
162 162
163 163 def tempfilter(s, cmd):
164 164 '''filter string S through a pair of temporary files with CMD.
165 165 CMD is used as a template to create the real command to be run,
166 166 with the strings INFILE and OUTFILE replaced by the real names of
167 167 the temporary files generated.'''
168 168 inname, outname = None, None
169 169 try:
170 170 infd, inname = pycompat.mkstemp(prefix='hg-filter-in-')
171 171 fp = os.fdopen(infd, r'wb')
172 172 fp.write(s)
173 173 fp.close()
174 174 outfd, outname = pycompat.mkstemp(prefix='hg-filter-out-')
175 175 os.close(outfd)
176 176 cmd = cmd.replace('INFILE', inname)
177 177 cmd = cmd.replace('OUTFILE', outname)
178 178 code = system(cmd)
179 179 if pycompat.sysplatform == 'OpenVMS' and code & 1:
180 180 code = 0
181 181 if code:
182 182 raise error.Abort(_("command '%s' failed: %s") %
183 183 (cmd, explainexit(code)))
184 184 with open(outname, 'rb') as fp:
185 185 return fp.read()
186 186 finally:
187 187 try:
188 188 if inname:
189 189 os.unlink(inname)
190 190 except OSError:
191 191 pass
192 192 try:
193 193 if outname:
194 194 os.unlink(outname)
195 195 except OSError:
196 196 pass
197 197
198 198 _filtertable = {
199 199 'tempfile:': tempfilter,
200 200 'pipe:': pipefilter,
201 201 }
202 202
203 203 def filter(s, cmd):
204 204 "filter a string through a command that transforms its input to its output"
205 205 for name, fn in _filtertable.iteritems():
206 206 if cmd.startswith(name):
207 207 return fn(s, cmd[len(name):].lstrip())
208 208 return pipefilter(s, cmd)
209 209
210 210 def mainfrozen():
211 211 """return True if we are a frozen executable.
212 212
213 213 The code supports py2exe (most common, Windows only) and tools/freeze
214 214 (portable, not much used).
215 215 """
216 216 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
217 217 pycompat.safehasattr(sys, "importers") or # old py2exe
218 218 imp.is_frozen(u"__main__")) # tools/freeze
219 219
220 220 _hgexecutable = None
221 221
222 222 def hgexecutable():
223 223 """return location of the 'hg' executable.
224 224
225 225 Defaults to $HG or 'hg' in the search path.
226 226 """
227 227 if _hgexecutable is None:
228 228 hg = encoding.environ.get('HG')
229 229 mainmod = sys.modules[r'__main__']
230 230 if hg:
231 231 _sethgexecutable(hg)
232 232 elif mainfrozen():
233 233 if getattr(sys, 'frozen', None) == 'macosx_app':
234 234 # Env variable set by py2app
235 235 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
236 236 else:
237 237 _sethgexecutable(pycompat.sysexecutable)
238 238 elif (os.path.basename(
239 239 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
240 240 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
241 241 else:
242 242 exe = findexe('hg') or os.path.basename(sys.argv[0])
243 243 _sethgexecutable(exe)
244 244 return _hgexecutable
245 245
246 246 def _sethgexecutable(path):
247 247 """set location of the 'hg' executable"""
248 248 global _hgexecutable
249 249 _hgexecutable = path
250 250
251 251 def _testfileno(f, stdf):
252 252 fileno = getattr(f, 'fileno', None)
253 253 try:
254 254 return fileno and fileno() == stdf.fileno()
255 255 except io.UnsupportedOperation:
256 256 return False # fileno() raised UnsupportedOperation
257 257
258 258 def isstdin(f):
259 259 return _testfileno(f, sys.__stdin__)
260 260
261 261 def isstdout(f):
262 262 return _testfileno(f, sys.__stdout__)
263 263
264 264 def protectstdio(uin, uout):
265 265 """Duplicate streams and redirect original if (uin, uout) are stdio
266 266
267 267 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
268 268 redirected to stderr so the output is still readable.
269 269
270 270 Returns (fin, fout) which point to the original (uin, uout) fds, but
271 271 may be copy of (uin, uout). The returned streams can be considered
272 272 "owned" in that print(), exec(), etc. never reach to them.
273 273 """
274 274 uout.flush()
275 275 fin, fout = uin, uout
276 276 if uin is stdin:
277 277 newfd = os.dup(uin.fileno())
278 278 nullfd = os.open(os.devnull, os.O_RDONLY)
279 279 os.dup2(nullfd, uin.fileno())
280 280 os.close(nullfd)
281 281 fin = os.fdopen(newfd, r'rb')
282 282 if uout is stdout:
283 283 newfd = os.dup(uout.fileno())
284 284 os.dup2(stderr.fileno(), uout.fileno())
285 285 fout = os.fdopen(newfd, r'wb')
286 286 return fin, fout
287 287
288 288 def restorestdio(uin, uout, fin, fout):
289 289 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
290 290 uout.flush()
291 291 for f, uif in [(fin, uin), (fout, uout)]:
292 292 if f is not uif:
293 293 os.dup2(f.fileno(), uif.fileno())
294 294 f.close()
295 295
296 296 @contextlib.contextmanager
297 297 def protectedstdio(uin, uout):
298 298 """Run code block with protected standard streams"""
299 299 fin, fout = protectstdio(uin, uout)
300 300 try:
301 301 yield fin, fout
302 302 finally:
303 303 restorestdio(uin, uout, fin, fout)
304 304
305 305 def shellenviron(environ=None):
306 306 """return environ with optional override, useful for shelling out"""
307 307 def py2shell(val):
308 308 'convert python object into string that is useful to shell'
309 309 if val is None or val is False:
310 310 return '0'
311 311 if val is True:
312 312 return '1'
313 313 return pycompat.bytestr(val)
314 314 env = dict(encoding.environ)
315 315 if environ:
316 316 env.update((k, py2shell(v)) for k, v in environ.iteritems())
317 317 env['HG'] = hgexecutable()
318 318 return env
319 319
320 320 if pycompat.iswindows:
321 321 def shelltonative(cmd, env):
322 322 return platform.shelltocmdexe(cmd, shellenviron(env))
323 323 else:
324 324 def shelltonative(cmd, env):
325 325 return cmd
326 326
327 327 def system(cmd, environ=None, cwd=None, out=None):
328 328 '''enhanced shell command execution.
329 329 run with environment maybe modified, maybe in different dir.
330 330
331 331 if out is specified, it is assumed to be a file-like object that has a
332 332 write() method. stdout and stderr will be redirected to out.'''
333 333 try:
334 334 stdout.flush()
335 335 except Exception:
336 336 pass
337 337 cmd = quotecommand(cmd)
338 338 env = shellenviron(environ)
339 339 if out is None or isstdout(out):
340 340 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
341 341 env=env, cwd=cwd)
342 342 else:
343 343 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
344 344 env=env, cwd=cwd, stdout=subprocess.PIPE,
345 345 stderr=subprocess.STDOUT)
346 346 for line in iter(proc.stdout.readline, ''):
347 347 out.write(line)
348 348 proc.wait()
349 349 rc = proc.returncode
350 350 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
351 351 rc = 0
352 352 return rc
353 353
354 354 def gui():
355 355 '''Are we running in a GUI?'''
356 356 if pycompat.isdarwin:
357 357 if 'SSH_CONNECTION' in encoding.environ:
358 358 # handle SSH access to a box where the user is logged in
359 359 return False
360 360 elif getattr(osutil, 'isgui', None):
361 361 # check if a CoreGraphics session is available
362 362 return osutil.isgui()
363 363 else:
364 364 # pure build; use a safe default
365 365 return True
366 366 else:
367 367 return pycompat.iswindows or encoding.environ.get("DISPLAY")
368 368
369 369 def hgcmd():
370 370 """Return the command used to execute current hg
371 371
372 372 This is different from hgexecutable() because on Windows we want
373 373 to avoid things opening new shell windows like batch files, so we
374 374 get either the python call or current executable.
375 375 """
376 376 if mainfrozen():
377 377 if getattr(sys, 'frozen', None) == 'macosx_app':
378 378 # Env variable set by py2app
379 379 return [encoding.environ['EXECUTABLEPATH']]
380 380 else:
381 381 return [pycompat.sysexecutable]
382 382 return _gethgcmd()
383 383
384 384 def rundetached(args, condfn):
385 385 """Execute the argument list in a detached process.
386 386
387 387 condfn is a callable which is called repeatedly and should return
388 388 True once the child process is known to have started successfully.
389 389 At this point, the child process PID is returned. If the child
390 390 process fails to start or finishes before condfn() evaluates to
391 391 True, return -1.
392 392 """
393 393 # Windows case is easier because the child process is either
394 394 # successfully starting and validating the condition or exiting
395 395 # on failure. We just poll on its PID. On Unix, if the child
396 396 # process fails to start, it will be left in a zombie state until
397 397 # the parent wait on it, which we cannot do since we expect a long
398 398 # running process on success. Instead we listen for SIGCHLD telling
399 399 # us our child process terminated.
400 400 terminated = set()
401 401 def handler(signum, frame):
402 402 terminated.add(os.wait())
403 403 prevhandler = None
404 404 SIGCHLD = getattr(signal, 'SIGCHLD', None)
405 405 if SIGCHLD is not None:
406 406 prevhandler = signal.signal(SIGCHLD, handler)
407 407 try:
408 408 pid = spawndetached(args)
409 409 while not condfn():
410 410 if ((pid in terminated or not testpid(pid))
411 411 and not condfn()):
412 412 return -1
413 413 time.sleep(0.1)
414 414 return pid
415 415 finally:
416 416 if prevhandler is not None:
417 417 signal.signal(signal.SIGCHLD, prevhandler)
418
419 @contextlib.contextmanager
420 def uninterruptable(warn):
421 """Inhibit SIGINT handling on a region of code.
422
423 Note that if this is called in a non-main thread, it turns into a no-op.
424
425 Args:
426 warn: A callable which takes no arguments, and returns True if the
427 previous signal handling should be restored.
428 """
429
430 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
431 shouldbail = []
432
433 def disabledsiginthandler(*args):
434 if warn():
435 signal.signal(signal.SIGINT, oldsiginthandler[0])
436 del oldsiginthandler[0]
437 shouldbail.append(True)
438
439 try:
440 try:
441 signal.signal(signal.SIGINT, disabledsiginthandler)
442 except ValueError:
443 # wrong thread, oh well, we tried
444 del oldsiginthandler[0]
445 yield
446 finally:
447 if oldsiginthandler:
448 signal.signal(signal.SIGINT, oldsiginthandler[0])
449 if shouldbail:
450 raise KeyboardInterrupt
General Comments 0
You need to be logged in to leave comments. Login now