##// END OF EJS Templates
ui: move _isatty near user
Matt Mackall -
r8132:cece135f default
parent child Browse files
Show More
@@ -1,490 +1,489 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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, re, socket, sys, tempfile
10 10 import ConfigParser, traceback, util
11 11
12 12 def dupconfig(orig):
13 13 new = util.configparser(orig.defaults())
14 14 updateconfig(orig, new)
15 15 return new
16 16
17 17 def updateconfig(source, dest, sections=None):
18 18 if not sections:
19 19 sections = source.sections()
20 20 for section in sections:
21 21 if not dest.has_section(section):
22 22 dest.add_section(section)
23 23 for name, value in source.items(section, raw=True):
24 24 dest.set(section, name, value)
25 25
26 26 class ui(object):
27 _isatty = None
28
29 27 def __init__(self, verbose=False, debug=False, quiet=False,
30 28 interactive=True, traceback=False, report_untrusted=True,
31 29 parentui=None):
32 30 self.overlay = None
33 31 self.buffers = []
34 32 if parentui is None:
35 33 # this is the parent of all ui children
36 34 self.parentui = None
37 35 self.quiet = quiet
38 36 self.verbose = verbose
39 37 self.debugflag = debug
40 38 self.interactive = interactive
41 39 self.traceback = traceback
42 40 self.report_untrusted = report_untrusted
43 41 self.trusted_users = {}
44 42 self.trusted_groups = {}
45 43 # if ucdata is not None, its keys must be a superset of cdata's
46 44 self.cdata = util.configparser()
47 45 self.ucdata = None
48 46 # we always trust global config files
49 47 self.check_trusted = False
50 48 self.readconfig(util.rcpath())
51 49 self.check_trusted = True
52 50 self.updateopts(verbose, debug, quiet, interactive)
53 51 else:
54 52 # parentui may point to an ui object which is already a child
55 53 self.parentui = parentui.parentui or parentui
56 54 self.trusted_users = parentui.trusted_users.copy()
57 55 self.trusted_groups = parentui.trusted_groups.copy()
58 56 self.cdata = dupconfig(self.parentui.cdata)
59 57 if self.parentui.ucdata:
60 58 self.ucdata = dupconfig(self.parentui.ucdata)
61 59 if self.parentui.overlay:
62 60 self.overlay = dupconfig(self.parentui.overlay)
63 61 if self.parentui is not parentui and parentui.overlay is not None:
64 62 if self.overlay is None:
65 63 self.overlay = util.configparser()
66 64 updateconfig(parentui.overlay, self.overlay)
67 65 self.buffers = parentui.buffers
68 66
69 67 def __getattr__(self, key):
70 68 return getattr(self.parentui, key)
71 69
70 _isatty = None
72 71 def isatty(self):
73 72 if ui._isatty is None:
74 73 ui._isatty = sys.stdin.isatty()
75 74 return ui._isatty
76 75
77 76 def updateopts(self, verbose=False, debug=False, quiet=False,
78 77 interactive=True, traceback=False, config=[]):
79 78 for section, name, value in config:
80 79 self.setconfig(section, name, value)
81 80
82 81 if quiet or verbose or debug:
83 82 self.setconfig('ui', 'quiet', str(bool(quiet)))
84 83 self.setconfig('ui', 'verbose', str(bool(verbose)))
85 84 self.setconfig('ui', 'debug', str(bool(debug)))
86 85
87 86 self.verbosity_constraints()
88 87
89 88 if not interactive:
90 89 self.setconfig('ui', 'interactive', 'False')
91 90 self.interactive = False
92 91
93 92 self.traceback = self.traceback or traceback
94 93
95 94 def verbosity_constraints(self):
96 95 self.quiet = self.configbool('ui', 'quiet')
97 96 self.verbose = self.configbool('ui', 'verbose')
98 97 self.debugflag = self.configbool('ui', 'debug')
99 98
100 99 if self.debugflag:
101 100 self.verbose = True
102 101 self.quiet = False
103 102 elif self.verbose and self.quiet:
104 103 self.quiet = self.verbose = False
105 104
106 105 def _is_trusted(self, fp, f, warn=True):
107 106 if not self.check_trusted:
108 107 return True
109 108 st = util.fstat(fp)
110 109 if util.isowner(fp, st):
111 110 return True
112 111 tusers = self.trusted_users
113 112 tgroups = self.trusted_groups
114 113 if not tusers:
115 114 user = util.username()
116 115 if user is not None:
117 116 self.trusted_users[user] = 1
118 117 self.fixconfig(section='trusted')
119 118 if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
120 119 user = util.username(st.st_uid)
121 120 group = util.groupname(st.st_gid)
122 121 if user not in tusers and group not in tgroups:
123 122 if warn and self.report_untrusted:
124 123 self.warn(_('Not trusting file %s from untrusted '
125 124 'user %s, group %s\n') % (f, user, group))
126 125 return False
127 126 return True
128 127
129 128 def readconfig(self, fn, root=None):
130 129 if isinstance(fn, basestring):
131 130 fn = [fn]
132 131 for f in fn:
133 132 try:
134 133 fp = open(f)
135 134 except IOError:
136 135 continue
137 136 cdata = self.cdata
138 137 trusted = self._is_trusted(fp, f)
139 138 if not trusted:
140 139 if self.ucdata is None:
141 140 self.ucdata = dupconfig(self.cdata)
142 141 cdata = self.ucdata
143 142 elif self.ucdata is not None:
144 143 # use a separate configparser, so that we don't accidentally
145 144 # override ucdata settings later on.
146 145 cdata = util.configparser()
147 146
148 147 try:
149 148 cdata.readfp(fp, f)
150 149 except ConfigParser.ParsingError, inst:
151 150 msg = _("Failed to parse %s\n%s") % (f, inst)
152 151 if trusted:
153 152 raise util.Abort(msg)
154 153 self.warn(_("Ignored: %s\n") % msg)
155 154
156 155 if trusted:
157 156 if cdata != self.cdata:
158 157 updateconfig(cdata, self.cdata)
159 158 if self.ucdata is not None:
160 159 updateconfig(cdata, self.ucdata)
161 160 # override data from config files with data set with ui.setconfig
162 161 if self.overlay:
163 162 updateconfig(self.overlay, self.cdata)
164 163 if root is None:
165 164 root = os.path.expanduser('~')
166 165 self.fixconfig(root=root)
167 166
168 167 def readsections(self, filename, *sections):
169 168 """Read filename and add only the specified sections to the config data
170 169
171 170 The settings are added to the trusted config data.
172 171 """
173 172 if not sections:
174 173 return
175 174
176 175 cdata = util.configparser()
177 176 try:
178 177 try:
179 178 fp = open(filename)
180 179 except IOError, inst:
181 180 raise util.Abort(_("unable to open %s: %s") %
182 181 (filename, getattr(inst, "strerror", inst)))
183 182 try:
184 183 cdata.readfp(fp, filename)
185 184 finally:
186 185 fp.close()
187 186 except ConfigParser.ParsingError, inst:
188 187 raise util.Abort(_("failed to parse %s\n%s") % (filename, inst))
189 188
190 189 for section in sections:
191 190 if not cdata.has_section(section):
192 191 cdata.add_section(section)
193 192
194 193 updateconfig(cdata, self.cdata, sections)
195 194 if self.ucdata:
196 195 updateconfig(cdata, self.ucdata, sections)
197 196
198 197 def fixconfig(self, section=None, name=None, value=None, root=None):
199 198 # translate paths relative to root (or home) into absolute paths
200 199 if section is None or section == 'paths':
201 200 if root is None:
202 201 root = os.getcwd()
203 202 items = section and [(name, value)] or []
204 203 for cdata in self.cdata, self.ucdata, self.overlay:
205 204 if not cdata: continue
206 205 if not items and cdata.has_section('paths'):
207 206 pathsitems = cdata.items('paths')
208 207 else:
209 208 pathsitems = items
210 209 for n, path in pathsitems:
211 210 if path and "://" not in path and not os.path.isabs(path):
212 211 cdata.set("paths", n,
213 212 os.path.normpath(os.path.join(root, path)))
214 213
215 214 # update verbosity/interactive/report_untrusted settings
216 215 if section is None or section == 'ui':
217 216 if name is None or name in ('quiet', 'verbose', 'debug'):
218 217 self.verbosity_constraints()
219 218 if name is None or name == 'interactive':
220 219 interactive = self.configbool("ui", "interactive", None)
221 220 if interactive is None and self.interactive:
222 221 self.interactive = self.isatty()
223 222 else:
224 223 self.interactive = interactive
225 224 if name is None or name == 'report_untrusted':
226 225 self.report_untrusted = (
227 226 self.configbool("ui", "report_untrusted", True))
228 227
229 228 # update trust information
230 229 if (section is None or section == 'trusted') and self.trusted_users:
231 230 for user in self.configlist('trusted', 'users'):
232 231 self.trusted_users[user] = 1
233 232 for group in self.configlist('trusted', 'groups'):
234 233 self.trusted_groups[group] = 1
235 234
236 235 def setconfig(self, section, name, value):
237 236 if not self.overlay:
238 237 self.overlay = util.configparser()
239 238 for cdata in (self.overlay, self.cdata, self.ucdata):
240 239 if not cdata: continue
241 240 if not cdata.has_section(section):
242 241 cdata.add_section(section)
243 242 cdata.set(section, name, value)
244 243 self.fixconfig(section, name, value)
245 244
246 245 def _get_cdata(self, untrusted):
247 246 if untrusted and self.ucdata:
248 247 return self.ucdata
249 248 return self.cdata
250 249
251 250 def _config(self, section, name, default, funcname, untrusted, abort):
252 251 cdata = self._get_cdata(untrusted)
253 252 if cdata.has_option(section, name):
254 253 try:
255 254 func = getattr(cdata, funcname)
256 255 return func(section, name)
257 256 except (ConfigParser.InterpolationError, ValueError), inst:
258 257 msg = _("Error in configuration section [%s] "
259 258 "parameter '%s':\n%s") % (section, name, inst)
260 259 if abort:
261 260 raise util.Abort(msg)
262 261 self.warn(_("Ignored: %s\n") % msg)
263 262 return default
264 263
265 264 def _configcommon(self, section, name, default, funcname, untrusted):
266 265 value = self._config(section, name, default, funcname,
267 266 untrusted, abort=True)
268 267 if self.debugflag and not untrusted and self.ucdata:
269 268 uvalue = self._config(section, name, None, funcname,
270 269 untrusted=True, abort=False)
271 270 if uvalue is not None and uvalue != value:
272 271 self.warn(_("Ignoring untrusted configuration option "
273 272 "%s.%s = %s\n") % (section, name, uvalue))
274 273 return value
275 274
276 275 def config(self, section, name, default=None, untrusted=False):
277 276 return self._configcommon(section, name, default, 'get', untrusted)
278 277
279 278 def configbool(self, section, name, default=False, untrusted=False):
280 279 return self._configcommon(section, name, default, 'getboolean',
281 280 untrusted)
282 281
283 282 def configlist(self, section, name, default=None, untrusted=False):
284 283 """Return a list of comma/space separated strings"""
285 284 result = self.config(section, name, untrusted=untrusted)
286 285 if result is None:
287 286 result = default or []
288 287 if isinstance(result, basestring):
289 288 result = result.replace(",", " ").split()
290 289 return result
291 290
292 291 def has_section(self, section, untrusted=False):
293 292 '''tell whether section exists in config.'''
294 293 cdata = self._get_cdata(untrusted)
295 294 return cdata.has_section(section)
296 295
297 296 def _configitems(self, section, untrusted, abort):
298 297 items = {}
299 298 cdata = self._get_cdata(untrusted)
300 299 if cdata.has_section(section):
301 300 try:
302 301 items.update(dict(cdata.items(section)))
303 302 except ConfigParser.InterpolationError, inst:
304 303 msg = _("Error in configuration section [%s]:\n"
305 304 "%s") % (section, inst)
306 305 if abort:
307 306 raise util.Abort(msg)
308 307 self.warn(_("Ignored: %s\n") % msg)
309 308 return items
310 309
311 310 def configitems(self, section, untrusted=False):
312 311 items = self._configitems(section, untrusted=untrusted, abort=True)
313 312 if self.debugflag and not untrusted and self.ucdata:
314 313 uitems = self._configitems(section, untrusted=True, abort=False)
315 314 for k in util.sort(uitems):
316 315 if uitems[k] != items.get(k):
317 316 self.warn(_("Ignoring untrusted configuration option "
318 317 "%s.%s = %s\n") % (section, k, uitems[k]))
319 318 return util.sort(items.items())
320 319
321 320 def walkconfig(self, untrusted=False):
322 321 cdata = self._get_cdata(untrusted)
323 322 sections = cdata.sections()
324 323 sections.sort()
325 324 for section in sections:
326 325 for name, value in self.configitems(section, untrusted):
327 326 yield section, name, str(value).replace('\n', '\\n')
328 327
329 328 def username(self):
330 329 """Return default username to be used in commits.
331 330
332 331 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
333 332 and stop searching if one of these is set.
334 333 If not found and ui.askusername is True, ask the user, else use
335 334 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
336 335 """
337 336 user = os.environ.get("HGUSER")
338 337 if user is None:
339 338 user = self.config("ui", "username")
340 339 if user is None:
341 340 user = os.environ.get("EMAIL")
342 341 if user is None and self.configbool("ui", "askusername"):
343 342 user = self.prompt(_("enter a commit username:"), default=None)
344 343 if user is None:
345 344 try:
346 345 user = '%s@%s' % (util.getuser(), socket.getfqdn())
347 346 self.warn(_("No username found, using '%s' instead\n") % user)
348 347 except KeyError:
349 348 pass
350 349 if not user:
351 350 raise util.Abort(_("Please specify a username."))
352 351 if "\n" in user:
353 352 raise util.Abort(_("username %s contains a newline\n") % repr(user))
354 353 return user
355 354
356 355 def shortuser(self, user):
357 356 """Return a short representation of a user name or email address."""
358 357 if not self.verbose: user = util.shortuser(user)
359 358 return user
360 359
361 360 def expandpath(self, loc, default=None):
362 361 """Return repository location relative to cwd or from [paths]"""
363 362 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
364 363 return loc
365 364
366 365 path = self.config("paths", loc)
367 366 if not path and default is not None:
368 367 path = self.config("paths", default)
369 368 return path or loc
370 369
371 370 def pushbuffer(self):
372 371 self.buffers.append([])
373 372
374 373 def popbuffer(self):
375 374 return "".join(self.buffers.pop())
376 375
377 376 def write(self, *args):
378 377 if self.buffers:
379 378 self.buffers[-1].extend([str(a) for a in args])
380 379 else:
381 380 for a in args:
382 381 sys.stdout.write(str(a))
383 382
384 383 def write_err(self, *args):
385 384 try:
386 385 if not sys.stdout.closed: sys.stdout.flush()
387 386 for a in args:
388 387 sys.stderr.write(str(a))
389 388 # stderr may be buffered under win32 when redirected to files,
390 389 # including stdout.
391 390 if not sys.stderr.closed: sys.stderr.flush()
392 391 except IOError, inst:
393 392 if inst.errno != errno.EPIPE:
394 393 raise
395 394
396 395 def flush(self):
397 396 try: sys.stdout.flush()
398 397 except: pass
399 398 try: sys.stderr.flush()
400 399 except: pass
401 400
402 401 def _readline(self, prompt=''):
403 402 if self.isatty():
404 403 try:
405 404 # magically add command line editing support, where
406 405 # available
407 406 import readline
408 407 # force demandimport to really load the module
409 408 readline.read_history_file
410 409 # windows sometimes raises something other than ImportError
411 410 except Exception:
412 411 pass
413 412 line = raw_input(prompt)
414 413 # When stdin is in binary mode on Windows, it can cause
415 414 # raw_input() to emit an extra trailing carriage return
416 415 if os.linesep == '\r\n' and line and line[-1] == '\r':
417 416 line = line[:-1]
418 417 return line
419 418
420 419 def prompt(self, msg, pat=None, default="y"):
421 420 """Prompt user with msg, read response, and ensure it matches pat
422 421
423 422 If not interactive -- the default is returned
424 423 """
425 424 if not self.interactive:
426 425 self.note(msg, ' ', default, "\n")
427 426 return default
428 427 while True:
429 428 try:
430 429 r = self._readline(msg + ' ')
431 430 if not r:
432 431 return default
433 432 if not pat or re.match(pat, r):
434 433 return r
435 434 else:
436 435 self.write(_("unrecognized response\n"))
437 436 except EOFError:
438 437 raise util.Abort(_('response expected'))
439 438
440 439 def getpass(self, prompt=None, default=None):
441 440 if not self.interactive: return default
442 441 try:
443 442 return getpass.getpass(prompt or _('password: '))
444 443 except EOFError:
445 444 raise util.Abort(_('response expected'))
446 445 def status(self, *msg):
447 446 if not self.quiet: self.write(*msg)
448 447 def warn(self, *msg):
449 448 self.write_err(*msg)
450 449 def note(self, *msg):
451 450 if self.verbose: self.write(*msg)
452 451 def debug(self, *msg):
453 452 if self.debugflag: self.write(*msg)
454 453 def edit(self, text, user):
455 454 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
456 455 text=True)
457 456 try:
458 457 f = os.fdopen(fd, "w")
459 458 f.write(text)
460 459 f.close()
461 460
462 461 editor = self.geteditor()
463 462
464 463 util.system("%s \"%s\"" % (editor, name),
465 464 environ={'HGUSER': user},
466 465 onerr=util.Abort, errprefix=_("edit failed"))
467 466
468 467 f = open(name)
469 468 t = f.read()
470 469 f.close()
471 470 t = re.sub("(?m)^HG:.*\n", "", t)
472 471 finally:
473 472 os.unlink(name)
474 473
475 474 return t
476 475
477 476 def print_exc(self):
478 477 '''print exception traceback if traceback printing enabled.
479 478 only to call in exception handler. returns true if traceback
480 479 printed.'''
481 480 if self.traceback:
482 481 traceback.print_exc()
483 482 return self.traceback
484 483
485 484 def geteditor(self):
486 485 '''return editor to use'''
487 486 return (os.environ.get("HGEDITOR") or
488 487 self.config("ui", "editor") or
489 488 os.environ.get("VISUAL") or
490 489 os.environ.get("EDITOR", "vi"))
General Comments 0
You need to be logged in to leave comments. Login now