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