##// END OF EJS Templates
ui: convert to/from Unicode on Python 3 in ui.editor()...
Augie Fackler -
r31532:713e984b default
parent child Browse files
Show More
@@ -1,1613 +1,1613
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 f.write(text)
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 t = f.read()
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 1238 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1239 1239 out = self.fout
1240 1240 if any(s[1] for s in self._bufferstates):
1241 1241 out = self
1242 1242 with self.timeblockedsection(blockedtag):
1243 1243 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1244 1244 if rc and onerr:
1245 1245 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1246 1246 util.explainexit(rc)[0])
1247 1247 if errprefix:
1248 1248 errmsg = '%s: %s' % (errprefix, errmsg)
1249 1249 raise onerr(errmsg)
1250 1250 return rc
1251 1251
1252 1252 def _runsystem(self, cmd, environ, cwd, out):
1253 1253 """actually execute the given shell command (can be overridden by
1254 1254 extensions like chg)"""
1255 1255 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1256 1256
1257 1257 def traceback(self, exc=None, force=False):
1258 1258 '''print exception traceback if traceback printing enabled or forced.
1259 1259 only to call in exception handler. returns true if traceback
1260 1260 printed.'''
1261 1261 if self.tracebackflag or force:
1262 1262 if exc is None:
1263 1263 exc = sys.exc_info()
1264 1264 cause = getattr(exc[1], 'cause', None)
1265 1265
1266 1266 if cause is not None:
1267 1267 causetb = traceback.format_tb(cause[2])
1268 1268 exctb = traceback.format_tb(exc[2])
1269 1269 exconly = traceback.format_exception_only(cause[0], cause[1])
1270 1270
1271 1271 # exclude frame where 'exc' was chained and rethrown from exctb
1272 1272 self.write_err('Traceback (most recent call last):\n',
1273 1273 ''.join(exctb[:-1]),
1274 1274 ''.join(causetb),
1275 1275 ''.join(exconly))
1276 1276 else:
1277 1277 output = traceback.format_exception(exc[0], exc[1], exc[2])
1278 1278 data = r''.join(output)
1279 1279 if pycompat.ispy3:
1280 1280 enc = pycompat.sysstr(encoding.encoding)
1281 1281 data = data.encode(enc, errors=r'replace')
1282 1282 self.write_err(data)
1283 1283 return self.tracebackflag or force
1284 1284
1285 1285 def geteditor(self):
1286 1286 '''return editor to use'''
1287 1287 if pycompat.sysplatform == 'plan9':
1288 1288 # vi is the MIPS instruction simulator on Plan 9. We
1289 1289 # instead default to E to plumb commit messages to
1290 1290 # avoid confusion.
1291 1291 editor = 'E'
1292 1292 else:
1293 1293 editor = 'vi'
1294 1294 return (encoding.environ.get("HGEDITOR") or
1295 1295 self.config("ui", "editor") or
1296 1296 encoding.environ.get("VISUAL") or
1297 1297 encoding.environ.get("EDITOR", editor))
1298 1298
1299 1299 @util.propertycache
1300 1300 def _progbar(self):
1301 1301 """setup the progbar singleton to the ui object"""
1302 1302 if (self.quiet or self.debugflag
1303 1303 or self.configbool('progress', 'disable', False)
1304 1304 or not progress.shouldprint(self)):
1305 1305 return None
1306 1306 return getprogbar(self)
1307 1307
1308 1308 def _progclear(self):
1309 1309 """clear progress bar output if any. use it before any output"""
1310 1310 if '_progbar' not in vars(self): # nothing loaded yet
1311 1311 return
1312 1312 if self._progbar is not None and self._progbar.printed:
1313 1313 self._progbar.clear()
1314 1314
1315 1315 def progress(self, topic, pos, item="", unit="", total=None):
1316 1316 '''show a progress message
1317 1317
1318 1318 By default a textual progress bar will be displayed if an operation
1319 1319 takes too long. 'topic' is the current operation, 'item' is a
1320 1320 non-numeric marker of the current position (i.e. the currently
1321 1321 in-process file), 'pos' is the current numeric position (i.e.
1322 1322 revision, bytes, etc.), unit is a corresponding unit label,
1323 1323 and total is the highest expected pos.
1324 1324
1325 1325 Multiple nested topics may be active at a time.
1326 1326
1327 1327 All topics should be marked closed by setting pos to None at
1328 1328 termination.
1329 1329 '''
1330 1330 if self._progbar is not None:
1331 1331 self._progbar.progress(topic, pos, item=item, unit=unit,
1332 1332 total=total)
1333 1333 if pos is None or not self.configbool('progress', 'debug'):
1334 1334 return
1335 1335
1336 1336 if unit:
1337 1337 unit = ' ' + unit
1338 1338 if item:
1339 1339 item = ' ' + item
1340 1340
1341 1341 if total:
1342 1342 pct = 100.0 * pos / total
1343 1343 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1344 1344 % (topic, item, pos, total, unit, pct))
1345 1345 else:
1346 1346 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1347 1347
1348 1348 def log(self, service, *msg, **opts):
1349 1349 '''hook for logging facility extensions
1350 1350
1351 1351 service should be a readily-identifiable subsystem, which will
1352 1352 allow filtering.
1353 1353
1354 1354 *msg should be a newline-terminated format string to log, and
1355 1355 then any values to %-format into that format string.
1356 1356
1357 1357 **opts currently has no defined meanings.
1358 1358 '''
1359 1359
1360 1360 def label(self, msg, label):
1361 1361 '''style msg based on supplied label
1362 1362
1363 1363 If some color mode is enabled, this will add the necessary control
1364 1364 characters to apply such color. In addition, 'debug' color mode adds
1365 1365 markup showing which label affects a piece of text.
1366 1366
1367 1367 ui.write(s, 'label') is equivalent to
1368 1368 ui.write(ui.label(s, 'label')).
1369 1369 '''
1370 1370 if self._colormode is not None:
1371 1371 return color.colorlabel(self, msg, label)
1372 1372 return msg
1373 1373
1374 1374 def develwarn(self, msg, stacklevel=1, config=None):
1375 1375 """issue a developer warning message
1376 1376
1377 1377 Use 'stacklevel' to report the offender some layers further up in the
1378 1378 stack.
1379 1379 """
1380 1380 if not self.configbool('devel', 'all-warnings'):
1381 1381 if config is not None and not self.configbool('devel', config):
1382 1382 return
1383 1383 msg = 'devel-warn: ' + msg
1384 1384 stacklevel += 1 # get in develwarn
1385 1385 if self.tracebackflag:
1386 1386 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1387 1387 self.log('develwarn', '%s at:\n%s' %
1388 1388 (msg, ''.join(util.getstackframes(stacklevel))))
1389 1389 else:
1390 1390 curframe = inspect.currentframe()
1391 1391 calframe = inspect.getouterframes(curframe, 2)
1392 1392 self.write_err('%s at: %s:%s (%s)\n'
1393 1393 % ((msg,) + calframe[stacklevel][1:4]))
1394 1394 self.log('develwarn', '%s at: %s:%s (%s)\n',
1395 1395 msg, *calframe[stacklevel][1:4])
1396 1396 curframe = calframe = None # avoid cycles
1397 1397
1398 1398 def deprecwarn(self, msg, version):
1399 1399 """issue a deprecation warning
1400 1400
1401 1401 - msg: message explaining what is deprecated and how to upgrade,
1402 1402 - version: last version where the API will be supported,
1403 1403 """
1404 1404 if not (self.configbool('devel', 'all-warnings')
1405 1405 or self.configbool('devel', 'deprec-warn')):
1406 1406 return
1407 1407 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1408 1408 " update your code.)") % version
1409 1409 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1410 1410
1411 1411 def exportableenviron(self):
1412 1412 """The environment variables that are safe to export, e.g. through
1413 1413 hgweb.
1414 1414 """
1415 1415 return self._exportableenviron
1416 1416
1417 1417 @contextlib.contextmanager
1418 1418 def configoverride(self, overrides, source=""):
1419 1419 """Context manager for temporary config overrides
1420 1420 `overrides` must be a dict of the following structure:
1421 1421 {(section, name) : value}"""
1422 1422 backups = {}
1423 1423 try:
1424 1424 for (section, name), value in overrides.items():
1425 1425 backups[(section, name)] = self.backupconfig(section, name)
1426 1426 self.setconfig(section, name, value, source)
1427 1427 yield
1428 1428 finally:
1429 1429 for __, backup in backups.items():
1430 1430 self.restoreconfig(backup)
1431 1431 # just restoring ui.quiet config to the previous value is not enough
1432 1432 # as it does not update ui.quiet class member
1433 1433 if ('ui', 'quiet') in overrides:
1434 1434 self.fixconfig(section='ui')
1435 1435
1436 1436 class paths(dict):
1437 1437 """Represents a collection of paths and their configs.
1438 1438
1439 1439 Data is initially derived from ui instances and the config files they have
1440 1440 loaded.
1441 1441 """
1442 1442 def __init__(self, ui):
1443 1443 dict.__init__(self)
1444 1444
1445 1445 for name, loc in ui.configitems('paths', ignoresub=True):
1446 1446 # No location is the same as not existing.
1447 1447 if not loc:
1448 1448 continue
1449 1449 loc, sub = ui.configsuboptions('paths', name)
1450 1450 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1451 1451
1452 1452 def getpath(self, name, default=None):
1453 1453 """Return a ``path`` from a string, falling back to default.
1454 1454
1455 1455 ``name`` can be a named path or locations. Locations are filesystem
1456 1456 paths or URIs.
1457 1457
1458 1458 Returns None if ``name`` is not a registered path, a URI, or a local
1459 1459 path to a repo.
1460 1460 """
1461 1461 # Only fall back to default if no path was requested.
1462 1462 if name is None:
1463 1463 if not default:
1464 1464 default = ()
1465 1465 elif not isinstance(default, (tuple, list)):
1466 1466 default = (default,)
1467 1467 for k in default:
1468 1468 try:
1469 1469 return self[k]
1470 1470 except KeyError:
1471 1471 continue
1472 1472 return None
1473 1473
1474 1474 # Most likely empty string.
1475 1475 # This may need to raise in the future.
1476 1476 if not name:
1477 1477 return None
1478 1478
1479 1479 try:
1480 1480 return self[name]
1481 1481 except KeyError:
1482 1482 # Try to resolve as a local path or URI.
1483 1483 try:
1484 1484 # We don't pass sub-options in, so no need to pass ui instance.
1485 1485 return path(None, None, rawloc=name)
1486 1486 except ValueError:
1487 1487 raise error.RepoError(_('repository %s does not exist') %
1488 1488 name)
1489 1489
1490 1490 _pathsuboptions = {}
1491 1491
1492 1492 def pathsuboption(option, attr):
1493 1493 """Decorator used to declare a path sub-option.
1494 1494
1495 1495 Arguments are the sub-option name and the attribute it should set on
1496 1496 ``path`` instances.
1497 1497
1498 1498 The decorated function will receive as arguments a ``ui`` instance,
1499 1499 ``path`` instance, and the string value of this option from the config.
1500 1500 The function should return the value that will be set on the ``path``
1501 1501 instance.
1502 1502
1503 1503 This decorator can be used to perform additional verification of
1504 1504 sub-options and to change the type of sub-options.
1505 1505 """
1506 1506 def register(func):
1507 1507 _pathsuboptions[option] = (attr, func)
1508 1508 return func
1509 1509 return register
1510 1510
1511 1511 @pathsuboption('pushurl', 'pushloc')
1512 1512 def pushurlpathoption(ui, path, value):
1513 1513 u = util.url(value)
1514 1514 # Actually require a URL.
1515 1515 if not u.scheme:
1516 1516 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1517 1517 return None
1518 1518
1519 1519 # Don't support the #foo syntax in the push URL to declare branch to
1520 1520 # push.
1521 1521 if u.fragment:
1522 1522 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1523 1523 'ignoring)\n') % path.name)
1524 1524 u.fragment = None
1525 1525
1526 1526 return str(u)
1527 1527
1528 1528 @pathsuboption('pushrev', 'pushrev')
1529 1529 def pushrevpathoption(ui, path, value):
1530 1530 return value
1531 1531
1532 1532 class path(object):
1533 1533 """Represents an individual path and its configuration."""
1534 1534
1535 1535 def __init__(self, ui, name, rawloc=None, suboptions=None):
1536 1536 """Construct a path from its config options.
1537 1537
1538 1538 ``ui`` is the ``ui`` instance the path is coming from.
1539 1539 ``name`` is the symbolic name of the path.
1540 1540 ``rawloc`` is the raw location, as defined in the config.
1541 1541 ``pushloc`` is the raw locations pushes should be made to.
1542 1542
1543 1543 If ``name`` is not defined, we require that the location be a) a local
1544 1544 filesystem path with a .hg directory or b) a URL. If not,
1545 1545 ``ValueError`` is raised.
1546 1546 """
1547 1547 if not rawloc:
1548 1548 raise ValueError('rawloc must be defined')
1549 1549
1550 1550 # Locations may define branches via syntax <base>#<branch>.
1551 1551 u = util.url(rawloc)
1552 1552 branch = None
1553 1553 if u.fragment:
1554 1554 branch = u.fragment
1555 1555 u.fragment = None
1556 1556
1557 1557 self.url = u
1558 1558 self.branch = branch
1559 1559
1560 1560 self.name = name
1561 1561 self.rawloc = rawloc
1562 1562 self.loc = '%s' % u
1563 1563
1564 1564 # When given a raw location but not a symbolic name, validate the
1565 1565 # location is valid.
1566 1566 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1567 1567 raise ValueError('location is not a URL or path to a local '
1568 1568 'repo: %s' % rawloc)
1569 1569
1570 1570 suboptions = suboptions or {}
1571 1571
1572 1572 # Now process the sub-options. If a sub-option is registered, its
1573 1573 # attribute will always be present. The value will be None if there
1574 1574 # was no valid sub-option.
1575 1575 for suboption, (attr, func) in _pathsuboptions.iteritems():
1576 1576 if suboption not in suboptions:
1577 1577 setattr(self, attr, None)
1578 1578 continue
1579 1579
1580 1580 value = func(ui, self, suboptions[suboption])
1581 1581 setattr(self, attr, value)
1582 1582
1583 1583 def _isvalidlocalpath(self, path):
1584 1584 """Returns True if the given path is a potentially valid repository.
1585 1585 This is its own function so that extensions can change the definition of
1586 1586 'valid' in this case (like when pulling from a git repo into a hg
1587 1587 one)."""
1588 1588 return os.path.isdir(os.path.join(path, '.hg'))
1589 1589
1590 1590 @property
1591 1591 def suboptions(self):
1592 1592 """Return sub-options and their values for this path.
1593 1593
1594 1594 This is intended to be used for presentation purposes.
1595 1595 """
1596 1596 d = {}
1597 1597 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1598 1598 value = getattr(self, attr)
1599 1599 if value is not None:
1600 1600 d[subopt] = value
1601 1601 return d
1602 1602
1603 1603 # we instantiate one globally shared progress bar to avoid
1604 1604 # competing progress bars when multiple UI objects get created
1605 1605 _progresssingleton = None
1606 1606
1607 1607 def getprogbar(ui):
1608 1608 global _progresssingleton
1609 1609 if _progresssingleton is None:
1610 1610 # passing 'ui' object to the singleton is fishy,
1611 1611 # this is how the extension used to work but feel free to rework it.
1612 1612 _progresssingleton = progress.progbar(ui)
1613 1613 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now