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