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