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