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