##// END OF EJS Templates
configitems: register 'ui.interactive'...
marmoute -
r33061:c41cbe98 default
parent child Browse files
Show More
@@ -1,47 +1,50
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from . import (
11 11 error,
12 12 )
13 13
14 14 class configitem(object):
15 15 """represent a known config item
16 16
17 17 :section: the official config section where to find this item,
18 18 :name: the official name within the section,
19 19 :default: default value for this item,
20 20 """
21 21
22 22 def __init__(self, section, name, default=None):
23 23 self.section = section
24 24 self.name = name
25 25 self.default = default
26 26
27 27 coreitems = {}
28 28
29 29 def coreconfigitem(*args, **kwargs):
30 30 item = configitem(*args, **kwargs)
31 31 section = coreitems.setdefault(item.section, {})
32 32 if item.name in section:
33 33 msg = "duplicated config item registration for '%s.%s'"
34 34 raise error.ProgrammingError(msg % (item.section, item.name))
35 35 section[item.name] = item
36 36
37 37 # Registering actual config items
38 38
39 39 coreconfigitem('patch', 'fuzz',
40 40 default=2,
41 41 )
42 42 coreconfigitem('ui', 'clonebundleprefers',
43 43 default=[],
44 44 )
45 coreconfigitem('ui', 'interactive',
46 default=None,
47 )
45 48 coreconfigitem('ui', 'quiet',
46 49 default=False,
47 50 )
@@ -1,1768 +1,1768
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 """return the plain string version of a config"""
443 443 value = self._config(section, name, default=default,
444 444 untrusted=untrusted)
445 445 if value is _unset:
446 446 return None
447 447 return value
448 448
449 449 def _config(self, section, name, default=_unset, untrusted=False):
450 450 value = default
451 451 if isinstance(name, list):
452 452 alternates = name
453 453 else:
454 454 item = self._knownconfig.get(section, {}).get(name)
455 455 if default is _unset:
456 456 if item is None:
457 457 value = default
458 458 else:
459 459 value = item.default
460 460 elif item is not None:
461 461 msg = ("specifying a default value for a registered "
462 462 "config item: '%s.%s' '%s'")
463 463 msg %= (section, name, default)
464 464 self.develwarn(msg, 2, 'warn-config-default')
465 465
466 466 alternates = [name]
467 467
468 468 for n in alternates:
469 469 candidate = self._data(untrusted).get(section, n, None)
470 470 if candidate is not None:
471 471 value = candidate
472 472 name = n
473 473 break
474 474
475 475 if self.debugflag and not untrusted and self._reportuntrusted:
476 476 for n in alternates:
477 477 uvalue = self._ucfg.get(section, n)
478 478 if uvalue is not None and uvalue != value:
479 479 self.debug("ignoring untrusted configuration option "
480 480 "%s.%s = %s\n" % (section, n, uvalue))
481 481 return value
482 482
483 483 def configsuboptions(self, section, name, default=_unset, untrusted=False):
484 484 """Get a config option and all sub-options.
485 485
486 486 Some config options have sub-options that are declared with the
487 487 format "key:opt = value". This method is used to return the main
488 488 option and all its declared sub-options.
489 489
490 490 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
491 491 is a dict of defined sub-options where keys and values are strings.
492 492 """
493 493 main = self.config(section, name, default, untrusted=untrusted)
494 494 data = self._data(untrusted)
495 495 sub = {}
496 496 prefix = '%s:' % name
497 497 for k, v in data.items(section):
498 498 if k.startswith(prefix):
499 499 sub[k[len(prefix):]] = v
500 500
501 501 if self.debugflag and not untrusted and self._reportuntrusted:
502 502 for k, v in sub.items():
503 503 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
504 504 if uvalue is not None and uvalue != v:
505 505 self.debug('ignoring untrusted configuration option '
506 506 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
507 507
508 508 return main, sub
509 509
510 510 def configpath(self, section, name, default=_unset, untrusted=False):
511 511 'get a path config item, expanded relative to repo root or config file'
512 512 v = self.config(section, name, default, untrusted)
513 513 if v is None:
514 514 return None
515 515 if not os.path.isabs(v) or "://" not in v:
516 516 src = self.configsource(section, name, untrusted)
517 517 if ':' in src:
518 518 base = os.path.dirname(src.rsplit(':')[0])
519 519 v = os.path.join(base, os.path.expanduser(v))
520 520 return v
521 521
522 522 def configbool(self, section, name, default=_unset, untrusted=False):
523 523 """parse a configuration element as a boolean
524 524
525 525 >>> u = ui(); s = 'foo'
526 526 >>> u.setconfig(s, 'true', 'yes')
527 527 >>> u.configbool(s, 'true')
528 528 True
529 529 >>> u.setconfig(s, 'false', 'no')
530 530 >>> u.configbool(s, 'false')
531 531 False
532 532 >>> u.configbool(s, 'unknown')
533 533 False
534 534 >>> u.configbool(s, 'unknown', True)
535 535 True
536 536 >>> u.setconfig(s, 'invalid', 'somevalue')
537 537 >>> u.configbool(s, 'invalid')
538 538 Traceback (most recent call last):
539 539 ...
540 540 ConfigError: foo.invalid is not a boolean ('somevalue')
541 541 """
542 542
543 543 v = self._config(section, name, default, untrusted=untrusted)
544 544 if v is None:
545 545 return v
546 546 if v is _unset:
547 547 if default is _unset:
548 548 return False
549 549 return default
550 550 if isinstance(v, bool):
551 551 return v
552 552 b = util.parsebool(v)
553 553 if b is None:
554 554 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
555 555 % (section, name, v))
556 556 return b
557 557
558 558 def configwith(self, convert, section, name, default=_unset,
559 559 desc=None, untrusted=False):
560 560 """parse a configuration element with a conversion function
561 561
562 562 >>> u = ui(); s = 'foo'
563 563 >>> u.setconfig(s, 'float1', '42')
564 564 >>> u.configwith(float, s, 'float1')
565 565 42.0
566 566 >>> u.setconfig(s, 'float2', '-4.25')
567 567 >>> u.configwith(float, s, 'float2')
568 568 -4.25
569 569 >>> u.configwith(float, s, 'unknown', 7)
570 570 7.0
571 571 >>> u.setconfig(s, 'invalid', 'somevalue')
572 572 >>> u.configwith(float, s, 'invalid')
573 573 Traceback (most recent call last):
574 574 ...
575 575 ConfigError: foo.invalid is not a valid float ('somevalue')
576 576 >>> u.configwith(float, s, 'invalid', desc='womble')
577 577 Traceback (most recent call last):
578 578 ...
579 579 ConfigError: foo.invalid is not a valid womble ('somevalue')
580 580 """
581 581
582 582 v = self.config(section, name, default, untrusted)
583 583 if v is None:
584 584 return v # do not attempt to convert None
585 585 try:
586 586 return convert(v)
587 587 except (ValueError, error.ParseError):
588 588 if desc is None:
589 589 desc = convert.__name__
590 590 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
591 591 % (section, name, desc, v))
592 592
593 593 def configint(self, section, name, default=_unset, untrusted=False):
594 594 """parse a configuration element as an integer
595 595
596 596 >>> u = ui(); s = 'foo'
597 597 >>> u.setconfig(s, 'int1', '42')
598 598 >>> u.configint(s, 'int1')
599 599 42
600 600 >>> u.setconfig(s, 'int2', '-42')
601 601 >>> u.configint(s, 'int2')
602 602 -42
603 603 >>> u.configint(s, 'unknown', 7)
604 604 7
605 605 >>> u.setconfig(s, 'invalid', 'somevalue')
606 606 >>> u.configint(s, 'invalid')
607 607 Traceback (most recent call last):
608 608 ...
609 609 ConfigError: foo.invalid is not a valid integer ('somevalue')
610 610 """
611 611
612 612 return self.configwith(int, section, name, default, 'integer',
613 613 untrusted)
614 614
615 615 def configbytes(self, section, name, default=_unset, untrusted=False):
616 616 """parse a configuration element as a quantity in bytes
617 617
618 618 Units can be specified as b (bytes), k or kb (kilobytes), m or
619 619 mb (megabytes), g or gb (gigabytes).
620 620
621 621 >>> u = ui(); s = 'foo'
622 622 >>> u.setconfig(s, 'val1', '42')
623 623 >>> u.configbytes(s, 'val1')
624 624 42
625 625 >>> u.setconfig(s, 'val2', '42.5 kb')
626 626 >>> u.configbytes(s, 'val2')
627 627 43520
628 628 >>> u.configbytes(s, 'unknown', '7 MB')
629 629 7340032
630 630 >>> u.setconfig(s, 'invalid', 'somevalue')
631 631 >>> u.configbytes(s, 'invalid')
632 632 Traceback (most recent call last):
633 633 ...
634 634 ConfigError: foo.invalid is not a byte quantity ('somevalue')
635 635 """
636 636
637 637 value = self._config(section, name, default, untrusted)
638 638 if value is _unset:
639 639 if default is _unset:
640 640 default = 0
641 641 value = default
642 642 if not isinstance(value, str):
643 643 return value
644 644 try:
645 645 return util.sizetoint(value)
646 646 except error.ParseError:
647 647 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
648 648 % (section, name, value))
649 649
650 650 def configlist(self, section, name, default=_unset, untrusted=False):
651 651 """parse a configuration element as a list of comma/space separated
652 652 strings
653 653
654 654 >>> u = ui(); s = 'foo'
655 655 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
656 656 >>> u.configlist(s, 'list1')
657 657 ['this', 'is', 'a small', 'test']
658 658 """
659 659 # default is not always a list
660 660 v = self.configwith(config.parselist, section, name, default,
661 661 'list', untrusted)
662 662 if isinstance(v, bytes):
663 663 return config.parselist(v)
664 664 elif v is None:
665 665 return []
666 666 return v
667 667
668 668 def configdate(self, section, name, default=_unset, untrusted=False):
669 669 """parse a configuration element as a tuple of ints
670 670
671 671 >>> u = ui(); s = 'foo'
672 672 >>> u.setconfig(s, 'date', '0 0')
673 673 >>> u.configdate(s, 'date')
674 674 (0, 0)
675 675 """
676 676 if self.config(section, name, default, untrusted):
677 677 return self.configwith(util.parsedate, section, name, default,
678 678 'date', untrusted)
679 679 if default is _unset:
680 680 return None
681 681 return default
682 682
683 683 def hasconfig(self, section, name, untrusted=False):
684 684 return self._data(untrusted).hasitem(section, name)
685 685
686 686 def has_section(self, section, untrusted=False):
687 687 '''tell whether section exists in config.'''
688 688 return section in self._data(untrusted)
689 689
690 690 def configitems(self, section, untrusted=False, ignoresub=False):
691 691 items = self._data(untrusted).items(section)
692 692 if ignoresub:
693 693 newitems = {}
694 694 for k, v in items:
695 695 if ':' not in k:
696 696 newitems[k] = v
697 697 items = newitems.items()
698 698 if self.debugflag and not untrusted and self._reportuntrusted:
699 699 for k, v in self._ucfg.items(section):
700 700 if self._tcfg.get(section, k) != v:
701 701 self.debug("ignoring untrusted configuration option "
702 702 "%s.%s = %s\n" % (section, k, v))
703 703 return items
704 704
705 705 def walkconfig(self, untrusted=False):
706 706 cfg = self._data(untrusted)
707 707 for section in cfg.sections():
708 708 for name, value in self.configitems(section, untrusted):
709 709 yield section, name, value
710 710
711 711 def plain(self, feature=None):
712 712 '''is plain mode active?
713 713
714 714 Plain mode means that all configuration variables which affect
715 715 the behavior and output of Mercurial should be
716 716 ignored. Additionally, the output should be stable,
717 717 reproducible and suitable for use in scripts or applications.
718 718
719 719 The only way to trigger plain mode is by setting either the
720 720 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
721 721
722 722 The return value can either be
723 723 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
724 724 - True otherwise
725 725 '''
726 726 if ('HGPLAIN' not in encoding.environ and
727 727 'HGPLAINEXCEPT' not in encoding.environ):
728 728 return False
729 729 exceptions = encoding.environ.get('HGPLAINEXCEPT',
730 730 '').strip().split(',')
731 731 if feature and exceptions:
732 732 return feature not in exceptions
733 733 return True
734 734
735 735 def username(self):
736 736 """Return default username to be used in commits.
737 737
738 738 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
739 739 and stop searching if one of these is set.
740 740 If not found and ui.askusername is True, ask the user, else use
741 741 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
742 742 """
743 743 user = encoding.environ.get("HGUSER")
744 744 if user is None:
745 745 user = self.config("ui", ["username", "user"])
746 746 if user is not None:
747 747 user = os.path.expandvars(user)
748 748 if user is None:
749 749 user = encoding.environ.get("EMAIL")
750 750 if user is None and self.configbool("ui", "askusername"):
751 751 user = self.prompt(_("enter a commit username:"), default=None)
752 752 if user is None and not self.interactive():
753 753 try:
754 754 user = '%s@%s' % (util.getuser(), socket.getfqdn())
755 755 self.warn(_("no username found, using '%s' instead\n") % user)
756 756 except KeyError:
757 757 pass
758 758 if not user:
759 759 raise error.Abort(_('no username supplied'),
760 760 hint=_("use 'hg config --edit' "
761 761 'to set your username'))
762 762 if "\n" in user:
763 763 raise error.Abort(_("username %s contains a newline\n")
764 764 % repr(user))
765 765 return user
766 766
767 767 def shortuser(self, user):
768 768 """Return a short representation of a user name or email address."""
769 769 if not self.verbose:
770 770 user = util.shortuser(user)
771 771 return user
772 772
773 773 def expandpath(self, loc, default=None):
774 774 """Return repository location relative to cwd or from [paths]"""
775 775 try:
776 776 p = self.paths.getpath(loc)
777 777 if p:
778 778 return p.rawloc
779 779 except error.RepoError:
780 780 pass
781 781
782 782 if default:
783 783 try:
784 784 p = self.paths.getpath(default)
785 785 if p:
786 786 return p.rawloc
787 787 except error.RepoError:
788 788 pass
789 789
790 790 return loc
791 791
792 792 @util.propertycache
793 793 def paths(self):
794 794 return paths(self)
795 795
796 796 def pushbuffer(self, error=False, subproc=False, labeled=False):
797 797 """install a buffer to capture standard output of the ui object
798 798
799 799 If error is True, the error output will be captured too.
800 800
801 801 If subproc is True, output from subprocesses (typically hooks) will be
802 802 captured too.
803 803
804 804 If labeled is True, any labels associated with buffered
805 805 output will be handled. By default, this has no effect
806 806 on the output returned, but extensions and GUI tools may
807 807 handle this argument and returned styled output. If output
808 808 is being buffered so it can be captured and parsed or
809 809 processed, labeled should not be set to True.
810 810 """
811 811 self._buffers.append([])
812 812 self._bufferstates.append((error, subproc, labeled))
813 813 self._bufferapplylabels = labeled
814 814
815 815 def popbuffer(self):
816 816 '''pop the last buffer and return the buffered output'''
817 817 self._bufferstates.pop()
818 818 if self._bufferstates:
819 819 self._bufferapplylabels = self._bufferstates[-1][2]
820 820 else:
821 821 self._bufferapplylabels = None
822 822
823 823 return "".join(self._buffers.pop())
824 824
825 825 def write(self, *args, **opts):
826 826 '''write args to output
827 827
828 828 By default, this method simply writes to the buffer or stdout.
829 829 Color mode can be set on the UI class to have the output decorated
830 830 with color modifier before being written to stdout.
831 831
832 832 The color used is controlled by an optional keyword argument, "label".
833 833 This should be a string containing label names separated by space.
834 834 Label names take the form of "topic.type". For example, ui.debug()
835 835 issues a label of "ui.debug".
836 836
837 837 When labeling output for a specific command, a label of
838 838 "cmdname.type" is recommended. For example, status issues
839 839 a label of "status.modified" for modified files.
840 840 '''
841 841 if self._buffers and not opts.get('prompt', False):
842 842 if self._bufferapplylabels:
843 843 label = opts.get('label', '')
844 844 self._buffers[-1].extend(self.label(a, label) for a in args)
845 845 else:
846 846 self._buffers[-1].extend(args)
847 847 elif self._colormode == 'win32':
848 848 # windows color printing is its own can of crab, defer to
849 849 # the color module and that is it.
850 850 color.win32print(self, self._write, *args, **opts)
851 851 else:
852 852 msgs = args
853 853 if self._colormode is not None:
854 854 label = opts.get('label', '')
855 855 msgs = [self.label(a, label) for a in args]
856 856 self._write(*msgs, **opts)
857 857
858 858 def _write(self, *msgs, **opts):
859 859 self._progclear()
860 860 # opencode timeblockedsection because this is a critical path
861 861 starttime = util.timer()
862 862 try:
863 863 for a in msgs:
864 864 self.fout.write(a)
865 865 except IOError as err:
866 866 raise error.StdioError(err)
867 867 finally:
868 868 self._blockedtimes['stdio_blocked'] += \
869 869 (util.timer() - starttime) * 1000
870 870
871 871 def write_err(self, *args, **opts):
872 872 self._progclear()
873 873 if self._bufferstates and self._bufferstates[-1][0]:
874 874 self.write(*args, **opts)
875 875 elif self._colormode == 'win32':
876 876 # windows color printing is its own can of crab, defer to
877 877 # the color module and that is it.
878 878 color.win32print(self, self._write_err, *args, **opts)
879 879 else:
880 880 msgs = args
881 881 if self._colormode is not None:
882 882 label = opts.get('label', '')
883 883 msgs = [self.label(a, label) for a in args]
884 884 self._write_err(*msgs, **opts)
885 885
886 886 def _write_err(self, *msgs, **opts):
887 887 try:
888 888 with self.timeblockedsection('stdio'):
889 889 if not getattr(self.fout, 'closed', False):
890 890 self.fout.flush()
891 891 for a in msgs:
892 892 self.ferr.write(a)
893 893 # stderr may be buffered under win32 when redirected to files,
894 894 # including stdout.
895 895 if not getattr(self.ferr, 'closed', False):
896 896 self.ferr.flush()
897 897 except IOError as inst:
898 898 raise error.StdioError(inst)
899 899
900 900 def flush(self):
901 901 # opencode timeblockedsection because this is a critical path
902 902 starttime = util.timer()
903 903 try:
904 904 try:
905 905 self.fout.flush()
906 906 except IOError as err:
907 907 raise error.StdioError(err)
908 908 finally:
909 909 try:
910 910 self.ferr.flush()
911 911 except IOError as err:
912 912 raise error.StdioError(err)
913 913 finally:
914 914 self._blockedtimes['stdio_blocked'] += \
915 915 (util.timer() - starttime) * 1000
916 916
917 917 def _isatty(self, fh):
918 918 if self.configbool('ui', 'nontty', False):
919 919 return False
920 920 return util.isatty(fh)
921 921
922 922 def disablepager(self):
923 923 self._disablepager = True
924 924
925 925 def pager(self, command):
926 926 """Start a pager for subsequent command output.
927 927
928 928 Commands which produce a long stream of output should call
929 929 this function to activate the user's preferred pagination
930 930 mechanism (which may be no pager). Calling this function
931 931 precludes any future use of interactive functionality, such as
932 932 prompting the user or activating curses.
933 933
934 934 Args:
935 935 command: The full, non-aliased name of the command. That is, "log"
936 936 not "history, "summary" not "summ", etc.
937 937 """
938 938 if (self._disablepager
939 939 or self.pageractive
940 940 or command in self.configlist('pager', 'ignore')
941 941 or not self.configbool('ui', 'paginate', True)
942 942 or not self.configbool('pager', 'attend-' + command, True)
943 943 # TODO: if we want to allow HGPLAINEXCEPT=pager,
944 944 # formatted() will need some adjustment.
945 945 or not self.formatted()
946 946 or self.plain()
947 947 # TODO: expose debugger-enabled on the UI object
948 948 or '--debugger' in pycompat.sysargv):
949 949 # We only want to paginate if the ui appears to be
950 950 # interactive, the user didn't say HGPLAIN or
951 951 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
952 952 return
953 953
954 954 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
955 955 if not pagercmd:
956 956 return
957 957
958 958 pagerenv = {}
959 959 for name, value in rcutil.defaultpagerenv().items():
960 960 if name not in encoding.environ:
961 961 pagerenv[name] = value
962 962
963 963 self.debug('starting pager for command %r\n' % command)
964 964 self.flush()
965 965
966 966 wasformatted = self.formatted()
967 967 if util.safehasattr(signal, "SIGPIPE"):
968 968 signal.signal(signal.SIGPIPE, _catchterm)
969 969 if self._runpager(pagercmd, pagerenv):
970 970 self.pageractive = True
971 971 # Preserve the formatted-ness of the UI. This is important
972 972 # because we mess with stdout, which might confuse
973 973 # auto-detection of things being formatted.
974 974 self.setconfig('ui', 'formatted', wasformatted, 'pager')
975 975 self.setconfig('ui', 'interactive', False, 'pager')
976 976
977 977 # If pagermode differs from color.mode, reconfigure color now that
978 978 # pageractive is set.
979 979 cm = self._colormode
980 980 if cm != self.config('color', 'pagermode', cm):
981 981 color.setup(self)
982 982 else:
983 983 # If the pager can't be spawned in dispatch when --pager=on is
984 984 # given, don't try again when the command runs, to avoid a duplicate
985 985 # warning about a missing pager command.
986 986 self.disablepager()
987 987
988 988 def _runpager(self, command, env=None):
989 989 """Actually start the pager and set up file descriptors.
990 990
991 991 This is separate in part so that extensions (like chg) can
992 992 override how a pager is invoked.
993 993 """
994 994 if command == 'cat':
995 995 # Save ourselves some work.
996 996 return False
997 997 # If the command doesn't contain any of these characters, we
998 998 # assume it's a binary and exec it directly. This means for
999 999 # simple pager command configurations, we can degrade
1000 1000 # gracefully and tell the user about their broken pager.
1001 1001 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1002 1002
1003 1003 if pycompat.osname == 'nt' and not shell:
1004 1004 # Window's built-in `more` cannot be invoked with shell=False, but
1005 1005 # its `more.com` can. Hide this implementation detail from the
1006 1006 # user so we can also get sane bad PAGER behavior. MSYS has
1007 1007 # `more.exe`, so do a cmd.exe style resolution of the executable to
1008 1008 # determine which one to use.
1009 1009 fullcmd = util.findexe(command)
1010 1010 if not fullcmd:
1011 1011 self.warn(_("missing pager command '%s', skipping pager\n")
1012 1012 % command)
1013 1013 return False
1014 1014
1015 1015 command = fullcmd
1016 1016
1017 1017 try:
1018 1018 pager = subprocess.Popen(
1019 1019 command, shell=shell, bufsize=-1,
1020 1020 close_fds=util.closefds, stdin=subprocess.PIPE,
1021 1021 stdout=util.stdout, stderr=util.stderr,
1022 1022 env=util.shellenviron(env))
1023 1023 except OSError as e:
1024 1024 if e.errno == errno.ENOENT and not shell:
1025 1025 self.warn(_("missing pager command '%s', skipping pager\n")
1026 1026 % command)
1027 1027 return False
1028 1028 raise
1029 1029
1030 1030 # back up original file descriptors
1031 1031 stdoutfd = os.dup(util.stdout.fileno())
1032 1032 stderrfd = os.dup(util.stderr.fileno())
1033 1033
1034 1034 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1035 1035 if self._isatty(util.stderr):
1036 1036 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1037 1037
1038 1038 @self.atexit
1039 1039 def killpager():
1040 1040 if util.safehasattr(signal, "SIGINT"):
1041 1041 signal.signal(signal.SIGINT, signal.SIG_IGN)
1042 1042 # restore original fds, closing pager.stdin copies in the process
1043 1043 os.dup2(stdoutfd, util.stdout.fileno())
1044 1044 os.dup2(stderrfd, util.stderr.fileno())
1045 1045 pager.stdin.close()
1046 1046 pager.wait()
1047 1047
1048 1048 return True
1049 1049
1050 1050 def atexit(self, func, *args, **kwargs):
1051 1051 '''register a function to run after dispatching a request
1052 1052
1053 1053 Handlers do not stay registered across request boundaries.'''
1054 1054 self._exithandlers.append((func, args, kwargs))
1055 1055 return func
1056 1056
1057 1057 def interface(self, feature):
1058 1058 """what interface to use for interactive console features?
1059 1059
1060 1060 The interface is controlled by the value of `ui.interface` but also by
1061 1061 the value of feature-specific configuration. For example:
1062 1062
1063 1063 ui.interface.histedit = text
1064 1064 ui.interface.chunkselector = curses
1065 1065
1066 1066 Here the features are "histedit" and "chunkselector".
1067 1067
1068 1068 The configuration above means that the default interfaces for commands
1069 1069 is curses, the interface for histedit is text and the interface for
1070 1070 selecting chunk is crecord (the best curses interface available).
1071 1071
1072 1072 Consider the following example:
1073 1073 ui.interface = curses
1074 1074 ui.interface.histedit = text
1075 1075
1076 1076 Then histedit will use the text interface and chunkselector will use
1077 1077 the default curses interface (crecord at the moment).
1078 1078 """
1079 1079 alldefaults = frozenset(["text", "curses"])
1080 1080
1081 1081 featureinterfaces = {
1082 1082 "chunkselector": [
1083 1083 "text",
1084 1084 "curses",
1085 1085 ]
1086 1086 }
1087 1087
1088 1088 # Feature-specific interface
1089 1089 if feature not in featureinterfaces.keys():
1090 1090 # Programming error, not user error
1091 1091 raise ValueError("Unknown feature requested %s" % feature)
1092 1092
1093 1093 availableinterfaces = frozenset(featureinterfaces[feature])
1094 1094 if alldefaults > availableinterfaces:
1095 1095 # Programming error, not user error. We need a use case to
1096 1096 # define the right thing to do here.
1097 1097 raise ValueError(
1098 1098 "Feature %s does not handle all default interfaces" %
1099 1099 feature)
1100 1100
1101 1101 if self.plain():
1102 1102 return "text"
1103 1103
1104 1104 # Default interface for all the features
1105 1105 defaultinterface = "text"
1106 1106 i = self.config("ui", "interface", None)
1107 1107 if i in alldefaults:
1108 1108 defaultinterface = i
1109 1109
1110 1110 choseninterface = defaultinterface
1111 1111 f = self.config("ui", "interface.%s" % feature, None)
1112 1112 if f in availableinterfaces:
1113 1113 choseninterface = f
1114 1114
1115 1115 if i is not None and defaultinterface != i:
1116 1116 if f is not None:
1117 1117 self.warn(_("invalid value for ui.interface: %s\n") %
1118 1118 (i,))
1119 1119 else:
1120 1120 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1121 1121 (i, choseninterface))
1122 1122 if f is not None and choseninterface != f:
1123 1123 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1124 1124 (feature, f, choseninterface))
1125 1125
1126 1126 return choseninterface
1127 1127
1128 1128 def interactive(self):
1129 1129 '''is interactive input allowed?
1130 1130
1131 1131 An interactive session is a session where input can be reasonably read
1132 1132 from `sys.stdin'. If this function returns false, any attempt to read
1133 1133 from stdin should fail with an error, unless a sensible default has been
1134 1134 specified.
1135 1135
1136 1136 Interactiveness is triggered by the value of the `ui.interactive'
1137 1137 configuration variable or - if it is unset - when `sys.stdin' points
1138 1138 to a terminal device.
1139 1139
1140 1140 This function refers to input only; for output, see `ui.formatted()'.
1141 1141 '''
1142 i = self.configbool("ui", "interactive", None)
1142 i = self.configbool("ui", "interactive")
1143 1143 if i is None:
1144 1144 # some environments replace stdin without implementing isatty
1145 1145 # usually those are non-interactive
1146 1146 return self._isatty(self.fin)
1147 1147
1148 1148 return i
1149 1149
1150 1150 def termwidth(self):
1151 1151 '''how wide is the terminal in columns?
1152 1152 '''
1153 1153 if 'COLUMNS' in encoding.environ:
1154 1154 try:
1155 1155 return int(encoding.environ['COLUMNS'])
1156 1156 except ValueError:
1157 1157 pass
1158 1158 return scmutil.termsize(self)[0]
1159 1159
1160 1160 def formatted(self):
1161 1161 '''should formatted output be used?
1162 1162
1163 1163 It is often desirable to format the output to suite the output medium.
1164 1164 Examples of this are truncating long lines or colorizing messages.
1165 1165 However, this is not often not desirable when piping output into other
1166 1166 utilities, e.g. `grep'.
1167 1167
1168 1168 Formatted output is triggered by the value of the `ui.formatted'
1169 1169 configuration variable or - if it is unset - when `sys.stdout' points
1170 1170 to a terminal device. Please note that `ui.formatted' should be
1171 1171 considered an implementation detail; it is not intended for use outside
1172 1172 Mercurial or its extensions.
1173 1173
1174 1174 This function refers to output only; for input, see `ui.interactive()'.
1175 1175 This function always returns false when in plain mode, see `ui.plain()'.
1176 1176 '''
1177 1177 if self.plain():
1178 1178 return False
1179 1179
1180 1180 i = self.configbool("ui", "formatted", None)
1181 1181 if i is None:
1182 1182 # some environments replace stdout without implementing isatty
1183 1183 # usually those are non-interactive
1184 1184 return self._isatty(self.fout)
1185 1185
1186 1186 return i
1187 1187
1188 1188 def _readline(self, prompt=''):
1189 1189 if self._isatty(self.fin):
1190 1190 try:
1191 1191 # magically add command line editing support, where
1192 1192 # available
1193 1193 import readline
1194 1194 # force demandimport to really load the module
1195 1195 readline.read_history_file
1196 1196 # windows sometimes raises something other than ImportError
1197 1197 except Exception:
1198 1198 pass
1199 1199
1200 1200 # call write() so output goes through subclassed implementation
1201 1201 # e.g. color extension on Windows
1202 1202 self.write(prompt, prompt=True)
1203 1203
1204 1204 # instead of trying to emulate raw_input, swap (self.fin,
1205 1205 # self.fout) with (sys.stdin, sys.stdout)
1206 1206 oldin = sys.stdin
1207 1207 oldout = sys.stdout
1208 1208 sys.stdin = self.fin
1209 1209 sys.stdout = self.fout
1210 1210 # prompt ' ' must exist; otherwise readline may delete entire line
1211 1211 # - http://bugs.python.org/issue12833
1212 1212 with self.timeblockedsection('stdio'):
1213 1213 line = raw_input(' ')
1214 1214 sys.stdin = oldin
1215 1215 sys.stdout = oldout
1216 1216
1217 1217 # When stdin is in binary mode on Windows, it can cause
1218 1218 # raw_input() to emit an extra trailing carriage return
1219 1219 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1220 1220 line = line[:-1]
1221 1221 return line
1222 1222
1223 1223 def prompt(self, msg, default="y"):
1224 1224 """Prompt user with msg, read response.
1225 1225 If ui is not interactive, the default is returned.
1226 1226 """
1227 1227 if not self.interactive():
1228 1228 self.write(msg, ' ', default or '', "\n")
1229 1229 return default
1230 1230 try:
1231 1231 r = self._readline(self.label(msg, 'ui.prompt'))
1232 1232 if not r:
1233 1233 r = default
1234 1234 if self.configbool('ui', 'promptecho'):
1235 1235 self.write(r, "\n")
1236 1236 return r
1237 1237 except EOFError:
1238 1238 raise error.ResponseExpected()
1239 1239
1240 1240 @staticmethod
1241 1241 def extractchoices(prompt):
1242 1242 """Extract prompt message and list of choices from specified prompt.
1243 1243
1244 1244 This returns tuple "(message, choices)", and "choices" is the
1245 1245 list of tuple "(response character, text without &)".
1246 1246
1247 1247 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1248 1248 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1249 1249 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1250 1250 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1251 1251 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1252 1252 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1253 1253 """
1254 1254
1255 1255 # Sadly, the prompt string may have been built with a filename
1256 1256 # containing "$$" so let's try to find the first valid-looking
1257 1257 # prompt to start parsing. Sadly, we also can't rely on
1258 1258 # choices containing spaces, ASCII, or basically anything
1259 1259 # except an ampersand followed by a character.
1260 1260 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1261 1261 msg = m.group(1)
1262 1262 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1263 1263 return (msg,
1264 1264 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1265 1265 for s in choices])
1266 1266
1267 1267 def promptchoice(self, prompt, default=0):
1268 1268 """Prompt user with a message, read response, and ensure it matches
1269 1269 one of the provided choices. The prompt is formatted as follows:
1270 1270
1271 1271 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1272 1272
1273 1273 The index of the choice is returned. Responses are case
1274 1274 insensitive. If ui is not interactive, the default is
1275 1275 returned.
1276 1276 """
1277 1277
1278 1278 msg, choices = self.extractchoices(prompt)
1279 1279 resps = [r for r, t in choices]
1280 1280 while True:
1281 1281 r = self.prompt(msg, resps[default])
1282 1282 if r.lower() in resps:
1283 1283 return resps.index(r.lower())
1284 1284 self.write(_("unrecognized response\n"))
1285 1285
1286 1286 def getpass(self, prompt=None, default=None):
1287 1287 if not self.interactive():
1288 1288 return default
1289 1289 try:
1290 1290 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1291 1291 # disable getpass() only if explicitly specified. it's still valid
1292 1292 # to interact with tty even if fin is not a tty.
1293 1293 with self.timeblockedsection('stdio'):
1294 1294 if self.configbool('ui', 'nontty'):
1295 1295 l = self.fin.readline()
1296 1296 if not l:
1297 1297 raise EOFError
1298 1298 return l.rstrip('\n')
1299 1299 else:
1300 1300 return getpass.getpass('')
1301 1301 except EOFError:
1302 1302 raise error.ResponseExpected()
1303 1303 def status(self, *msg, **opts):
1304 1304 '''write status message to output (if ui.quiet is False)
1305 1305
1306 1306 This adds an output label of "ui.status".
1307 1307 '''
1308 1308 if not self.quiet:
1309 1309 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1310 1310 self.write(*msg, **opts)
1311 1311 def warn(self, *msg, **opts):
1312 1312 '''write warning message to output (stderr)
1313 1313
1314 1314 This adds an output label of "ui.warning".
1315 1315 '''
1316 1316 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1317 1317 self.write_err(*msg, **opts)
1318 1318 def note(self, *msg, **opts):
1319 1319 '''write note to output (if ui.verbose is True)
1320 1320
1321 1321 This adds an output label of "ui.note".
1322 1322 '''
1323 1323 if self.verbose:
1324 1324 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1325 1325 self.write(*msg, **opts)
1326 1326 def debug(self, *msg, **opts):
1327 1327 '''write debug message to output (if ui.debugflag is True)
1328 1328
1329 1329 This adds an output label of "ui.debug".
1330 1330 '''
1331 1331 if self.debugflag:
1332 1332 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1333 1333 self.write(*msg, **opts)
1334 1334
1335 1335 def edit(self, text, user, extra=None, editform=None, pending=None,
1336 1336 repopath=None):
1337 1337 extra_defaults = {
1338 1338 'prefix': 'editor',
1339 1339 'suffix': '.txt',
1340 1340 }
1341 1341 if extra is not None:
1342 1342 extra_defaults.update(extra)
1343 1343 extra = extra_defaults
1344 1344
1345 1345 rdir = None
1346 1346 if self.configbool('experimental', 'editortmpinhg'):
1347 1347 rdir = repopath
1348 1348 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1349 1349 suffix=extra['suffix'],
1350 1350 dir=rdir)
1351 1351 try:
1352 1352 f = os.fdopen(fd, r'wb')
1353 1353 f.write(util.tonativeeol(text))
1354 1354 f.close()
1355 1355
1356 1356 environ = {'HGUSER': user}
1357 1357 if 'transplant_source' in extra:
1358 1358 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1359 1359 for label in ('intermediate-source', 'source', 'rebase_source'):
1360 1360 if label in extra:
1361 1361 environ.update({'HGREVISION': extra[label]})
1362 1362 break
1363 1363 if editform:
1364 1364 environ.update({'HGEDITFORM': editform})
1365 1365 if pending:
1366 1366 environ.update({'HG_PENDING': pending})
1367 1367
1368 1368 editor = self.geteditor()
1369 1369
1370 1370 self.system("%s \"%s\"" % (editor, name),
1371 1371 environ=environ,
1372 1372 onerr=error.Abort, errprefix=_("edit failed"),
1373 1373 blockedtag='editor')
1374 1374
1375 1375 f = open(name, r'rb')
1376 1376 t = util.fromnativeeol(f.read())
1377 1377 f.close()
1378 1378 finally:
1379 1379 os.unlink(name)
1380 1380
1381 1381 return t
1382 1382
1383 1383 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1384 1384 blockedtag=None):
1385 1385 '''execute shell command with appropriate output stream. command
1386 1386 output will be redirected if fout is not stdout.
1387 1387
1388 1388 if command fails and onerr is None, return status, else raise onerr
1389 1389 object as exception.
1390 1390 '''
1391 1391 if blockedtag is None:
1392 1392 # Long cmds tend to be because of an absolute path on cmd. Keep
1393 1393 # the tail end instead
1394 1394 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1395 1395 blockedtag = 'unknown_system_' + cmdsuffix
1396 1396 out = self.fout
1397 1397 if any(s[1] for s in self._bufferstates):
1398 1398 out = self
1399 1399 with self.timeblockedsection(blockedtag):
1400 1400 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1401 1401 if rc and onerr:
1402 1402 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1403 1403 util.explainexit(rc)[0])
1404 1404 if errprefix:
1405 1405 errmsg = '%s: %s' % (errprefix, errmsg)
1406 1406 raise onerr(errmsg)
1407 1407 return rc
1408 1408
1409 1409 def _runsystem(self, cmd, environ, cwd, out):
1410 1410 """actually execute the given shell command (can be overridden by
1411 1411 extensions like chg)"""
1412 1412 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1413 1413
1414 1414 def traceback(self, exc=None, force=False):
1415 1415 '''print exception traceback if traceback printing enabled or forced.
1416 1416 only to call in exception handler. returns true if traceback
1417 1417 printed.'''
1418 1418 if self.tracebackflag or force:
1419 1419 if exc is None:
1420 1420 exc = sys.exc_info()
1421 1421 cause = getattr(exc[1], 'cause', None)
1422 1422
1423 1423 if cause is not None:
1424 1424 causetb = traceback.format_tb(cause[2])
1425 1425 exctb = traceback.format_tb(exc[2])
1426 1426 exconly = traceback.format_exception_only(cause[0], cause[1])
1427 1427
1428 1428 # exclude frame where 'exc' was chained and rethrown from exctb
1429 1429 self.write_err('Traceback (most recent call last):\n',
1430 1430 ''.join(exctb[:-1]),
1431 1431 ''.join(causetb),
1432 1432 ''.join(exconly))
1433 1433 else:
1434 1434 output = traceback.format_exception(exc[0], exc[1], exc[2])
1435 1435 data = r''.join(output)
1436 1436 if pycompat.ispy3:
1437 1437 enc = pycompat.sysstr(encoding.encoding)
1438 1438 data = data.encode(enc, errors=r'replace')
1439 1439 self.write_err(data)
1440 1440 return self.tracebackflag or force
1441 1441
1442 1442 def geteditor(self):
1443 1443 '''return editor to use'''
1444 1444 if pycompat.sysplatform == 'plan9':
1445 1445 # vi is the MIPS instruction simulator on Plan 9. We
1446 1446 # instead default to E to plumb commit messages to
1447 1447 # avoid confusion.
1448 1448 editor = 'E'
1449 1449 else:
1450 1450 editor = 'vi'
1451 1451 return (encoding.environ.get("HGEDITOR") or
1452 1452 self.config("ui", "editor", editor))
1453 1453
1454 1454 @util.propertycache
1455 1455 def _progbar(self):
1456 1456 """setup the progbar singleton to the ui object"""
1457 1457 if (self.quiet or self.debugflag
1458 1458 or self.configbool('progress', 'disable', False)
1459 1459 or not progress.shouldprint(self)):
1460 1460 return None
1461 1461 return getprogbar(self)
1462 1462
1463 1463 def _progclear(self):
1464 1464 """clear progress bar output if any. use it before any output"""
1465 1465 if '_progbar' not in vars(self): # nothing loaded yet
1466 1466 return
1467 1467 if self._progbar is not None and self._progbar.printed:
1468 1468 self._progbar.clear()
1469 1469
1470 1470 def progress(self, topic, pos, item="", unit="", total=None):
1471 1471 '''show a progress message
1472 1472
1473 1473 By default a textual progress bar will be displayed if an operation
1474 1474 takes too long. 'topic' is the current operation, 'item' is a
1475 1475 non-numeric marker of the current position (i.e. the currently
1476 1476 in-process file), 'pos' is the current numeric position (i.e.
1477 1477 revision, bytes, etc.), unit is a corresponding unit label,
1478 1478 and total is the highest expected pos.
1479 1479
1480 1480 Multiple nested topics may be active at a time.
1481 1481
1482 1482 All topics should be marked closed by setting pos to None at
1483 1483 termination.
1484 1484 '''
1485 1485 if self._progbar is not None:
1486 1486 self._progbar.progress(topic, pos, item=item, unit=unit,
1487 1487 total=total)
1488 1488 if pos is None or not self.configbool('progress', 'debug'):
1489 1489 return
1490 1490
1491 1491 if unit:
1492 1492 unit = ' ' + unit
1493 1493 if item:
1494 1494 item = ' ' + item
1495 1495
1496 1496 if total:
1497 1497 pct = 100.0 * pos / total
1498 1498 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1499 1499 % (topic, item, pos, total, unit, pct))
1500 1500 else:
1501 1501 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1502 1502
1503 1503 def log(self, service, *msg, **opts):
1504 1504 '''hook for logging facility extensions
1505 1505
1506 1506 service should be a readily-identifiable subsystem, which will
1507 1507 allow filtering.
1508 1508
1509 1509 *msg should be a newline-terminated format string to log, and
1510 1510 then any values to %-format into that format string.
1511 1511
1512 1512 **opts currently has no defined meanings.
1513 1513 '''
1514 1514
1515 1515 def label(self, msg, label):
1516 1516 '''style msg based on supplied label
1517 1517
1518 1518 If some color mode is enabled, this will add the necessary control
1519 1519 characters to apply such color. In addition, 'debug' color mode adds
1520 1520 markup showing which label affects a piece of text.
1521 1521
1522 1522 ui.write(s, 'label') is equivalent to
1523 1523 ui.write(ui.label(s, 'label')).
1524 1524 '''
1525 1525 if self._colormode is not None:
1526 1526 return color.colorlabel(self, msg, label)
1527 1527 return msg
1528 1528
1529 1529 def develwarn(self, msg, stacklevel=1, config=None):
1530 1530 """issue a developer warning message
1531 1531
1532 1532 Use 'stacklevel' to report the offender some layers further up in the
1533 1533 stack.
1534 1534 """
1535 1535 if not self.configbool('devel', 'all-warnings'):
1536 1536 if config is not None and not self.configbool('devel', config):
1537 1537 return
1538 1538 msg = 'devel-warn: ' + msg
1539 1539 stacklevel += 1 # get in develwarn
1540 1540 if self.tracebackflag:
1541 1541 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1542 1542 self.log('develwarn', '%s at:\n%s' %
1543 1543 (msg, ''.join(util.getstackframes(stacklevel))))
1544 1544 else:
1545 1545 curframe = inspect.currentframe()
1546 1546 calframe = inspect.getouterframes(curframe, 2)
1547 1547 self.write_err('%s at: %s:%s (%s)\n'
1548 1548 % ((msg,) + calframe[stacklevel][1:4]))
1549 1549 self.log('develwarn', '%s at: %s:%s (%s)\n',
1550 1550 msg, *calframe[stacklevel][1:4])
1551 1551 curframe = calframe = None # avoid cycles
1552 1552
1553 1553 def deprecwarn(self, msg, version):
1554 1554 """issue a deprecation warning
1555 1555
1556 1556 - msg: message explaining what is deprecated and how to upgrade,
1557 1557 - version: last version where the API will be supported,
1558 1558 """
1559 1559 if not (self.configbool('devel', 'all-warnings')
1560 1560 or self.configbool('devel', 'deprec-warn')):
1561 1561 return
1562 1562 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1563 1563 " update your code.)") % version
1564 1564 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1565 1565
1566 1566 def exportableenviron(self):
1567 1567 """The environment variables that are safe to export, e.g. through
1568 1568 hgweb.
1569 1569 """
1570 1570 return self._exportableenviron
1571 1571
1572 1572 @contextlib.contextmanager
1573 1573 def configoverride(self, overrides, source=""):
1574 1574 """Context manager for temporary config overrides
1575 1575 `overrides` must be a dict of the following structure:
1576 1576 {(section, name) : value}"""
1577 1577 backups = {}
1578 1578 try:
1579 1579 for (section, name), value in overrides.items():
1580 1580 backups[(section, name)] = self.backupconfig(section, name)
1581 1581 self.setconfig(section, name, value, source)
1582 1582 yield
1583 1583 finally:
1584 1584 for __, backup in backups.items():
1585 1585 self.restoreconfig(backup)
1586 1586 # just restoring ui.quiet config to the previous value is not enough
1587 1587 # as it does not update ui.quiet class member
1588 1588 if ('ui', 'quiet') in overrides:
1589 1589 self.fixconfig(section='ui')
1590 1590
1591 1591 class paths(dict):
1592 1592 """Represents a collection of paths and their configs.
1593 1593
1594 1594 Data is initially derived from ui instances and the config files they have
1595 1595 loaded.
1596 1596 """
1597 1597 def __init__(self, ui):
1598 1598 dict.__init__(self)
1599 1599
1600 1600 for name, loc in ui.configitems('paths', ignoresub=True):
1601 1601 # No location is the same as not existing.
1602 1602 if not loc:
1603 1603 continue
1604 1604 loc, sub = ui.configsuboptions('paths', name)
1605 1605 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1606 1606
1607 1607 def getpath(self, name, default=None):
1608 1608 """Return a ``path`` from a string, falling back to default.
1609 1609
1610 1610 ``name`` can be a named path or locations. Locations are filesystem
1611 1611 paths or URIs.
1612 1612
1613 1613 Returns None if ``name`` is not a registered path, a URI, or a local
1614 1614 path to a repo.
1615 1615 """
1616 1616 # Only fall back to default if no path was requested.
1617 1617 if name is None:
1618 1618 if not default:
1619 1619 default = ()
1620 1620 elif not isinstance(default, (tuple, list)):
1621 1621 default = (default,)
1622 1622 for k in default:
1623 1623 try:
1624 1624 return self[k]
1625 1625 except KeyError:
1626 1626 continue
1627 1627 return None
1628 1628
1629 1629 # Most likely empty string.
1630 1630 # This may need to raise in the future.
1631 1631 if not name:
1632 1632 return None
1633 1633
1634 1634 try:
1635 1635 return self[name]
1636 1636 except KeyError:
1637 1637 # Try to resolve as a local path or URI.
1638 1638 try:
1639 1639 # We don't pass sub-options in, so no need to pass ui instance.
1640 1640 return path(None, None, rawloc=name)
1641 1641 except ValueError:
1642 1642 raise error.RepoError(_('repository %s does not exist') %
1643 1643 name)
1644 1644
1645 1645 _pathsuboptions = {}
1646 1646
1647 1647 def pathsuboption(option, attr):
1648 1648 """Decorator used to declare a path sub-option.
1649 1649
1650 1650 Arguments are the sub-option name and the attribute it should set on
1651 1651 ``path`` instances.
1652 1652
1653 1653 The decorated function will receive as arguments a ``ui`` instance,
1654 1654 ``path`` instance, and the string value of this option from the config.
1655 1655 The function should return the value that will be set on the ``path``
1656 1656 instance.
1657 1657
1658 1658 This decorator can be used to perform additional verification of
1659 1659 sub-options and to change the type of sub-options.
1660 1660 """
1661 1661 def register(func):
1662 1662 _pathsuboptions[option] = (attr, func)
1663 1663 return func
1664 1664 return register
1665 1665
1666 1666 @pathsuboption('pushurl', 'pushloc')
1667 1667 def pushurlpathoption(ui, path, value):
1668 1668 u = util.url(value)
1669 1669 # Actually require a URL.
1670 1670 if not u.scheme:
1671 1671 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1672 1672 return None
1673 1673
1674 1674 # Don't support the #foo syntax in the push URL to declare branch to
1675 1675 # push.
1676 1676 if u.fragment:
1677 1677 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1678 1678 'ignoring)\n') % path.name)
1679 1679 u.fragment = None
1680 1680
1681 1681 return str(u)
1682 1682
1683 1683 @pathsuboption('pushrev', 'pushrev')
1684 1684 def pushrevpathoption(ui, path, value):
1685 1685 return value
1686 1686
1687 1687 class path(object):
1688 1688 """Represents an individual path and its configuration."""
1689 1689
1690 1690 def __init__(self, ui, name, rawloc=None, suboptions=None):
1691 1691 """Construct a path from its config options.
1692 1692
1693 1693 ``ui`` is the ``ui`` instance the path is coming from.
1694 1694 ``name`` is the symbolic name of the path.
1695 1695 ``rawloc`` is the raw location, as defined in the config.
1696 1696 ``pushloc`` is the raw locations pushes should be made to.
1697 1697
1698 1698 If ``name`` is not defined, we require that the location be a) a local
1699 1699 filesystem path with a .hg directory or b) a URL. If not,
1700 1700 ``ValueError`` is raised.
1701 1701 """
1702 1702 if not rawloc:
1703 1703 raise ValueError('rawloc must be defined')
1704 1704
1705 1705 # Locations may define branches via syntax <base>#<branch>.
1706 1706 u = util.url(rawloc)
1707 1707 branch = None
1708 1708 if u.fragment:
1709 1709 branch = u.fragment
1710 1710 u.fragment = None
1711 1711
1712 1712 self.url = u
1713 1713 self.branch = branch
1714 1714
1715 1715 self.name = name
1716 1716 self.rawloc = rawloc
1717 1717 self.loc = '%s' % u
1718 1718
1719 1719 # When given a raw location but not a symbolic name, validate the
1720 1720 # location is valid.
1721 1721 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1722 1722 raise ValueError('location is not a URL or path to a local '
1723 1723 'repo: %s' % rawloc)
1724 1724
1725 1725 suboptions = suboptions or {}
1726 1726
1727 1727 # Now process the sub-options. If a sub-option is registered, its
1728 1728 # attribute will always be present. The value will be None if there
1729 1729 # was no valid sub-option.
1730 1730 for suboption, (attr, func) in _pathsuboptions.iteritems():
1731 1731 if suboption not in suboptions:
1732 1732 setattr(self, attr, None)
1733 1733 continue
1734 1734
1735 1735 value = func(ui, self, suboptions[suboption])
1736 1736 setattr(self, attr, value)
1737 1737
1738 1738 def _isvalidlocalpath(self, path):
1739 1739 """Returns True if the given path is a potentially valid repository.
1740 1740 This is its own function so that extensions can change the definition of
1741 1741 'valid' in this case (like when pulling from a git repo into a hg
1742 1742 one)."""
1743 1743 return os.path.isdir(os.path.join(path, '.hg'))
1744 1744
1745 1745 @property
1746 1746 def suboptions(self):
1747 1747 """Return sub-options and their values for this path.
1748 1748
1749 1749 This is intended to be used for presentation purposes.
1750 1750 """
1751 1751 d = {}
1752 1752 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1753 1753 value = getattr(self, attr)
1754 1754 if value is not None:
1755 1755 d[subopt] = value
1756 1756 return d
1757 1757
1758 1758 # we instantiate one globally shared progress bar to avoid
1759 1759 # competing progress bars when multiple UI objects get created
1760 1760 _progresssingleton = None
1761 1761
1762 1762 def getprogbar(ui):
1763 1763 global _progresssingleton
1764 1764 if _progresssingleton is None:
1765 1765 # passing 'ui' object to the singleton is fishy,
1766 1766 # this is how the extension used to work but feel free to rework it.
1767 1767 _progresssingleton = progress.progbar(ui)
1768 1768 return _progresssingleton
@@ -1,215 +1,216
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 196 Test warning on config option access and registration
197 197
198 198 $ cat << EOF > ${TESTTMP}/buggyconfig.py
199 199 > """A small extension that tests our developer warnings for config"""
200 200 >
201 201 > from mercurial import registrar
202 202 >
203 203 > cmdtable = {}
204 204 > command = registrar.command(cmdtable)
205 205 >
206 206 > @command('buggyconfig')
207 207 > def cmdbuggyconfig(ui, repo):
208 208 > repo.ui.config('ui', 'quiet', False)
209 209 > repo.ui.config('ui', 'interactive', None)
210 210 > EOF
211 211
212 212 $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig
213 213 devel-warn: specifying a default value for a registered config item: 'ui.quiet' 'False' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
214 devel-warn: specifying a default value for a registered config item: 'ui.interactive' 'None' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
214 215
215 216 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now