##// END OF EJS Templates
configitems: issue a devel warning when overriding default config...
marmoute -
r32987:149b6822 default
parent child Browse files
Show More
@@ -1,1754 +1,1760 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 tempfile
22 22 import traceback
23 23
24 24 from .i18n import _
25 25 from .node import hex
26 26
27 27 from . import (
28 28 color,
29 29 config,
30 30 configitems,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 progress,
35 35 pycompat,
36 36 rcutil,
37 37 scmutil,
38 38 util,
39 39 )
40 40
41 41 urlreq = util.urlreq
42 42
43 43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 45 if not c.isalnum())
46 46
47 47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 48 tweakrc = """
49 49 [ui]
50 50 # The rollback command is dangerous. As a rule, don't use it.
51 51 rollback = False
52 52
53 53 [commands]
54 54 # Make `hg status` emit cwd-relative paths by default.
55 55 status.relative = yes
56 56
57 57 [diff]
58 58 git = 1
59 59 """
60 60
61 61 samplehgrcs = {
62 62 'user':
63 63 """# example user config (see 'hg help config' for more info)
64 64 [ui]
65 65 # name and email, e.g.
66 66 # username = Jane Doe <jdoe@example.com>
67 67 username =
68 68
69 69 # uncomment to disable color in command output
70 70 # (see 'hg help color' for details)
71 71 # color = never
72 72
73 73 # uncomment to disable command output pagination
74 74 # (see 'hg help pager' for details)
75 75 # paginate = never
76 76
77 77 [extensions]
78 78 # uncomment these lines to enable some popular extensions
79 79 # (see 'hg help extensions' for more info)
80 80 #
81 81 # churn =
82 82 """,
83 83
84 84 'cloned':
85 85 """# example repository config (see 'hg help config' for more info)
86 86 [paths]
87 87 default = %s
88 88
89 89 # path aliases to other clones of this repo in URLs or filesystem paths
90 90 # (see 'hg help config.paths' for more info)
91 91 #
92 92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
93 93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
94 94 # my-clone = /home/jdoe/jdoes-clone
95 95
96 96 [ui]
97 97 # name and email (local to this repository, optional), e.g.
98 98 # username = Jane Doe <jdoe@example.com>
99 99 """,
100 100
101 101 'local':
102 102 """# example repository config (see 'hg help config' for more info)
103 103 [paths]
104 104 # path aliases to other clones of this repo in URLs or filesystem paths
105 105 # (see 'hg help config.paths' for more info)
106 106 #
107 107 # default = http://example.com/hg/example-repo
108 108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
109 109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
110 110 # my-clone = /home/jdoe/jdoes-clone
111 111
112 112 [ui]
113 113 # name and email (local to this repository, optional), e.g.
114 114 # username = Jane Doe <jdoe@example.com>
115 115 """,
116 116
117 117 'global':
118 118 """# example system-wide hg config (see 'hg help config' for more info)
119 119
120 120 [ui]
121 121 # uncomment to disable color in command output
122 122 # (see 'hg help color' for details)
123 123 # color = never
124 124
125 125 # uncomment to disable command output pagination
126 126 # (see 'hg help pager' for details)
127 127 # paginate = never
128 128
129 129 [extensions]
130 130 # uncomment these lines to enable some popular extensions
131 131 # (see 'hg help extensions' for more info)
132 132 #
133 133 # blackbox =
134 134 # churn =
135 135 """,
136 136 }
137 137
138 138
139 139 class httppasswordmgrdbproxy(object):
140 140 """Delays loading urllib2 until it's needed."""
141 141 def __init__(self):
142 142 self._mgr = None
143 143
144 144 def _get_mgr(self):
145 145 if self._mgr is None:
146 146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
147 147 return self._mgr
148 148
149 149 def add_password(self, *args, **kwargs):
150 150 return self._get_mgr().add_password(*args, **kwargs)
151 151
152 152 def find_user_password(self, *args, **kwargs):
153 153 return self._get_mgr().find_user_password(*args, **kwargs)
154 154
155 155 def _catchterm(*args):
156 156 raise error.SignalInterrupt
157 157
158 158 # unique object used to detect no default value has been provided when
159 159 # retrieving configuration value.
160 160 _unset = object()
161 161
162 162 class ui(object):
163 163 def __init__(self, src=None):
164 164 """Create a fresh new ui object if no src given
165 165
166 166 Use uimod.ui.load() to create a ui which knows global and user configs.
167 167 In most cases, you should use ui.copy() to create a copy of an existing
168 168 ui object.
169 169 """
170 170 # _buffers: used for temporary capture of output
171 171 self._buffers = []
172 172 # _exithandlers: callbacks run at the end of a request
173 173 self._exithandlers = []
174 174 # 3-tuple describing how each buffer in the stack behaves.
175 175 # Values are (capture stderr, capture subprocesses, apply labels).
176 176 self._bufferstates = []
177 177 # When a buffer is active, defines whether we are expanding labels.
178 178 # This exists to prevent an extra list lookup.
179 179 self._bufferapplylabels = None
180 180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
181 181 self._reportuntrusted = True
182 182 self._knownconfig = configitems.coreitems
183 183 self._ocfg = config.config() # overlay
184 184 self._tcfg = config.config() # trusted
185 185 self._ucfg = config.config() # untrusted
186 186 self._trustusers = set()
187 187 self._trustgroups = set()
188 188 self.callhooks = True
189 189 # Insecure server connections requested.
190 190 self.insecureconnections = False
191 191 # Blocked time
192 192 self.logblockedtimes = False
193 193 # color mode: see mercurial/color.py for possible value
194 194 self._colormode = None
195 195 self._terminfoparams = {}
196 196 self._styles = {}
197 197
198 198 if src:
199 199 self._exithandlers = src._exithandlers
200 200 self.fout = src.fout
201 201 self.ferr = src.ferr
202 202 self.fin = src.fin
203 203 self.pageractive = src.pageractive
204 204 self._disablepager = src._disablepager
205 205 self._tweaked = src._tweaked
206 206
207 207 self._tcfg = src._tcfg.copy()
208 208 self._ucfg = src._ucfg.copy()
209 209 self._ocfg = src._ocfg.copy()
210 210 self._trustusers = src._trustusers.copy()
211 211 self._trustgroups = src._trustgroups.copy()
212 212 self.environ = src.environ
213 213 self.callhooks = src.callhooks
214 214 self.insecureconnections = src.insecureconnections
215 215 self._colormode = src._colormode
216 216 self._terminfoparams = src._terminfoparams.copy()
217 217 self._styles = src._styles.copy()
218 218
219 219 self.fixconfig()
220 220
221 221 self.httppasswordmgrdb = src.httppasswordmgrdb
222 222 self._blockedtimes = src._blockedtimes
223 223 else:
224 224 self.fout = util.stdout
225 225 self.ferr = util.stderr
226 226 self.fin = util.stdin
227 227 self.pageractive = False
228 228 self._disablepager = False
229 229 self._tweaked = False
230 230
231 231 # shared read-only environment
232 232 self.environ = encoding.environ
233 233
234 234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
235 235 self._blockedtimes = collections.defaultdict(int)
236 236
237 237 allowed = self.configlist('experimental', 'exportableenviron')
238 238 if '*' in allowed:
239 239 self._exportableenviron = self.environ
240 240 else:
241 241 self._exportableenviron = {}
242 242 for k in allowed:
243 243 if k in self.environ:
244 244 self._exportableenviron[k] = self.environ[k]
245 245
246 246 @classmethod
247 247 def load(cls):
248 248 """Create a ui and load global and user configs"""
249 249 u = cls()
250 250 # we always trust global config files and environment variables
251 251 for t, f in rcutil.rccomponents():
252 252 if t == 'path':
253 253 u.readconfig(f, trust=True)
254 254 elif t == 'items':
255 255 sections = set()
256 256 for section, name, value, source in f:
257 257 # do not set u._ocfg
258 258 # XXX clean this up once immutable config object is a thing
259 259 u._tcfg.set(section, name, value, source)
260 260 u._ucfg.set(section, name, value, source)
261 261 sections.add(section)
262 262 for section in sections:
263 263 u.fixconfig(section=section)
264 264 else:
265 265 raise error.ProgrammingError('unknown rctype: %s' % t)
266 266 u._maybetweakdefaults()
267 267 return u
268 268
269 269 def _maybetweakdefaults(self):
270 270 if not self.configbool('ui', 'tweakdefaults'):
271 271 return
272 272 if self._tweaked or self.plain('tweakdefaults'):
273 273 return
274 274
275 275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
276 276 # True *before* any calls to setconfig(), otherwise you'll get
277 277 # infinite recursion between setconfig and this method.
278 278 #
279 279 # TODO: We should extract an inner method in setconfig() to
280 280 # avoid this weirdness.
281 281 self._tweaked = True
282 282 tmpcfg = config.config()
283 283 tmpcfg.parse('<tweakdefaults>', tweakrc)
284 284 for section in tmpcfg:
285 285 for name, value in tmpcfg.items(section):
286 286 if not self.hasconfig(section, name):
287 287 self.setconfig(section, name, value, "<tweakdefaults>")
288 288
289 289 def copy(self):
290 290 return self.__class__(self)
291 291
292 292 def resetstate(self):
293 293 """Clear internal state that shouldn't persist across commands"""
294 294 if self._progbar:
295 295 self._progbar.resetstate() # reset last-print time of progress bar
296 296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
297 297
298 298 @contextlib.contextmanager
299 299 def timeblockedsection(self, key):
300 300 # this is open-coded below - search for timeblockedsection to find them
301 301 starttime = util.timer()
302 302 try:
303 303 yield
304 304 finally:
305 305 self._blockedtimes[key + '_blocked'] += \
306 306 (util.timer() - starttime) * 1000
307 307
308 308 def formatter(self, topic, opts):
309 309 return formatter.formatter(self, self, topic, opts)
310 310
311 311 def _trusted(self, fp, f):
312 312 st = util.fstat(fp)
313 313 if util.isowner(st):
314 314 return True
315 315
316 316 tusers, tgroups = self._trustusers, self._trustgroups
317 317 if '*' in tusers or '*' in tgroups:
318 318 return True
319 319
320 320 user = util.username(st.st_uid)
321 321 group = util.groupname(st.st_gid)
322 322 if user in tusers or group in tgroups or user == util.username():
323 323 return True
324 324
325 325 if self._reportuntrusted:
326 326 self.warn(_('not trusting file %s from untrusted '
327 327 'user %s, group %s\n') % (f, user, group))
328 328 return False
329 329
330 330 def readconfig(self, filename, root=None, trust=False,
331 331 sections=None, remap=None):
332 332 try:
333 333 fp = open(filename, u'rb')
334 334 except IOError:
335 335 if not sections: # ignore unless we were looking for something
336 336 return
337 337 raise
338 338
339 339 cfg = config.config()
340 340 trusted = sections or trust or self._trusted(fp, filename)
341 341
342 342 try:
343 343 cfg.read(filename, fp, sections=sections, remap=remap)
344 344 fp.close()
345 345 except error.ConfigError as inst:
346 346 if trusted:
347 347 raise
348 348 self.warn(_("ignored: %s\n") % str(inst))
349 349
350 350 if self.plain():
351 351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
352 352 'logtemplate', 'statuscopies', 'style',
353 353 'traceback', 'verbose'):
354 354 if k in cfg['ui']:
355 355 del cfg['ui'][k]
356 356 for k, v in cfg.items('defaults'):
357 357 del cfg['defaults'][k]
358 358 for k, v in cfg.items('commands'):
359 359 del cfg['commands'][k]
360 360 # Don't remove aliases from the configuration if in the exceptionlist
361 361 if self.plain('alias'):
362 362 for k, v in cfg.items('alias'):
363 363 del cfg['alias'][k]
364 364 if self.plain('revsetalias'):
365 365 for k, v in cfg.items('revsetalias'):
366 366 del cfg['revsetalias'][k]
367 367 if self.plain('templatealias'):
368 368 for k, v in cfg.items('templatealias'):
369 369 del cfg['templatealias'][k]
370 370
371 371 if trusted:
372 372 self._tcfg.update(cfg)
373 373 self._tcfg.update(self._ocfg)
374 374 self._ucfg.update(cfg)
375 375 self._ucfg.update(self._ocfg)
376 376
377 377 if root is None:
378 378 root = os.path.expanduser('~')
379 379 self.fixconfig(root=root)
380 380
381 381 def fixconfig(self, root=None, section=None):
382 382 if section in (None, 'paths'):
383 383 # expand vars and ~
384 384 # translate paths relative to root (or home) into absolute paths
385 385 root = root or pycompat.getcwd()
386 386 for c in self._tcfg, self._ucfg, self._ocfg:
387 387 for n, p in c.items('paths'):
388 388 # Ignore sub-options.
389 389 if ':' in n:
390 390 continue
391 391 if not p:
392 392 continue
393 393 if '%%' in p:
394 394 s = self.configsource('paths', n) or 'none'
395 395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
396 396 % (n, p, s))
397 397 p = p.replace('%%', '%')
398 398 p = util.expandpath(p)
399 399 if not util.hasscheme(p) and not os.path.isabs(p):
400 400 p = os.path.normpath(os.path.join(root, p))
401 401 c.set("paths", n, p)
402 402
403 403 if section in (None, 'ui'):
404 404 # update ui options
405 405 self.debugflag = self.configbool('ui', 'debug')
406 406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
407 407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
408 408 if self.verbose and self.quiet:
409 409 self.quiet = self.verbose = False
410 410 self._reportuntrusted = self.debugflag or self.configbool("ui",
411 411 "report_untrusted", True)
412 412 self.tracebackflag = self.configbool('ui', 'traceback', False)
413 413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
414 414
415 415 if section in (None, 'trusted'):
416 416 # update trust information
417 417 self._trustusers.update(self.configlist('trusted', 'users'))
418 418 self._trustgroups.update(self.configlist('trusted', 'groups'))
419 419
420 420 def backupconfig(self, section, item):
421 421 return (self._ocfg.backup(section, item),
422 422 self._tcfg.backup(section, item),
423 423 self._ucfg.backup(section, item),)
424 424 def restoreconfig(self, data):
425 425 self._ocfg.restore(data[0])
426 426 self._tcfg.restore(data[1])
427 427 self._ucfg.restore(data[2])
428 428
429 429 def setconfig(self, section, name, value, source=''):
430 430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
431 431 cfg.set(section, name, value, source)
432 432 self.fixconfig(section=section)
433 433 self._maybetweakdefaults()
434 434
435 435 def _data(self, untrusted):
436 436 return untrusted and self._ucfg or self._tcfg
437 437
438 438 def configsource(self, section, name, untrusted=False):
439 439 return self._data(untrusted).source(section, name)
440 440
441 441 def config(self, section, name, default=_unset, untrusted=False):
442 442 if isinstance(name, list):
443 443 alternates = name
444 444 # let us ignore the config items in the alternates case for now
445 445 if default is _unset:
446 446 default = None
447 447 else:
448 item = self._knownconfig.get(section, {}).get(name)
448 449 if default is _unset:
449 450 default = None
450 item = self._knownconfig.get(section, {}).get(name)
451 451 if item is not None:
452 452 default = item.default
453 elif item is not None:
454 msg = ("specifying a default value for a registered "
455 "config item: '%s.%s' '%s'")
456 msg %= (section, name, default)
457 self.develwarn(msg, 1, 'warn-config-default')
458
453 459 alternates = [name]
454 460
455 461 for n in alternates:
456 462 value = self._data(untrusted).get(section, n, None)
457 463 if value is not None:
458 464 name = n
459 465 break
460 466 else:
461 467 value = default
462 468
463 469 if self.debugflag and not untrusted and self._reportuntrusted:
464 470 for n in alternates:
465 471 uvalue = self._ucfg.get(section, n)
466 472 if uvalue is not None and uvalue != value:
467 473 self.debug("ignoring untrusted configuration option "
468 474 "%s.%s = %s\n" % (section, n, uvalue))
469 475 return value
470 476
471 477 def configsuboptions(self, section, name, default=_unset, untrusted=False):
472 478 """Get a config option and all sub-options.
473 479
474 480 Some config options have sub-options that are declared with the
475 481 format "key:opt = value". This method is used to return the main
476 482 option and all its declared sub-options.
477 483
478 484 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
479 485 is a dict of defined sub-options where keys and values are strings.
480 486 """
481 487 main = self.config(section, name, default, untrusted=untrusted)
482 488 data = self._data(untrusted)
483 489 sub = {}
484 490 prefix = '%s:' % name
485 491 for k, v in data.items(section):
486 492 if k.startswith(prefix):
487 493 sub[k[len(prefix):]] = v
488 494
489 495 if self.debugflag and not untrusted and self._reportuntrusted:
490 496 for k, v in sub.items():
491 497 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
492 498 if uvalue is not None and uvalue != v:
493 499 self.debug('ignoring untrusted configuration option '
494 500 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
495 501
496 502 return main, sub
497 503
498 504 def configpath(self, section, name, default=_unset, untrusted=False):
499 505 'get a path config item, expanded relative to repo root or config file'
500 506 v = self.config(section, name, default, untrusted)
501 507 if v is None:
502 508 return None
503 509 if not os.path.isabs(v) or "://" not in v:
504 510 src = self.configsource(section, name, untrusted)
505 511 if ':' in src:
506 512 base = os.path.dirname(src.rsplit(':')[0])
507 513 v = os.path.join(base, os.path.expanduser(v))
508 514 return v
509 515
510 516 def configbool(self, section, name, default=_unset, untrusted=False):
511 517 """parse a configuration element as a boolean
512 518
513 519 >>> u = ui(); s = 'foo'
514 520 >>> u.setconfig(s, 'true', 'yes')
515 521 >>> u.configbool(s, 'true')
516 522 True
517 523 >>> u.setconfig(s, 'false', 'no')
518 524 >>> u.configbool(s, 'false')
519 525 False
520 526 >>> u.configbool(s, 'unknown')
521 527 False
522 528 >>> u.configbool(s, 'unknown', True)
523 529 True
524 530 >>> u.setconfig(s, 'invalid', 'somevalue')
525 531 >>> u.configbool(s, 'invalid')
526 532 Traceback (most recent call last):
527 533 ...
528 534 ConfigError: foo.invalid is not a boolean ('somevalue')
529 535 """
530 536
531 537 v = self.config(section, name, default, untrusted=untrusted)
532 538 if v is None:
533 539 if default is _unset:
534 540 return False
535 541 return default
536 542 if isinstance(v, bool):
537 543 return v
538 544 b = util.parsebool(v)
539 545 if b is None:
540 546 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
541 547 % (section, name, v))
542 548 return b
543 549
544 550 def configwith(self, convert, section, name, default=_unset,
545 551 desc=None, untrusted=False):
546 552 """parse a configuration element with a conversion function
547 553
548 554 >>> u = ui(); s = 'foo'
549 555 >>> u.setconfig(s, 'float1', '42')
550 556 >>> u.configwith(float, s, 'float1')
551 557 42.0
552 558 >>> u.setconfig(s, 'float2', '-4.25')
553 559 >>> u.configwith(float, s, 'float2')
554 560 -4.25
555 561 >>> u.configwith(float, s, 'unknown', 7)
556 562 7.0
557 563 >>> u.setconfig(s, 'invalid', 'somevalue')
558 564 >>> u.configwith(float, s, 'invalid')
559 565 Traceback (most recent call last):
560 566 ...
561 567 ConfigError: foo.invalid is not a valid float ('somevalue')
562 568 >>> u.configwith(float, s, 'invalid', desc='womble')
563 569 Traceback (most recent call last):
564 570 ...
565 571 ConfigError: foo.invalid is not a valid womble ('somevalue')
566 572 """
567 573
568 574 v = self.config(section, name, default, untrusted)
569 575 if v is None:
570 576 return v # do not attempt to convert None
571 577 try:
572 578 return convert(v)
573 579 except (ValueError, error.ParseError):
574 580 if desc is None:
575 581 desc = convert.__name__
576 582 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
577 583 % (section, name, desc, v))
578 584
579 585 def configint(self, section, name, default=_unset, untrusted=False):
580 586 """parse a configuration element as an integer
581 587
582 588 >>> u = ui(); s = 'foo'
583 589 >>> u.setconfig(s, 'int1', '42')
584 590 >>> u.configint(s, 'int1')
585 591 42
586 592 >>> u.setconfig(s, 'int2', '-42')
587 593 >>> u.configint(s, 'int2')
588 594 -42
589 595 >>> u.configint(s, 'unknown', 7)
590 596 7
591 597 >>> u.setconfig(s, 'invalid', 'somevalue')
592 598 >>> u.configint(s, 'invalid')
593 599 Traceback (most recent call last):
594 600 ...
595 601 ConfigError: foo.invalid is not a valid integer ('somevalue')
596 602 """
597 603
598 604 return self.configwith(int, section, name, default, 'integer',
599 605 untrusted)
600 606
601 607 def configbytes(self, section, name, default=_unset, untrusted=False):
602 608 """parse a configuration element as a quantity in bytes
603 609
604 610 Units can be specified as b (bytes), k or kb (kilobytes), m or
605 611 mb (megabytes), g or gb (gigabytes).
606 612
607 613 >>> u = ui(); s = 'foo'
608 614 >>> u.setconfig(s, 'val1', '42')
609 615 >>> u.configbytes(s, 'val1')
610 616 42
611 617 >>> u.setconfig(s, 'val2', '42.5 kb')
612 618 >>> u.configbytes(s, 'val2')
613 619 43520
614 620 >>> u.configbytes(s, 'unknown', '7 MB')
615 621 7340032
616 622 >>> u.setconfig(s, 'invalid', 'somevalue')
617 623 >>> u.configbytes(s, 'invalid')
618 624 Traceback (most recent call last):
619 625 ...
620 626 ConfigError: foo.invalid is not a byte quantity ('somevalue')
621 627 """
622 628
623 629 value = self.config(section, name, default, untrusted)
624 630 if value is None:
625 631 if default is _unset:
626 632 default = 0
627 633 value = default
628 634 if not isinstance(value, str):
629 635 return value
630 636 try:
631 637 return util.sizetoint(value)
632 638 except error.ParseError:
633 639 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
634 640 % (section, name, value))
635 641
636 642 def configlist(self, section, name, default=_unset, untrusted=False):
637 643 """parse a configuration element as a list of comma/space separated
638 644 strings
639 645
640 646 >>> u = ui(); s = 'foo'
641 647 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
642 648 >>> u.configlist(s, 'list1')
643 649 ['this', 'is', 'a small', 'test']
644 650 """
645 651 # default is not always a list
646 652 v = self.configwith(config.parselist, section, name, default,
647 653 'list', untrusted)
648 654 if isinstance(v, bytes):
649 655 return config.parselist(v)
650 656 elif v is None:
651 657 return []
652 658 return v
653 659
654 660 def configdate(self, section, name, default=_unset, untrusted=False):
655 661 """parse a configuration element as a tuple of ints
656 662
657 663 >>> u = ui(); s = 'foo'
658 664 >>> u.setconfig(s, 'date', '0 0')
659 665 >>> u.configdate(s, 'date')
660 666 (0, 0)
661 667 """
662 668 if self.config(section, name, default, untrusted):
663 669 return self.configwith(util.parsedate, section, name, default,
664 670 'date', untrusted)
665 671 if default is _unset:
666 672 return None
667 673 return default
668 674
669 675 def hasconfig(self, section, name, untrusted=False):
670 676 return self._data(untrusted).hasitem(section, name)
671 677
672 678 def has_section(self, section, untrusted=False):
673 679 '''tell whether section exists in config.'''
674 680 return section in self._data(untrusted)
675 681
676 682 def configitems(self, section, untrusted=False, ignoresub=False):
677 683 items = self._data(untrusted).items(section)
678 684 if ignoresub:
679 685 newitems = {}
680 686 for k, v in items:
681 687 if ':' not in k:
682 688 newitems[k] = v
683 689 items = newitems.items()
684 690 if self.debugflag and not untrusted and self._reportuntrusted:
685 691 for k, v in self._ucfg.items(section):
686 692 if self._tcfg.get(section, k) != v:
687 693 self.debug("ignoring untrusted configuration option "
688 694 "%s.%s = %s\n" % (section, k, v))
689 695 return items
690 696
691 697 def walkconfig(self, untrusted=False):
692 698 cfg = self._data(untrusted)
693 699 for section in cfg.sections():
694 700 for name, value in self.configitems(section, untrusted):
695 701 yield section, name, value
696 702
697 703 def plain(self, feature=None):
698 704 '''is plain mode active?
699 705
700 706 Plain mode means that all configuration variables which affect
701 707 the behavior and output of Mercurial should be
702 708 ignored. Additionally, the output should be stable,
703 709 reproducible and suitable for use in scripts or applications.
704 710
705 711 The only way to trigger plain mode is by setting either the
706 712 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
707 713
708 714 The return value can either be
709 715 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
710 716 - True otherwise
711 717 '''
712 718 if ('HGPLAIN' not in encoding.environ and
713 719 'HGPLAINEXCEPT' not in encoding.environ):
714 720 return False
715 721 exceptions = encoding.environ.get('HGPLAINEXCEPT',
716 722 '').strip().split(',')
717 723 if feature and exceptions:
718 724 return feature not in exceptions
719 725 return True
720 726
721 727 def username(self):
722 728 """Return default username to be used in commits.
723 729
724 730 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
725 731 and stop searching if one of these is set.
726 732 If not found and ui.askusername is True, ask the user, else use
727 733 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
728 734 """
729 735 user = encoding.environ.get("HGUSER")
730 736 if user is None:
731 737 user = self.config("ui", ["username", "user"])
732 738 if user is not None:
733 739 user = os.path.expandvars(user)
734 740 if user is None:
735 741 user = encoding.environ.get("EMAIL")
736 742 if user is None and self.configbool("ui", "askusername"):
737 743 user = self.prompt(_("enter a commit username:"), default=None)
738 744 if user is None and not self.interactive():
739 745 try:
740 746 user = '%s@%s' % (util.getuser(), socket.getfqdn())
741 747 self.warn(_("no username found, using '%s' instead\n") % user)
742 748 except KeyError:
743 749 pass
744 750 if not user:
745 751 raise error.Abort(_('no username supplied'),
746 752 hint=_("use 'hg config --edit' "
747 753 'to set your username'))
748 754 if "\n" in user:
749 755 raise error.Abort(_("username %s contains a newline\n")
750 756 % repr(user))
751 757 return user
752 758
753 759 def shortuser(self, user):
754 760 """Return a short representation of a user name or email address."""
755 761 if not self.verbose:
756 762 user = util.shortuser(user)
757 763 return user
758 764
759 765 def expandpath(self, loc, default=None):
760 766 """Return repository location relative to cwd or from [paths]"""
761 767 try:
762 768 p = self.paths.getpath(loc)
763 769 if p:
764 770 return p.rawloc
765 771 except error.RepoError:
766 772 pass
767 773
768 774 if default:
769 775 try:
770 776 p = self.paths.getpath(default)
771 777 if p:
772 778 return p.rawloc
773 779 except error.RepoError:
774 780 pass
775 781
776 782 return loc
777 783
778 784 @util.propertycache
779 785 def paths(self):
780 786 return paths(self)
781 787
782 788 def pushbuffer(self, error=False, subproc=False, labeled=False):
783 789 """install a buffer to capture standard output of the ui object
784 790
785 791 If error is True, the error output will be captured too.
786 792
787 793 If subproc is True, output from subprocesses (typically hooks) will be
788 794 captured too.
789 795
790 796 If labeled is True, any labels associated with buffered
791 797 output will be handled. By default, this has no effect
792 798 on the output returned, but extensions and GUI tools may
793 799 handle this argument and returned styled output. If output
794 800 is being buffered so it can be captured and parsed or
795 801 processed, labeled should not be set to True.
796 802 """
797 803 self._buffers.append([])
798 804 self._bufferstates.append((error, subproc, labeled))
799 805 self._bufferapplylabels = labeled
800 806
801 807 def popbuffer(self):
802 808 '''pop the last buffer and return the buffered output'''
803 809 self._bufferstates.pop()
804 810 if self._bufferstates:
805 811 self._bufferapplylabels = self._bufferstates[-1][2]
806 812 else:
807 813 self._bufferapplylabels = None
808 814
809 815 return "".join(self._buffers.pop())
810 816
811 817 def write(self, *args, **opts):
812 818 '''write args to output
813 819
814 820 By default, this method simply writes to the buffer or stdout.
815 821 Color mode can be set on the UI class to have the output decorated
816 822 with color modifier before being written to stdout.
817 823
818 824 The color used is controlled by an optional keyword argument, "label".
819 825 This should be a string containing label names separated by space.
820 826 Label names take the form of "topic.type". For example, ui.debug()
821 827 issues a label of "ui.debug".
822 828
823 829 When labeling output for a specific command, a label of
824 830 "cmdname.type" is recommended. For example, status issues
825 831 a label of "status.modified" for modified files.
826 832 '''
827 833 if self._buffers and not opts.get('prompt', False):
828 834 if self._bufferapplylabels:
829 835 label = opts.get('label', '')
830 836 self._buffers[-1].extend(self.label(a, label) for a in args)
831 837 else:
832 838 self._buffers[-1].extend(args)
833 839 elif self._colormode == 'win32':
834 840 # windows color printing is its own can of crab, defer to
835 841 # the color module and that is it.
836 842 color.win32print(self, self._write, *args, **opts)
837 843 else:
838 844 msgs = args
839 845 if self._colormode is not None:
840 846 label = opts.get('label', '')
841 847 msgs = [self.label(a, label) for a in args]
842 848 self._write(*msgs, **opts)
843 849
844 850 def _write(self, *msgs, **opts):
845 851 self._progclear()
846 852 # opencode timeblockedsection because this is a critical path
847 853 starttime = util.timer()
848 854 try:
849 855 for a in msgs:
850 856 self.fout.write(a)
851 857 except IOError as err:
852 858 raise error.StdioError(err)
853 859 finally:
854 860 self._blockedtimes['stdio_blocked'] += \
855 861 (util.timer() - starttime) * 1000
856 862
857 863 def write_err(self, *args, **opts):
858 864 self._progclear()
859 865 if self._bufferstates and self._bufferstates[-1][0]:
860 866 self.write(*args, **opts)
861 867 elif self._colormode == 'win32':
862 868 # windows color printing is its own can of crab, defer to
863 869 # the color module and that is it.
864 870 color.win32print(self, self._write_err, *args, **opts)
865 871 else:
866 872 msgs = args
867 873 if self._colormode is not None:
868 874 label = opts.get('label', '')
869 875 msgs = [self.label(a, label) for a in args]
870 876 self._write_err(*msgs, **opts)
871 877
872 878 def _write_err(self, *msgs, **opts):
873 879 try:
874 880 with self.timeblockedsection('stdio'):
875 881 if not getattr(self.fout, 'closed', False):
876 882 self.fout.flush()
877 883 for a in msgs:
878 884 self.ferr.write(a)
879 885 # stderr may be buffered under win32 when redirected to files,
880 886 # including stdout.
881 887 if not getattr(self.ferr, 'closed', False):
882 888 self.ferr.flush()
883 889 except IOError as inst:
884 890 raise error.StdioError(inst)
885 891
886 892 def flush(self):
887 893 # opencode timeblockedsection because this is a critical path
888 894 starttime = util.timer()
889 895 try:
890 896 try:
891 897 self.fout.flush()
892 898 except IOError as err:
893 899 raise error.StdioError(err)
894 900 finally:
895 901 try:
896 902 self.ferr.flush()
897 903 except IOError as err:
898 904 raise error.StdioError(err)
899 905 finally:
900 906 self._blockedtimes['stdio_blocked'] += \
901 907 (util.timer() - starttime) * 1000
902 908
903 909 def _isatty(self, fh):
904 910 if self.configbool('ui', 'nontty', False):
905 911 return False
906 912 return util.isatty(fh)
907 913
908 914 def disablepager(self):
909 915 self._disablepager = True
910 916
911 917 def pager(self, command):
912 918 """Start a pager for subsequent command output.
913 919
914 920 Commands which produce a long stream of output should call
915 921 this function to activate the user's preferred pagination
916 922 mechanism (which may be no pager). Calling this function
917 923 precludes any future use of interactive functionality, such as
918 924 prompting the user or activating curses.
919 925
920 926 Args:
921 927 command: The full, non-aliased name of the command. That is, "log"
922 928 not "history, "summary" not "summ", etc.
923 929 """
924 930 if (self._disablepager
925 931 or self.pageractive
926 932 or command in self.configlist('pager', 'ignore')
927 933 or not self.configbool('ui', 'paginate', True)
928 934 or not self.configbool('pager', 'attend-' + command, True)
929 935 # TODO: if we want to allow HGPLAINEXCEPT=pager,
930 936 # formatted() will need some adjustment.
931 937 or not self.formatted()
932 938 or self.plain()
933 939 # TODO: expose debugger-enabled on the UI object
934 940 or '--debugger' in pycompat.sysargv):
935 941 # We only want to paginate if the ui appears to be
936 942 # interactive, the user didn't say HGPLAIN or
937 943 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
938 944 return
939 945
940 946 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
941 947 if not pagercmd:
942 948 return
943 949
944 950 pagerenv = {}
945 951 for name, value in rcutil.defaultpagerenv().items():
946 952 if name not in encoding.environ:
947 953 pagerenv[name] = value
948 954
949 955 self.debug('starting pager for command %r\n' % command)
950 956 self.flush()
951 957
952 958 wasformatted = self.formatted()
953 959 if util.safehasattr(signal, "SIGPIPE"):
954 960 signal.signal(signal.SIGPIPE, _catchterm)
955 961 if self._runpager(pagercmd, pagerenv):
956 962 self.pageractive = True
957 963 # Preserve the formatted-ness of the UI. This is important
958 964 # because we mess with stdout, which might confuse
959 965 # auto-detection of things being formatted.
960 966 self.setconfig('ui', 'formatted', wasformatted, 'pager')
961 967 self.setconfig('ui', 'interactive', False, 'pager')
962 968
963 969 # If pagermode differs from color.mode, reconfigure color now that
964 970 # pageractive is set.
965 971 cm = self._colormode
966 972 if cm != self.config('color', 'pagermode', cm):
967 973 color.setup(self)
968 974 else:
969 975 # If the pager can't be spawned in dispatch when --pager=on is
970 976 # given, don't try again when the command runs, to avoid a duplicate
971 977 # warning about a missing pager command.
972 978 self.disablepager()
973 979
974 980 def _runpager(self, command, env=None):
975 981 """Actually start the pager and set up file descriptors.
976 982
977 983 This is separate in part so that extensions (like chg) can
978 984 override how a pager is invoked.
979 985 """
980 986 if command == 'cat':
981 987 # Save ourselves some work.
982 988 return False
983 989 # If the command doesn't contain any of these characters, we
984 990 # assume it's a binary and exec it directly. This means for
985 991 # simple pager command configurations, we can degrade
986 992 # gracefully and tell the user about their broken pager.
987 993 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
988 994
989 995 if pycompat.osname == 'nt' and not shell:
990 996 # Window's built-in `more` cannot be invoked with shell=False, but
991 997 # its `more.com` can. Hide this implementation detail from the
992 998 # user so we can also get sane bad PAGER behavior. MSYS has
993 999 # `more.exe`, so do a cmd.exe style resolution of the executable to
994 1000 # determine which one to use.
995 1001 fullcmd = util.findexe(command)
996 1002 if not fullcmd:
997 1003 self.warn(_("missing pager command '%s', skipping pager\n")
998 1004 % command)
999 1005 return False
1000 1006
1001 1007 command = fullcmd
1002 1008
1003 1009 try:
1004 1010 pager = subprocess.Popen(
1005 1011 command, shell=shell, bufsize=-1,
1006 1012 close_fds=util.closefds, stdin=subprocess.PIPE,
1007 1013 stdout=util.stdout, stderr=util.stderr,
1008 1014 env=util.shellenviron(env))
1009 1015 except OSError as e:
1010 1016 if e.errno == errno.ENOENT and not shell:
1011 1017 self.warn(_("missing pager command '%s', skipping pager\n")
1012 1018 % command)
1013 1019 return False
1014 1020 raise
1015 1021
1016 1022 # back up original file descriptors
1017 1023 stdoutfd = os.dup(util.stdout.fileno())
1018 1024 stderrfd = os.dup(util.stderr.fileno())
1019 1025
1020 1026 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1021 1027 if self._isatty(util.stderr):
1022 1028 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1023 1029
1024 1030 @self.atexit
1025 1031 def killpager():
1026 1032 if util.safehasattr(signal, "SIGINT"):
1027 1033 signal.signal(signal.SIGINT, signal.SIG_IGN)
1028 1034 # restore original fds, closing pager.stdin copies in the process
1029 1035 os.dup2(stdoutfd, util.stdout.fileno())
1030 1036 os.dup2(stderrfd, util.stderr.fileno())
1031 1037 pager.stdin.close()
1032 1038 pager.wait()
1033 1039
1034 1040 return True
1035 1041
1036 1042 def atexit(self, func, *args, **kwargs):
1037 1043 '''register a function to run after dispatching a request
1038 1044
1039 1045 Handlers do not stay registered across request boundaries.'''
1040 1046 self._exithandlers.append((func, args, kwargs))
1041 1047 return func
1042 1048
1043 1049 def interface(self, feature):
1044 1050 """what interface to use for interactive console features?
1045 1051
1046 1052 The interface is controlled by the value of `ui.interface` but also by
1047 1053 the value of feature-specific configuration. For example:
1048 1054
1049 1055 ui.interface.histedit = text
1050 1056 ui.interface.chunkselector = curses
1051 1057
1052 1058 Here the features are "histedit" and "chunkselector".
1053 1059
1054 1060 The configuration above means that the default interfaces for commands
1055 1061 is curses, the interface for histedit is text and the interface for
1056 1062 selecting chunk is crecord (the best curses interface available).
1057 1063
1058 1064 Consider the following example:
1059 1065 ui.interface = curses
1060 1066 ui.interface.histedit = text
1061 1067
1062 1068 Then histedit will use the text interface and chunkselector will use
1063 1069 the default curses interface (crecord at the moment).
1064 1070 """
1065 1071 alldefaults = frozenset(["text", "curses"])
1066 1072
1067 1073 featureinterfaces = {
1068 1074 "chunkselector": [
1069 1075 "text",
1070 1076 "curses",
1071 1077 ]
1072 1078 }
1073 1079
1074 1080 # Feature-specific interface
1075 1081 if feature not in featureinterfaces.keys():
1076 1082 # Programming error, not user error
1077 1083 raise ValueError("Unknown feature requested %s" % feature)
1078 1084
1079 1085 availableinterfaces = frozenset(featureinterfaces[feature])
1080 1086 if alldefaults > availableinterfaces:
1081 1087 # Programming error, not user error. We need a use case to
1082 1088 # define the right thing to do here.
1083 1089 raise ValueError(
1084 1090 "Feature %s does not handle all default interfaces" %
1085 1091 feature)
1086 1092
1087 1093 if self.plain():
1088 1094 return "text"
1089 1095
1090 1096 # Default interface for all the features
1091 1097 defaultinterface = "text"
1092 1098 i = self.config("ui", "interface", None)
1093 1099 if i in alldefaults:
1094 1100 defaultinterface = i
1095 1101
1096 1102 choseninterface = defaultinterface
1097 1103 f = self.config("ui", "interface.%s" % feature, None)
1098 1104 if f in availableinterfaces:
1099 1105 choseninterface = f
1100 1106
1101 1107 if i is not None and defaultinterface != i:
1102 1108 if f is not None:
1103 1109 self.warn(_("invalid value for ui.interface: %s\n") %
1104 1110 (i,))
1105 1111 else:
1106 1112 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1107 1113 (i, choseninterface))
1108 1114 if f is not None and choseninterface != f:
1109 1115 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1110 1116 (feature, f, choseninterface))
1111 1117
1112 1118 return choseninterface
1113 1119
1114 1120 def interactive(self):
1115 1121 '''is interactive input allowed?
1116 1122
1117 1123 An interactive session is a session where input can be reasonably read
1118 1124 from `sys.stdin'. If this function returns false, any attempt to read
1119 1125 from stdin should fail with an error, unless a sensible default has been
1120 1126 specified.
1121 1127
1122 1128 Interactiveness is triggered by the value of the `ui.interactive'
1123 1129 configuration variable or - if it is unset - when `sys.stdin' points
1124 1130 to a terminal device.
1125 1131
1126 1132 This function refers to input only; for output, see `ui.formatted()'.
1127 1133 '''
1128 1134 i = self.configbool("ui", "interactive", None)
1129 1135 if i is None:
1130 1136 # some environments replace stdin without implementing isatty
1131 1137 # usually those are non-interactive
1132 1138 return self._isatty(self.fin)
1133 1139
1134 1140 return i
1135 1141
1136 1142 def termwidth(self):
1137 1143 '''how wide is the terminal in columns?
1138 1144 '''
1139 1145 if 'COLUMNS' in encoding.environ:
1140 1146 try:
1141 1147 return int(encoding.environ['COLUMNS'])
1142 1148 except ValueError:
1143 1149 pass
1144 1150 return scmutil.termsize(self)[0]
1145 1151
1146 1152 def formatted(self):
1147 1153 '''should formatted output be used?
1148 1154
1149 1155 It is often desirable to format the output to suite the output medium.
1150 1156 Examples of this are truncating long lines or colorizing messages.
1151 1157 However, this is not often not desirable when piping output into other
1152 1158 utilities, e.g. `grep'.
1153 1159
1154 1160 Formatted output is triggered by the value of the `ui.formatted'
1155 1161 configuration variable or - if it is unset - when `sys.stdout' points
1156 1162 to a terminal device. Please note that `ui.formatted' should be
1157 1163 considered an implementation detail; it is not intended for use outside
1158 1164 Mercurial or its extensions.
1159 1165
1160 1166 This function refers to output only; for input, see `ui.interactive()'.
1161 1167 This function always returns false when in plain mode, see `ui.plain()'.
1162 1168 '''
1163 1169 if self.plain():
1164 1170 return False
1165 1171
1166 1172 i = self.configbool("ui", "formatted", None)
1167 1173 if i is None:
1168 1174 # some environments replace stdout without implementing isatty
1169 1175 # usually those are non-interactive
1170 1176 return self._isatty(self.fout)
1171 1177
1172 1178 return i
1173 1179
1174 1180 def _readline(self, prompt=''):
1175 1181 if self._isatty(self.fin):
1176 1182 try:
1177 1183 # magically add command line editing support, where
1178 1184 # available
1179 1185 import readline
1180 1186 # force demandimport to really load the module
1181 1187 readline.read_history_file
1182 1188 # windows sometimes raises something other than ImportError
1183 1189 except Exception:
1184 1190 pass
1185 1191
1186 1192 # call write() so output goes through subclassed implementation
1187 1193 # e.g. color extension on Windows
1188 1194 self.write(prompt, prompt=True)
1189 1195
1190 1196 # instead of trying to emulate raw_input, swap (self.fin,
1191 1197 # self.fout) with (sys.stdin, sys.stdout)
1192 1198 oldin = sys.stdin
1193 1199 oldout = sys.stdout
1194 1200 sys.stdin = self.fin
1195 1201 sys.stdout = self.fout
1196 1202 # prompt ' ' must exist; otherwise readline may delete entire line
1197 1203 # - http://bugs.python.org/issue12833
1198 1204 with self.timeblockedsection('stdio'):
1199 1205 line = raw_input(' ')
1200 1206 sys.stdin = oldin
1201 1207 sys.stdout = oldout
1202 1208
1203 1209 # When stdin is in binary mode on Windows, it can cause
1204 1210 # raw_input() to emit an extra trailing carriage return
1205 1211 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1206 1212 line = line[:-1]
1207 1213 return line
1208 1214
1209 1215 def prompt(self, msg, default="y"):
1210 1216 """Prompt user with msg, read response.
1211 1217 If ui is not interactive, the default is returned.
1212 1218 """
1213 1219 if not self.interactive():
1214 1220 self.write(msg, ' ', default or '', "\n")
1215 1221 return default
1216 1222 try:
1217 1223 r = self._readline(self.label(msg, 'ui.prompt'))
1218 1224 if not r:
1219 1225 r = default
1220 1226 if self.configbool('ui', 'promptecho'):
1221 1227 self.write(r, "\n")
1222 1228 return r
1223 1229 except EOFError:
1224 1230 raise error.ResponseExpected()
1225 1231
1226 1232 @staticmethod
1227 1233 def extractchoices(prompt):
1228 1234 """Extract prompt message and list of choices from specified prompt.
1229 1235
1230 1236 This returns tuple "(message, choices)", and "choices" is the
1231 1237 list of tuple "(response character, text without &)".
1232 1238
1233 1239 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1234 1240 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1235 1241 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1236 1242 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1237 1243 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1238 1244 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1239 1245 """
1240 1246
1241 1247 # Sadly, the prompt string may have been built with a filename
1242 1248 # containing "$$" so let's try to find the first valid-looking
1243 1249 # prompt to start parsing. Sadly, we also can't rely on
1244 1250 # choices containing spaces, ASCII, or basically anything
1245 1251 # except an ampersand followed by a character.
1246 1252 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1247 1253 msg = m.group(1)
1248 1254 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1249 1255 return (msg,
1250 1256 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1251 1257 for s in choices])
1252 1258
1253 1259 def promptchoice(self, prompt, default=0):
1254 1260 """Prompt user with a message, read response, and ensure it matches
1255 1261 one of the provided choices. The prompt is formatted as follows:
1256 1262
1257 1263 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1258 1264
1259 1265 The index of the choice is returned. Responses are case
1260 1266 insensitive. If ui is not interactive, the default is
1261 1267 returned.
1262 1268 """
1263 1269
1264 1270 msg, choices = self.extractchoices(prompt)
1265 1271 resps = [r for r, t in choices]
1266 1272 while True:
1267 1273 r = self.prompt(msg, resps[default])
1268 1274 if r.lower() in resps:
1269 1275 return resps.index(r.lower())
1270 1276 self.write(_("unrecognized response\n"))
1271 1277
1272 1278 def getpass(self, prompt=None, default=None):
1273 1279 if not self.interactive():
1274 1280 return default
1275 1281 try:
1276 1282 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1277 1283 # disable getpass() only if explicitly specified. it's still valid
1278 1284 # to interact with tty even if fin is not a tty.
1279 1285 with self.timeblockedsection('stdio'):
1280 1286 if self.configbool('ui', 'nontty'):
1281 1287 l = self.fin.readline()
1282 1288 if not l:
1283 1289 raise EOFError
1284 1290 return l.rstrip('\n')
1285 1291 else:
1286 1292 return getpass.getpass('')
1287 1293 except EOFError:
1288 1294 raise error.ResponseExpected()
1289 1295 def status(self, *msg, **opts):
1290 1296 '''write status message to output (if ui.quiet is False)
1291 1297
1292 1298 This adds an output label of "ui.status".
1293 1299 '''
1294 1300 if not self.quiet:
1295 1301 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1296 1302 self.write(*msg, **opts)
1297 1303 def warn(self, *msg, **opts):
1298 1304 '''write warning message to output (stderr)
1299 1305
1300 1306 This adds an output label of "ui.warning".
1301 1307 '''
1302 1308 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1303 1309 self.write_err(*msg, **opts)
1304 1310 def note(self, *msg, **opts):
1305 1311 '''write note to output (if ui.verbose is True)
1306 1312
1307 1313 This adds an output label of "ui.note".
1308 1314 '''
1309 1315 if self.verbose:
1310 1316 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1311 1317 self.write(*msg, **opts)
1312 1318 def debug(self, *msg, **opts):
1313 1319 '''write debug message to output (if ui.debugflag is True)
1314 1320
1315 1321 This adds an output label of "ui.debug".
1316 1322 '''
1317 1323 if self.debugflag:
1318 1324 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1319 1325 self.write(*msg, **opts)
1320 1326
1321 1327 def edit(self, text, user, extra=None, editform=None, pending=None,
1322 1328 repopath=None):
1323 1329 extra_defaults = {
1324 1330 'prefix': 'editor',
1325 1331 'suffix': '.txt',
1326 1332 }
1327 1333 if extra is not None:
1328 1334 extra_defaults.update(extra)
1329 1335 extra = extra_defaults
1330 1336
1331 1337 rdir = None
1332 1338 if self.configbool('experimental', 'editortmpinhg'):
1333 1339 rdir = repopath
1334 1340 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1335 1341 suffix=extra['suffix'],
1336 1342 dir=rdir)
1337 1343 try:
1338 1344 f = os.fdopen(fd, r'wb')
1339 1345 f.write(util.tonativeeol(text))
1340 1346 f.close()
1341 1347
1342 1348 environ = {'HGUSER': user}
1343 1349 if 'transplant_source' in extra:
1344 1350 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1345 1351 for label in ('intermediate-source', 'source', 'rebase_source'):
1346 1352 if label in extra:
1347 1353 environ.update({'HGREVISION': extra[label]})
1348 1354 break
1349 1355 if editform:
1350 1356 environ.update({'HGEDITFORM': editform})
1351 1357 if pending:
1352 1358 environ.update({'HG_PENDING': pending})
1353 1359
1354 1360 editor = self.geteditor()
1355 1361
1356 1362 self.system("%s \"%s\"" % (editor, name),
1357 1363 environ=environ,
1358 1364 onerr=error.Abort, errprefix=_("edit failed"),
1359 1365 blockedtag='editor')
1360 1366
1361 1367 f = open(name, r'rb')
1362 1368 t = util.fromnativeeol(f.read())
1363 1369 f.close()
1364 1370 finally:
1365 1371 os.unlink(name)
1366 1372
1367 1373 return t
1368 1374
1369 1375 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1370 1376 blockedtag=None):
1371 1377 '''execute shell command with appropriate output stream. command
1372 1378 output will be redirected if fout is not stdout.
1373 1379
1374 1380 if command fails and onerr is None, return status, else raise onerr
1375 1381 object as exception.
1376 1382 '''
1377 1383 if blockedtag is None:
1378 1384 # Long cmds tend to be because of an absolute path on cmd. Keep
1379 1385 # the tail end instead
1380 1386 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1381 1387 blockedtag = 'unknown_system_' + cmdsuffix
1382 1388 out = self.fout
1383 1389 if any(s[1] for s in self._bufferstates):
1384 1390 out = self
1385 1391 with self.timeblockedsection(blockedtag):
1386 1392 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1387 1393 if rc and onerr:
1388 1394 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1389 1395 util.explainexit(rc)[0])
1390 1396 if errprefix:
1391 1397 errmsg = '%s: %s' % (errprefix, errmsg)
1392 1398 raise onerr(errmsg)
1393 1399 return rc
1394 1400
1395 1401 def _runsystem(self, cmd, environ, cwd, out):
1396 1402 """actually execute the given shell command (can be overridden by
1397 1403 extensions like chg)"""
1398 1404 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1399 1405
1400 1406 def traceback(self, exc=None, force=False):
1401 1407 '''print exception traceback if traceback printing enabled or forced.
1402 1408 only to call in exception handler. returns true if traceback
1403 1409 printed.'''
1404 1410 if self.tracebackflag or force:
1405 1411 if exc is None:
1406 1412 exc = sys.exc_info()
1407 1413 cause = getattr(exc[1], 'cause', None)
1408 1414
1409 1415 if cause is not None:
1410 1416 causetb = traceback.format_tb(cause[2])
1411 1417 exctb = traceback.format_tb(exc[2])
1412 1418 exconly = traceback.format_exception_only(cause[0], cause[1])
1413 1419
1414 1420 # exclude frame where 'exc' was chained and rethrown from exctb
1415 1421 self.write_err('Traceback (most recent call last):\n',
1416 1422 ''.join(exctb[:-1]),
1417 1423 ''.join(causetb),
1418 1424 ''.join(exconly))
1419 1425 else:
1420 1426 output = traceback.format_exception(exc[0], exc[1], exc[2])
1421 1427 data = r''.join(output)
1422 1428 if pycompat.ispy3:
1423 1429 enc = pycompat.sysstr(encoding.encoding)
1424 1430 data = data.encode(enc, errors=r'replace')
1425 1431 self.write_err(data)
1426 1432 return self.tracebackflag or force
1427 1433
1428 1434 def geteditor(self):
1429 1435 '''return editor to use'''
1430 1436 if pycompat.sysplatform == 'plan9':
1431 1437 # vi is the MIPS instruction simulator on Plan 9. We
1432 1438 # instead default to E to plumb commit messages to
1433 1439 # avoid confusion.
1434 1440 editor = 'E'
1435 1441 else:
1436 1442 editor = 'vi'
1437 1443 return (encoding.environ.get("HGEDITOR") or
1438 1444 self.config("ui", "editor", editor))
1439 1445
1440 1446 @util.propertycache
1441 1447 def _progbar(self):
1442 1448 """setup the progbar singleton to the ui object"""
1443 1449 if (self.quiet or self.debugflag
1444 1450 or self.configbool('progress', 'disable', False)
1445 1451 or not progress.shouldprint(self)):
1446 1452 return None
1447 1453 return getprogbar(self)
1448 1454
1449 1455 def _progclear(self):
1450 1456 """clear progress bar output if any. use it before any output"""
1451 1457 if '_progbar' not in vars(self): # nothing loaded yet
1452 1458 return
1453 1459 if self._progbar is not None and self._progbar.printed:
1454 1460 self._progbar.clear()
1455 1461
1456 1462 def progress(self, topic, pos, item="", unit="", total=None):
1457 1463 '''show a progress message
1458 1464
1459 1465 By default a textual progress bar will be displayed if an operation
1460 1466 takes too long. 'topic' is the current operation, 'item' is a
1461 1467 non-numeric marker of the current position (i.e. the currently
1462 1468 in-process file), 'pos' is the current numeric position (i.e.
1463 1469 revision, bytes, etc.), unit is a corresponding unit label,
1464 1470 and total is the highest expected pos.
1465 1471
1466 1472 Multiple nested topics may be active at a time.
1467 1473
1468 1474 All topics should be marked closed by setting pos to None at
1469 1475 termination.
1470 1476 '''
1471 1477 if self._progbar is not None:
1472 1478 self._progbar.progress(topic, pos, item=item, unit=unit,
1473 1479 total=total)
1474 1480 if pos is None or not self.configbool('progress', 'debug'):
1475 1481 return
1476 1482
1477 1483 if unit:
1478 1484 unit = ' ' + unit
1479 1485 if item:
1480 1486 item = ' ' + item
1481 1487
1482 1488 if total:
1483 1489 pct = 100.0 * pos / total
1484 1490 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1485 1491 % (topic, item, pos, total, unit, pct))
1486 1492 else:
1487 1493 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1488 1494
1489 1495 def log(self, service, *msg, **opts):
1490 1496 '''hook for logging facility extensions
1491 1497
1492 1498 service should be a readily-identifiable subsystem, which will
1493 1499 allow filtering.
1494 1500
1495 1501 *msg should be a newline-terminated format string to log, and
1496 1502 then any values to %-format into that format string.
1497 1503
1498 1504 **opts currently has no defined meanings.
1499 1505 '''
1500 1506
1501 1507 def label(self, msg, label):
1502 1508 '''style msg based on supplied label
1503 1509
1504 1510 If some color mode is enabled, this will add the necessary control
1505 1511 characters to apply such color. In addition, 'debug' color mode adds
1506 1512 markup showing which label affects a piece of text.
1507 1513
1508 1514 ui.write(s, 'label') is equivalent to
1509 1515 ui.write(ui.label(s, 'label')).
1510 1516 '''
1511 1517 if self._colormode is not None:
1512 1518 return color.colorlabel(self, msg, label)
1513 1519 return msg
1514 1520
1515 1521 def develwarn(self, msg, stacklevel=1, config=None):
1516 1522 """issue a developer warning message
1517 1523
1518 1524 Use 'stacklevel' to report the offender some layers further up in the
1519 1525 stack.
1520 1526 """
1521 1527 if not self.configbool('devel', 'all-warnings'):
1522 1528 if config is not None and not self.configbool('devel', config):
1523 1529 return
1524 1530 msg = 'devel-warn: ' + msg
1525 1531 stacklevel += 1 # get in develwarn
1526 1532 if self.tracebackflag:
1527 1533 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1528 1534 self.log('develwarn', '%s at:\n%s' %
1529 1535 (msg, ''.join(util.getstackframes(stacklevel))))
1530 1536 else:
1531 1537 curframe = inspect.currentframe()
1532 1538 calframe = inspect.getouterframes(curframe, 2)
1533 1539 self.write_err('%s at: %s:%s (%s)\n'
1534 1540 % ((msg,) + calframe[stacklevel][1:4]))
1535 1541 self.log('develwarn', '%s at: %s:%s (%s)\n',
1536 1542 msg, *calframe[stacklevel][1:4])
1537 1543 curframe = calframe = None # avoid cycles
1538 1544
1539 1545 def deprecwarn(self, msg, version):
1540 1546 """issue a deprecation warning
1541 1547
1542 1548 - msg: message explaining what is deprecated and how to upgrade,
1543 1549 - version: last version where the API will be supported,
1544 1550 """
1545 1551 if not (self.configbool('devel', 'all-warnings')
1546 1552 or self.configbool('devel', 'deprec-warn')):
1547 1553 return
1548 1554 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1549 1555 " update your code.)") % version
1550 1556 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1551 1557
1552 1558 def exportableenviron(self):
1553 1559 """The environment variables that are safe to export, e.g. through
1554 1560 hgweb.
1555 1561 """
1556 1562 return self._exportableenviron
1557 1563
1558 1564 @contextlib.contextmanager
1559 1565 def configoverride(self, overrides, source=""):
1560 1566 """Context manager for temporary config overrides
1561 1567 `overrides` must be a dict of the following structure:
1562 1568 {(section, name) : value}"""
1563 1569 backups = {}
1564 1570 try:
1565 1571 for (section, name), value in overrides.items():
1566 1572 backups[(section, name)] = self.backupconfig(section, name)
1567 1573 self.setconfig(section, name, value, source)
1568 1574 yield
1569 1575 finally:
1570 1576 for __, backup in backups.items():
1571 1577 self.restoreconfig(backup)
1572 1578 # just restoring ui.quiet config to the previous value is not enough
1573 1579 # as it does not update ui.quiet class member
1574 1580 if ('ui', 'quiet') in overrides:
1575 1581 self.fixconfig(section='ui')
1576 1582
1577 1583 class paths(dict):
1578 1584 """Represents a collection of paths and their configs.
1579 1585
1580 1586 Data is initially derived from ui instances and the config files they have
1581 1587 loaded.
1582 1588 """
1583 1589 def __init__(self, ui):
1584 1590 dict.__init__(self)
1585 1591
1586 1592 for name, loc in ui.configitems('paths', ignoresub=True):
1587 1593 # No location is the same as not existing.
1588 1594 if not loc:
1589 1595 continue
1590 1596 loc, sub = ui.configsuboptions('paths', name)
1591 1597 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1592 1598
1593 1599 def getpath(self, name, default=None):
1594 1600 """Return a ``path`` from a string, falling back to default.
1595 1601
1596 1602 ``name`` can be a named path or locations. Locations are filesystem
1597 1603 paths or URIs.
1598 1604
1599 1605 Returns None if ``name`` is not a registered path, a URI, or a local
1600 1606 path to a repo.
1601 1607 """
1602 1608 # Only fall back to default if no path was requested.
1603 1609 if name is None:
1604 1610 if not default:
1605 1611 default = ()
1606 1612 elif not isinstance(default, (tuple, list)):
1607 1613 default = (default,)
1608 1614 for k in default:
1609 1615 try:
1610 1616 return self[k]
1611 1617 except KeyError:
1612 1618 continue
1613 1619 return None
1614 1620
1615 1621 # Most likely empty string.
1616 1622 # This may need to raise in the future.
1617 1623 if not name:
1618 1624 return None
1619 1625
1620 1626 try:
1621 1627 return self[name]
1622 1628 except KeyError:
1623 1629 # Try to resolve as a local path or URI.
1624 1630 try:
1625 1631 # We don't pass sub-options in, so no need to pass ui instance.
1626 1632 return path(None, None, rawloc=name)
1627 1633 except ValueError:
1628 1634 raise error.RepoError(_('repository %s does not exist') %
1629 1635 name)
1630 1636
1631 1637 _pathsuboptions = {}
1632 1638
1633 1639 def pathsuboption(option, attr):
1634 1640 """Decorator used to declare a path sub-option.
1635 1641
1636 1642 Arguments are the sub-option name and the attribute it should set on
1637 1643 ``path`` instances.
1638 1644
1639 1645 The decorated function will receive as arguments a ``ui`` instance,
1640 1646 ``path`` instance, and the string value of this option from the config.
1641 1647 The function should return the value that will be set on the ``path``
1642 1648 instance.
1643 1649
1644 1650 This decorator can be used to perform additional verification of
1645 1651 sub-options and to change the type of sub-options.
1646 1652 """
1647 1653 def register(func):
1648 1654 _pathsuboptions[option] = (attr, func)
1649 1655 return func
1650 1656 return register
1651 1657
1652 1658 @pathsuboption('pushurl', 'pushloc')
1653 1659 def pushurlpathoption(ui, path, value):
1654 1660 u = util.url(value)
1655 1661 # Actually require a URL.
1656 1662 if not u.scheme:
1657 1663 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1658 1664 return None
1659 1665
1660 1666 # Don't support the #foo syntax in the push URL to declare branch to
1661 1667 # push.
1662 1668 if u.fragment:
1663 1669 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1664 1670 'ignoring)\n') % path.name)
1665 1671 u.fragment = None
1666 1672
1667 1673 return str(u)
1668 1674
1669 1675 @pathsuboption('pushrev', 'pushrev')
1670 1676 def pushrevpathoption(ui, path, value):
1671 1677 return value
1672 1678
1673 1679 class path(object):
1674 1680 """Represents an individual path and its configuration."""
1675 1681
1676 1682 def __init__(self, ui, name, rawloc=None, suboptions=None):
1677 1683 """Construct a path from its config options.
1678 1684
1679 1685 ``ui`` is the ``ui`` instance the path is coming from.
1680 1686 ``name`` is the symbolic name of the path.
1681 1687 ``rawloc`` is the raw location, as defined in the config.
1682 1688 ``pushloc`` is the raw locations pushes should be made to.
1683 1689
1684 1690 If ``name`` is not defined, we require that the location be a) a local
1685 1691 filesystem path with a .hg directory or b) a URL. If not,
1686 1692 ``ValueError`` is raised.
1687 1693 """
1688 1694 if not rawloc:
1689 1695 raise ValueError('rawloc must be defined')
1690 1696
1691 1697 # Locations may define branches via syntax <base>#<branch>.
1692 1698 u = util.url(rawloc)
1693 1699 branch = None
1694 1700 if u.fragment:
1695 1701 branch = u.fragment
1696 1702 u.fragment = None
1697 1703
1698 1704 self.url = u
1699 1705 self.branch = branch
1700 1706
1701 1707 self.name = name
1702 1708 self.rawloc = rawloc
1703 1709 self.loc = '%s' % u
1704 1710
1705 1711 # When given a raw location but not a symbolic name, validate the
1706 1712 # location is valid.
1707 1713 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1708 1714 raise ValueError('location is not a URL or path to a local '
1709 1715 'repo: %s' % rawloc)
1710 1716
1711 1717 suboptions = suboptions or {}
1712 1718
1713 1719 # Now process the sub-options. If a sub-option is registered, its
1714 1720 # attribute will always be present. The value will be None if there
1715 1721 # was no valid sub-option.
1716 1722 for suboption, (attr, func) in _pathsuboptions.iteritems():
1717 1723 if suboption not in suboptions:
1718 1724 setattr(self, attr, None)
1719 1725 continue
1720 1726
1721 1727 value = func(ui, self, suboptions[suboption])
1722 1728 setattr(self, attr, value)
1723 1729
1724 1730 def _isvalidlocalpath(self, path):
1725 1731 """Returns True if the given path is a potentially valid repository.
1726 1732 This is its own function so that extensions can change the definition of
1727 1733 'valid' in this case (like when pulling from a git repo into a hg
1728 1734 one)."""
1729 1735 return os.path.isdir(os.path.join(path, '.hg'))
1730 1736
1731 1737 @property
1732 1738 def suboptions(self):
1733 1739 """Return sub-options and their values for this path.
1734 1740
1735 1741 This is intended to be used for presentation purposes.
1736 1742 """
1737 1743 d = {}
1738 1744 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1739 1745 value = getattr(self, attr)
1740 1746 if value is not None:
1741 1747 d[subopt] = value
1742 1748 return d
1743 1749
1744 1750 # we instantiate one globally shared progress bar to avoid
1745 1751 # competing progress bars when multiple UI objects get created
1746 1752 _progresssingleton = None
1747 1753
1748 1754 def getprogbar(ui):
1749 1755 global _progresssingleton
1750 1756 if _progresssingleton is None:
1751 1757 # passing 'ui' object to the singleton is fishy,
1752 1758 # this is how the extension used to work but feel free to rework it.
1753 1759 _progresssingleton = progress.progbar(ui)
1754 1760 return _progresssingleton
@@ -1,196 +1,215 b''
1 1
2 2 $ cat << EOF > buggylocking.py
3 3 > """A small extension that tests our developer warnings
4 4 > """
5 5 >
6 6 > from mercurial import error, registrar, repair, util
7 7 >
8 8 > cmdtable = {}
9 9 > command = registrar.command(cmdtable)
10 10 >
11 11 > @command(b'buggylocking', [], '')
12 12 > def buggylocking(ui, repo):
13 13 > lo = repo.lock()
14 14 > wl = repo.wlock()
15 15 > wl.release()
16 16 > lo.release()
17 17 >
18 18 > @command(b'buggytransaction', [], '')
19 19 > def buggylocking(ui, repo):
20 20 > tr = repo.transaction('buggy')
21 21 > # make sure we rollback the transaction as we don't want to rely on the__del__
22 22 > tr.release()
23 23 >
24 24 > @command(b'properlocking', [], '')
25 25 > def properlocking(ui, repo):
26 26 > """check that reentrance is fine"""
27 27 > wl = repo.wlock()
28 28 > lo = repo.lock()
29 29 > tr = repo.transaction('proper')
30 30 > tr2 = repo.transaction('proper')
31 31 > lo2 = repo.lock()
32 32 > wl2 = repo.wlock()
33 33 > wl2.release()
34 34 > lo2.release()
35 35 > tr2.close()
36 36 > tr.close()
37 37 > lo.release()
38 38 > wl.release()
39 39 >
40 40 > @command(b'nowaitlocking', [], '')
41 41 > def nowaitlocking(ui, repo):
42 42 > lo = repo.lock()
43 43 > wl = repo.wlock(wait=False)
44 44 > wl.release()
45 45 > lo.release()
46 46 >
47 47 > @command(b'stripintr', [], '')
48 48 > def stripintr(ui, repo):
49 49 > lo = repo.lock()
50 50 > tr = repo.transaction('foobar')
51 51 > try:
52 52 > repair.strip(repo.ui, repo, [repo['.'].node()])
53 53 > finally:
54 54 > lo.release()
55 55 > @command(b'oldanddeprecated', [], '')
56 56 > def oldanddeprecated(ui, repo):
57 57 > """test deprecation warning API"""
58 58 > def foobar(ui):
59 59 > ui.deprecwarn('foorbar is deprecated, go shopping', '42.1337')
60 60 > foobar(ui)
61 61 > @command(b'nouiwarning', [], '')
62 62 > def nouiwarning(ui, repo):
63 63 > util.nouideprecwarn('this is a test', '13.37')
64 64 > @command(b'programmingerror', [], '')
65 65 > def programmingerror(ui, repo):
66 66 > raise error.ProgrammingError('something went wrong', hint='try again')
67 67 > EOF
68 68
69 69 $ cat << EOF >> $HGRCPATH
70 70 > [extensions]
71 71 > buggylocking=$TESTTMP/buggylocking.py
72 72 > mock=$TESTDIR/mockblackbox.py
73 73 > blackbox=
74 74 > [devel]
75 75 > all-warnings=1
76 76 > EOF
77 77
78 78 $ hg init lock-checker
79 79 $ cd lock-checker
80 80 $ hg buggylocking
81 81 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
82 82 $ cat << EOF >> $HGRCPATH
83 83 > [devel]
84 84 > all=0
85 85 > check-locks=1
86 86 > EOF
87 87 $ hg buggylocking
88 88 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
89 89 $ hg buggylocking --traceback
90 90 devel-warn: "wlock" acquired after "lock" at:
91 91 */hg:* in * (glob)
92 92 */mercurial/dispatch.py:* in run (glob)
93 93 */mercurial/dispatch.py:* in dispatch (glob)
94 94 */mercurial/dispatch.py:* in _runcatch (glob)
95 95 */mercurial/dispatch.py:* in _callcatch (glob)
96 96 */mercurial/scmutil.py* in callcatch (glob)
97 97 */mercurial/dispatch.py:* in _runcatchfunc (glob)
98 98 */mercurial/dispatch.py:* in _dispatch (glob)
99 99 */mercurial/dispatch.py:* in runcommand (glob)
100 100 */mercurial/dispatch.py:* in _runcommand (glob)
101 101 */mercurial/dispatch.py:* in <lambda> (glob)
102 102 */mercurial/util.py:* in check (glob)
103 103 $TESTTMP/buggylocking.py:* in buggylocking (glob)
104 104 $ hg properlocking
105 105 $ hg nowaitlocking
106 106
107 107 $ echo a > a
108 108 $ hg add a
109 109 $ hg commit -m a
110 110 $ hg stripintr 2>&1 | egrep -v '^(\*\*| )'
111 111 Traceback (most recent call last):
112 112 mercurial.error.ProgrammingError: cannot strip from inside a transaction
113 113
114 114 $ hg oldanddeprecated
115 115 devel-warn: foorbar is deprecated, go shopping
116 116 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
117 117
118 118 $ hg oldanddeprecated --traceback
119 119 devel-warn: foorbar is deprecated, go shopping
120 120 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
121 121 */hg:* in <module> (glob)
122 122 */mercurial/dispatch.py:* in run (glob)
123 123 */mercurial/dispatch.py:* in dispatch (glob)
124 124 */mercurial/dispatch.py:* in _runcatch (glob)
125 125 */mercurial/dispatch.py:* in _callcatch (glob)
126 126 */mercurial/scmutil.py* in callcatch (glob)
127 127 */mercurial/dispatch.py:* in _runcatchfunc (glob)
128 128 */mercurial/dispatch.py:* in _dispatch (glob)
129 129 */mercurial/dispatch.py:* in runcommand (glob)
130 130 */mercurial/dispatch.py:* in _runcommand (glob)
131 131 */mercurial/dispatch.py:* in <lambda> (glob)
132 132 */mercurial/util.py:* in check (glob)
133 133 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
134 134 $ hg blackbox -l 7
135 135 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
136 136 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
137 137 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
138 138 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
139 139 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
140 140 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
141 141 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
142 142 */hg:* in <module> (glob)
143 143 */mercurial/dispatch.py:* in run (glob)
144 144 */mercurial/dispatch.py:* in dispatch (glob)
145 145 */mercurial/dispatch.py:* in _runcatch (glob)
146 146 */mercurial/dispatch.py:* in _callcatch (glob)
147 147 */mercurial/scmutil.py* in callcatch (glob)
148 148 */mercurial/dispatch.py:* in _runcatchfunc (glob)
149 149 */mercurial/dispatch.py:* in _dispatch (glob)
150 150 */mercurial/dispatch.py:* in runcommand (glob)
151 151 */mercurial/dispatch.py:* in _runcommand (glob)
152 152 */mercurial/dispatch.py:* in <lambda> (glob)
153 153 */mercurial/util.py:* in check (glob)
154 154 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
155 155 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
156 156 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
157 157
158 158 Test programming error failure:
159 159
160 160 $ hg buggytransaction 2>&1 | egrep -v '^ '
161 161 ** Unknown exception encountered with possibly-broken third-party extension buggylocking
162 162 ** which supports versions unknown of Mercurial.
163 163 ** Please disable buggylocking and try your action again.
164 164 ** If that fixes the bug please report it to the extension author.
165 165 ** Python * (glob)
166 166 ** Mercurial Distributed SCM (*) (glob)
167 167 ** Extensions loaded: * (glob)
168 168 ** ProgrammingError: transaction requires locking
169 169 Traceback (most recent call last):
170 170 mercurial.error.ProgrammingError: transaction requires locking
171 171
172 172 $ hg programmingerror 2>&1 | egrep -v '^ '
173 173 ** Unknown exception encountered with possibly-broken third-party extension buggylocking
174 174 ** which supports versions unknown of Mercurial.
175 175 ** Please disable buggylocking and try your action again.
176 176 ** If that fixes the bug please report it to the extension author.
177 177 ** Python * (glob)
178 178 ** Mercurial Distributed SCM (*) (glob)
179 179 ** Extensions loaded: * (glob)
180 180 ** ProgrammingError: something went wrong
181 181 ** (try again)
182 182 Traceback (most recent call last):
183 183 mercurial.error.ProgrammingError: something went wrong
184 184
185 185 Old style deprecation warning
186 186
187 187 $ hg nouiwarning
188 188 $TESTTMP/buggylocking.py:61: DeprecationWarning: this is a test
189 189 (compatibility will be dropped after Mercurial-13.37, update your code.)
190 190 util.nouideprecwarn('this is a test', '13.37')
191 191
192 192 (disabled outside of test run)
193 193
194 194 $ HGEMITWARNINGS= hg nouiwarning
195 195
196 Test warning on config option access and registration
197
198 $ cat << EOF > ${TESTTMP}/buggyconfig.py
199 > """A small extension that tests our developer warnings for config"""
200 >
201 > from mercurial import registrar
202 >
203 > cmdtable = {}
204 > command = registrar.command(cmdtable)
205 >
206 > @command('buggyconfig')
207 > def cmdbuggyconfig(ui, repo):
208 > repo.ui.config('ui', 'quiet', False)
209 > repo.ui.config('ui', 'interactive', None)
210 > EOF
211
212 $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig
213 devel-warn: specifying a default value for a registered config item: 'ui.quiet' 'False' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
214
196 215 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now