##// END OF EJS Templates
ui: swallow EBADF on stderr...
Kevin Bullock -
r16367:c14898df stable
parent child Browse files
Show More
@@ -1,743 +1,743
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 i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, scmutil, util, error
11 11
12 12 class ui(object):
13 13 def __init__(self, src=None):
14 14 self._buffers = []
15 15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 16 self._reportuntrusted = True
17 17 self._ocfg = config.config() # overlay
18 18 self._tcfg = config.config() # trusted
19 19 self._ucfg = config.config() # untrusted
20 20 self._trustusers = set()
21 21 self._trustgroups = set()
22 22
23 23 if src:
24 24 self.fout = src.fout
25 25 self.ferr = src.ferr
26 26 self.fin = src.fin
27 27
28 28 self._tcfg = src._tcfg.copy()
29 29 self._ucfg = src._ucfg.copy()
30 30 self._ocfg = src._ocfg.copy()
31 31 self._trustusers = src._trustusers.copy()
32 32 self._trustgroups = src._trustgroups.copy()
33 33 self.environ = src.environ
34 34 self.fixconfig()
35 35 else:
36 36 self.fout = sys.stdout
37 37 self.ferr = sys.stderr
38 38 self.fin = sys.stdin
39 39
40 40 # shared read-only environment
41 41 self.environ = os.environ
42 42 # we always trust global config files
43 43 for f in scmutil.rcpath():
44 44 self.readconfig(f, trust=True)
45 45
46 46 def copy(self):
47 47 return self.__class__(self)
48 48
49 49 def _trusted(self, fp, f):
50 50 st = util.fstat(fp)
51 51 if util.isowner(st):
52 52 return True
53 53
54 54 tusers, tgroups = self._trustusers, self._trustgroups
55 55 if '*' in tusers or '*' in tgroups:
56 56 return True
57 57
58 58 user = util.username(st.st_uid)
59 59 group = util.groupname(st.st_gid)
60 60 if user in tusers or group in tgroups or user == util.username():
61 61 return True
62 62
63 63 if self._reportuntrusted:
64 64 self.warn(_('Not trusting file %s from untrusted '
65 65 'user %s, group %s\n') % (f, user, group))
66 66 return False
67 67
68 68 def readconfig(self, filename, root=None, trust=False,
69 69 sections=None, remap=None):
70 70 try:
71 71 fp = open(filename)
72 72 except IOError:
73 73 if not sections: # ignore unless we were looking for something
74 74 return
75 75 raise
76 76
77 77 cfg = config.config()
78 78 trusted = sections or trust or self._trusted(fp, filename)
79 79
80 80 try:
81 81 cfg.read(filename, fp, sections=sections, remap=remap)
82 82 fp.close()
83 83 except error.ConfigError, inst:
84 84 if trusted:
85 85 raise
86 86 self.warn(_("Ignored: %s\n") % str(inst))
87 87
88 88 if self.plain():
89 89 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
90 90 'logtemplate', 'style',
91 91 'traceback', 'verbose'):
92 92 if k in cfg['ui']:
93 93 del cfg['ui'][k]
94 94 for k, v in cfg.items('defaults'):
95 95 del cfg['defaults'][k]
96 96 # Don't remove aliases from the configuration if in the exceptionlist
97 97 if self.plain('alias'):
98 98 for k, v in cfg.items('alias'):
99 99 del cfg['alias'][k]
100 100
101 101 if trusted:
102 102 self._tcfg.update(cfg)
103 103 self._tcfg.update(self._ocfg)
104 104 self._ucfg.update(cfg)
105 105 self._ucfg.update(self._ocfg)
106 106
107 107 if root is None:
108 108 root = os.path.expanduser('~')
109 109 self.fixconfig(root=root)
110 110
111 111 def fixconfig(self, root=None, section=None):
112 112 if section in (None, 'paths'):
113 113 # expand vars and ~
114 114 # translate paths relative to root (or home) into absolute paths
115 115 root = root or os.getcwd()
116 116 for c in self._tcfg, self._ucfg, self._ocfg:
117 117 for n, p in c.items('paths'):
118 118 if not p:
119 119 continue
120 120 if '%%' in p:
121 121 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
122 122 % (n, p, self.configsource('paths', n)))
123 123 p = p.replace('%%', '%')
124 124 p = util.expandpath(p)
125 125 if not util.hasscheme(p) and not os.path.isabs(p):
126 126 p = os.path.normpath(os.path.join(root, p))
127 127 c.set("paths", n, p)
128 128
129 129 if section in (None, 'ui'):
130 130 # update ui options
131 131 self.debugflag = self.configbool('ui', 'debug')
132 132 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
133 133 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
134 134 if self.verbose and self.quiet:
135 135 self.quiet = self.verbose = False
136 136 self._reportuntrusted = self.debugflag or self.configbool("ui",
137 137 "report_untrusted", True)
138 138 self.tracebackflag = self.configbool('ui', 'traceback', False)
139 139
140 140 if section in (None, 'trusted'):
141 141 # update trust information
142 142 self._trustusers.update(self.configlist('trusted', 'users'))
143 143 self._trustgroups.update(self.configlist('trusted', 'groups'))
144 144
145 145 def backupconfig(self, section, item):
146 146 return (self._ocfg.backup(section, item),
147 147 self._tcfg.backup(section, item),
148 148 self._ucfg.backup(section, item),)
149 149 def restoreconfig(self, data):
150 150 self._ocfg.restore(data[0])
151 151 self._tcfg.restore(data[1])
152 152 self._ucfg.restore(data[2])
153 153
154 154 def setconfig(self, section, name, value, overlay=True):
155 155 if overlay:
156 156 self._ocfg.set(section, name, value)
157 157 self._tcfg.set(section, name, value)
158 158 self._ucfg.set(section, name, value)
159 159 self.fixconfig(section=section)
160 160
161 161 def _data(self, untrusted):
162 162 return untrusted and self._ucfg or self._tcfg
163 163
164 164 def configsource(self, section, name, untrusted=False):
165 165 return self._data(untrusted).source(section, name) or 'none'
166 166
167 167 def config(self, section, name, default=None, untrusted=False):
168 168 if isinstance(name, list):
169 169 alternates = name
170 170 else:
171 171 alternates = [name]
172 172
173 173 for n in alternates:
174 174 value = self._data(untrusted).get(section, name, None)
175 175 if value is not None:
176 176 name = n
177 177 break
178 178 else:
179 179 value = default
180 180
181 181 if self.debugflag and not untrusted and self._reportuntrusted:
182 182 uvalue = self._ucfg.get(section, name)
183 183 if uvalue is not None and uvalue != value:
184 184 self.debug("ignoring untrusted configuration option "
185 185 "%s.%s = %s\n" % (section, name, uvalue))
186 186 return value
187 187
188 188 def configpath(self, section, name, default=None, untrusted=False):
189 189 'get a path config item, expanded relative to repo root or config file'
190 190 v = self.config(section, name, default, untrusted)
191 191 if v is None:
192 192 return None
193 193 if not os.path.isabs(v) or "://" not in v:
194 194 src = self.configsource(section, name, untrusted)
195 195 if ':' in src:
196 196 base = os.path.dirname(src.rsplit(':')[0])
197 197 v = os.path.join(base, os.path.expanduser(v))
198 198 return v
199 199
200 200 def configbool(self, section, name, default=False, untrusted=False):
201 201 """parse a configuration element as a boolean
202 202
203 203 >>> u = ui(); s = 'foo'
204 204 >>> u.setconfig(s, 'true', 'yes')
205 205 >>> u.configbool(s, 'true')
206 206 True
207 207 >>> u.setconfig(s, 'false', 'no')
208 208 >>> u.configbool(s, 'false')
209 209 False
210 210 >>> u.configbool(s, 'unknown')
211 211 False
212 212 >>> u.configbool(s, 'unknown', True)
213 213 True
214 214 >>> u.setconfig(s, 'invalid', 'somevalue')
215 215 >>> u.configbool(s, 'invalid')
216 216 Traceback (most recent call last):
217 217 ...
218 218 ConfigError: foo.invalid is not a boolean ('somevalue')
219 219 """
220 220
221 221 v = self.config(section, name, None, untrusted)
222 222 if v is None:
223 223 return default
224 224 if isinstance(v, bool):
225 225 return v
226 226 b = util.parsebool(v)
227 227 if b is None:
228 228 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
229 229 % (section, name, v))
230 230 return b
231 231
232 232 def configint(self, section, name, default=None, untrusted=False):
233 233 """parse a configuration element as an integer
234 234
235 235 >>> u = ui(); s = 'foo'
236 236 >>> u.setconfig(s, 'int1', '42')
237 237 >>> u.configint(s, 'int1')
238 238 42
239 239 >>> u.setconfig(s, 'int2', '-42')
240 240 >>> u.configint(s, 'int2')
241 241 -42
242 242 >>> u.configint(s, 'unknown', 7)
243 243 7
244 244 >>> u.setconfig(s, 'invalid', 'somevalue')
245 245 >>> u.configint(s, 'invalid')
246 246 Traceback (most recent call last):
247 247 ...
248 248 ConfigError: foo.invalid is not an integer ('somevalue')
249 249 """
250 250
251 251 v = self.config(section, name, None, untrusted)
252 252 if v is None:
253 253 return default
254 254 try:
255 255 return int(v)
256 256 except ValueError:
257 257 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
258 258 % (section, name, v))
259 259
260 260 def configlist(self, section, name, default=None, untrusted=False):
261 261 """parse a configuration element as a list of comma/space separated
262 262 strings
263 263
264 264 >>> u = ui(); s = 'foo'
265 265 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
266 266 >>> u.configlist(s, 'list1')
267 267 ['this', 'is', 'a small', 'test']
268 268 """
269 269
270 270 def _parse_plain(parts, s, offset):
271 271 whitespace = False
272 272 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
273 273 whitespace = True
274 274 offset += 1
275 275 if offset >= len(s):
276 276 return None, parts, offset
277 277 if whitespace:
278 278 parts.append('')
279 279 if s[offset] == '"' and not parts[-1]:
280 280 return _parse_quote, parts, offset + 1
281 281 elif s[offset] == '"' and parts[-1][-1] == '\\':
282 282 parts[-1] = parts[-1][:-1] + s[offset]
283 283 return _parse_plain, parts, offset + 1
284 284 parts[-1] += s[offset]
285 285 return _parse_plain, parts, offset + 1
286 286
287 287 def _parse_quote(parts, s, offset):
288 288 if offset < len(s) and s[offset] == '"': # ""
289 289 parts.append('')
290 290 offset += 1
291 291 while offset < len(s) and (s[offset].isspace() or
292 292 s[offset] == ','):
293 293 offset += 1
294 294 return _parse_plain, parts, offset
295 295
296 296 while offset < len(s) and s[offset] != '"':
297 297 if (s[offset] == '\\' and offset + 1 < len(s)
298 298 and s[offset + 1] == '"'):
299 299 offset += 1
300 300 parts[-1] += '"'
301 301 else:
302 302 parts[-1] += s[offset]
303 303 offset += 1
304 304
305 305 if offset >= len(s):
306 306 real_parts = _configlist(parts[-1])
307 307 if not real_parts:
308 308 parts[-1] = '"'
309 309 else:
310 310 real_parts[0] = '"' + real_parts[0]
311 311 parts = parts[:-1]
312 312 parts.extend(real_parts)
313 313 return None, parts, offset
314 314
315 315 offset += 1
316 316 while offset < len(s) and s[offset] in [' ', ',']:
317 317 offset += 1
318 318
319 319 if offset < len(s):
320 320 if offset + 1 == len(s) and s[offset] == '"':
321 321 parts[-1] += '"'
322 322 offset += 1
323 323 else:
324 324 parts.append('')
325 325 else:
326 326 return None, parts, offset
327 327
328 328 return _parse_plain, parts, offset
329 329
330 330 def _configlist(s):
331 331 s = s.rstrip(' ,')
332 332 if not s:
333 333 return []
334 334 parser, parts, offset = _parse_plain, [''], 0
335 335 while parser:
336 336 parser, parts, offset = parser(parts, s, offset)
337 337 return parts
338 338
339 339 result = self.config(section, name, untrusted=untrusted)
340 340 if result is None:
341 341 result = default or []
342 342 if isinstance(result, basestring):
343 343 result = _configlist(result.lstrip(' ,\n'))
344 344 if result is None:
345 345 result = default or []
346 346 return result
347 347
348 348 def has_section(self, section, untrusted=False):
349 349 '''tell whether section exists in config.'''
350 350 return section in self._data(untrusted)
351 351
352 352 def configitems(self, section, untrusted=False):
353 353 items = self._data(untrusted).items(section)
354 354 if self.debugflag and not untrusted and self._reportuntrusted:
355 355 for k, v in self._ucfg.items(section):
356 356 if self._tcfg.get(section, k) != v:
357 357 self.debug("ignoring untrusted configuration option "
358 358 "%s.%s = %s\n" % (section, k, v))
359 359 return items
360 360
361 361 def walkconfig(self, untrusted=False):
362 362 cfg = self._data(untrusted)
363 363 for section in cfg.sections():
364 364 for name, value in self.configitems(section, untrusted):
365 365 yield section, name, value
366 366
367 367 def plain(self, feature=None):
368 368 '''is plain mode active?
369 369
370 370 Plain mode means that all configuration variables which affect
371 371 the behavior and output of Mercurial should be
372 372 ignored. Additionally, the output should be stable,
373 373 reproducible and suitable for use in scripts or applications.
374 374
375 375 The only way to trigger plain mode is by setting either the
376 376 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
377 377
378 378 The return value can either be
379 379 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
380 380 - True otherwise
381 381 '''
382 382 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
383 383 return False
384 384 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
385 385 if feature and exceptions:
386 386 return feature not in exceptions
387 387 return True
388 388
389 389 def username(self):
390 390 """Return default username to be used in commits.
391 391
392 392 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
393 393 and stop searching if one of these is set.
394 394 If not found and ui.askusername is True, ask the user, else use
395 395 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
396 396 """
397 397 user = os.environ.get("HGUSER")
398 398 if user is None:
399 399 user = self.config("ui", "username")
400 400 if user is not None:
401 401 user = os.path.expandvars(user)
402 402 if user is None:
403 403 user = os.environ.get("EMAIL")
404 404 if user is None and self.configbool("ui", "askusername"):
405 405 user = self.prompt(_("enter a commit username:"), default=None)
406 406 if user is None and not self.interactive():
407 407 try:
408 408 user = '%s@%s' % (util.getuser(), socket.getfqdn())
409 409 self.warn(_("No username found, using '%s' instead\n") % user)
410 410 except KeyError:
411 411 pass
412 412 if not user:
413 413 raise util.Abort(_('no username supplied (see "hg help config")'))
414 414 if "\n" in user:
415 415 raise util.Abort(_("username %s contains a newline\n") % repr(user))
416 416 return user
417 417
418 418 def shortuser(self, user):
419 419 """Return a short representation of a user name or email address."""
420 420 if not self.verbose:
421 421 user = util.shortuser(user)
422 422 return user
423 423
424 424 def expandpath(self, loc, default=None):
425 425 """Return repository location relative to cwd or from [paths]"""
426 426 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
427 427 return loc
428 428
429 429 path = self.config('paths', loc)
430 430 if not path and default is not None:
431 431 path = self.config('paths', default)
432 432 return path or loc
433 433
434 434 def pushbuffer(self):
435 435 self._buffers.append([])
436 436
437 437 def popbuffer(self, labeled=False):
438 438 '''pop the last buffer and return the buffered output
439 439
440 440 If labeled is True, any labels associated with buffered
441 441 output will be handled. By default, this has no effect
442 442 on the output returned, but extensions and GUI tools may
443 443 handle this argument and returned styled output. If output
444 444 is being buffered so it can be captured and parsed or
445 445 processed, labeled should not be set to True.
446 446 '''
447 447 return "".join(self._buffers.pop())
448 448
449 449 def write(self, *args, **opts):
450 450 '''write args to output
451 451
452 452 By default, this method simply writes to the buffer or stdout,
453 453 but extensions or GUI tools may override this method,
454 454 write_err(), popbuffer(), and label() to style output from
455 455 various parts of hg.
456 456
457 457 An optional keyword argument, "label", can be passed in.
458 458 This should be a string containing label names separated by
459 459 space. Label names take the form of "topic.type". For example,
460 460 ui.debug() issues a label of "ui.debug".
461 461
462 462 When labeling output for a specific command, a label of
463 463 "cmdname.type" is recommended. For example, status issues
464 464 a label of "status.modified" for modified files.
465 465 '''
466 466 if self._buffers:
467 467 self._buffers[-1].extend([str(a) for a in args])
468 468 else:
469 469 for a in args:
470 470 self.fout.write(str(a))
471 471
472 472 def write_err(self, *args, **opts):
473 473 try:
474 474 if not getattr(self.fout, 'closed', False):
475 475 self.fout.flush()
476 476 for a in args:
477 477 self.ferr.write(str(a))
478 478 # stderr may be buffered under win32 when redirected to files,
479 479 # including stdout.
480 480 if not getattr(self.ferr, 'closed', False):
481 481 self.ferr.flush()
482 482 except IOError, inst:
483 if inst.errno not in (errno.EPIPE, errno.EIO):
483 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
484 484 raise
485 485
486 486 def flush(self):
487 487 try: self.fout.flush()
488 488 except: pass
489 489 try: self.ferr.flush()
490 490 except: pass
491 491
492 492 def interactive(self):
493 493 '''is interactive input allowed?
494 494
495 495 An interactive session is a session where input can be reasonably read
496 496 from `sys.stdin'. If this function returns false, any attempt to read
497 497 from stdin should fail with an error, unless a sensible default has been
498 498 specified.
499 499
500 500 Interactiveness is triggered by the value of the `ui.interactive'
501 501 configuration variable or - if it is unset - when `sys.stdin' points
502 502 to a terminal device.
503 503
504 504 This function refers to input only; for output, see `ui.formatted()'.
505 505 '''
506 506 i = self.configbool("ui", "interactive", None)
507 507 if i is None:
508 508 # some environments replace stdin without implementing isatty
509 509 # usually those are non-interactive
510 510 return util.isatty(self.fin)
511 511
512 512 return i
513 513
514 514 def termwidth(self):
515 515 '''how wide is the terminal in columns?
516 516 '''
517 517 if 'COLUMNS' in os.environ:
518 518 try:
519 519 return int(os.environ['COLUMNS'])
520 520 except ValueError:
521 521 pass
522 522 return util.termwidth()
523 523
524 524 def formatted(self):
525 525 '''should formatted output be used?
526 526
527 527 It is often desirable to format the output to suite the output medium.
528 528 Examples of this are truncating long lines or colorizing messages.
529 529 However, this is not often not desirable when piping output into other
530 530 utilities, e.g. `grep'.
531 531
532 532 Formatted output is triggered by the value of the `ui.formatted'
533 533 configuration variable or - if it is unset - when `sys.stdout' points
534 534 to a terminal device. Please note that `ui.formatted' should be
535 535 considered an implementation detail; it is not intended for use outside
536 536 Mercurial or its extensions.
537 537
538 538 This function refers to output only; for input, see `ui.interactive()'.
539 539 This function always returns false when in plain mode, see `ui.plain()'.
540 540 '''
541 541 if self.plain():
542 542 return False
543 543
544 544 i = self.configbool("ui", "formatted", None)
545 545 if i is None:
546 546 # some environments replace stdout without implementing isatty
547 547 # usually those are non-interactive
548 548 return util.isatty(self.fout)
549 549
550 550 return i
551 551
552 552 def _readline(self, prompt=''):
553 553 if util.isatty(self.fin):
554 554 try:
555 555 # magically add command line editing support, where
556 556 # available
557 557 import readline
558 558 # force demandimport to really load the module
559 559 readline.read_history_file
560 560 # windows sometimes raises something other than ImportError
561 561 except Exception:
562 562 pass
563 563
564 564 # call write() so output goes through subclassed implementation
565 565 # e.g. color extension on Windows
566 566 self.write(prompt)
567 567
568 568 # instead of trying to emulate raw_input, swap (self.fin,
569 569 # self.fout) with (sys.stdin, sys.stdout)
570 570 oldin = sys.stdin
571 571 oldout = sys.stdout
572 572 sys.stdin = self.fin
573 573 sys.stdout = self.fout
574 574 line = raw_input(' ')
575 575 sys.stdin = oldin
576 576 sys.stdout = oldout
577 577
578 578 # When stdin is in binary mode on Windows, it can cause
579 579 # raw_input() to emit an extra trailing carriage return
580 580 if os.linesep == '\r\n' and line and line[-1] == '\r':
581 581 line = line[:-1]
582 582 return line
583 583
584 584 def prompt(self, msg, default="y"):
585 585 """Prompt user with msg, read response.
586 586 If ui is not interactive, the default is returned.
587 587 """
588 588 if not self.interactive():
589 589 self.write(msg, ' ', default, "\n")
590 590 return default
591 591 try:
592 592 r = self._readline(self.label(msg, 'ui.prompt'))
593 593 if not r:
594 594 return default
595 595 return r
596 596 except EOFError:
597 597 raise util.Abort(_('response expected'))
598 598
599 599 def promptchoice(self, msg, choices, default=0):
600 600 """Prompt user with msg, read response, and ensure it matches
601 601 one of the provided choices. The index of the choice is returned.
602 602 choices is a sequence of acceptable responses with the format:
603 603 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
604 604 If ui is not interactive, the default is returned.
605 605 """
606 606 resps = [s[s.index('&')+1].lower() for s in choices]
607 607 while True:
608 608 r = self.prompt(msg, resps[default])
609 609 if r.lower() in resps:
610 610 return resps.index(r.lower())
611 611 self.write(_("unrecognized response\n"))
612 612
613 613 def getpass(self, prompt=None, default=None):
614 614 if not self.interactive():
615 615 return default
616 616 try:
617 617 return getpass.getpass(prompt or _('password: '))
618 618 except EOFError:
619 619 raise util.Abort(_('response expected'))
620 620 def status(self, *msg, **opts):
621 621 '''write status message to output (if ui.quiet is False)
622 622
623 623 This adds an output label of "ui.status".
624 624 '''
625 625 if not self.quiet:
626 626 opts['label'] = opts.get('label', '') + ' ui.status'
627 627 self.write(*msg, **opts)
628 628 def warn(self, *msg, **opts):
629 629 '''write warning message to output (stderr)
630 630
631 631 This adds an output label of "ui.warning".
632 632 '''
633 633 opts['label'] = opts.get('label', '') + ' ui.warning'
634 634 self.write_err(*msg, **opts)
635 635 def note(self, *msg, **opts):
636 636 '''write note to output (if ui.verbose is True)
637 637
638 638 This adds an output label of "ui.note".
639 639 '''
640 640 if self.verbose:
641 641 opts['label'] = opts.get('label', '') + ' ui.note'
642 642 self.write(*msg, **opts)
643 643 def debug(self, *msg, **opts):
644 644 '''write debug message to output (if ui.debugflag is True)
645 645
646 646 This adds an output label of "ui.debug".
647 647 '''
648 648 if self.debugflag:
649 649 opts['label'] = opts.get('label', '') + ' ui.debug'
650 650 self.write(*msg, **opts)
651 651 def edit(self, text, user):
652 652 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
653 653 text=True)
654 654 try:
655 655 f = os.fdopen(fd, "w")
656 656 f.write(text)
657 657 f.close()
658 658
659 659 editor = self.geteditor()
660 660
661 661 util.system("%s \"%s\"" % (editor, name),
662 662 environ={'HGUSER': user},
663 663 onerr=util.Abort, errprefix=_("edit failed"),
664 664 out=self.fout)
665 665
666 666 f = open(name)
667 667 t = f.read()
668 668 f.close()
669 669 finally:
670 670 os.unlink(name)
671 671
672 672 return t
673 673
674 674 def traceback(self, exc=None):
675 675 '''print exception traceback if traceback printing enabled.
676 676 only to call in exception handler. returns true if traceback
677 677 printed.'''
678 678 if self.tracebackflag:
679 679 if exc:
680 680 traceback.print_exception(exc[0], exc[1], exc[2], file=self.ferr)
681 681 else:
682 682 traceback.print_exc(file=self.ferr)
683 683 return self.tracebackflag
684 684
685 685 def geteditor(self):
686 686 '''return editor to use'''
687 687 return (os.environ.get("HGEDITOR") or
688 688 self.config("ui", "editor") or
689 689 os.environ.get("VISUAL") or
690 690 os.environ.get("EDITOR", "vi"))
691 691
692 692 def progress(self, topic, pos, item="", unit="", total=None):
693 693 '''show a progress message
694 694
695 695 With stock hg, this is simply a debug message that is hidden
696 696 by default, but with extensions or GUI tools it may be
697 697 visible. 'topic' is the current operation, 'item' is a
698 698 non-numeric marker of the current position (ie the currently
699 699 in-process file), 'pos' is the current numeric position (ie
700 700 revision, bytes, etc.), unit is a corresponding unit label,
701 701 and total is the highest expected pos.
702 702
703 703 Multiple nested topics may be active at a time.
704 704
705 705 All topics should be marked closed by setting pos to None at
706 706 termination.
707 707 '''
708 708
709 709 if pos is None or not self.debugflag:
710 710 return
711 711
712 712 if unit:
713 713 unit = ' ' + unit
714 714 if item:
715 715 item = ' ' + item
716 716
717 717 if total:
718 718 pct = 100.0 * pos / total
719 719 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
720 720 % (topic, item, pos, total, unit, pct))
721 721 else:
722 722 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
723 723
724 724 def log(self, service, message):
725 725 '''hook for logging facility extensions
726 726
727 727 service should be a readily-identifiable subsystem, which will
728 728 allow filtering.
729 729 message should be a newline-terminated string to log.
730 730 '''
731 731 pass
732 732
733 733 def label(self, msg, label):
734 734 '''style msg based on supplied label
735 735
736 736 Like ui.write(), this just returns msg unchanged, but extensions
737 737 and GUI tools can override it to allow styling output without
738 738 writing it.
739 739
740 740 ui.write(s, 'label') is equivalent to
741 741 ui.write(ui.label(s, 'label')).
742 742 '''
743 743 return msg
General Comments 0
You need to be logged in to leave comments. Login now