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