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