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