##// END OF EJS Templates
ui: allow open editor with custom filename...
Mykola Nikishov -
r27153:3553e40d default
parent child Browse files
Show More
@@ -1,1140 +1,1142 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 errno
11 11 import getpass
12 12 import inspect
13 13 import os
14 14 import socket
15 15 import sys
16 16 import tempfile
17 17 import traceback
18 18
19 19 from .i18n import _
20 20 from .node import hex
21 21
22 22 from . import (
23 23 config,
24 24 error,
25 25 formatter,
26 26 progress,
27 27 scmutil,
28 28 util,
29 29 )
30 30
31 31 samplehgrcs = {
32 32 'user':
33 33 """# example user config (see "hg help config" for more info)
34 34 [ui]
35 35 # name and email, e.g.
36 36 # username = Jane Doe <jdoe@example.com>
37 37 username =
38 38
39 39 [extensions]
40 40 # uncomment these lines to enable some popular extensions
41 41 # (see "hg help extensions" for more info)
42 42 #
43 43 # pager =
44 44 # progress =
45 45 # color =""",
46 46
47 47 'cloned':
48 48 """# example repository config (see "hg help config" for more info)
49 49 [paths]
50 50 default = %s
51 51
52 52 # path aliases to other clones of this repo in URLs or filesystem paths
53 53 # (see "hg help config.paths" for more info)
54 54 #
55 55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 57 # my-clone = /home/jdoe/jdoes-clone
58 58
59 59 [ui]
60 60 # name and email (local to this repository, optional), e.g.
61 61 # username = Jane Doe <jdoe@example.com>
62 62 """,
63 63
64 64 'local':
65 65 """# example repository config (see "hg help config" for more info)
66 66 [paths]
67 67 # path aliases to other clones of this repo in URLs or filesystem paths
68 68 # (see "hg help config.paths" for more info)
69 69 #
70 70 # default = http://example.com/hg/example-repo
71 71 # default-push = 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 'global':
81 81 """# example system-wide hg config (see "hg help config" for more info)
82 82
83 83 [extensions]
84 84 # uncomment these lines to enable some popular extensions
85 85 # (see "hg help extensions" for more info)
86 86 #
87 87 # blackbox =
88 88 # progress =
89 89 # color =
90 90 # pager =""",
91 91 }
92 92
93 93 class ui(object):
94 94 def __init__(self, src=None):
95 95 # _buffers: used for temporary capture of output
96 96 self._buffers = []
97 97 # 3-tuple describing how each buffer in the stack behaves.
98 98 # Values are (capture stderr, capture subprocesses, apply labels).
99 99 self._bufferstates = []
100 100 # When a buffer is active, defines whether we are expanding labels.
101 101 # This exists to prevent an extra list lookup.
102 102 self._bufferapplylabels = None
103 103 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
104 104 self._reportuntrusted = True
105 105 self._ocfg = config.config() # overlay
106 106 self._tcfg = config.config() # trusted
107 107 self._ucfg = config.config() # untrusted
108 108 self._trustusers = set()
109 109 self._trustgroups = set()
110 110 self.callhooks = True
111 111
112 112 if src:
113 113 self.fout = src.fout
114 114 self.ferr = src.ferr
115 115 self.fin = src.fin
116 116
117 117 self._tcfg = src._tcfg.copy()
118 118 self._ucfg = src._ucfg.copy()
119 119 self._ocfg = src._ocfg.copy()
120 120 self._trustusers = src._trustusers.copy()
121 121 self._trustgroups = src._trustgroups.copy()
122 122 self.environ = src.environ
123 123 self.callhooks = src.callhooks
124 124 self.fixconfig()
125 125 else:
126 126 self.fout = sys.stdout
127 127 self.ferr = sys.stderr
128 128 self.fin = sys.stdin
129 129
130 130 # shared read-only environment
131 131 self.environ = os.environ
132 132 # we always trust global config files
133 133 for f in scmutil.rcpath():
134 134 self.readconfig(f, trust=True)
135 135
136 136 def copy(self):
137 137 return self.__class__(self)
138 138
139 139 def formatter(self, topic, opts):
140 140 return formatter.formatter(self, topic, opts)
141 141
142 142 def _trusted(self, fp, f):
143 143 st = util.fstat(fp)
144 144 if util.isowner(st):
145 145 return True
146 146
147 147 tusers, tgroups = self._trustusers, self._trustgroups
148 148 if '*' in tusers or '*' in tgroups:
149 149 return True
150 150
151 151 user = util.username(st.st_uid)
152 152 group = util.groupname(st.st_gid)
153 153 if user in tusers or group in tgroups or user == util.username():
154 154 return True
155 155
156 156 if self._reportuntrusted:
157 157 self.warn(_('not trusting file %s from untrusted '
158 158 'user %s, group %s\n') % (f, user, group))
159 159 return False
160 160
161 161 def readconfig(self, filename, root=None, trust=False,
162 162 sections=None, remap=None):
163 163 try:
164 164 fp = open(filename)
165 165 except IOError:
166 166 if not sections: # ignore unless we were looking for something
167 167 return
168 168 raise
169 169
170 170 cfg = config.config()
171 171 trusted = sections or trust or self._trusted(fp, filename)
172 172
173 173 try:
174 174 cfg.read(filename, fp, sections=sections, remap=remap)
175 175 fp.close()
176 176 except error.ConfigError as inst:
177 177 if trusted:
178 178 raise
179 179 self.warn(_("ignored: %s\n") % str(inst))
180 180
181 181 if self.plain():
182 182 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
183 183 'logtemplate', 'statuscopies', 'style',
184 184 'traceback', 'verbose'):
185 185 if k in cfg['ui']:
186 186 del cfg['ui'][k]
187 187 for k, v in cfg.items('defaults'):
188 188 del cfg['defaults'][k]
189 189 # Don't remove aliases from the configuration if in the exceptionlist
190 190 if self.plain('alias'):
191 191 for k, v in cfg.items('alias'):
192 192 del cfg['alias'][k]
193 193 if self.plain('revsetalias'):
194 194 for k, v in cfg.items('revsetalias'):
195 195 del cfg['revsetalias'][k]
196 196
197 197 if trusted:
198 198 self._tcfg.update(cfg)
199 199 self._tcfg.update(self._ocfg)
200 200 self._ucfg.update(cfg)
201 201 self._ucfg.update(self._ocfg)
202 202
203 203 if root is None:
204 204 root = os.path.expanduser('~')
205 205 self.fixconfig(root=root)
206 206
207 207 def fixconfig(self, root=None, section=None):
208 208 if section in (None, 'paths'):
209 209 # expand vars and ~
210 210 # translate paths relative to root (or home) into absolute paths
211 211 root = root or os.getcwd()
212 212 for c in self._tcfg, self._ucfg, self._ocfg:
213 213 for n, p in c.items('paths'):
214 214 if not p:
215 215 continue
216 216 if '%%' in p:
217 217 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
218 218 % (n, p, self.configsource('paths', n)))
219 219 p = p.replace('%%', '%')
220 220 p = util.expandpath(p)
221 221 if not util.hasscheme(p) and not os.path.isabs(p):
222 222 p = os.path.normpath(os.path.join(root, p))
223 223 c.set("paths", n, p)
224 224
225 225 if section in (None, 'ui'):
226 226 # update ui options
227 227 self.debugflag = self.configbool('ui', 'debug')
228 228 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
229 229 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
230 230 if self.verbose and self.quiet:
231 231 self.quiet = self.verbose = False
232 232 self._reportuntrusted = self.debugflag or self.configbool("ui",
233 233 "report_untrusted", True)
234 234 self.tracebackflag = self.configbool('ui', 'traceback', False)
235 235
236 236 if section in (None, 'trusted'):
237 237 # update trust information
238 238 self._trustusers.update(self.configlist('trusted', 'users'))
239 239 self._trustgroups.update(self.configlist('trusted', 'groups'))
240 240
241 241 def backupconfig(self, section, item):
242 242 return (self._ocfg.backup(section, item),
243 243 self._tcfg.backup(section, item),
244 244 self._ucfg.backup(section, item),)
245 245 def restoreconfig(self, data):
246 246 self._ocfg.restore(data[0])
247 247 self._tcfg.restore(data[1])
248 248 self._ucfg.restore(data[2])
249 249
250 250 def setconfig(self, section, name, value, source=''):
251 251 for cfg in (self._ocfg, self._tcfg, self._ucfg):
252 252 cfg.set(section, name, value, source)
253 253 self.fixconfig(section=section)
254 254
255 255 def _data(self, untrusted):
256 256 return untrusted and self._ucfg or self._tcfg
257 257
258 258 def configsource(self, section, name, untrusted=False):
259 259 return self._data(untrusted).source(section, name) or 'none'
260 260
261 261 def config(self, section, name, default=None, untrusted=False):
262 262 if isinstance(name, list):
263 263 alternates = name
264 264 else:
265 265 alternates = [name]
266 266
267 267 for n in alternates:
268 268 value = self._data(untrusted).get(section, n, None)
269 269 if value is not None:
270 270 name = n
271 271 break
272 272 else:
273 273 value = default
274 274
275 275 if self.debugflag and not untrusted and self._reportuntrusted:
276 276 for n in alternates:
277 277 uvalue = self._ucfg.get(section, n)
278 278 if uvalue is not None and uvalue != value:
279 279 self.debug("ignoring untrusted configuration option "
280 280 "%s.%s = %s\n" % (section, n, uvalue))
281 281 return value
282 282
283 283 def configpath(self, section, name, default=None, untrusted=False):
284 284 'get a path config item, expanded relative to repo root or config file'
285 285 v = self.config(section, name, default, untrusted)
286 286 if v is None:
287 287 return None
288 288 if not os.path.isabs(v) or "://" not in v:
289 289 src = self.configsource(section, name, untrusted)
290 290 if ':' in src:
291 291 base = os.path.dirname(src.rsplit(':')[0])
292 292 v = os.path.join(base, os.path.expanduser(v))
293 293 return v
294 294
295 295 def configbool(self, section, name, default=False, untrusted=False):
296 296 """parse a configuration element as a boolean
297 297
298 298 >>> u = ui(); s = 'foo'
299 299 >>> u.setconfig(s, 'true', 'yes')
300 300 >>> u.configbool(s, 'true')
301 301 True
302 302 >>> u.setconfig(s, 'false', 'no')
303 303 >>> u.configbool(s, 'false')
304 304 False
305 305 >>> u.configbool(s, 'unknown')
306 306 False
307 307 >>> u.configbool(s, 'unknown', True)
308 308 True
309 309 >>> u.setconfig(s, 'invalid', 'somevalue')
310 310 >>> u.configbool(s, 'invalid')
311 311 Traceback (most recent call last):
312 312 ...
313 313 ConfigError: foo.invalid is not a boolean ('somevalue')
314 314 """
315 315
316 316 v = self.config(section, name, None, untrusted)
317 317 if v is None:
318 318 return default
319 319 if isinstance(v, bool):
320 320 return v
321 321 b = util.parsebool(v)
322 322 if b is None:
323 323 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
324 324 % (section, name, v))
325 325 return b
326 326
327 327 def configint(self, section, name, default=None, untrusted=False):
328 328 """parse a configuration element as an integer
329 329
330 330 >>> u = ui(); s = 'foo'
331 331 >>> u.setconfig(s, 'int1', '42')
332 332 >>> u.configint(s, 'int1')
333 333 42
334 334 >>> u.setconfig(s, 'int2', '-42')
335 335 >>> u.configint(s, 'int2')
336 336 -42
337 337 >>> u.configint(s, 'unknown', 7)
338 338 7
339 339 >>> u.setconfig(s, 'invalid', 'somevalue')
340 340 >>> u.configint(s, 'invalid')
341 341 Traceback (most recent call last):
342 342 ...
343 343 ConfigError: foo.invalid is not an integer ('somevalue')
344 344 """
345 345
346 346 v = self.config(section, name, None, untrusted)
347 347 if v is None:
348 348 return default
349 349 try:
350 350 return int(v)
351 351 except ValueError:
352 352 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
353 353 % (section, name, v))
354 354
355 355 def configbytes(self, section, name, default=0, untrusted=False):
356 356 """parse a configuration element as a quantity in bytes
357 357
358 358 Units can be specified as b (bytes), k or kb (kilobytes), m or
359 359 mb (megabytes), g or gb (gigabytes).
360 360
361 361 >>> u = ui(); s = 'foo'
362 362 >>> u.setconfig(s, 'val1', '42')
363 363 >>> u.configbytes(s, 'val1')
364 364 42
365 365 >>> u.setconfig(s, 'val2', '42.5 kb')
366 366 >>> u.configbytes(s, 'val2')
367 367 43520
368 368 >>> u.configbytes(s, 'unknown', '7 MB')
369 369 7340032
370 370 >>> u.setconfig(s, 'invalid', 'somevalue')
371 371 >>> u.configbytes(s, 'invalid')
372 372 Traceback (most recent call last):
373 373 ...
374 374 ConfigError: foo.invalid is not a byte quantity ('somevalue')
375 375 """
376 376
377 377 value = self.config(section, name)
378 378 if value is None:
379 379 if not isinstance(default, str):
380 380 return default
381 381 value = default
382 382 try:
383 383 return util.sizetoint(value)
384 384 except error.ParseError:
385 385 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
386 386 % (section, name, value))
387 387
388 388 def configlist(self, section, name, default=None, untrusted=False):
389 389 """parse a configuration element as a list of comma/space separated
390 390 strings
391 391
392 392 >>> u = ui(); s = 'foo'
393 393 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
394 394 >>> u.configlist(s, 'list1')
395 395 ['this', 'is', 'a small', 'test']
396 396 """
397 397
398 398 def _parse_plain(parts, s, offset):
399 399 whitespace = False
400 400 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
401 401 whitespace = True
402 402 offset += 1
403 403 if offset >= len(s):
404 404 return None, parts, offset
405 405 if whitespace:
406 406 parts.append('')
407 407 if s[offset] == '"' and not parts[-1]:
408 408 return _parse_quote, parts, offset + 1
409 409 elif s[offset] == '"' and parts[-1][-1] == '\\':
410 410 parts[-1] = parts[-1][:-1] + s[offset]
411 411 return _parse_plain, parts, offset + 1
412 412 parts[-1] += s[offset]
413 413 return _parse_plain, parts, offset + 1
414 414
415 415 def _parse_quote(parts, s, offset):
416 416 if offset < len(s) and s[offset] == '"': # ""
417 417 parts.append('')
418 418 offset += 1
419 419 while offset < len(s) and (s[offset].isspace() or
420 420 s[offset] == ','):
421 421 offset += 1
422 422 return _parse_plain, parts, offset
423 423
424 424 while offset < len(s) and s[offset] != '"':
425 425 if (s[offset] == '\\' and offset + 1 < len(s)
426 426 and s[offset + 1] == '"'):
427 427 offset += 1
428 428 parts[-1] += '"'
429 429 else:
430 430 parts[-1] += s[offset]
431 431 offset += 1
432 432
433 433 if offset >= len(s):
434 434 real_parts = _configlist(parts[-1])
435 435 if not real_parts:
436 436 parts[-1] = '"'
437 437 else:
438 438 real_parts[0] = '"' + real_parts[0]
439 439 parts = parts[:-1]
440 440 parts.extend(real_parts)
441 441 return None, parts, offset
442 442
443 443 offset += 1
444 444 while offset < len(s) and s[offset] in [' ', ',']:
445 445 offset += 1
446 446
447 447 if offset < len(s):
448 448 if offset + 1 == len(s) and s[offset] == '"':
449 449 parts[-1] += '"'
450 450 offset += 1
451 451 else:
452 452 parts.append('')
453 453 else:
454 454 return None, parts, offset
455 455
456 456 return _parse_plain, parts, offset
457 457
458 458 def _configlist(s):
459 459 s = s.rstrip(' ,')
460 460 if not s:
461 461 return []
462 462 parser, parts, offset = _parse_plain, [''], 0
463 463 while parser:
464 464 parser, parts, offset = parser(parts, s, offset)
465 465 return parts
466 466
467 467 result = self.config(section, name, untrusted=untrusted)
468 468 if result is None:
469 469 result = default or []
470 470 if isinstance(result, basestring):
471 471 result = _configlist(result.lstrip(' ,\n'))
472 472 if result is None:
473 473 result = default or []
474 474 return result
475 475
476 476 def has_section(self, section, untrusted=False):
477 477 '''tell whether section exists in config.'''
478 478 return section in self._data(untrusted)
479 479
480 480 def configitems(self, section, untrusted=False):
481 481 items = self._data(untrusted).items(section)
482 482 if self.debugflag and not untrusted and self._reportuntrusted:
483 483 for k, v in self._ucfg.items(section):
484 484 if self._tcfg.get(section, k) != v:
485 485 self.debug("ignoring untrusted configuration option "
486 486 "%s.%s = %s\n" % (section, k, v))
487 487 return items
488 488
489 489 def walkconfig(self, untrusted=False):
490 490 cfg = self._data(untrusted)
491 491 for section in cfg.sections():
492 492 for name, value in self.configitems(section, untrusted):
493 493 yield section, name, value
494 494
495 495 def plain(self, feature=None):
496 496 '''is plain mode active?
497 497
498 498 Plain mode means that all configuration variables which affect
499 499 the behavior and output of Mercurial should be
500 500 ignored. Additionally, the output should be stable,
501 501 reproducible and suitable for use in scripts or applications.
502 502
503 503 The only way to trigger plain mode is by setting either the
504 504 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
505 505
506 506 The return value can either be
507 507 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
508 508 - True otherwise
509 509 '''
510 510 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
511 511 return False
512 512 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
513 513 if feature and exceptions:
514 514 return feature not in exceptions
515 515 return True
516 516
517 517 def username(self):
518 518 """Return default username to be used in commits.
519 519
520 520 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
521 521 and stop searching if one of these is set.
522 522 If not found and ui.askusername is True, ask the user, else use
523 523 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
524 524 """
525 525 user = os.environ.get("HGUSER")
526 526 if user is None:
527 527 user = self.config("ui", ["username", "user"])
528 528 if user is not None:
529 529 user = os.path.expandvars(user)
530 530 if user is None:
531 531 user = os.environ.get("EMAIL")
532 532 if user is None and self.configbool("ui", "askusername"):
533 533 user = self.prompt(_("enter a commit username:"), default=None)
534 534 if user is None and not self.interactive():
535 535 try:
536 536 user = '%s@%s' % (util.getuser(), socket.getfqdn())
537 537 self.warn(_("no username found, using '%s' instead\n") % user)
538 538 except KeyError:
539 539 pass
540 540 if not user:
541 541 raise error.Abort(_('no username supplied'),
542 542 hint=_('use "hg config --edit" '
543 543 'to set your username'))
544 544 if "\n" in user:
545 545 raise error.Abort(_("username %s contains a newline\n")
546 546 % repr(user))
547 547 return user
548 548
549 549 def shortuser(self, user):
550 550 """Return a short representation of a user name or email address."""
551 551 if not self.verbose:
552 552 user = util.shortuser(user)
553 553 return user
554 554
555 555 def expandpath(self, loc, default=None):
556 556 """Return repository location relative to cwd or from [paths]"""
557 557 try:
558 558 p = self.paths.getpath(loc)
559 559 if p:
560 560 return p.rawloc
561 561 except error.RepoError:
562 562 pass
563 563
564 564 if default:
565 565 try:
566 566 p = self.paths.getpath(default)
567 567 if p:
568 568 return p.rawloc
569 569 except error.RepoError:
570 570 pass
571 571
572 572 return loc
573 573
574 574 @util.propertycache
575 575 def paths(self):
576 576 return paths(self)
577 577
578 578 def pushbuffer(self, error=False, subproc=False, labeled=False):
579 579 """install a buffer to capture standard output of the ui object
580 580
581 581 If error is True, the error output will be captured too.
582 582
583 583 If subproc is True, output from subprocesses (typically hooks) will be
584 584 captured too.
585 585
586 586 If labeled is True, any labels associated with buffered
587 587 output will be handled. By default, this has no effect
588 588 on the output returned, but extensions and GUI tools may
589 589 handle this argument and returned styled output. If output
590 590 is being buffered so it can be captured and parsed or
591 591 processed, labeled should not be set to True.
592 592 """
593 593 self._buffers.append([])
594 594 self._bufferstates.append((error, subproc, labeled))
595 595 self._bufferapplylabels = labeled
596 596
597 597 def popbuffer(self):
598 598 '''pop the last buffer and return the buffered output'''
599 599 self._bufferstates.pop()
600 600 if self._bufferstates:
601 601 self._bufferapplylabels = self._bufferstates[-1][2]
602 602 else:
603 603 self._bufferapplylabels = None
604 604
605 605 return "".join(self._buffers.pop())
606 606
607 607 def write(self, *args, **opts):
608 608 '''write args to output
609 609
610 610 By default, this method simply writes to the buffer or stdout,
611 611 but extensions or GUI tools may override this method,
612 612 write_err(), popbuffer(), and label() to style output from
613 613 various parts of hg.
614 614
615 615 An optional keyword argument, "label", can be passed in.
616 616 This should be a string containing label names separated by
617 617 space. Label names take the form of "topic.type". For example,
618 618 ui.debug() issues a label of "ui.debug".
619 619
620 620 When labeling output for a specific command, a label of
621 621 "cmdname.type" is recommended. For example, status issues
622 622 a label of "status.modified" for modified files.
623 623 '''
624 624 if self._buffers:
625 625 self._buffers[-1].extend(a for a in args)
626 626 else:
627 627 self._progclear()
628 628 for a in args:
629 629 self.fout.write(a)
630 630
631 631 def write_err(self, *args, **opts):
632 632 self._progclear()
633 633 try:
634 634 if self._bufferstates and self._bufferstates[-1][0]:
635 635 return self.write(*args, **opts)
636 636 if not getattr(self.fout, 'closed', False):
637 637 self.fout.flush()
638 638 for a in args:
639 639 self.ferr.write(a)
640 640 # stderr may be buffered under win32 when redirected to files,
641 641 # including stdout.
642 642 if not getattr(self.ferr, 'closed', False):
643 643 self.ferr.flush()
644 644 except IOError as inst:
645 645 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
646 646 raise
647 647
648 648 def flush(self):
649 649 try: self.fout.flush()
650 650 except (IOError, ValueError): pass
651 651 try: self.ferr.flush()
652 652 except (IOError, ValueError): pass
653 653
654 654 def _isatty(self, fh):
655 655 if self.configbool('ui', 'nontty', False):
656 656 return False
657 657 return util.isatty(fh)
658 658
659 659 def interactive(self):
660 660 '''is interactive input allowed?
661 661
662 662 An interactive session is a session where input can be reasonably read
663 663 from `sys.stdin'. If this function returns false, any attempt to read
664 664 from stdin should fail with an error, unless a sensible default has been
665 665 specified.
666 666
667 667 Interactiveness is triggered by the value of the `ui.interactive'
668 668 configuration variable or - if it is unset - when `sys.stdin' points
669 669 to a terminal device.
670 670
671 671 This function refers to input only; for output, see `ui.formatted()'.
672 672 '''
673 673 i = self.configbool("ui", "interactive", None)
674 674 if i is None:
675 675 # some environments replace stdin without implementing isatty
676 676 # usually those are non-interactive
677 677 return self._isatty(self.fin)
678 678
679 679 return i
680 680
681 681 def termwidth(self):
682 682 '''how wide is the terminal in columns?
683 683 '''
684 684 if 'COLUMNS' in os.environ:
685 685 try:
686 686 return int(os.environ['COLUMNS'])
687 687 except ValueError:
688 688 pass
689 689 return util.termwidth()
690 690
691 691 def formatted(self):
692 692 '''should formatted output be used?
693 693
694 694 It is often desirable to format the output to suite the output medium.
695 695 Examples of this are truncating long lines or colorizing messages.
696 696 However, this is not often not desirable when piping output into other
697 697 utilities, e.g. `grep'.
698 698
699 699 Formatted output is triggered by the value of the `ui.formatted'
700 700 configuration variable or - if it is unset - when `sys.stdout' points
701 701 to a terminal device. Please note that `ui.formatted' should be
702 702 considered an implementation detail; it is not intended for use outside
703 703 Mercurial or its extensions.
704 704
705 705 This function refers to output only; for input, see `ui.interactive()'.
706 706 This function always returns false when in plain mode, see `ui.plain()'.
707 707 '''
708 708 if self.plain():
709 709 return False
710 710
711 711 i = self.configbool("ui", "formatted", None)
712 712 if i is None:
713 713 # some environments replace stdout without implementing isatty
714 714 # usually those are non-interactive
715 715 return self._isatty(self.fout)
716 716
717 717 return i
718 718
719 719 def _readline(self, prompt=''):
720 720 if self._isatty(self.fin):
721 721 try:
722 722 # magically add command line editing support, where
723 723 # available
724 724 import readline
725 725 # force demandimport to really load the module
726 726 readline.read_history_file
727 727 # windows sometimes raises something other than ImportError
728 728 except Exception:
729 729 pass
730 730
731 731 # call write() so output goes through subclassed implementation
732 732 # e.g. color extension on Windows
733 733 self.write(prompt)
734 734
735 735 # instead of trying to emulate raw_input, swap (self.fin,
736 736 # self.fout) with (sys.stdin, sys.stdout)
737 737 oldin = sys.stdin
738 738 oldout = sys.stdout
739 739 sys.stdin = self.fin
740 740 sys.stdout = self.fout
741 741 # prompt ' ' must exist; otherwise readline may delete entire line
742 742 # - http://bugs.python.org/issue12833
743 743 line = raw_input(' ')
744 744 sys.stdin = oldin
745 745 sys.stdout = oldout
746 746
747 747 # When stdin is in binary mode on Windows, it can cause
748 748 # raw_input() to emit an extra trailing carriage return
749 749 if os.linesep == '\r\n' and line and line[-1] == '\r':
750 750 line = line[:-1]
751 751 return line
752 752
753 753 def prompt(self, msg, default="y"):
754 754 """Prompt user with msg, read response.
755 755 If ui is not interactive, the default is returned.
756 756 """
757 757 if not self.interactive():
758 758 self.write(msg, ' ', default, "\n")
759 759 return default
760 760 try:
761 761 r = self._readline(self.label(msg, 'ui.prompt'))
762 762 if not r:
763 763 r = default
764 764 if self.configbool('ui', 'promptecho'):
765 765 self.write(r, "\n")
766 766 return r
767 767 except EOFError:
768 768 raise error.ResponseExpected()
769 769
770 770 @staticmethod
771 771 def extractchoices(prompt):
772 772 """Extract prompt message and list of choices from specified prompt.
773 773
774 774 This returns tuple "(message, choices)", and "choices" is the
775 775 list of tuple "(response character, text without &)".
776 776 """
777 777 parts = prompt.split('$$')
778 778 msg = parts[0].rstrip(' ')
779 779 choices = [p.strip(' ') for p in parts[1:]]
780 780 return (msg,
781 781 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
782 782 for s in choices])
783 783
784 784 def promptchoice(self, prompt, default=0):
785 785 """Prompt user with a message, read response, and ensure it matches
786 786 one of the provided choices. The prompt is formatted as follows:
787 787
788 788 "would you like fries with that (Yn)? $$ &Yes $$ &No"
789 789
790 790 The index of the choice is returned. Responses are case
791 791 insensitive. If ui is not interactive, the default is
792 792 returned.
793 793 """
794 794
795 795 msg, choices = self.extractchoices(prompt)
796 796 resps = [r for r, t in choices]
797 797 while True:
798 798 r = self.prompt(msg, resps[default])
799 799 if r.lower() in resps:
800 800 return resps.index(r.lower())
801 801 self.write(_("unrecognized response\n"))
802 802
803 803 def getpass(self, prompt=None, default=None):
804 804 if not self.interactive():
805 805 return default
806 806 try:
807 807 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
808 808 # disable getpass() only if explicitly specified. it's still valid
809 809 # to interact with tty even if fin is not a tty.
810 810 if self.configbool('ui', 'nontty'):
811 811 return self.fin.readline().rstrip('\n')
812 812 else:
813 813 return getpass.getpass('')
814 814 except EOFError:
815 815 raise error.ResponseExpected()
816 816 def status(self, *msg, **opts):
817 817 '''write status message to output (if ui.quiet is False)
818 818
819 819 This adds an output label of "ui.status".
820 820 '''
821 821 if not self.quiet:
822 822 opts['label'] = opts.get('label', '') + ' ui.status'
823 823 self.write(*msg, **opts)
824 824 def warn(self, *msg, **opts):
825 825 '''write warning message to output (stderr)
826 826
827 827 This adds an output label of "ui.warning".
828 828 '''
829 829 opts['label'] = opts.get('label', '') + ' ui.warning'
830 830 self.write_err(*msg, **opts)
831 831 def note(self, *msg, **opts):
832 832 '''write note to output (if ui.verbose is True)
833 833
834 834 This adds an output label of "ui.note".
835 835 '''
836 836 if self.verbose:
837 837 opts['label'] = opts.get('label', '') + ' ui.note'
838 838 self.write(*msg, **opts)
839 839 def debug(self, *msg, **opts):
840 840 '''write debug message to output (if ui.debugflag is True)
841 841
842 842 This adds an output label of "ui.debug".
843 843 '''
844 844 if self.debugflag:
845 845 opts['label'] = opts.get('label', '') + ' ui.debug'
846 846 self.write(*msg, **opts)
847 847
848 848 def edit(self, text, user, extra=None, editform=None, pending=None):
849 if extra is None:
850 extra = {}
851 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
852 text=True)
849 extra_defaults = { 'prefix': 'editor' }
850 if extra is not None:
851 extra_defaults.update(extra)
852 extra = extra_defaults
853 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
854 suffix=".txt", text=True)
853 855 try:
854 856 f = os.fdopen(fd, "w")
855 857 f.write(text)
856 858 f.close()
857 859
858 860 environ = {'HGUSER': user}
859 861 if 'transplant_source' in extra:
860 862 environ.update({'HGREVISION': hex(extra['transplant_source'])})
861 863 for label in ('intermediate-source', 'source', 'rebase_source'):
862 864 if label in extra:
863 865 environ.update({'HGREVISION': extra[label]})
864 866 break
865 867 if editform:
866 868 environ.update({'HGEDITFORM': editform})
867 869 if pending:
868 870 environ.update({'HG_PENDING': pending})
869 871
870 872 editor = self.geteditor()
871 873
872 874 self.system("%s \"%s\"" % (editor, name),
873 875 environ=environ,
874 876 onerr=error.Abort, errprefix=_("edit failed"))
875 877
876 878 f = open(name)
877 879 t = f.read()
878 880 f.close()
879 881 finally:
880 882 os.unlink(name)
881 883
882 884 return t
883 885
884 886 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
885 887 '''execute shell command with appropriate output stream. command
886 888 output will be redirected if fout is not stdout.
887 889 '''
888 890 out = self.fout
889 891 if any(s[1] for s in self._bufferstates):
890 892 out = self
891 893 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
892 894 errprefix=errprefix, out=out)
893 895
894 896 def traceback(self, exc=None, force=False):
895 897 '''print exception traceback if traceback printing enabled or forced.
896 898 only to call in exception handler. returns true if traceback
897 899 printed.'''
898 900 if self.tracebackflag or force:
899 901 if exc is None:
900 902 exc = sys.exc_info()
901 903 cause = getattr(exc[1], 'cause', None)
902 904
903 905 if cause is not None:
904 906 causetb = traceback.format_tb(cause[2])
905 907 exctb = traceback.format_tb(exc[2])
906 908 exconly = traceback.format_exception_only(cause[0], cause[1])
907 909
908 910 # exclude frame where 'exc' was chained and rethrown from exctb
909 911 self.write_err('Traceback (most recent call last):\n',
910 912 ''.join(exctb[:-1]),
911 913 ''.join(causetb),
912 914 ''.join(exconly))
913 915 else:
914 916 output = traceback.format_exception(exc[0], exc[1], exc[2])
915 917 self.write_err(''.join(output))
916 918 return self.tracebackflag or force
917 919
918 920 def geteditor(self):
919 921 '''return editor to use'''
920 922 if sys.platform == 'plan9':
921 923 # vi is the MIPS instruction simulator on Plan 9. We
922 924 # instead default to E to plumb commit messages to
923 925 # avoid confusion.
924 926 editor = 'E'
925 927 else:
926 928 editor = 'vi'
927 929 return (os.environ.get("HGEDITOR") or
928 930 self.config("ui", "editor") or
929 931 os.environ.get("VISUAL") or
930 932 os.environ.get("EDITOR", editor))
931 933
932 934 @util.propertycache
933 935 def _progbar(self):
934 936 """setup the progbar singleton to the ui object"""
935 937 if (self.quiet or self.debugflag
936 938 or self.configbool('progress', 'disable', False)
937 939 or not progress.shouldprint(self)):
938 940 return None
939 941 return getprogbar(self)
940 942
941 943 def _progclear(self):
942 944 """clear progress bar output if any. use it before any output"""
943 945 if '_progbar' not in vars(self): # nothing loaded yet
944 946 return
945 947 if self._progbar is not None and self._progbar.printed:
946 948 self._progbar.clear()
947 949
948 950 def progress(self, topic, pos, item="", unit="", total=None):
949 951 '''show a progress message
950 952
951 953 With stock hg, this is simply a debug message that is hidden
952 954 by default, but with extensions or GUI tools it may be
953 955 visible. 'topic' is the current operation, 'item' is a
954 956 non-numeric marker of the current position (i.e. the currently
955 957 in-process file), 'pos' is the current numeric position (i.e.
956 958 revision, bytes, etc.), unit is a corresponding unit label,
957 959 and total is the highest expected pos.
958 960
959 961 Multiple nested topics may be active at a time.
960 962
961 963 All topics should be marked closed by setting pos to None at
962 964 termination.
963 965 '''
964 966 if self._progbar is not None:
965 967 self._progbar.progress(topic, pos, item=item, unit=unit,
966 968 total=total)
967 969 if pos is None or not self.configbool('progress', 'debug'):
968 970 return
969 971
970 972 if unit:
971 973 unit = ' ' + unit
972 974 if item:
973 975 item = ' ' + item
974 976
975 977 if total:
976 978 pct = 100.0 * pos / total
977 979 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
978 980 % (topic, item, pos, total, unit, pct))
979 981 else:
980 982 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
981 983
982 984 def log(self, service, *msg, **opts):
983 985 '''hook for logging facility extensions
984 986
985 987 service should be a readily-identifiable subsystem, which will
986 988 allow filtering.
987 989
988 990 *msg should be a newline-terminated format string to log, and
989 991 then any values to %-format into that format string.
990 992
991 993 **opts currently has no defined meanings.
992 994 '''
993 995
994 996 def label(self, msg, label):
995 997 '''style msg based on supplied label
996 998
997 999 Like ui.write(), this just returns msg unchanged, but extensions
998 1000 and GUI tools can override it to allow styling output without
999 1001 writing it.
1000 1002
1001 1003 ui.write(s, 'label') is equivalent to
1002 1004 ui.write(ui.label(s, 'label')).
1003 1005 '''
1004 1006 return msg
1005 1007
1006 1008 def develwarn(self, msg):
1007 1009 """issue a developer warning message"""
1008 1010 msg = 'devel-warn: ' + msg
1009 1011 if self.tracebackflag:
1010 1012 util.debugstacktrace(msg, 2, self.ferr, self.fout)
1011 1013 else:
1012 1014 curframe = inspect.currentframe()
1013 1015 calframe = inspect.getouterframes(curframe, 2)
1014 1016 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1015 1017
1016 1018 class paths(dict):
1017 1019 """Represents a collection of paths and their configs.
1018 1020
1019 1021 Data is initially derived from ui instances and the config files they have
1020 1022 loaded.
1021 1023 """
1022 1024 def __init__(self, ui):
1023 1025 dict.__init__(self)
1024 1026
1025 1027 for name, loc in ui.configitems('paths'):
1026 1028 # No location is the same as not existing.
1027 1029 if not loc:
1028 1030 continue
1029 1031
1030 1032 # TODO ignore default-push once all consumers stop referencing it
1031 1033 # since it is handled specifically below.
1032 1034
1033 1035 self[name] = path(name, rawloc=loc)
1034 1036
1035 1037 # Handle default-push, which is a one-off that defines the push URL for
1036 1038 # the "default" path.
1037 1039 defaultpush = ui.config('paths', 'default-push')
1038 1040 if defaultpush:
1039 1041 # "default-push" can be defined without "default" entry. This is a
1040 1042 # bit weird, but is allowed for backwards compatibility.
1041 1043 if 'default' not in self:
1042 1044 self['default'] = path('default', rawloc=defaultpush)
1043 1045 self['default']._pushloc = defaultpush
1044 1046
1045 1047 def getpath(self, name, default=None):
1046 1048 """Return a ``path`` from a string, falling back to a default.
1047 1049
1048 1050 ``name`` can be a named path or locations. Locations are filesystem
1049 1051 paths or URIs.
1050 1052
1051 1053 Returns None if ``name`` is not a registered path, a URI, or a local
1052 1054 path to a repo.
1053 1055 """
1054 1056 # Only fall back to default if no path was requested.
1055 1057 if name is None:
1056 1058 if default:
1057 1059 try:
1058 1060 return self[default]
1059 1061 except KeyError:
1060 1062 return None
1061 1063 else:
1062 1064 return None
1063 1065
1064 1066 # Most likely empty string.
1065 1067 # This may need to raise in the future.
1066 1068 if not name:
1067 1069 return None
1068 1070
1069 1071 try:
1070 1072 return self[name]
1071 1073 except KeyError:
1072 1074 # Try to resolve as a local path or URI.
1073 1075 try:
1074 1076 return path(None, rawloc=name)
1075 1077 except ValueError:
1076 1078 raise error.RepoError(_('repository %s does not exist') %
1077 1079 name)
1078 1080
1079 1081 assert False
1080 1082
1081 1083 class path(object):
1082 1084 """Represents an individual path and its configuration."""
1083 1085
1084 1086 def __init__(self, name, rawloc=None, pushloc=None):
1085 1087 """Construct a path from its config options.
1086 1088
1087 1089 ``name`` is the symbolic name of the path.
1088 1090 ``rawloc`` is the raw location, as defined in the config.
1089 1091 ``pushloc`` is the raw locations pushes should be made to.
1090 1092
1091 1093 If ``name`` is not defined, we require that the location be a) a local
1092 1094 filesystem path with a .hg directory or b) a URL. If not,
1093 1095 ``ValueError`` is raised.
1094 1096 """
1095 1097 if not rawloc:
1096 1098 raise ValueError('rawloc must be defined')
1097 1099
1098 1100 # Locations may define branches via syntax <base>#<branch>.
1099 1101 u = util.url(rawloc)
1100 1102 branch = None
1101 1103 if u.fragment:
1102 1104 branch = u.fragment
1103 1105 u.fragment = None
1104 1106
1105 1107 self.url = u
1106 1108 self.branch = branch
1107 1109
1108 1110 self.name = name
1109 1111 self.rawloc = rawloc
1110 1112 self.loc = str(u)
1111 1113 self._pushloc = pushloc
1112 1114
1113 1115 # When given a raw location but not a symbolic name, validate the
1114 1116 # location is valid.
1115 1117 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1116 1118 raise ValueError('location is not a URL or path to a local '
1117 1119 'repo: %s' % rawloc)
1118 1120
1119 1121 def _isvalidlocalpath(self, path):
1120 1122 """Returns True if the given path is a potentially valid repository.
1121 1123 This is its own function so that extensions can change the definition of
1122 1124 'valid' in this case (like when pulling from a git repo into a hg
1123 1125 one)."""
1124 1126 return os.path.isdir(os.path.join(path, '.hg'))
1125 1127
1126 1128 @property
1127 1129 def pushloc(self):
1128 1130 return self._pushloc or self.loc
1129 1131
1130 1132 # we instantiate one globally shared progress bar to avoid
1131 1133 # competing progress bars when multiple UI objects get created
1132 1134 _progresssingleton = None
1133 1135
1134 1136 def getprogbar(ui):
1135 1137 global _progresssingleton
1136 1138 if _progresssingleton is None:
1137 1139 # passing 'ui' object to the singleton is fishy,
1138 1140 # this is how the extension used to work but feel free to rework it.
1139 1141 _progresssingleton = progress.progbar(ui)
1140 1142 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now