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