##// END OF EJS Templates
ui: check for --debugger in sys.argv using r-string to avoid bytes on py3...
Augie Fackler -
r31341:66f1c244 default
parent child Browse files
Show More
@@ -1,1670 +1,1670 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import atexit
11 11 import collections
12 12 import contextlib
13 13 import errno
14 14 import getpass
15 15 import inspect
16 16 import os
17 17 import re
18 18 import signal
19 19 import socket
20 20 import subprocess
21 21 import sys
22 22 import tempfile
23 23 import traceback
24 24
25 25 from .i18n import _
26 26 from .node import hex
27 27
28 28 from . import (
29 29 color,
30 30 config,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 progress,
35 35 pycompat,
36 36 scmutil,
37 37 util,
38 38 )
39 39
40 40 urlreq = util.urlreq
41 41
42 42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 44 if not c.isalnum())
45 45
46 46 samplehgrcs = {
47 47 'user':
48 48 """# example user config (see 'hg help config' for more info)
49 49 [ui]
50 50 # name and email, e.g.
51 51 # username = Jane Doe <jdoe@example.com>
52 52 username =
53 53
54 54 # uncomment to colorize command output
55 55 # color = auto
56 56
57 57 [extensions]
58 58 # uncomment these lines to enable some popular extensions
59 59 # (see 'hg help extensions' for more info)
60 60 #
61 61 # pager =""",
62 62
63 63 'cloned':
64 64 """# example repository config (see 'hg help config' for more info)
65 65 [paths]
66 66 default = %s
67 67
68 68 # path aliases to other clones of this repo in URLs or filesystem paths
69 69 # (see 'hg help config.paths' for more info)
70 70 #
71 71 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
72 72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 73 # my-clone = /home/jdoe/jdoes-clone
74 74
75 75 [ui]
76 76 # name and email (local to this repository, optional), e.g.
77 77 # username = Jane Doe <jdoe@example.com>
78 78 """,
79 79
80 80 'local':
81 81 """# example repository config (see 'hg help config' for more info)
82 82 [paths]
83 83 # path aliases to other clones of this repo in URLs or filesystem paths
84 84 # (see 'hg help config.paths' for more info)
85 85 #
86 86 # default = http://example.com/hg/example-repo
87 87 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
88 88 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
89 89 # my-clone = /home/jdoe/jdoes-clone
90 90
91 91 [ui]
92 92 # name and email (local to this repository, optional), e.g.
93 93 # username = Jane Doe <jdoe@example.com>
94 94 """,
95 95
96 96 'global':
97 97 """# example system-wide hg config (see 'hg help config' for more info)
98 98
99 99 [ui]
100 100 # uncomment to colorize command output
101 101 # color = auto
102 102
103 103 [extensions]
104 104 # uncomment these lines to enable some popular extensions
105 105 # (see 'hg help extensions' for more info)
106 106 #
107 107 # blackbox =
108 108 # pager =""",
109 109 }
110 110
111 111
112 112 class httppasswordmgrdbproxy(object):
113 113 """Delays loading urllib2 until it's needed."""
114 114 def __init__(self):
115 115 self._mgr = None
116 116
117 117 def _get_mgr(self):
118 118 if self._mgr is None:
119 119 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
120 120 return self._mgr
121 121
122 122 def add_password(self, *args, **kwargs):
123 123 return self._get_mgr().add_password(*args, **kwargs)
124 124
125 125 def find_user_password(self, *args, **kwargs):
126 126 return self._get_mgr().find_user_password(*args, **kwargs)
127 127
128 128 def _catchterm(*args):
129 129 raise error.SignalInterrupt
130 130
131 131 class ui(object):
132 132 def __init__(self, src=None):
133 133 """Create a fresh new ui object if no src given
134 134
135 135 Use uimod.ui.load() to create a ui which knows global and user configs.
136 136 In most cases, you should use ui.copy() to create a copy of an existing
137 137 ui object.
138 138 """
139 139 # _buffers: used for temporary capture of output
140 140 self._buffers = []
141 141 # 3-tuple describing how each buffer in the stack behaves.
142 142 # Values are (capture stderr, capture subprocesses, apply labels).
143 143 self._bufferstates = []
144 144 # When a buffer is active, defines whether we are expanding labels.
145 145 # This exists to prevent an extra list lookup.
146 146 self._bufferapplylabels = None
147 147 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
148 148 self._reportuntrusted = True
149 149 self._ocfg = config.config() # overlay
150 150 self._tcfg = config.config() # trusted
151 151 self._ucfg = config.config() # untrusted
152 152 self._trustusers = set()
153 153 self._trustgroups = set()
154 154 self.callhooks = True
155 155 # Insecure server connections requested.
156 156 self.insecureconnections = False
157 157 # Blocked time
158 158 self.logblockedtimes = False
159 159 # color mode: see mercurial/color.py for possible value
160 160 self._colormode = None
161 161 self._terminfoparams = {}
162 162 self._styles = {}
163 163
164 164 if src:
165 165 self.fout = src.fout
166 166 self.ferr = src.ferr
167 167 self.fin = src.fin
168 168 self.pageractive = src.pageractive
169 169 self._disablepager = src._disablepager
170 170
171 171 self._tcfg = src._tcfg.copy()
172 172 self._ucfg = src._ucfg.copy()
173 173 self._ocfg = src._ocfg.copy()
174 174 self._trustusers = src._trustusers.copy()
175 175 self._trustgroups = src._trustgroups.copy()
176 176 self.environ = src.environ
177 177 self.callhooks = src.callhooks
178 178 self.insecureconnections = src.insecureconnections
179 179 self._colormode = src._colormode
180 180 self._terminfoparams = src._terminfoparams.copy()
181 181 self._styles = src._styles.copy()
182 182
183 183 self.fixconfig()
184 184
185 185 self.httppasswordmgrdb = src.httppasswordmgrdb
186 186 self._blockedtimes = src._blockedtimes
187 187 else:
188 188 self.fout = util.stdout
189 189 self.ferr = util.stderr
190 190 self.fin = util.stdin
191 191 self.pageractive = False
192 192 self._disablepager = False
193 193
194 194 # shared read-only environment
195 195 self.environ = encoding.environ
196 196
197 197 self.httppasswordmgrdb = httppasswordmgrdbproxy()
198 198 self._blockedtimes = collections.defaultdict(int)
199 199
200 200 allowed = self.configlist('experimental', 'exportableenviron')
201 201 if '*' in allowed:
202 202 self._exportableenviron = self.environ
203 203 else:
204 204 self._exportableenviron = {}
205 205 for k in allowed:
206 206 if k in self.environ:
207 207 self._exportableenviron[k] = self.environ[k]
208 208
209 209 @classmethod
210 210 def load(cls):
211 211 """Create a ui and load global and user configs"""
212 212 u = cls()
213 213 # we always trust global config files
214 214 for f in scmutil.rcpath():
215 215 u.readconfig(f, trust=True)
216 216 return u
217 217
218 218 def copy(self):
219 219 return self.__class__(self)
220 220
221 221 def resetstate(self):
222 222 """Clear internal state that shouldn't persist across commands"""
223 223 if self._progbar:
224 224 self._progbar.resetstate() # reset last-print time of progress bar
225 225 self.httppasswordmgrdb = httppasswordmgrdbproxy()
226 226
227 227 @contextlib.contextmanager
228 228 def timeblockedsection(self, key):
229 229 # this is open-coded below - search for timeblockedsection to find them
230 230 starttime = util.timer()
231 231 try:
232 232 yield
233 233 finally:
234 234 self._blockedtimes[key + '_blocked'] += \
235 235 (util.timer() - starttime) * 1000
236 236
237 237 def formatter(self, topic, opts):
238 238 return formatter.formatter(self, topic, opts)
239 239
240 240 def _trusted(self, fp, f):
241 241 st = util.fstat(fp)
242 242 if util.isowner(st):
243 243 return True
244 244
245 245 tusers, tgroups = self._trustusers, self._trustgroups
246 246 if '*' in tusers or '*' in tgroups:
247 247 return True
248 248
249 249 user = util.username(st.st_uid)
250 250 group = util.groupname(st.st_gid)
251 251 if user in tusers or group in tgroups or user == util.username():
252 252 return True
253 253
254 254 if self._reportuntrusted:
255 255 self.warn(_('not trusting file %s from untrusted '
256 256 'user %s, group %s\n') % (f, user, group))
257 257 return False
258 258
259 259 def readconfig(self, filename, root=None, trust=False,
260 260 sections=None, remap=None):
261 261 try:
262 262 fp = open(filename, u'rb')
263 263 except IOError:
264 264 if not sections: # ignore unless we were looking for something
265 265 return
266 266 raise
267 267
268 268 cfg = config.config()
269 269 trusted = sections or trust or self._trusted(fp, filename)
270 270
271 271 try:
272 272 cfg.read(filename, fp, sections=sections, remap=remap)
273 273 fp.close()
274 274 except error.ConfigError as inst:
275 275 if trusted:
276 276 raise
277 277 self.warn(_("ignored: %s\n") % str(inst))
278 278
279 279 if self.plain():
280 280 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
281 281 'logtemplate', 'statuscopies', 'style',
282 282 'traceback', 'verbose'):
283 283 if k in cfg['ui']:
284 284 del cfg['ui'][k]
285 285 for k, v in cfg.items('defaults'):
286 286 del cfg['defaults'][k]
287 287 # Don't remove aliases from the configuration if in the exceptionlist
288 288 if self.plain('alias'):
289 289 for k, v in cfg.items('alias'):
290 290 del cfg['alias'][k]
291 291 if self.plain('revsetalias'):
292 292 for k, v in cfg.items('revsetalias'):
293 293 del cfg['revsetalias'][k]
294 294 if self.plain('templatealias'):
295 295 for k, v in cfg.items('templatealias'):
296 296 del cfg['templatealias'][k]
297 297
298 298 if trusted:
299 299 self._tcfg.update(cfg)
300 300 self._tcfg.update(self._ocfg)
301 301 self._ucfg.update(cfg)
302 302 self._ucfg.update(self._ocfg)
303 303
304 304 if root is None:
305 305 root = os.path.expanduser('~')
306 306 self.fixconfig(root=root)
307 307
308 308 def fixconfig(self, root=None, section=None):
309 309 if section in (None, 'paths'):
310 310 # expand vars and ~
311 311 # translate paths relative to root (or home) into absolute paths
312 312 root = root or pycompat.getcwd()
313 313 for c in self._tcfg, self._ucfg, self._ocfg:
314 314 for n, p in c.items('paths'):
315 315 # Ignore sub-options.
316 316 if ':' in n:
317 317 continue
318 318 if not p:
319 319 continue
320 320 if '%%' in p:
321 321 s = self.configsource('paths', n) or 'none'
322 322 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
323 323 % (n, p, s))
324 324 p = p.replace('%%', '%')
325 325 p = util.expandpath(p)
326 326 if not util.hasscheme(p) and not os.path.isabs(p):
327 327 p = os.path.normpath(os.path.join(root, p))
328 328 c.set("paths", n, p)
329 329
330 330 if section in (None, 'ui'):
331 331 # update ui options
332 332 self.debugflag = self.configbool('ui', 'debug')
333 333 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
334 334 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
335 335 if self.verbose and self.quiet:
336 336 self.quiet = self.verbose = False
337 337 self._reportuntrusted = self.debugflag or self.configbool("ui",
338 338 "report_untrusted", True)
339 339 self.tracebackflag = self.configbool('ui', 'traceback', False)
340 340 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
341 341
342 342 if section in (None, 'trusted'):
343 343 # update trust information
344 344 self._trustusers.update(self.configlist('trusted', 'users'))
345 345 self._trustgroups.update(self.configlist('trusted', 'groups'))
346 346
347 347 def backupconfig(self, section, item):
348 348 return (self._ocfg.backup(section, item),
349 349 self._tcfg.backup(section, item),
350 350 self._ucfg.backup(section, item),)
351 351 def restoreconfig(self, data):
352 352 self._ocfg.restore(data[0])
353 353 self._tcfg.restore(data[1])
354 354 self._ucfg.restore(data[2])
355 355
356 356 def setconfig(self, section, name, value, source=''):
357 357 for cfg in (self._ocfg, self._tcfg, self._ucfg):
358 358 cfg.set(section, name, value, source)
359 359 self.fixconfig(section=section)
360 360
361 361 def _data(self, untrusted):
362 362 return untrusted and self._ucfg or self._tcfg
363 363
364 364 def configsource(self, section, name, untrusted=False):
365 365 return self._data(untrusted).source(section, name)
366 366
367 367 def config(self, section, name, default=None, untrusted=False):
368 368 if isinstance(name, list):
369 369 alternates = name
370 370 else:
371 371 alternates = [name]
372 372
373 373 for n in alternates:
374 374 value = self._data(untrusted).get(section, n, None)
375 375 if value is not None:
376 376 name = n
377 377 break
378 378 else:
379 379 value = default
380 380
381 381 if self.debugflag and not untrusted and self._reportuntrusted:
382 382 for n in alternates:
383 383 uvalue = self._ucfg.get(section, n)
384 384 if uvalue is not None and uvalue != value:
385 385 self.debug("ignoring untrusted configuration option "
386 386 "%s.%s = %s\n" % (section, n, uvalue))
387 387 return value
388 388
389 389 def configsuboptions(self, section, name, default=None, untrusted=False):
390 390 """Get a config option and all sub-options.
391 391
392 392 Some config options have sub-options that are declared with the
393 393 format "key:opt = value". This method is used to return the main
394 394 option and all its declared sub-options.
395 395
396 396 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
397 397 is a dict of defined sub-options where keys and values are strings.
398 398 """
399 399 data = self._data(untrusted)
400 400 main = data.get(section, name, default)
401 401 if self.debugflag and not untrusted and self._reportuntrusted:
402 402 uvalue = self._ucfg.get(section, name)
403 403 if uvalue is not None and uvalue != main:
404 404 self.debug('ignoring untrusted configuration option '
405 405 '%s.%s = %s\n' % (section, name, uvalue))
406 406
407 407 sub = {}
408 408 prefix = '%s:' % name
409 409 for k, v in data.items(section):
410 410 if k.startswith(prefix):
411 411 sub[k[len(prefix):]] = v
412 412
413 413 if self.debugflag and not untrusted and self._reportuntrusted:
414 414 for k, v in sub.items():
415 415 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
416 416 if uvalue is not None and uvalue != v:
417 417 self.debug('ignoring untrusted configuration option '
418 418 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
419 419
420 420 return main, sub
421 421
422 422 def configpath(self, section, name, default=None, untrusted=False):
423 423 'get a path config item, expanded relative to repo root or config file'
424 424 v = self.config(section, name, default, untrusted)
425 425 if v is None:
426 426 return None
427 427 if not os.path.isabs(v) or "://" not in v:
428 428 src = self.configsource(section, name, untrusted)
429 429 if ':' in src:
430 430 base = os.path.dirname(src.rsplit(':')[0])
431 431 v = os.path.join(base, os.path.expanduser(v))
432 432 return v
433 433
434 434 def configbool(self, section, name, default=False, untrusted=False):
435 435 """parse a configuration element as a boolean
436 436
437 437 >>> u = ui(); s = 'foo'
438 438 >>> u.setconfig(s, 'true', 'yes')
439 439 >>> u.configbool(s, 'true')
440 440 True
441 441 >>> u.setconfig(s, 'false', 'no')
442 442 >>> u.configbool(s, 'false')
443 443 False
444 444 >>> u.configbool(s, 'unknown')
445 445 False
446 446 >>> u.configbool(s, 'unknown', True)
447 447 True
448 448 >>> u.setconfig(s, 'invalid', 'somevalue')
449 449 >>> u.configbool(s, 'invalid')
450 450 Traceback (most recent call last):
451 451 ...
452 452 ConfigError: foo.invalid is not a boolean ('somevalue')
453 453 """
454 454
455 455 v = self.config(section, name, None, untrusted)
456 456 if v is None:
457 457 return default
458 458 if isinstance(v, bool):
459 459 return v
460 460 b = util.parsebool(v)
461 461 if b is None:
462 462 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
463 463 % (section, name, v))
464 464 return b
465 465
466 466 def configwith(self, convert, section, name, default=None,
467 467 desc=None, untrusted=False):
468 468 """parse a configuration element with a conversion function
469 469
470 470 >>> u = ui(); s = 'foo'
471 471 >>> u.setconfig(s, 'float1', '42')
472 472 >>> u.configwith(float, s, 'float1')
473 473 42.0
474 474 >>> u.setconfig(s, 'float2', '-4.25')
475 475 >>> u.configwith(float, s, 'float2')
476 476 -4.25
477 477 >>> u.configwith(float, s, 'unknown', 7)
478 478 7
479 479 >>> u.setconfig(s, 'invalid', 'somevalue')
480 480 >>> u.configwith(float, s, 'invalid')
481 481 Traceback (most recent call last):
482 482 ...
483 483 ConfigError: foo.invalid is not a valid float ('somevalue')
484 484 >>> u.configwith(float, s, 'invalid', desc='womble')
485 485 Traceback (most recent call last):
486 486 ...
487 487 ConfigError: foo.invalid is not a valid womble ('somevalue')
488 488 """
489 489
490 490 v = self.config(section, name, None, untrusted)
491 491 if v is None:
492 492 return default
493 493 try:
494 494 return convert(v)
495 495 except ValueError:
496 496 if desc is None:
497 497 desc = convert.__name__
498 498 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
499 499 % (section, name, desc, v))
500 500
501 501 def configint(self, section, name, default=None, untrusted=False):
502 502 """parse a configuration element as an integer
503 503
504 504 >>> u = ui(); s = 'foo'
505 505 >>> u.setconfig(s, 'int1', '42')
506 506 >>> u.configint(s, 'int1')
507 507 42
508 508 >>> u.setconfig(s, 'int2', '-42')
509 509 >>> u.configint(s, 'int2')
510 510 -42
511 511 >>> u.configint(s, 'unknown', 7)
512 512 7
513 513 >>> u.setconfig(s, 'invalid', 'somevalue')
514 514 >>> u.configint(s, 'invalid')
515 515 Traceback (most recent call last):
516 516 ...
517 517 ConfigError: foo.invalid is not a valid integer ('somevalue')
518 518 """
519 519
520 520 return self.configwith(int, section, name, default, 'integer',
521 521 untrusted)
522 522
523 523 def configbytes(self, section, name, default=0, untrusted=False):
524 524 """parse a configuration element as a quantity in bytes
525 525
526 526 Units can be specified as b (bytes), k or kb (kilobytes), m or
527 527 mb (megabytes), g or gb (gigabytes).
528 528
529 529 >>> u = ui(); s = 'foo'
530 530 >>> u.setconfig(s, 'val1', '42')
531 531 >>> u.configbytes(s, 'val1')
532 532 42
533 533 >>> u.setconfig(s, 'val2', '42.5 kb')
534 534 >>> u.configbytes(s, 'val2')
535 535 43520
536 536 >>> u.configbytes(s, 'unknown', '7 MB')
537 537 7340032
538 538 >>> u.setconfig(s, 'invalid', 'somevalue')
539 539 >>> u.configbytes(s, 'invalid')
540 540 Traceback (most recent call last):
541 541 ...
542 542 ConfigError: foo.invalid is not a byte quantity ('somevalue')
543 543 """
544 544
545 545 value = self.config(section, name)
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 or '--debugger' in sys.argv):
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 938 pager = subprocess.Popen(command, shell=True, bufsize=-1,
939 939 close_fds=util.closefds, stdin=subprocess.PIPE,
940 940 stdout=util.stdout, stderr=util.stderr)
941 941
942 942 # back up original file descriptors
943 943 stdoutfd = os.dup(util.stdout.fileno())
944 944 stderrfd = os.dup(util.stderr.fileno())
945 945
946 946 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
947 947 if self._isatty(util.stderr):
948 948 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
949 949
950 950 @atexit.register
951 951 def killpager():
952 952 if util.safehasattr(signal, "SIGINT"):
953 953 signal.signal(signal.SIGINT, signal.SIG_IGN)
954 954 # restore original fds, closing pager.stdin copies in the process
955 955 os.dup2(stdoutfd, util.stdout.fileno())
956 956 os.dup2(stderrfd, util.stderr.fileno())
957 957 pager.stdin.close()
958 958 pager.wait()
959 959
960 960 def interface(self, feature):
961 961 """what interface to use for interactive console features?
962 962
963 963 The interface is controlled by the value of `ui.interface` but also by
964 964 the value of feature-specific configuration. For example:
965 965
966 966 ui.interface.histedit = text
967 967 ui.interface.chunkselector = curses
968 968
969 969 Here the features are "histedit" and "chunkselector".
970 970
971 971 The configuration above means that the default interfaces for commands
972 972 is curses, the interface for histedit is text and the interface for
973 973 selecting chunk is crecord (the best curses interface available).
974 974
975 975 Consider the following example:
976 976 ui.interface = curses
977 977 ui.interface.histedit = text
978 978
979 979 Then histedit will use the text interface and chunkselector will use
980 980 the default curses interface (crecord at the moment).
981 981 """
982 982 alldefaults = frozenset(["text", "curses"])
983 983
984 984 featureinterfaces = {
985 985 "chunkselector": [
986 986 "text",
987 987 "curses",
988 988 ]
989 989 }
990 990
991 991 # Feature-specific interface
992 992 if feature not in featureinterfaces.keys():
993 993 # Programming error, not user error
994 994 raise ValueError("Unknown feature requested %s" % feature)
995 995
996 996 availableinterfaces = frozenset(featureinterfaces[feature])
997 997 if alldefaults > availableinterfaces:
998 998 # Programming error, not user error. We need a use case to
999 999 # define the right thing to do here.
1000 1000 raise ValueError(
1001 1001 "Feature %s does not handle all default interfaces" %
1002 1002 feature)
1003 1003
1004 1004 if self.plain():
1005 1005 return "text"
1006 1006
1007 1007 # Default interface for all the features
1008 1008 defaultinterface = "text"
1009 1009 i = self.config("ui", "interface", None)
1010 1010 if i in alldefaults:
1011 1011 defaultinterface = i
1012 1012
1013 1013 choseninterface = defaultinterface
1014 1014 f = self.config("ui", "interface.%s" % feature, None)
1015 1015 if f in availableinterfaces:
1016 1016 choseninterface = f
1017 1017
1018 1018 if i is not None and defaultinterface != i:
1019 1019 if f is not None:
1020 1020 self.warn(_("invalid value for ui.interface: %s\n") %
1021 1021 (i,))
1022 1022 else:
1023 1023 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1024 1024 (i, choseninterface))
1025 1025 if f is not None and choseninterface != f:
1026 1026 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1027 1027 (feature, f, choseninterface))
1028 1028
1029 1029 return choseninterface
1030 1030
1031 1031 def interactive(self):
1032 1032 '''is interactive input allowed?
1033 1033
1034 1034 An interactive session is a session where input can be reasonably read
1035 1035 from `sys.stdin'. If this function returns false, any attempt to read
1036 1036 from stdin should fail with an error, unless a sensible default has been
1037 1037 specified.
1038 1038
1039 1039 Interactiveness is triggered by the value of the `ui.interactive'
1040 1040 configuration variable or - if it is unset - when `sys.stdin' points
1041 1041 to a terminal device.
1042 1042
1043 1043 This function refers to input only; for output, see `ui.formatted()'.
1044 1044 '''
1045 1045 i = self.configbool("ui", "interactive", None)
1046 1046 if i is None:
1047 1047 # some environments replace stdin without implementing isatty
1048 1048 # usually those are non-interactive
1049 1049 return self._isatty(self.fin)
1050 1050
1051 1051 return i
1052 1052
1053 1053 def termwidth(self):
1054 1054 '''how wide is the terminal in columns?
1055 1055 '''
1056 1056 if 'COLUMNS' in encoding.environ:
1057 1057 try:
1058 1058 return int(encoding.environ['COLUMNS'])
1059 1059 except ValueError:
1060 1060 pass
1061 1061 return scmutil.termsize(self)[0]
1062 1062
1063 1063 def formatted(self):
1064 1064 '''should formatted output be used?
1065 1065
1066 1066 It is often desirable to format the output to suite the output medium.
1067 1067 Examples of this are truncating long lines or colorizing messages.
1068 1068 However, this is not often not desirable when piping output into other
1069 1069 utilities, e.g. `grep'.
1070 1070
1071 1071 Formatted output is triggered by the value of the `ui.formatted'
1072 1072 configuration variable or - if it is unset - when `sys.stdout' points
1073 1073 to a terminal device. Please note that `ui.formatted' should be
1074 1074 considered an implementation detail; it is not intended for use outside
1075 1075 Mercurial or its extensions.
1076 1076
1077 1077 This function refers to output only; for input, see `ui.interactive()'.
1078 1078 This function always returns false when in plain mode, see `ui.plain()'.
1079 1079 '''
1080 1080 if self.plain():
1081 1081 return False
1082 1082
1083 1083 i = self.configbool("ui", "formatted", None)
1084 1084 if i is None:
1085 1085 # some environments replace stdout without implementing isatty
1086 1086 # usually those are non-interactive
1087 1087 return self._isatty(self.fout)
1088 1088
1089 1089 return i
1090 1090
1091 1091 def _readline(self, prompt=''):
1092 1092 if self._isatty(self.fin):
1093 1093 try:
1094 1094 # magically add command line editing support, where
1095 1095 # available
1096 1096 import readline
1097 1097 # force demandimport to really load the module
1098 1098 readline.read_history_file
1099 1099 # windows sometimes raises something other than ImportError
1100 1100 except Exception:
1101 1101 pass
1102 1102
1103 1103 # call write() so output goes through subclassed implementation
1104 1104 # e.g. color extension on Windows
1105 1105 self.write(prompt, prompt=True)
1106 1106
1107 1107 # instead of trying to emulate raw_input, swap (self.fin,
1108 1108 # self.fout) with (sys.stdin, sys.stdout)
1109 1109 oldin = sys.stdin
1110 1110 oldout = sys.stdout
1111 1111 sys.stdin = self.fin
1112 1112 sys.stdout = self.fout
1113 1113 # prompt ' ' must exist; otherwise readline may delete entire line
1114 1114 # - http://bugs.python.org/issue12833
1115 1115 with self.timeblockedsection('stdio'):
1116 1116 line = raw_input(' ')
1117 1117 sys.stdin = oldin
1118 1118 sys.stdout = oldout
1119 1119
1120 1120 # When stdin is in binary mode on Windows, it can cause
1121 1121 # raw_input() to emit an extra trailing carriage return
1122 1122 if os.linesep == '\r\n' and line and line[-1] == '\r':
1123 1123 line = line[:-1]
1124 1124 return line
1125 1125
1126 1126 def prompt(self, msg, default="y"):
1127 1127 """Prompt user with msg, read response.
1128 1128 If ui is not interactive, the default is returned.
1129 1129 """
1130 1130 if not self.interactive():
1131 1131 self.write(msg, ' ', default or '', "\n")
1132 1132 return default
1133 1133 try:
1134 1134 r = self._readline(self.label(msg, 'ui.prompt'))
1135 1135 if not r:
1136 1136 r = default
1137 1137 if self.configbool('ui', 'promptecho'):
1138 1138 self.write(r, "\n")
1139 1139 return r
1140 1140 except EOFError:
1141 1141 raise error.ResponseExpected()
1142 1142
1143 1143 @staticmethod
1144 1144 def extractchoices(prompt):
1145 1145 """Extract prompt message and list of choices from specified prompt.
1146 1146
1147 1147 This returns tuple "(message, choices)", and "choices" is the
1148 1148 list of tuple "(response character, text without &)".
1149 1149
1150 1150 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1151 1151 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1152 1152 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1153 1153 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1154 1154 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1155 1155 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1156 1156 """
1157 1157
1158 1158 # Sadly, the prompt string may have been built with a filename
1159 1159 # containing "$$" so let's try to find the first valid-looking
1160 1160 # prompt to start parsing. Sadly, we also can't rely on
1161 1161 # choices containing spaces, ASCII, or basically anything
1162 1162 # except an ampersand followed by a character.
1163 1163 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1164 1164 msg = m.group(1)
1165 1165 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1166 1166 return (msg,
1167 1167 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1168 1168 for s in choices])
1169 1169
1170 1170 def promptchoice(self, prompt, default=0):
1171 1171 """Prompt user with a message, read response, and ensure it matches
1172 1172 one of the provided choices. The prompt is formatted as follows:
1173 1173
1174 1174 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1175 1175
1176 1176 The index of the choice is returned. Responses are case
1177 1177 insensitive. If ui is not interactive, the default is
1178 1178 returned.
1179 1179 """
1180 1180
1181 1181 msg, choices = self.extractchoices(prompt)
1182 1182 resps = [r for r, t in choices]
1183 1183 while True:
1184 1184 r = self.prompt(msg, resps[default])
1185 1185 if r.lower() in resps:
1186 1186 return resps.index(r.lower())
1187 1187 self.write(_("unrecognized response\n"))
1188 1188
1189 1189 def getpass(self, prompt=None, default=None):
1190 1190 if not self.interactive():
1191 1191 return default
1192 1192 try:
1193 1193 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1194 1194 # disable getpass() only if explicitly specified. it's still valid
1195 1195 # to interact with tty even if fin is not a tty.
1196 1196 with self.timeblockedsection('stdio'):
1197 1197 if self.configbool('ui', 'nontty'):
1198 1198 l = self.fin.readline()
1199 1199 if not l:
1200 1200 raise EOFError
1201 1201 return l.rstrip('\n')
1202 1202 else:
1203 1203 return getpass.getpass('')
1204 1204 except EOFError:
1205 1205 raise error.ResponseExpected()
1206 1206 def status(self, *msg, **opts):
1207 1207 '''write status message to output (if ui.quiet is False)
1208 1208
1209 1209 This adds an output label of "ui.status".
1210 1210 '''
1211 1211 if not self.quiet:
1212 1212 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1213 1213 self.write(*msg, **opts)
1214 1214 def warn(self, *msg, **opts):
1215 1215 '''write warning message to output (stderr)
1216 1216
1217 1217 This adds an output label of "ui.warning".
1218 1218 '''
1219 1219 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1220 1220 self.write_err(*msg, **opts)
1221 1221 def note(self, *msg, **opts):
1222 1222 '''write note to output (if ui.verbose is True)
1223 1223
1224 1224 This adds an output label of "ui.note".
1225 1225 '''
1226 1226 if self.verbose:
1227 1227 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1228 1228 self.write(*msg, **opts)
1229 1229 def debug(self, *msg, **opts):
1230 1230 '''write debug message to output (if ui.debugflag is True)
1231 1231
1232 1232 This adds an output label of "ui.debug".
1233 1233 '''
1234 1234 if self.debugflag:
1235 1235 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1236 1236 self.write(*msg, **opts)
1237 1237
1238 1238 def edit(self, text, user, extra=None, editform=None, pending=None,
1239 1239 repopath=None):
1240 1240 extra_defaults = {
1241 1241 'prefix': 'editor',
1242 1242 'suffix': '.txt',
1243 1243 }
1244 1244 if extra is not None:
1245 1245 extra_defaults.update(extra)
1246 1246 extra = extra_defaults
1247 1247
1248 1248 rdir = None
1249 1249 if self.configbool('experimental', 'editortmpinhg'):
1250 1250 rdir = repopath
1251 1251 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1252 1252 suffix=extra['suffix'], text=True,
1253 1253 dir=rdir)
1254 1254 try:
1255 1255 f = os.fdopen(fd, pycompat.sysstr("w"))
1256 1256 f.write(text)
1257 1257 f.close()
1258 1258
1259 1259 environ = {'HGUSER': user}
1260 1260 if 'transplant_source' in extra:
1261 1261 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1262 1262 for label in ('intermediate-source', 'source', 'rebase_source'):
1263 1263 if label in extra:
1264 1264 environ.update({'HGREVISION': extra[label]})
1265 1265 break
1266 1266 if editform:
1267 1267 environ.update({'HGEDITFORM': editform})
1268 1268 if pending:
1269 1269 environ.update({'HG_PENDING': pending})
1270 1270
1271 1271 editor = self.geteditor()
1272 1272
1273 1273 self.system("%s \"%s\"" % (editor, name),
1274 1274 environ=environ,
1275 1275 onerr=error.Abort, errprefix=_("edit failed"),
1276 1276 blockedtag='editor')
1277 1277
1278 1278 f = open(name)
1279 1279 t = f.read()
1280 1280 f.close()
1281 1281 finally:
1282 1282 os.unlink(name)
1283 1283
1284 1284 return t
1285 1285
1286 1286 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1287 1287 blockedtag=None):
1288 1288 '''execute shell command with appropriate output stream. command
1289 1289 output will be redirected if fout is not stdout.
1290 1290
1291 1291 if command fails and onerr is None, return status, else raise onerr
1292 1292 object as exception.
1293 1293 '''
1294 1294 if blockedtag is None:
1295 1295 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1296 1296 out = self.fout
1297 1297 if any(s[1] for s in self._bufferstates):
1298 1298 out = self
1299 1299 with self.timeblockedsection(blockedtag):
1300 1300 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1301 1301 if rc and onerr:
1302 1302 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1303 1303 util.explainexit(rc)[0])
1304 1304 if errprefix:
1305 1305 errmsg = '%s: %s' % (errprefix, errmsg)
1306 1306 raise onerr(errmsg)
1307 1307 return rc
1308 1308
1309 1309 def _runsystem(self, cmd, environ, cwd, out):
1310 1310 """actually execute the given shell command (can be overridden by
1311 1311 extensions like chg)"""
1312 1312 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1313 1313
1314 1314 def traceback(self, exc=None, force=False):
1315 1315 '''print exception traceback if traceback printing enabled or forced.
1316 1316 only to call in exception handler. returns true if traceback
1317 1317 printed.'''
1318 1318 if self.tracebackflag or force:
1319 1319 if exc is None:
1320 1320 exc = sys.exc_info()
1321 1321 cause = getattr(exc[1], 'cause', None)
1322 1322
1323 1323 if cause is not None:
1324 1324 causetb = traceback.format_tb(cause[2])
1325 1325 exctb = traceback.format_tb(exc[2])
1326 1326 exconly = traceback.format_exception_only(cause[0], cause[1])
1327 1327
1328 1328 # exclude frame where 'exc' was chained and rethrown from exctb
1329 1329 self.write_err('Traceback (most recent call last):\n',
1330 1330 ''.join(exctb[:-1]),
1331 1331 ''.join(causetb),
1332 1332 ''.join(exconly))
1333 1333 else:
1334 1334 output = traceback.format_exception(exc[0], exc[1], exc[2])
1335 1335 data = r''.join(output)
1336 1336 if pycompat.ispy3:
1337 1337 enc = pycompat.sysstr(encoding.encoding)
1338 1338 data = data.encode(enc, errors=r'replace')
1339 1339 self.write_err(data)
1340 1340 return self.tracebackflag or force
1341 1341
1342 1342 def geteditor(self):
1343 1343 '''return editor to use'''
1344 1344 if pycompat.sysplatform == 'plan9':
1345 1345 # vi is the MIPS instruction simulator on Plan 9. We
1346 1346 # instead default to E to plumb commit messages to
1347 1347 # avoid confusion.
1348 1348 editor = 'E'
1349 1349 else:
1350 1350 editor = 'vi'
1351 1351 return (encoding.environ.get("HGEDITOR") or
1352 1352 self.config("ui", "editor") or
1353 1353 encoding.environ.get("VISUAL") or
1354 1354 encoding.environ.get("EDITOR", editor))
1355 1355
1356 1356 @util.propertycache
1357 1357 def _progbar(self):
1358 1358 """setup the progbar singleton to the ui object"""
1359 1359 if (self.quiet or self.debugflag
1360 1360 or self.configbool('progress', 'disable', False)
1361 1361 or not progress.shouldprint(self)):
1362 1362 return None
1363 1363 return getprogbar(self)
1364 1364
1365 1365 def _progclear(self):
1366 1366 """clear progress bar output if any. use it before any output"""
1367 1367 if '_progbar' not in vars(self): # nothing loaded yet
1368 1368 return
1369 1369 if self._progbar is not None and self._progbar.printed:
1370 1370 self._progbar.clear()
1371 1371
1372 1372 def progress(self, topic, pos, item="", unit="", total=None):
1373 1373 '''show a progress message
1374 1374
1375 1375 By default a textual progress bar will be displayed if an operation
1376 1376 takes too long. 'topic' is the current operation, 'item' is a
1377 1377 non-numeric marker of the current position (i.e. the currently
1378 1378 in-process file), 'pos' is the current numeric position (i.e.
1379 1379 revision, bytes, etc.), unit is a corresponding unit label,
1380 1380 and total is the highest expected pos.
1381 1381
1382 1382 Multiple nested topics may be active at a time.
1383 1383
1384 1384 All topics should be marked closed by setting pos to None at
1385 1385 termination.
1386 1386 '''
1387 1387 if self._progbar is not None:
1388 1388 self._progbar.progress(topic, pos, item=item, unit=unit,
1389 1389 total=total)
1390 1390 if pos is None or not self.configbool('progress', 'debug'):
1391 1391 return
1392 1392
1393 1393 if unit:
1394 1394 unit = ' ' + unit
1395 1395 if item:
1396 1396 item = ' ' + item
1397 1397
1398 1398 if total:
1399 1399 pct = 100.0 * pos / total
1400 1400 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1401 1401 % (topic, item, pos, total, unit, pct))
1402 1402 else:
1403 1403 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1404 1404
1405 1405 def log(self, service, *msg, **opts):
1406 1406 '''hook for logging facility extensions
1407 1407
1408 1408 service should be a readily-identifiable subsystem, which will
1409 1409 allow filtering.
1410 1410
1411 1411 *msg should be a newline-terminated format string to log, and
1412 1412 then any values to %-format into that format string.
1413 1413
1414 1414 **opts currently has no defined meanings.
1415 1415 '''
1416 1416
1417 1417 def label(self, msg, label):
1418 1418 '''style msg based on supplied label
1419 1419
1420 1420 If some color mode is enabled, this will add the necessary control
1421 1421 characters to apply such color. In addition, 'debug' color mode adds
1422 1422 markup showing which label affects a piece of text.
1423 1423
1424 1424 ui.write(s, 'label') is equivalent to
1425 1425 ui.write(ui.label(s, 'label')).
1426 1426 '''
1427 1427 if self._colormode is not None:
1428 1428 return color.colorlabel(self, msg, label)
1429 1429 return msg
1430 1430
1431 1431 def develwarn(self, msg, stacklevel=1, config=None):
1432 1432 """issue a developer warning message
1433 1433
1434 1434 Use 'stacklevel' to report the offender some layers further up in the
1435 1435 stack.
1436 1436 """
1437 1437 if not self.configbool('devel', 'all-warnings'):
1438 1438 if config is not None and not self.configbool('devel', config):
1439 1439 return
1440 1440 msg = 'devel-warn: ' + msg
1441 1441 stacklevel += 1 # get in develwarn
1442 1442 if self.tracebackflag:
1443 1443 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1444 1444 self.log('develwarn', '%s at:\n%s' %
1445 1445 (msg, ''.join(util.getstackframes(stacklevel))))
1446 1446 else:
1447 1447 curframe = inspect.currentframe()
1448 1448 calframe = inspect.getouterframes(curframe, 2)
1449 1449 self.write_err('%s at: %s:%s (%s)\n'
1450 1450 % ((msg,) + calframe[stacklevel][1:4]))
1451 1451 self.log('develwarn', '%s at: %s:%s (%s)\n',
1452 1452 msg, *calframe[stacklevel][1:4])
1453 1453 curframe = calframe = None # avoid cycles
1454 1454
1455 1455 def deprecwarn(self, msg, version):
1456 1456 """issue a deprecation warning
1457 1457
1458 1458 - msg: message explaining what is deprecated and how to upgrade,
1459 1459 - version: last version where the API will be supported,
1460 1460 """
1461 1461 if not (self.configbool('devel', 'all-warnings')
1462 1462 or self.configbool('devel', 'deprec-warn')):
1463 1463 return
1464 1464 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1465 1465 " update your code.)") % version
1466 1466 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1467 1467
1468 1468 def exportableenviron(self):
1469 1469 """The environment variables that are safe to export, e.g. through
1470 1470 hgweb.
1471 1471 """
1472 1472 return self._exportableenviron
1473 1473
1474 1474 @contextlib.contextmanager
1475 1475 def configoverride(self, overrides, source=""):
1476 1476 """Context manager for temporary config overrides
1477 1477 `overrides` must be a dict of the following structure:
1478 1478 {(section, name) : value}"""
1479 1479 backups = {}
1480 1480 try:
1481 1481 for (section, name), value in overrides.items():
1482 1482 backups[(section, name)] = self.backupconfig(section, name)
1483 1483 self.setconfig(section, name, value, source)
1484 1484 yield
1485 1485 finally:
1486 1486 for __, backup in backups.items():
1487 1487 self.restoreconfig(backup)
1488 1488 # just restoring ui.quiet config to the previous value is not enough
1489 1489 # as it does not update ui.quiet class member
1490 1490 if ('ui', 'quiet') in overrides:
1491 1491 self.fixconfig(section='ui')
1492 1492
1493 1493 class paths(dict):
1494 1494 """Represents a collection of paths and their configs.
1495 1495
1496 1496 Data is initially derived from ui instances and the config files they have
1497 1497 loaded.
1498 1498 """
1499 1499 def __init__(self, ui):
1500 1500 dict.__init__(self)
1501 1501
1502 1502 for name, loc in ui.configitems('paths', ignoresub=True):
1503 1503 # No location is the same as not existing.
1504 1504 if not loc:
1505 1505 continue
1506 1506 loc, sub = ui.configsuboptions('paths', name)
1507 1507 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1508 1508
1509 1509 def getpath(self, name, default=None):
1510 1510 """Return a ``path`` from a string, falling back to default.
1511 1511
1512 1512 ``name`` can be a named path or locations. Locations are filesystem
1513 1513 paths or URIs.
1514 1514
1515 1515 Returns None if ``name`` is not a registered path, a URI, or a local
1516 1516 path to a repo.
1517 1517 """
1518 1518 # Only fall back to default if no path was requested.
1519 1519 if name is None:
1520 1520 if not default:
1521 1521 default = ()
1522 1522 elif not isinstance(default, (tuple, list)):
1523 1523 default = (default,)
1524 1524 for k in default:
1525 1525 try:
1526 1526 return self[k]
1527 1527 except KeyError:
1528 1528 continue
1529 1529 return None
1530 1530
1531 1531 # Most likely empty string.
1532 1532 # This may need to raise in the future.
1533 1533 if not name:
1534 1534 return None
1535 1535
1536 1536 try:
1537 1537 return self[name]
1538 1538 except KeyError:
1539 1539 # Try to resolve as a local path or URI.
1540 1540 try:
1541 1541 # We don't pass sub-options in, so no need to pass ui instance.
1542 1542 return path(None, None, rawloc=name)
1543 1543 except ValueError:
1544 1544 raise error.RepoError(_('repository %s does not exist') %
1545 1545 name)
1546 1546
1547 1547 _pathsuboptions = {}
1548 1548
1549 1549 def pathsuboption(option, attr):
1550 1550 """Decorator used to declare a path sub-option.
1551 1551
1552 1552 Arguments are the sub-option name and the attribute it should set on
1553 1553 ``path`` instances.
1554 1554
1555 1555 The decorated function will receive as arguments a ``ui`` instance,
1556 1556 ``path`` instance, and the string value of this option from the config.
1557 1557 The function should return the value that will be set on the ``path``
1558 1558 instance.
1559 1559
1560 1560 This decorator can be used to perform additional verification of
1561 1561 sub-options and to change the type of sub-options.
1562 1562 """
1563 1563 def register(func):
1564 1564 _pathsuboptions[option] = (attr, func)
1565 1565 return func
1566 1566 return register
1567 1567
1568 1568 @pathsuboption('pushurl', 'pushloc')
1569 1569 def pushurlpathoption(ui, path, value):
1570 1570 u = util.url(value)
1571 1571 # Actually require a URL.
1572 1572 if not u.scheme:
1573 1573 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1574 1574 return None
1575 1575
1576 1576 # Don't support the #foo syntax in the push URL to declare branch to
1577 1577 # push.
1578 1578 if u.fragment:
1579 1579 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1580 1580 'ignoring)\n') % path.name)
1581 1581 u.fragment = None
1582 1582
1583 1583 return str(u)
1584 1584
1585 1585 @pathsuboption('pushrev', 'pushrev')
1586 1586 def pushrevpathoption(ui, path, value):
1587 1587 return value
1588 1588
1589 1589 class path(object):
1590 1590 """Represents an individual path and its configuration."""
1591 1591
1592 1592 def __init__(self, ui, name, rawloc=None, suboptions=None):
1593 1593 """Construct a path from its config options.
1594 1594
1595 1595 ``ui`` is the ``ui`` instance the path is coming from.
1596 1596 ``name`` is the symbolic name of the path.
1597 1597 ``rawloc`` is the raw location, as defined in the config.
1598 1598 ``pushloc`` is the raw locations pushes should be made to.
1599 1599
1600 1600 If ``name`` is not defined, we require that the location be a) a local
1601 1601 filesystem path with a .hg directory or b) a URL. If not,
1602 1602 ``ValueError`` is raised.
1603 1603 """
1604 1604 if not rawloc:
1605 1605 raise ValueError('rawloc must be defined')
1606 1606
1607 1607 # Locations may define branches via syntax <base>#<branch>.
1608 1608 u = util.url(rawloc)
1609 1609 branch = None
1610 1610 if u.fragment:
1611 1611 branch = u.fragment
1612 1612 u.fragment = None
1613 1613
1614 1614 self.url = u
1615 1615 self.branch = branch
1616 1616
1617 1617 self.name = name
1618 1618 self.rawloc = rawloc
1619 1619 self.loc = str(u)
1620 1620
1621 1621 # When given a raw location but not a symbolic name, validate the
1622 1622 # location is valid.
1623 1623 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1624 1624 raise ValueError('location is not a URL or path to a local '
1625 1625 'repo: %s' % rawloc)
1626 1626
1627 1627 suboptions = suboptions or {}
1628 1628
1629 1629 # Now process the sub-options. If a sub-option is registered, its
1630 1630 # attribute will always be present. The value will be None if there
1631 1631 # was no valid sub-option.
1632 1632 for suboption, (attr, func) in _pathsuboptions.iteritems():
1633 1633 if suboption not in suboptions:
1634 1634 setattr(self, attr, None)
1635 1635 continue
1636 1636
1637 1637 value = func(ui, self, suboptions[suboption])
1638 1638 setattr(self, attr, value)
1639 1639
1640 1640 def _isvalidlocalpath(self, path):
1641 1641 """Returns True if the given path is a potentially valid repository.
1642 1642 This is its own function so that extensions can change the definition of
1643 1643 'valid' in this case (like when pulling from a git repo into a hg
1644 1644 one)."""
1645 1645 return os.path.isdir(os.path.join(path, '.hg'))
1646 1646
1647 1647 @property
1648 1648 def suboptions(self):
1649 1649 """Return sub-options and their values for this path.
1650 1650
1651 1651 This is intended to be used for presentation purposes.
1652 1652 """
1653 1653 d = {}
1654 1654 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1655 1655 value = getattr(self, attr)
1656 1656 if value is not None:
1657 1657 d[subopt] = value
1658 1658 return d
1659 1659
1660 1660 # we instantiate one globally shared progress bar to avoid
1661 1661 # competing progress bars when multiple UI objects get created
1662 1662 _progresssingleton = None
1663 1663
1664 1664 def getprogbar(ui):
1665 1665 global _progresssingleton
1666 1666 if _progresssingleton is None:
1667 1667 # passing 'ui' object to the singleton is fishy,
1668 1668 # this is how the extension used to work but feel free to rework it.
1669 1669 _progresssingleton = progress.progbar(ui)
1670 1670 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now