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