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