##// END OF EJS Templates
pager: skip running the pager if it's set to 'cat'...
Augie Fackler -
r31479:96929bd6 default
parent child Browse files
Show More
@@ -1,1683 +1,1686
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
566 566 def _parse_plain(parts, s, offset):
567 567 whitespace = False
568 568 while offset < len(s) and (s[offset:offset + 1].isspace()
569 569 or s[offset:offset + 1] == ','):
570 570 whitespace = True
571 571 offset += 1
572 572 if offset >= len(s):
573 573 return None, parts, offset
574 574 if whitespace:
575 575 parts.append('')
576 576 if s[offset:offset + 1] == '"' and not parts[-1]:
577 577 return _parse_quote, parts, offset + 1
578 578 elif s[offset:offset + 1] == '"' and parts[-1][-1] == '\\':
579 579 parts[-1] = parts[-1][:-1] + s[offset:offset + 1]
580 580 return _parse_plain, parts, offset + 1
581 581 parts[-1] += s[offset:offset + 1]
582 582 return _parse_plain, parts, offset + 1
583 583
584 584 def _parse_quote(parts, s, offset):
585 585 if offset < len(s) and s[offset:offset + 1] == '"': # ""
586 586 parts.append('')
587 587 offset += 1
588 588 while offset < len(s) and (s[offset:offset + 1].isspace() or
589 589 s[offset:offset + 1] == ','):
590 590 offset += 1
591 591 return _parse_plain, parts, offset
592 592
593 593 while offset < len(s) and s[offset:offset + 1] != '"':
594 594 if (s[offset:offset + 1] == '\\' and offset + 1 < len(s)
595 595 and s[offset + 1:offset + 2] == '"'):
596 596 offset += 1
597 597 parts[-1] += '"'
598 598 else:
599 599 parts[-1] += s[offset:offset + 1]
600 600 offset += 1
601 601
602 602 if offset >= len(s):
603 603 real_parts = _configlist(parts[-1])
604 604 if not real_parts:
605 605 parts[-1] = '"'
606 606 else:
607 607 real_parts[0] = '"' + real_parts[0]
608 608 parts = parts[:-1]
609 609 parts.extend(real_parts)
610 610 return None, parts, offset
611 611
612 612 offset += 1
613 613 while offset < len(s) and s[offset:offset + 1] in [' ', ',']:
614 614 offset += 1
615 615
616 616 if offset < len(s):
617 617 if offset + 1 == len(s) and s[offset:offset + 1] == '"':
618 618 parts[-1] += '"'
619 619 offset += 1
620 620 else:
621 621 parts.append('')
622 622 else:
623 623 return None, parts, offset
624 624
625 625 return _parse_plain, parts, offset
626 626
627 627 def _configlist(s):
628 628 s = s.rstrip(' ,')
629 629 if not s:
630 630 return []
631 631 parser, parts, offset = _parse_plain, [''], 0
632 632 while parser:
633 633 parser, parts, offset = parser(parts, s, offset)
634 634 return parts
635 635
636 636 result = self.config(section, name, untrusted=untrusted)
637 637 if result is None:
638 638 result = default or []
639 639 if isinstance(result, bytes):
640 640 result = _configlist(result.lstrip(' ,\n'))
641 641 if result is None:
642 642 result = default or []
643 643 return result
644 644
645 645 def hasconfig(self, section, name, untrusted=False):
646 646 return self._data(untrusted).hasitem(section, name)
647 647
648 648 def has_section(self, section, untrusted=False):
649 649 '''tell whether section exists in config.'''
650 650 return section in self._data(untrusted)
651 651
652 652 def configitems(self, section, untrusted=False, ignoresub=False):
653 653 items = self._data(untrusted).items(section)
654 654 if ignoresub:
655 655 newitems = {}
656 656 for k, v in items:
657 657 if ':' not in k:
658 658 newitems[k] = v
659 659 items = newitems.items()
660 660 if self.debugflag and not untrusted and self._reportuntrusted:
661 661 for k, v in self._ucfg.items(section):
662 662 if self._tcfg.get(section, k) != v:
663 663 self.debug("ignoring untrusted configuration option "
664 664 "%s.%s = %s\n" % (section, k, v))
665 665 return items
666 666
667 667 def walkconfig(self, untrusted=False):
668 668 cfg = self._data(untrusted)
669 669 for section in cfg.sections():
670 670 for name, value in self.configitems(section, untrusted):
671 671 yield section, name, value
672 672
673 673 def plain(self, feature=None):
674 674 '''is plain mode active?
675 675
676 676 Plain mode means that all configuration variables which affect
677 677 the behavior and output of Mercurial should be
678 678 ignored. Additionally, the output should be stable,
679 679 reproducible and suitable for use in scripts or applications.
680 680
681 681 The only way to trigger plain mode is by setting either the
682 682 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
683 683
684 684 The return value can either be
685 685 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
686 686 - True otherwise
687 687 '''
688 688 if ('HGPLAIN' not in encoding.environ and
689 689 'HGPLAINEXCEPT' not in encoding.environ):
690 690 return False
691 691 exceptions = encoding.environ.get('HGPLAINEXCEPT',
692 692 '').strip().split(',')
693 693 if feature and exceptions:
694 694 return feature not in exceptions
695 695 return True
696 696
697 697 def username(self):
698 698 """Return default username to be used in commits.
699 699
700 700 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
701 701 and stop searching if one of these is set.
702 702 If not found and ui.askusername is True, ask the user, else use
703 703 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
704 704 """
705 705 user = encoding.environ.get("HGUSER")
706 706 if user is None:
707 707 user = self.config("ui", ["username", "user"])
708 708 if user is not None:
709 709 user = os.path.expandvars(user)
710 710 if user is None:
711 711 user = encoding.environ.get("EMAIL")
712 712 if user is None and self.configbool("ui", "askusername"):
713 713 user = self.prompt(_("enter a commit username:"), default=None)
714 714 if user is None and not self.interactive():
715 715 try:
716 716 user = '%s@%s' % (util.getuser(), socket.getfqdn())
717 717 self.warn(_("no username found, using '%s' instead\n") % user)
718 718 except KeyError:
719 719 pass
720 720 if not user:
721 721 raise error.Abort(_('no username supplied'),
722 722 hint=_("use 'hg config --edit' "
723 723 'to set your username'))
724 724 if "\n" in user:
725 725 raise error.Abort(_("username %s contains a newline\n")
726 726 % repr(user))
727 727 return user
728 728
729 729 def shortuser(self, user):
730 730 """Return a short representation of a user name or email address."""
731 731 if not self.verbose:
732 732 user = util.shortuser(user)
733 733 return user
734 734
735 735 def expandpath(self, loc, default=None):
736 736 """Return repository location relative to cwd or from [paths]"""
737 737 try:
738 738 p = self.paths.getpath(loc)
739 739 if p:
740 740 return p.rawloc
741 741 except error.RepoError:
742 742 pass
743 743
744 744 if default:
745 745 try:
746 746 p = self.paths.getpath(default)
747 747 if p:
748 748 return p.rawloc
749 749 except error.RepoError:
750 750 pass
751 751
752 752 return loc
753 753
754 754 @util.propertycache
755 755 def paths(self):
756 756 return paths(self)
757 757
758 758 def pushbuffer(self, error=False, subproc=False, labeled=False):
759 759 """install a buffer to capture standard output of the ui object
760 760
761 761 If error is True, the error output will be captured too.
762 762
763 763 If subproc is True, output from subprocesses (typically hooks) will be
764 764 captured too.
765 765
766 766 If labeled is True, any labels associated with buffered
767 767 output will be handled. By default, this has no effect
768 768 on the output returned, but extensions and GUI tools may
769 769 handle this argument and returned styled output. If output
770 770 is being buffered so it can be captured and parsed or
771 771 processed, labeled should not be set to True.
772 772 """
773 773 self._buffers.append([])
774 774 self._bufferstates.append((error, subproc, labeled))
775 775 self._bufferapplylabels = labeled
776 776
777 777 def popbuffer(self):
778 778 '''pop the last buffer and return the buffered output'''
779 779 self._bufferstates.pop()
780 780 if self._bufferstates:
781 781 self._bufferapplylabels = self._bufferstates[-1][2]
782 782 else:
783 783 self._bufferapplylabels = None
784 784
785 785 return "".join(self._buffers.pop())
786 786
787 787 def write(self, *args, **opts):
788 788 '''write args to output
789 789
790 790 By default, this method simply writes to the buffer or stdout.
791 791 Color mode can be set on the UI class to have the output decorated
792 792 with color modifier before being written to stdout.
793 793
794 794 The color used is controlled by an optional keyword argument, "label".
795 795 This should be a string containing label names separated by space.
796 796 Label names take the form of "topic.type". For example, ui.debug()
797 797 issues a label of "ui.debug".
798 798
799 799 When labeling output for a specific command, a label of
800 800 "cmdname.type" is recommended. For example, status issues
801 801 a label of "status.modified" for modified files.
802 802 '''
803 803 if self._buffers and not opts.get('prompt', False):
804 804 if self._bufferapplylabels:
805 805 label = opts.get('label', '')
806 806 self._buffers[-1].extend(self.label(a, label) for a in args)
807 807 else:
808 808 self._buffers[-1].extend(args)
809 809 elif self._colormode == 'win32':
810 810 # windows color printing is its own can of crab, defer to
811 811 # the color module and that is it.
812 812 color.win32print(self, self._write, *args, **opts)
813 813 else:
814 814 msgs = args
815 815 if self._colormode is not None:
816 816 label = opts.get('label', '')
817 817 msgs = [self.label(a, label) for a in args]
818 818 self._write(*msgs, **opts)
819 819
820 820 def _write(self, *msgs, **opts):
821 821 self._progclear()
822 822 # opencode timeblockedsection because this is a critical path
823 823 starttime = util.timer()
824 824 try:
825 825 for a in msgs:
826 826 self.fout.write(a)
827 827 finally:
828 828 self._blockedtimes['stdio_blocked'] += \
829 829 (util.timer() - starttime) * 1000
830 830
831 831 def write_err(self, *args, **opts):
832 832 self._progclear()
833 833 if self._bufferstates and self._bufferstates[-1][0]:
834 834 self.write(*args, **opts)
835 835 elif self._colormode == 'win32':
836 836 # windows color printing is its own can of crab, defer to
837 837 # the color module and that is it.
838 838 color.win32print(self, self._write_err, *args, **opts)
839 839 else:
840 840 msgs = args
841 841 if self._colormode is not None:
842 842 label = opts.get('label', '')
843 843 msgs = [self.label(a, label) for a in args]
844 844 self._write_err(*msgs, **opts)
845 845
846 846 def _write_err(self, *msgs, **opts):
847 847 try:
848 848 with self.timeblockedsection('stdio'):
849 849 if not getattr(self.fout, 'closed', False):
850 850 self.fout.flush()
851 851 for a in msgs:
852 852 self.ferr.write(a)
853 853 # stderr may be buffered under win32 when redirected to files,
854 854 # including stdout.
855 855 if not getattr(self.ferr, 'closed', False):
856 856 self.ferr.flush()
857 857 except IOError as inst:
858 858 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
859 859 raise
860 860
861 861 def flush(self):
862 862 # opencode timeblockedsection because this is a critical path
863 863 starttime = util.timer()
864 864 try:
865 865 try: self.fout.flush()
866 866 except (IOError, ValueError): pass
867 867 try: self.ferr.flush()
868 868 except (IOError, ValueError): pass
869 869 finally:
870 870 self._blockedtimes['stdio_blocked'] += \
871 871 (util.timer() - starttime) * 1000
872 872
873 873 def _isatty(self, fh):
874 874 if self.configbool('ui', 'nontty', False):
875 875 return False
876 876 return util.isatty(fh)
877 877
878 878 def disablepager(self):
879 879 self._disablepager = True
880 880
881 881 def pager(self, command):
882 882 """Start a pager for subsequent command output.
883 883
884 884 Commands which produce a long stream of output should call
885 885 this function to activate the user's preferred pagination
886 886 mechanism (which may be no pager). Calling this function
887 887 precludes any future use of interactive functionality, such as
888 888 prompting the user or activating curses.
889 889
890 890 Args:
891 891 command: The full, non-aliased name of the command. That is, "log"
892 892 not "history, "summary" not "summ", etc.
893 893 """
894 894 if (self._disablepager
895 895 or self.pageractive
896 896 or command in self.configlist('pager', 'ignore')
897 897 or not self.configbool('pager', 'enable', True)
898 898 or not self.configbool('pager', 'attend-' + command, True)
899 899 # TODO: if we want to allow HGPLAINEXCEPT=pager,
900 900 # formatted() will need some adjustment.
901 901 or not self.formatted()
902 902 or self.plain()
903 903 # TODO: expose debugger-enabled on the UI object
904 904 or '--debugger' in pycompat.sysargv):
905 905 # We only want to paginate if the ui appears to be
906 906 # interactive, the user didn't say HGPLAIN or
907 907 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
908 908 return
909 909
910 910 # TODO: add a "system defaults" config section so this default
911 911 # of more(1) can be easily replaced with a global
912 912 # configuration file. For example, on OS X the sane default is
913 913 # less(1), not more(1), and on debian it's
914 914 # sensible-pager(1). We should probably also give the system
915 915 # default editor command similar treatment.
916 916 envpager = encoding.environ.get('PAGER', 'more')
917 917 pagercmd = self.config('pager', 'pager', envpager)
918 918 if not pagercmd:
919 919 return
920 920
921 921 self.debug('starting pager for command %r\n' % command)
922 922 self.pageractive = True
923 923 # Preserve the formatted-ness of the UI. This is important
924 924 # because we mess with stdout, which might confuse
925 925 # auto-detection of things being formatted.
926 926 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
927 927 self.setconfig('ui', 'interactive', False, 'pager')
928 928 if util.safehasattr(signal, "SIGPIPE"):
929 929 signal.signal(signal.SIGPIPE, _catchterm)
930 930 self._runpager(pagercmd)
931 931
932 932 def _runpager(self, command):
933 933 """Actually start the pager and set up file descriptors.
934 934
935 935 This is separate in part so that extensions (like chg) can
936 936 override how a pager is invoked.
937 937 """
938 if command == 'cat':
939 # Save ourselves some work.
940 return
938 941 # If the command doesn't contain any of these characters, we
939 942 # assume it's a binary and exec it directly. This means for
940 943 # simple pager command configurations, we can degrade
941 944 # gracefully and tell the user about their broken pager.
942 945 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
943 946 try:
944 947 pager = subprocess.Popen(
945 948 command, shell=shell, bufsize=-1,
946 949 close_fds=util.closefds, stdin=subprocess.PIPE,
947 950 stdout=util.stdout, stderr=util.stderr)
948 951 except OSError as e:
949 952 if e.errno == errno.ENOENT and not shell:
950 953 self.warn(_("missing pager command '%s', skipping pager\n")
951 954 % command)
952 955 return
953 956 raise
954 957
955 958 # back up original file descriptors
956 959 stdoutfd = os.dup(util.stdout.fileno())
957 960 stderrfd = os.dup(util.stderr.fileno())
958 961
959 962 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
960 963 if self._isatty(util.stderr):
961 964 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
962 965
963 966 @atexit.register
964 967 def killpager():
965 968 if util.safehasattr(signal, "SIGINT"):
966 969 signal.signal(signal.SIGINT, signal.SIG_IGN)
967 970 # restore original fds, closing pager.stdin copies in the process
968 971 os.dup2(stdoutfd, util.stdout.fileno())
969 972 os.dup2(stderrfd, util.stderr.fileno())
970 973 pager.stdin.close()
971 974 pager.wait()
972 975
973 976 def interface(self, feature):
974 977 """what interface to use for interactive console features?
975 978
976 979 The interface is controlled by the value of `ui.interface` but also by
977 980 the value of feature-specific configuration. For example:
978 981
979 982 ui.interface.histedit = text
980 983 ui.interface.chunkselector = curses
981 984
982 985 Here the features are "histedit" and "chunkselector".
983 986
984 987 The configuration above means that the default interfaces for commands
985 988 is curses, the interface for histedit is text and the interface for
986 989 selecting chunk is crecord (the best curses interface available).
987 990
988 991 Consider the following example:
989 992 ui.interface = curses
990 993 ui.interface.histedit = text
991 994
992 995 Then histedit will use the text interface and chunkselector will use
993 996 the default curses interface (crecord at the moment).
994 997 """
995 998 alldefaults = frozenset(["text", "curses"])
996 999
997 1000 featureinterfaces = {
998 1001 "chunkselector": [
999 1002 "text",
1000 1003 "curses",
1001 1004 ]
1002 1005 }
1003 1006
1004 1007 # Feature-specific interface
1005 1008 if feature not in featureinterfaces.keys():
1006 1009 # Programming error, not user error
1007 1010 raise ValueError("Unknown feature requested %s" % feature)
1008 1011
1009 1012 availableinterfaces = frozenset(featureinterfaces[feature])
1010 1013 if alldefaults > availableinterfaces:
1011 1014 # Programming error, not user error. We need a use case to
1012 1015 # define the right thing to do here.
1013 1016 raise ValueError(
1014 1017 "Feature %s does not handle all default interfaces" %
1015 1018 feature)
1016 1019
1017 1020 if self.plain():
1018 1021 return "text"
1019 1022
1020 1023 # Default interface for all the features
1021 1024 defaultinterface = "text"
1022 1025 i = self.config("ui", "interface", None)
1023 1026 if i in alldefaults:
1024 1027 defaultinterface = i
1025 1028
1026 1029 choseninterface = defaultinterface
1027 1030 f = self.config("ui", "interface.%s" % feature, None)
1028 1031 if f in availableinterfaces:
1029 1032 choseninterface = f
1030 1033
1031 1034 if i is not None and defaultinterface != i:
1032 1035 if f is not None:
1033 1036 self.warn(_("invalid value for ui.interface: %s\n") %
1034 1037 (i,))
1035 1038 else:
1036 1039 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1037 1040 (i, choseninterface))
1038 1041 if f is not None and choseninterface != f:
1039 1042 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1040 1043 (feature, f, choseninterface))
1041 1044
1042 1045 return choseninterface
1043 1046
1044 1047 def interactive(self):
1045 1048 '''is interactive input allowed?
1046 1049
1047 1050 An interactive session is a session where input can be reasonably read
1048 1051 from `sys.stdin'. If this function returns false, any attempt to read
1049 1052 from stdin should fail with an error, unless a sensible default has been
1050 1053 specified.
1051 1054
1052 1055 Interactiveness is triggered by the value of the `ui.interactive'
1053 1056 configuration variable or - if it is unset - when `sys.stdin' points
1054 1057 to a terminal device.
1055 1058
1056 1059 This function refers to input only; for output, see `ui.formatted()'.
1057 1060 '''
1058 1061 i = self.configbool("ui", "interactive", None)
1059 1062 if i is None:
1060 1063 # some environments replace stdin without implementing isatty
1061 1064 # usually those are non-interactive
1062 1065 return self._isatty(self.fin)
1063 1066
1064 1067 return i
1065 1068
1066 1069 def termwidth(self):
1067 1070 '''how wide is the terminal in columns?
1068 1071 '''
1069 1072 if 'COLUMNS' in encoding.environ:
1070 1073 try:
1071 1074 return int(encoding.environ['COLUMNS'])
1072 1075 except ValueError:
1073 1076 pass
1074 1077 return scmutil.termsize(self)[0]
1075 1078
1076 1079 def formatted(self):
1077 1080 '''should formatted output be used?
1078 1081
1079 1082 It is often desirable to format the output to suite the output medium.
1080 1083 Examples of this are truncating long lines or colorizing messages.
1081 1084 However, this is not often not desirable when piping output into other
1082 1085 utilities, e.g. `grep'.
1083 1086
1084 1087 Formatted output is triggered by the value of the `ui.formatted'
1085 1088 configuration variable or - if it is unset - when `sys.stdout' points
1086 1089 to a terminal device. Please note that `ui.formatted' should be
1087 1090 considered an implementation detail; it is not intended for use outside
1088 1091 Mercurial or its extensions.
1089 1092
1090 1093 This function refers to output only; for input, see `ui.interactive()'.
1091 1094 This function always returns false when in plain mode, see `ui.plain()'.
1092 1095 '''
1093 1096 if self.plain():
1094 1097 return False
1095 1098
1096 1099 i = self.configbool("ui", "formatted", None)
1097 1100 if i is None:
1098 1101 # some environments replace stdout without implementing isatty
1099 1102 # usually those are non-interactive
1100 1103 return self._isatty(self.fout)
1101 1104
1102 1105 return i
1103 1106
1104 1107 def _readline(self, prompt=''):
1105 1108 if self._isatty(self.fin):
1106 1109 try:
1107 1110 # magically add command line editing support, where
1108 1111 # available
1109 1112 import readline
1110 1113 # force demandimport to really load the module
1111 1114 readline.read_history_file
1112 1115 # windows sometimes raises something other than ImportError
1113 1116 except Exception:
1114 1117 pass
1115 1118
1116 1119 # call write() so output goes through subclassed implementation
1117 1120 # e.g. color extension on Windows
1118 1121 self.write(prompt, prompt=True)
1119 1122
1120 1123 # instead of trying to emulate raw_input, swap (self.fin,
1121 1124 # self.fout) with (sys.stdin, sys.stdout)
1122 1125 oldin = sys.stdin
1123 1126 oldout = sys.stdout
1124 1127 sys.stdin = self.fin
1125 1128 sys.stdout = self.fout
1126 1129 # prompt ' ' must exist; otherwise readline may delete entire line
1127 1130 # - http://bugs.python.org/issue12833
1128 1131 with self.timeblockedsection('stdio'):
1129 1132 line = raw_input(' ')
1130 1133 sys.stdin = oldin
1131 1134 sys.stdout = oldout
1132 1135
1133 1136 # When stdin is in binary mode on Windows, it can cause
1134 1137 # raw_input() to emit an extra trailing carriage return
1135 1138 if os.linesep == '\r\n' and line and line[-1] == '\r':
1136 1139 line = line[:-1]
1137 1140 return line
1138 1141
1139 1142 def prompt(self, msg, default="y"):
1140 1143 """Prompt user with msg, read response.
1141 1144 If ui is not interactive, the default is returned.
1142 1145 """
1143 1146 if not self.interactive():
1144 1147 self.write(msg, ' ', default or '', "\n")
1145 1148 return default
1146 1149 try:
1147 1150 r = self._readline(self.label(msg, 'ui.prompt'))
1148 1151 if not r:
1149 1152 r = default
1150 1153 if self.configbool('ui', 'promptecho'):
1151 1154 self.write(r, "\n")
1152 1155 return r
1153 1156 except EOFError:
1154 1157 raise error.ResponseExpected()
1155 1158
1156 1159 @staticmethod
1157 1160 def extractchoices(prompt):
1158 1161 """Extract prompt message and list of choices from specified prompt.
1159 1162
1160 1163 This returns tuple "(message, choices)", and "choices" is the
1161 1164 list of tuple "(response character, text without &)".
1162 1165
1163 1166 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1164 1167 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1165 1168 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1166 1169 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1167 1170 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1168 1171 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1169 1172 """
1170 1173
1171 1174 # Sadly, the prompt string may have been built with a filename
1172 1175 # containing "$$" so let's try to find the first valid-looking
1173 1176 # prompt to start parsing. Sadly, we also can't rely on
1174 1177 # choices containing spaces, ASCII, or basically anything
1175 1178 # except an ampersand followed by a character.
1176 1179 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1177 1180 msg = m.group(1)
1178 1181 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1179 1182 return (msg,
1180 1183 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1181 1184 for s in choices])
1182 1185
1183 1186 def promptchoice(self, prompt, default=0):
1184 1187 """Prompt user with a message, read response, and ensure it matches
1185 1188 one of the provided choices. The prompt is formatted as follows:
1186 1189
1187 1190 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1188 1191
1189 1192 The index of the choice is returned. Responses are case
1190 1193 insensitive. If ui is not interactive, the default is
1191 1194 returned.
1192 1195 """
1193 1196
1194 1197 msg, choices = self.extractchoices(prompt)
1195 1198 resps = [r for r, t in choices]
1196 1199 while True:
1197 1200 r = self.prompt(msg, resps[default])
1198 1201 if r.lower() in resps:
1199 1202 return resps.index(r.lower())
1200 1203 self.write(_("unrecognized response\n"))
1201 1204
1202 1205 def getpass(self, prompt=None, default=None):
1203 1206 if not self.interactive():
1204 1207 return default
1205 1208 try:
1206 1209 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1207 1210 # disable getpass() only if explicitly specified. it's still valid
1208 1211 # to interact with tty even if fin is not a tty.
1209 1212 with self.timeblockedsection('stdio'):
1210 1213 if self.configbool('ui', 'nontty'):
1211 1214 l = self.fin.readline()
1212 1215 if not l:
1213 1216 raise EOFError
1214 1217 return l.rstrip('\n')
1215 1218 else:
1216 1219 return getpass.getpass('')
1217 1220 except EOFError:
1218 1221 raise error.ResponseExpected()
1219 1222 def status(self, *msg, **opts):
1220 1223 '''write status message to output (if ui.quiet is False)
1221 1224
1222 1225 This adds an output label of "ui.status".
1223 1226 '''
1224 1227 if not self.quiet:
1225 1228 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1226 1229 self.write(*msg, **opts)
1227 1230 def warn(self, *msg, **opts):
1228 1231 '''write warning message to output (stderr)
1229 1232
1230 1233 This adds an output label of "ui.warning".
1231 1234 '''
1232 1235 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1233 1236 self.write_err(*msg, **opts)
1234 1237 def note(self, *msg, **opts):
1235 1238 '''write note to output (if ui.verbose is True)
1236 1239
1237 1240 This adds an output label of "ui.note".
1238 1241 '''
1239 1242 if self.verbose:
1240 1243 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1241 1244 self.write(*msg, **opts)
1242 1245 def debug(self, *msg, **opts):
1243 1246 '''write debug message to output (if ui.debugflag is True)
1244 1247
1245 1248 This adds an output label of "ui.debug".
1246 1249 '''
1247 1250 if self.debugflag:
1248 1251 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1249 1252 self.write(*msg, **opts)
1250 1253
1251 1254 def edit(self, text, user, extra=None, editform=None, pending=None,
1252 1255 repopath=None):
1253 1256 extra_defaults = {
1254 1257 'prefix': 'editor',
1255 1258 'suffix': '.txt',
1256 1259 }
1257 1260 if extra is not None:
1258 1261 extra_defaults.update(extra)
1259 1262 extra = extra_defaults
1260 1263
1261 1264 rdir = None
1262 1265 if self.configbool('experimental', 'editortmpinhg'):
1263 1266 rdir = repopath
1264 1267 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1265 1268 suffix=extra['suffix'], text=True,
1266 1269 dir=rdir)
1267 1270 try:
1268 1271 f = os.fdopen(fd, pycompat.sysstr("w"))
1269 1272 f.write(text)
1270 1273 f.close()
1271 1274
1272 1275 environ = {'HGUSER': user}
1273 1276 if 'transplant_source' in extra:
1274 1277 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1275 1278 for label in ('intermediate-source', 'source', 'rebase_source'):
1276 1279 if label in extra:
1277 1280 environ.update({'HGREVISION': extra[label]})
1278 1281 break
1279 1282 if editform:
1280 1283 environ.update({'HGEDITFORM': editform})
1281 1284 if pending:
1282 1285 environ.update({'HG_PENDING': pending})
1283 1286
1284 1287 editor = self.geteditor()
1285 1288
1286 1289 self.system("%s \"%s\"" % (editor, name),
1287 1290 environ=environ,
1288 1291 onerr=error.Abort, errprefix=_("edit failed"),
1289 1292 blockedtag='editor')
1290 1293
1291 1294 f = open(name)
1292 1295 t = f.read()
1293 1296 f.close()
1294 1297 finally:
1295 1298 os.unlink(name)
1296 1299
1297 1300 return t
1298 1301
1299 1302 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1300 1303 blockedtag=None):
1301 1304 '''execute shell command with appropriate output stream. command
1302 1305 output will be redirected if fout is not stdout.
1303 1306
1304 1307 if command fails and onerr is None, return status, else raise onerr
1305 1308 object as exception.
1306 1309 '''
1307 1310 if blockedtag is None:
1308 1311 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1309 1312 out = self.fout
1310 1313 if any(s[1] for s in self._bufferstates):
1311 1314 out = self
1312 1315 with self.timeblockedsection(blockedtag):
1313 1316 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1314 1317 if rc and onerr:
1315 1318 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1316 1319 util.explainexit(rc)[0])
1317 1320 if errprefix:
1318 1321 errmsg = '%s: %s' % (errprefix, errmsg)
1319 1322 raise onerr(errmsg)
1320 1323 return rc
1321 1324
1322 1325 def _runsystem(self, cmd, environ, cwd, out):
1323 1326 """actually execute the given shell command (can be overridden by
1324 1327 extensions like chg)"""
1325 1328 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1326 1329
1327 1330 def traceback(self, exc=None, force=False):
1328 1331 '''print exception traceback if traceback printing enabled or forced.
1329 1332 only to call in exception handler. returns true if traceback
1330 1333 printed.'''
1331 1334 if self.tracebackflag or force:
1332 1335 if exc is None:
1333 1336 exc = sys.exc_info()
1334 1337 cause = getattr(exc[1], 'cause', None)
1335 1338
1336 1339 if cause is not None:
1337 1340 causetb = traceback.format_tb(cause[2])
1338 1341 exctb = traceback.format_tb(exc[2])
1339 1342 exconly = traceback.format_exception_only(cause[0], cause[1])
1340 1343
1341 1344 # exclude frame where 'exc' was chained and rethrown from exctb
1342 1345 self.write_err('Traceback (most recent call last):\n',
1343 1346 ''.join(exctb[:-1]),
1344 1347 ''.join(causetb),
1345 1348 ''.join(exconly))
1346 1349 else:
1347 1350 output = traceback.format_exception(exc[0], exc[1], exc[2])
1348 1351 data = r''.join(output)
1349 1352 if pycompat.ispy3:
1350 1353 enc = pycompat.sysstr(encoding.encoding)
1351 1354 data = data.encode(enc, errors=r'replace')
1352 1355 self.write_err(data)
1353 1356 return self.tracebackflag or force
1354 1357
1355 1358 def geteditor(self):
1356 1359 '''return editor to use'''
1357 1360 if pycompat.sysplatform == 'plan9':
1358 1361 # vi is the MIPS instruction simulator on Plan 9. We
1359 1362 # instead default to E to plumb commit messages to
1360 1363 # avoid confusion.
1361 1364 editor = 'E'
1362 1365 else:
1363 1366 editor = 'vi'
1364 1367 return (encoding.environ.get("HGEDITOR") or
1365 1368 self.config("ui", "editor") or
1366 1369 encoding.environ.get("VISUAL") or
1367 1370 encoding.environ.get("EDITOR", editor))
1368 1371
1369 1372 @util.propertycache
1370 1373 def _progbar(self):
1371 1374 """setup the progbar singleton to the ui object"""
1372 1375 if (self.quiet or self.debugflag
1373 1376 or self.configbool('progress', 'disable', False)
1374 1377 or not progress.shouldprint(self)):
1375 1378 return None
1376 1379 return getprogbar(self)
1377 1380
1378 1381 def _progclear(self):
1379 1382 """clear progress bar output if any. use it before any output"""
1380 1383 if '_progbar' not in vars(self): # nothing loaded yet
1381 1384 return
1382 1385 if self._progbar is not None and self._progbar.printed:
1383 1386 self._progbar.clear()
1384 1387
1385 1388 def progress(self, topic, pos, item="", unit="", total=None):
1386 1389 '''show a progress message
1387 1390
1388 1391 By default a textual progress bar will be displayed if an operation
1389 1392 takes too long. 'topic' is the current operation, 'item' is a
1390 1393 non-numeric marker of the current position (i.e. the currently
1391 1394 in-process file), 'pos' is the current numeric position (i.e.
1392 1395 revision, bytes, etc.), unit is a corresponding unit label,
1393 1396 and total is the highest expected pos.
1394 1397
1395 1398 Multiple nested topics may be active at a time.
1396 1399
1397 1400 All topics should be marked closed by setting pos to None at
1398 1401 termination.
1399 1402 '''
1400 1403 if self._progbar is not None:
1401 1404 self._progbar.progress(topic, pos, item=item, unit=unit,
1402 1405 total=total)
1403 1406 if pos is None or not self.configbool('progress', 'debug'):
1404 1407 return
1405 1408
1406 1409 if unit:
1407 1410 unit = ' ' + unit
1408 1411 if item:
1409 1412 item = ' ' + item
1410 1413
1411 1414 if total:
1412 1415 pct = 100.0 * pos / total
1413 1416 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1414 1417 % (topic, item, pos, total, unit, pct))
1415 1418 else:
1416 1419 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1417 1420
1418 1421 def log(self, service, *msg, **opts):
1419 1422 '''hook for logging facility extensions
1420 1423
1421 1424 service should be a readily-identifiable subsystem, which will
1422 1425 allow filtering.
1423 1426
1424 1427 *msg should be a newline-terminated format string to log, and
1425 1428 then any values to %-format into that format string.
1426 1429
1427 1430 **opts currently has no defined meanings.
1428 1431 '''
1429 1432
1430 1433 def label(self, msg, label):
1431 1434 '''style msg based on supplied label
1432 1435
1433 1436 If some color mode is enabled, this will add the necessary control
1434 1437 characters to apply such color. In addition, 'debug' color mode adds
1435 1438 markup showing which label affects a piece of text.
1436 1439
1437 1440 ui.write(s, 'label') is equivalent to
1438 1441 ui.write(ui.label(s, 'label')).
1439 1442 '''
1440 1443 if self._colormode is not None:
1441 1444 return color.colorlabel(self, msg, label)
1442 1445 return msg
1443 1446
1444 1447 def develwarn(self, msg, stacklevel=1, config=None):
1445 1448 """issue a developer warning message
1446 1449
1447 1450 Use 'stacklevel' to report the offender some layers further up in the
1448 1451 stack.
1449 1452 """
1450 1453 if not self.configbool('devel', 'all-warnings'):
1451 1454 if config is not None and not self.configbool('devel', config):
1452 1455 return
1453 1456 msg = 'devel-warn: ' + msg
1454 1457 stacklevel += 1 # get in develwarn
1455 1458 if self.tracebackflag:
1456 1459 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1457 1460 self.log('develwarn', '%s at:\n%s' %
1458 1461 (msg, ''.join(util.getstackframes(stacklevel))))
1459 1462 else:
1460 1463 curframe = inspect.currentframe()
1461 1464 calframe = inspect.getouterframes(curframe, 2)
1462 1465 self.write_err('%s at: %s:%s (%s)\n'
1463 1466 % ((msg,) + calframe[stacklevel][1:4]))
1464 1467 self.log('develwarn', '%s at: %s:%s (%s)\n',
1465 1468 msg, *calframe[stacklevel][1:4])
1466 1469 curframe = calframe = None # avoid cycles
1467 1470
1468 1471 def deprecwarn(self, msg, version):
1469 1472 """issue a deprecation warning
1470 1473
1471 1474 - msg: message explaining what is deprecated and how to upgrade,
1472 1475 - version: last version where the API will be supported,
1473 1476 """
1474 1477 if not (self.configbool('devel', 'all-warnings')
1475 1478 or self.configbool('devel', 'deprec-warn')):
1476 1479 return
1477 1480 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1478 1481 " update your code.)") % version
1479 1482 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1480 1483
1481 1484 def exportableenviron(self):
1482 1485 """The environment variables that are safe to export, e.g. through
1483 1486 hgweb.
1484 1487 """
1485 1488 return self._exportableenviron
1486 1489
1487 1490 @contextlib.contextmanager
1488 1491 def configoverride(self, overrides, source=""):
1489 1492 """Context manager for temporary config overrides
1490 1493 `overrides` must be a dict of the following structure:
1491 1494 {(section, name) : value}"""
1492 1495 backups = {}
1493 1496 try:
1494 1497 for (section, name), value in overrides.items():
1495 1498 backups[(section, name)] = self.backupconfig(section, name)
1496 1499 self.setconfig(section, name, value, source)
1497 1500 yield
1498 1501 finally:
1499 1502 for __, backup in backups.items():
1500 1503 self.restoreconfig(backup)
1501 1504 # just restoring ui.quiet config to the previous value is not enough
1502 1505 # as it does not update ui.quiet class member
1503 1506 if ('ui', 'quiet') in overrides:
1504 1507 self.fixconfig(section='ui')
1505 1508
1506 1509 class paths(dict):
1507 1510 """Represents a collection of paths and their configs.
1508 1511
1509 1512 Data is initially derived from ui instances and the config files they have
1510 1513 loaded.
1511 1514 """
1512 1515 def __init__(self, ui):
1513 1516 dict.__init__(self)
1514 1517
1515 1518 for name, loc in ui.configitems('paths', ignoresub=True):
1516 1519 # No location is the same as not existing.
1517 1520 if not loc:
1518 1521 continue
1519 1522 loc, sub = ui.configsuboptions('paths', name)
1520 1523 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1521 1524
1522 1525 def getpath(self, name, default=None):
1523 1526 """Return a ``path`` from a string, falling back to default.
1524 1527
1525 1528 ``name`` can be a named path or locations. Locations are filesystem
1526 1529 paths or URIs.
1527 1530
1528 1531 Returns None if ``name`` is not a registered path, a URI, or a local
1529 1532 path to a repo.
1530 1533 """
1531 1534 # Only fall back to default if no path was requested.
1532 1535 if name is None:
1533 1536 if not default:
1534 1537 default = ()
1535 1538 elif not isinstance(default, (tuple, list)):
1536 1539 default = (default,)
1537 1540 for k in default:
1538 1541 try:
1539 1542 return self[k]
1540 1543 except KeyError:
1541 1544 continue
1542 1545 return None
1543 1546
1544 1547 # Most likely empty string.
1545 1548 # This may need to raise in the future.
1546 1549 if not name:
1547 1550 return None
1548 1551
1549 1552 try:
1550 1553 return self[name]
1551 1554 except KeyError:
1552 1555 # Try to resolve as a local path or URI.
1553 1556 try:
1554 1557 # We don't pass sub-options in, so no need to pass ui instance.
1555 1558 return path(None, None, rawloc=name)
1556 1559 except ValueError:
1557 1560 raise error.RepoError(_('repository %s does not exist') %
1558 1561 name)
1559 1562
1560 1563 _pathsuboptions = {}
1561 1564
1562 1565 def pathsuboption(option, attr):
1563 1566 """Decorator used to declare a path sub-option.
1564 1567
1565 1568 Arguments are the sub-option name and the attribute it should set on
1566 1569 ``path`` instances.
1567 1570
1568 1571 The decorated function will receive as arguments a ``ui`` instance,
1569 1572 ``path`` instance, and the string value of this option from the config.
1570 1573 The function should return the value that will be set on the ``path``
1571 1574 instance.
1572 1575
1573 1576 This decorator can be used to perform additional verification of
1574 1577 sub-options and to change the type of sub-options.
1575 1578 """
1576 1579 def register(func):
1577 1580 _pathsuboptions[option] = (attr, func)
1578 1581 return func
1579 1582 return register
1580 1583
1581 1584 @pathsuboption('pushurl', 'pushloc')
1582 1585 def pushurlpathoption(ui, path, value):
1583 1586 u = util.url(value)
1584 1587 # Actually require a URL.
1585 1588 if not u.scheme:
1586 1589 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1587 1590 return None
1588 1591
1589 1592 # Don't support the #foo syntax in the push URL to declare branch to
1590 1593 # push.
1591 1594 if u.fragment:
1592 1595 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1593 1596 'ignoring)\n') % path.name)
1594 1597 u.fragment = None
1595 1598
1596 1599 return str(u)
1597 1600
1598 1601 @pathsuboption('pushrev', 'pushrev')
1599 1602 def pushrevpathoption(ui, path, value):
1600 1603 return value
1601 1604
1602 1605 class path(object):
1603 1606 """Represents an individual path and its configuration."""
1604 1607
1605 1608 def __init__(self, ui, name, rawloc=None, suboptions=None):
1606 1609 """Construct a path from its config options.
1607 1610
1608 1611 ``ui`` is the ``ui`` instance the path is coming from.
1609 1612 ``name`` is the symbolic name of the path.
1610 1613 ``rawloc`` is the raw location, as defined in the config.
1611 1614 ``pushloc`` is the raw locations pushes should be made to.
1612 1615
1613 1616 If ``name`` is not defined, we require that the location be a) a local
1614 1617 filesystem path with a .hg directory or b) a URL. If not,
1615 1618 ``ValueError`` is raised.
1616 1619 """
1617 1620 if not rawloc:
1618 1621 raise ValueError('rawloc must be defined')
1619 1622
1620 1623 # Locations may define branches via syntax <base>#<branch>.
1621 1624 u = util.url(rawloc)
1622 1625 branch = None
1623 1626 if u.fragment:
1624 1627 branch = u.fragment
1625 1628 u.fragment = None
1626 1629
1627 1630 self.url = u
1628 1631 self.branch = branch
1629 1632
1630 1633 self.name = name
1631 1634 self.rawloc = rawloc
1632 1635 self.loc = '%s' % u
1633 1636
1634 1637 # When given a raw location but not a symbolic name, validate the
1635 1638 # location is valid.
1636 1639 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1637 1640 raise ValueError('location is not a URL or path to a local '
1638 1641 'repo: %s' % rawloc)
1639 1642
1640 1643 suboptions = suboptions or {}
1641 1644
1642 1645 # Now process the sub-options. If a sub-option is registered, its
1643 1646 # attribute will always be present. The value will be None if there
1644 1647 # was no valid sub-option.
1645 1648 for suboption, (attr, func) in _pathsuboptions.iteritems():
1646 1649 if suboption not in suboptions:
1647 1650 setattr(self, attr, None)
1648 1651 continue
1649 1652
1650 1653 value = func(ui, self, suboptions[suboption])
1651 1654 setattr(self, attr, value)
1652 1655
1653 1656 def _isvalidlocalpath(self, path):
1654 1657 """Returns True if the given path is a potentially valid repository.
1655 1658 This is its own function so that extensions can change the definition of
1656 1659 'valid' in this case (like when pulling from a git repo into a hg
1657 1660 one)."""
1658 1661 return os.path.isdir(os.path.join(path, '.hg'))
1659 1662
1660 1663 @property
1661 1664 def suboptions(self):
1662 1665 """Return sub-options and their values for this path.
1663 1666
1664 1667 This is intended to be used for presentation purposes.
1665 1668 """
1666 1669 d = {}
1667 1670 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1668 1671 value = getattr(self, attr)
1669 1672 if value is not None:
1670 1673 d[subopt] = value
1671 1674 return d
1672 1675
1673 1676 # we instantiate one globally shared progress bar to avoid
1674 1677 # competing progress bars when multiple UI objects get created
1675 1678 _progresssingleton = None
1676 1679
1677 1680 def getprogbar(ui):
1678 1681 global _progresssingleton
1679 1682 if _progresssingleton is None:
1680 1683 # passing 'ui' object to the singleton is fishy,
1681 1684 # this is how the extension used to work but feel free to rework it.
1682 1685 _progresssingleton = progress.progbar(ui)
1683 1686 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now