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