##// END OF EJS Templates
ui: fix check-code error
Henrik Stuart -
r11036:4efdccac default
parent child Browse files
Show More
@@ -1,547 +1,548
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, util, error
11 11
12 12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 13 '0': False, 'no': False, 'false': False, 'off': False}
14 14
15 15 class ui(object):
16 16 def __init__(self, src=None):
17 17 self._buffers = []
18 18 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
19 19 self._reportuntrusted = True
20 20 self._ocfg = config.config() # overlay
21 21 self._tcfg = config.config() # trusted
22 22 self._ucfg = config.config() # untrusted
23 23 self._trustusers = set()
24 24 self._trustgroups = set()
25 25
26 26 if src:
27 27 self._tcfg = src._tcfg.copy()
28 28 self._ucfg = src._ucfg.copy()
29 29 self._ocfg = src._ocfg.copy()
30 30 self._trustusers = src._trustusers.copy()
31 31 self._trustgroups = src._trustgroups.copy()
32 32 self.environ = src.environ
33 33 self.fixconfig()
34 34 else:
35 35 # shared read-only environment
36 36 self.environ = os.environ
37 37 # we always trust global config files
38 38 for f in util.rcpath():
39 39 self.readconfig(f, trust=True)
40 40
41 41 def copy(self):
42 42 return self.__class__(self)
43 43
44 44 def _is_trusted(self, fp, f):
45 45 st = util.fstat(fp)
46 46 if util.isowner(st):
47 47 return True
48 48
49 49 tusers, tgroups = self._trustusers, self._trustgroups
50 50 if '*' in tusers or '*' in tgroups:
51 51 return True
52 52
53 53 user = util.username(st.st_uid)
54 54 group = util.groupname(st.st_gid)
55 55 if user in tusers or group in tgroups or user == util.username():
56 56 return True
57 57
58 58 if self._reportuntrusted:
59 59 self.warn(_('Not trusting file %s from untrusted '
60 60 'user %s, group %s\n') % (f, user, group))
61 61 return False
62 62
63 63 def readconfig(self, filename, root=None, trust=False,
64 64 sections=None, remap=None):
65 65 try:
66 66 fp = open(filename)
67 67 except IOError:
68 68 if not sections: # ignore unless we were looking for something
69 69 return
70 70 raise
71 71
72 72 cfg = config.config()
73 73 trusted = sections or trust or self._is_trusted(fp, filename)
74 74
75 75 try:
76 76 cfg.read(filename, fp, sections=sections, remap=remap)
77 77 except error.ConfigError, inst:
78 78 if trusted:
79 79 raise
80 80 self.warn(_("Ignored: %s\n") % str(inst))
81 81
82 82 if self.plain():
83 83 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
84 84 'logtemplate', 'style',
85 85 'traceback', 'verbose'):
86 86 if k in cfg['ui']:
87 87 del cfg['ui'][k]
88 88 for k, v in cfg.items('alias'):
89 89 del cfg['alias'][k]
90 90 for k, v in cfg.items('defaults'):
91 91 del cfg['defaults'][k]
92 92
93 93 if trusted:
94 94 self._tcfg.update(cfg)
95 95 self._tcfg.update(self._ocfg)
96 96 self._ucfg.update(cfg)
97 97 self._ucfg.update(self._ocfg)
98 98
99 99 if root is None:
100 100 root = os.path.expanduser('~')
101 101 self.fixconfig(root=root)
102 102
103 103 def fixconfig(self, root=None):
104 104 # translate paths relative to root (or home) into absolute paths
105 105 root = root or os.getcwd()
106 106 for c in self._tcfg, self._ucfg, self._ocfg:
107 107 for n, p in c.items('paths'):
108 108 if p and "://" not in p and not os.path.isabs(p):
109 109 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
110 110
111 111 # update ui options
112 112 self.debugflag = self.configbool('ui', 'debug')
113 113 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
114 114 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
115 115 if self.verbose and self.quiet:
116 116 self.quiet = self.verbose = False
117 117 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
118 118 self.tracebackflag = self.configbool('ui', 'traceback', False)
119 119
120 120 # update trust information
121 121 self._trustusers.update(self.configlist('trusted', 'users'))
122 122 self._trustgroups.update(self.configlist('trusted', 'groups'))
123 123
124 124 def setconfig(self, section, name, value):
125 125 for cfg in (self._ocfg, self._tcfg, self._ucfg):
126 126 cfg.set(section, name, value)
127 127 self.fixconfig()
128 128
129 129 def _data(self, untrusted):
130 130 return untrusted and self._ucfg or self._tcfg
131 131
132 132 def configsource(self, section, name, untrusted=False):
133 133 return self._data(untrusted).source(section, name) or 'none'
134 134
135 135 def config(self, section, name, default=None, untrusted=False):
136 136 value = self._data(untrusted).get(section, name, default)
137 137 if self.debugflag and not untrusted and self._reportuntrusted:
138 138 uvalue = self._ucfg.get(section, name)
139 139 if uvalue is not None and uvalue != value:
140 140 self.debug(_("ignoring untrusted configuration option "
141 141 "%s.%s = %s\n") % (section, name, uvalue))
142 142 return value
143 143
144 144 def configbool(self, section, name, default=False, untrusted=False):
145 145 v = self.config(section, name, None, untrusted)
146 146 if v is None:
147 147 return default
148 148 if isinstance(v, bool):
149 149 return v
150 150 if v.lower() not in _booleans:
151 151 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
152 152 % (section, name, v))
153 153 return _booleans[v.lower()]
154 154
155 155 def configlist(self, section, name, default=None, untrusted=False):
156 156 """Return a list of comma/space separated strings"""
157 157
158 158 def _parse_plain(parts, s, offset):
159 159 whitespace = False
160 160 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
161 161 whitespace = True
162 162 offset += 1
163 163 if offset >= len(s):
164 164 return None, parts, offset
165 165 if whitespace:
166 166 parts.append('')
167 167 if s[offset] == '"' and not parts[-1]:
168 168 return _parse_quote, parts, offset + 1
169 169 elif s[offset] == '"' and parts[-1][-1] == '\\':
170 170 parts[-1] = parts[-1][:-1] + s[offset]
171 171 return _parse_plain, parts, offset + 1
172 172 parts[-1] += s[offset]
173 173 return _parse_plain, parts, offset + 1
174 174
175 175 def _parse_quote(parts, s, offset):
176 176 if offset < len(s) and s[offset] == '"': # ""
177 177 parts.append('')
178 178 offset += 1
179 179 while offset < len(s) and (s[offset].isspace() or
180 180 s[offset] == ','):
181 181 offset += 1
182 182 return _parse_plain, parts, offset
183 183
184 184 while offset < len(s) and s[offset] != '"':
185 if s[offset] == '\\' and offset + 1 < len(s) and s[offset + 1] == '"':
185 if (s[offset] == '\\' and offset + 1 < len(s)
186 and s[offset + 1] == '"'):
186 187 offset += 1
187 188 parts[-1] += '"'
188 189 else:
189 190 parts[-1] += s[offset]
190 191 offset += 1
191 192
192 193 if offset >= len(s):
193 194 real_parts = _configlist(parts[-1])
194 195 if not real_parts:
195 196 parts[-1] = '"'
196 197 else:
197 198 real_parts[0] = '"' + real_parts[0]
198 199 parts = parts[:-1]
199 200 parts.extend(real_parts)
200 201 return None, parts, offset
201 202
202 203 offset += 1
203 204 while offset < len(s) and s[offset] in [' ', ',']:
204 205 offset += 1
205 206
206 207 if offset < len(s):
207 208 if offset + 1 == len(s) and s[offset] == '"':
208 209 parts[-1] += '"'
209 210 offset += 1
210 211 else:
211 212 parts.append('')
212 213 else:
213 214 return None, parts, offset
214 215
215 216 return _parse_plain, parts, offset
216 217
217 218 def _configlist(s):
218 219 s = s.rstrip(' ,')
219 220 if not s:
220 221 return None
221 222 parser, parts, offset = _parse_plain, [''], 0
222 223 while parser:
223 224 parser, parts, offset = parser(parts, s, offset)
224 225 return parts
225 226
226 227 result = self.config(section, name, untrusted=untrusted)
227 228 if result is None:
228 229 result = default or []
229 230 if isinstance(result, basestring):
230 231 result = _configlist(result)
231 232 if result is None:
232 233 result = default or []
233 234 return result
234 235
235 236 def has_section(self, section, untrusted=False):
236 237 '''tell whether section exists in config.'''
237 238 return section in self._data(untrusted)
238 239
239 240 def configitems(self, section, untrusted=False):
240 241 items = self._data(untrusted).items(section)
241 242 if self.debugflag and not untrusted and self._reportuntrusted:
242 243 for k, v in self._ucfg.items(section):
243 244 if self._tcfg.get(section, k) != v:
244 245 self.debug(_("ignoring untrusted configuration option "
245 246 "%s.%s = %s\n") % (section, k, v))
246 247 return items
247 248
248 249 def walkconfig(self, untrusted=False):
249 250 cfg = self._data(untrusted)
250 251 for section in cfg.sections():
251 252 for name, value in self.configitems(section, untrusted):
252 253 yield section, name, str(value).replace('\n', '\\n')
253 254
254 255 def plain(self):
255 256 return 'HGPLAIN' in os.environ
256 257
257 258 def username(self):
258 259 """Return default username to be used in commits.
259 260
260 261 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
261 262 and stop searching if one of these is set.
262 263 If not found and ui.askusername is True, ask the user, else use
263 264 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
264 265 """
265 266 user = os.environ.get("HGUSER")
266 267 if user is None:
267 268 user = self.config("ui", "username")
268 269 if user is None:
269 270 user = os.environ.get("EMAIL")
270 271 if user is None and self.configbool("ui", "askusername"):
271 272 user = self.prompt(_("enter a commit username:"), default=None)
272 273 if user is None and not self.interactive():
273 274 try:
274 275 user = '%s@%s' % (util.getuser(), socket.getfqdn())
275 276 self.warn(_("No username found, using '%s' instead\n") % user)
276 277 except KeyError:
277 278 pass
278 279 if not user:
279 280 raise util.Abort(_('no username supplied (see "hg help config")'))
280 281 if "\n" in user:
281 282 raise util.Abort(_("username %s contains a newline\n") % repr(user))
282 283 return user
283 284
284 285 def shortuser(self, user):
285 286 """Return a short representation of a user name or email address."""
286 287 if not self.verbose:
287 288 user = util.shortuser(user)
288 289 return user
289 290
290 291 def _path(self, loc):
291 292 p = self.config('paths', loc)
292 293 if p:
293 294 if '%%' in p:
294 295 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
295 296 (loc, p, self.configsource('paths', loc)))
296 297 p = p.replace('%%', '%')
297 298 p = util.expandpath(p)
298 299 return p
299 300
300 301 def expandpath(self, loc, default=None):
301 302 """Return repository location relative to cwd or from [paths]"""
302 303 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
303 304 return loc
304 305
305 306 path = self._path(loc)
306 307 if not path and default is not None:
307 308 path = self._path(default)
308 309 return path or loc
309 310
310 311 def pushbuffer(self):
311 312 self._buffers.append([])
312 313
313 314 def popbuffer(self, labeled=False):
314 315 '''pop the last buffer and return the buffered output
315 316
316 317 If labeled is True, any labels associated with buffered
317 318 output will be handled. By default, this has no effect
318 319 on the output returned, but extensions and GUI tools may
319 320 handle this argument and returned styled output. If output
320 321 is being buffered so it can be captured and parsed or
321 322 processed, labeled should not be set to True.
322 323 '''
323 324 return "".join(self._buffers.pop())
324 325
325 326 def write(self, *args, **opts):
326 327 '''write args to output
327 328
328 329 By default, this method simply writes to the buffer or stdout,
329 330 but extensions or GUI tools may override this method,
330 331 write_err(), popbuffer(), and label() to style output from
331 332 various parts of hg.
332 333
333 334 An optional keyword argument, "label", can be passed in.
334 335 This should be a string containing label names separated by
335 336 space. Label names take the form of "topic.type". For example,
336 337 ui.debug() issues a label of "ui.debug".
337 338
338 339 When labeling output for a specific command, a label of
339 340 "cmdname.type" is recommended. For example, status issues
340 341 a label of "status.modified" for modified files.
341 342 '''
342 343 if self._buffers:
343 344 self._buffers[-1].extend([str(a) for a in args])
344 345 else:
345 346 for a in args:
346 347 sys.stdout.write(str(a))
347 348
348 349 def write_err(self, *args, **opts):
349 350 try:
350 351 if not getattr(sys.stdout, 'closed', False):
351 352 sys.stdout.flush()
352 353 for a in args:
353 354 sys.stderr.write(str(a))
354 355 # stderr may be buffered under win32 when redirected to files,
355 356 # including stdout.
356 357 if not getattr(sys.stderr, 'closed', False):
357 358 sys.stderr.flush()
358 359 except IOError, inst:
359 360 if inst.errno != errno.EPIPE:
360 361 raise
361 362
362 363 def flush(self):
363 364 try: sys.stdout.flush()
364 365 except: pass
365 366 try: sys.stderr.flush()
366 367 except: pass
367 368
368 369 def interactive(self):
369 370 i = self.configbool("ui", "interactive", None)
370 371 if i is None:
371 372 try:
372 373 return sys.stdin.isatty()
373 374 except AttributeError:
374 375 # some environments replace stdin without implementing isatty
375 376 # usually those are non-interactive
376 377 return False
377 378
378 379 return i
379 380
380 381 def _readline(self, prompt=''):
381 382 if sys.stdin.isatty():
382 383 try:
383 384 # magically add command line editing support, where
384 385 # available
385 386 import readline
386 387 # force demandimport to really load the module
387 388 readline.read_history_file
388 389 # windows sometimes raises something other than ImportError
389 390 except Exception:
390 391 pass
391 392 line = raw_input(prompt)
392 393 # When stdin is in binary mode on Windows, it can cause
393 394 # raw_input() to emit an extra trailing carriage return
394 395 if os.linesep == '\r\n' and line and line[-1] == '\r':
395 396 line = line[:-1]
396 397 return line
397 398
398 399 def prompt(self, msg, default="y"):
399 400 """Prompt user with msg, read response.
400 401 If ui is not interactive, the default is returned.
401 402 """
402 403 if not self.interactive():
403 404 self.write(msg, ' ', default, "\n")
404 405 return default
405 406 try:
406 407 r = self._readline(msg + ' ')
407 408 if not r:
408 409 return default
409 410 return r
410 411 except EOFError:
411 412 raise util.Abort(_('response expected'))
412 413
413 414 def promptchoice(self, msg, choices, default=0):
414 415 """Prompt user with msg, read response, and ensure it matches
415 416 one of the provided choices. The index of the choice is returned.
416 417 choices is a sequence of acceptable responses with the format:
417 418 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
418 419 If ui is not interactive, the default is returned.
419 420 """
420 421 resps = [s[s.index('&')+1].lower() for s in choices]
421 422 while True:
422 423 r = self.prompt(msg, resps[default])
423 424 if r.lower() in resps:
424 425 return resps.index(r.lower())
425 426 self.write(_("unrecognized response\n"))
426 427
427 428 def getpass(self, prompt=None, default=None):
428 429 if not self.interactive():
429 430 return default
430 431 try:
431 432 return getpass.getpass(prompt or _('password: '))
432 433 except EOFError:
433 434 raise util.Abort(_('response expected'))
434 435 def status(self, *msg, **opts):
435 436 '''write status message to output (if ui.quiet is False)
436 437
437 438 This adds an output label of "ui.status".
438 439 '''
439 440 if not self.quiet:
440 441 opts['label'] = opts.get('label', '') + ' ui.status'
441 442 self.write(*msg, **opts)
442 443 def warn(self, *msg, **opts):
443 444 '''write warning message to output (stderr)
444 445
445 446 This adds an output label of "ui.warning".
446 447 '''
447 448 opts['label'] = opts.get('label', '') + ' ui.warning'
448 449 self.write_err(*msg, **opts)
449 450 def note(self, *msg, **opts):
450 451 '''write note to output (if ui.verbose is True)
451 452
452 453 This adds an output label of "ui.note".
453 454 '''
454 455 if self.verbose:
455 456 opts['label'] = opts.get('label', '') + ' ui.note'
456 457 self.write(*msg, **opts)
457 458 def debug(self, *msg, **opts):
458 459 '''write debug message to output (if ui.debugflag is True)
459 460
460 461 This adds an output label of "ui.debug".
461 462 '''
462 463 if self.debugflag:
463 464 opts['label'] = opts.get('label', '') + ' ui.debug'
464 465 self.write(*msg, **opts)
465 466 def edit(self, text, user):
466 467 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
467 468 text=True)
468 469 try:
469 470 f = os.fdopen(fd, "w")
470 471 f.write(text)
471 472 f.close()
472 473
473 474 editor = self.geteditor()
474 475
475 476 util.system("%s \"%s\"" % (editor, name),
476 477 environ={'HGUSER': user},
477 478 onerr=util.Abort, errprefix=_("edit failed"))
478 479
479 480 f = open(name)
480 481 t = f.read()
481 482 f.close()
482 483 finally:
483 484 os.unlink(name)
484 485
485 486 return t
486 487
487 488 def traceback(self, exc=None):
488 489 '''print exception traceback if traceback printing enabled.
489 490 only to call in exception handler. returns true if traceback
490 491 printed.'''
491 492 if self.tracebackflag:
492 493 if exc:
493 494 traceback.print_exception(exc[0], exc[1], exc[2])
494 495 else:
495 496 traceback.print_exc()
496 497 return self.tracebackflag
497 498
498 499 def geteditor(self):
499 500 '''return editor to use'''
500 501 return (os.environ.get("HGEDITOR") or
501 502 self.config("ui", "editor") or
502 503 os.environ.get("VISUAL") or
503 504 os.environ.get("EDITOR", "vi"))
504 505
505 506 def progress(self, topic, pos, item="", unit="", total=None):
506 507 '''show a progress message
507 508
508 509 With stock hg, this is simply a debug message that is hidden
509 510 by default, but with extensions or GUI tools it may be
510 511 visible. 'topic' is the current operation, 'item' is a
511 512 non-numeric marker of the current position (ie the currently
512 513 in-process file), 'pos' is the current numeric position (ie
513 514 revision, bytes, etc.), unit is a corresponding unit label,
514 515 and total is the highest expected pos.
515 516
516 517 Multiple nested topics may be active at a time.
517 518
518 519 All topics should be marked closed by setting pos to None at
519 520 termination.
520 521 '''
521 522
522 523 if pos == None or not self.debugflag:
523 524 return
524 525
525 526 if unit:
526 527 unit = ' ' + unit
527 528 if item:
528 529 item = ' ' + item
529 530
530 531 if total:
531 532 pct = 100.0 * pos / total
532 533 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
533 534 % (topic, item, pos, total, unit, pct))
534 535 else:
535 536 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
536 537
537 538 def label(self, msg, label):
538 539 '''style msg based on supplied label
539 540
540 541 Like ui.write(), this just returns msg unchanged, but extensions
541 542 and GUI tools can override it to allow styling output without
542 543 writing it.
543 544
544 545 ui.write(s, 'label') is equivalent to
545 546 ui.write(ui.label(s, 'label')).
546 547 '''
547 548 return msg
General Comments 0
You need to be logged in to leave comments. Login now