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