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