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