ui.py
353 lines
| 12.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / ui.py
mpm@selenic.com
|
r207 | # ui.py - user interface bits for mercurial | ||
# | ||||
Thomas Arendsen Hein
|
r4635 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||
mpm@selenic.com
|
r207 | # | ||
# This software may be used and distributed according to the terms | ||||
# of the GNU General Public License, incorporated herein by reference. | ||||
Matt Mackall
|
r3891 | from i18n import _ | ||
Matt Mackall
|
r3877 | import errno, getpass, os, re, socket, sys, tempfile | ||
Matt Mackall
|
r8144 | import config, traceback, util, error | ||
mpm@selenic.com
|
r207 | |||
Matt Mackall
|
r8144 | _booleans = {'1':True, 'yes':True, 'true':True, 'on':True, | ||
'0':False, 'no':False, 'false':False, 'off':False} | ||||
Alexis S. L. Carvalho
|
r3344 | |||
Eric Hopper
|
r1559 | class ui(object): | ||
Matt Mackall
|
r8190 | def __init__(self, src=None): | ||
Matt Mackall
|
r3737 | self.buffers = [] | ||
Matt Mackall
|
r8136 | self.quiet = self.verbose = self.debugflag = self.traceback = False | ||
self.interactive = self.report_untrusted = True | ||||
Matt Mackall
|
r8144 | self.overlay = config.config() | ||
self.cdata = config.config() | ||||
self.ucdata = config.config() | ||||
Matt Mackall
|
r8143 | self.trusted_users = {} | ||
self.trusted_groups = {} | ||||
Matt Mackall
|
r8136 | |||
Matt Mackall
|
r8190 | if src: | ||
self.cdata = src.cdata.copy() | ||||
self.ucdata = src.ucdata.copy() | ||||
self.overlay = src.overlay.copy() | ||||
self.trusted_users = src.trusted_users.copy() | ||||
self.trusted_groups = src.trusted_groups.copy() | ||||
Matt Mackall
|
r8143 | self.fixconfig() | ||
else: | ||||
Alexis S. L. Carvalho
|
r3676 | # we always trust global config files | ||
Matt Mackall
|
r8142 | for f in util.rcpath(): | ||
self.readconfig(f, assumetrusted=True) | ||||
Matt Mackall
|
r8189 | def copy(self): | ||
return ui(self) | ||||
Thomas Arendsen Hein
|
r1839 | |||
Matt Mackall
|
r8132 | _isatty = None | ||
Bryan O'Sullivan
|
r5036 | def isatty(self): | ||
if ui._isatty is None: | ||||
Matt Mackall
|
r8136 | try: | ||
ui._isatty = sys.stdin.isatty() | ||||
except AttributeError: # not a real file object | ||||
ui._isatty = False | ||||
Benjamin Wohlwend
|
r8175 | except IOError: | ||
# access to stdin is unsafe in a WSGI environment | ||||
ui._isatty = False | ||||
Bryan O'Sullivan
|
r5036 | return ui._isatty | ||
Matt Mackall
|
r8141 | def _is_trusted(self, fp, f): | ||
Alexis S. L. Carvalho
|
r3677 | st = util.fstat(fp) | ||
if util.isowner(fp, st): | ||||
return True | ||||
Matt Mackall
|
r8141 | |||
Alexis S. L. Carvalho
|
r3551 | tusers = self.trusted_users | ||
tgroups = self.trusted_groups | ||||
Matt Mackall
|
r8141 | if '*' in tusers or '*' in tgroups: | ||
return True | ||||
user = util.username(st.st_uid) | ||||
group = util.groupname(st.st_gid) | ||||
if user in tusers or group in tgroups or user == util.username(): | ||||
return True | ||||
if self.report_untrusted: | ||||
self.warn(_('Not trusting file %s from untrusted ' | ||||
'user %s, group %s\n') % (f, user, group)) | ||||
return False | ||||
Alexis S. L. Carvalho
|
r3551 | |||
Matt Mackall
|
r8142 | def readconfig(self, filename, root=None, assumetrusted=False, | ||
sections = None): | ||||
try: | ||||
fp = open(filename) | ||||
except IOError: | ||||
if not sections: # ignore unless we were looking for something | ||||
return | ||||
raise | ||||
Matt Mackall
|
r8139 | |||
Matt Mackall
|
r8144 | cdata = config.config() | ||
Matt Mackall
|
r8142 | trusted = sections or assumetrusted or self._is_trusted(fp, filename) | ||
Alexis S. L. Carvalho
|
r3552 | |||
Matt Mackall
|
r8142 | try: | ||
Matt Mackall
|
r8193 | cdata.read(filename, fp, sections=sections) | ||
Matt Mackall
|
r8144 | except error.ConfigError, inst: | ||
Matt Mackall
|
r8142 | if trusted: | ||
Matt Mackall
|
r8144 | raise | ||
self.warn(_("Ignored: %s\n") % str(inst)) | ||||
Alexis S. L. Carvalho
|
r3552 | |||
Matt Mackall
|
r8142 | if trusted: | ||
Matt Mackall
|
r8193 | self.cdata.update(cdata) | ||
self.cdata.update(self.overlay) | ||||
self.ucdata.update(cdata) | ||||
self.ucdata.update(self.overlay) | ||||
Matt Mackall
|
r8139 | |||
Alexis S. L. Carvalho
|
r3347 | if root is None: | ||
root = os.path.expanduser('~') | ||||
self.fixconfig(root=root) | ||||
Alexis S. L. Carvalho
|
r3014 | |||
Matt Mackall
|
r8197 | def fixconfig(self, root=None): | ||
Alexis S. L. Carvalho
|
r3347 | # translate paths relative to root (or home) into absolute paths | ||
Matt Mackall
|
r8197 | root = root or os.getcwd() | ||
for c in self.cdata, self.ucdata, self.overlay: | ||||
for n, p in c.items('paths'): | ||||
if p and "://" not in p and not os.path.isabs(p): | ||||
c.set("paths", n, os.path.normpath(os.path.join(root, p))) | ||||
Alexis S. L. Carvalho
|
r3347 | |||
Matt Mackall
|
r8138 | # update ui options | ||
Matt Mackall
|
r8197 | self.debugflag = self.configbool('ui', 'debug') | ||
self.verbose = self.debugflag or self.configbool('ui', 'verbose') | ||||
self.quiet = not self.debugflag and self.configbool('ui', 'quiet') | ||||
if self.verbose and self.quiet: | ||||
self.quiet = self.verbose = False | ||||
self.report_untrusted = self.configbool("ui", "report_untrusted", True) | ||||
self.interactive = self.configbool("ui", "interactive", self.isatty()) | ||||
self.traceback = self.configbool('ui', 'traceback', False) | ||||
Alexis S. L. Carvalho
|
r3350 | |||
Alexis S. L. Carvalho
|
r3551 | # update trust information | ||
Matt Mackall
|
r8197 | for user in self.configlist('trusted', 'users'): | ||
self.trusted_users[user] = 1 | ||||
for group in self.configlist('trusted', 'groups'): | ||||
self.trusted_groups[group] = 1 | ||||
Alexis S. L. Carvalho
|
r3551 | |||
Alexis S. L. Carvalho
|
r3344 | def setconfig(self, section, name, value): | ||
Alexis S. L. Carvalho
|
r3552 | for cdata in (self.overlay, self.cdata, self.ucdata): | ||
Alexis S. L. Carvalho
|
r3344 | cdata.set(section, name, value) | ||
Matt Mackall
|
r8197 | self.fixconfig() | ||
mpm@selenic.com
|
r960 | |||
Alexis S. L. Carvalho
|
r3552 | def _get_cdata(self, untrusted): | ||
Matt Mackall
|
r8139 | if untrusted: | ||
Alexis S. L. Carvalho
|
r3552 | return self.ucdata | ||
return self.cdata | ||||
Matt Mackall
|
r8182 | def configsource(self, section, name, untrusted=False): | ||
return self._get_cdata(untrusted).getsource(section, name) or 'none' | ||||
Matt Mackall
|
r8144 | def config(self, section, name, default=None, untrusted=False): | ||
value = self._get_cdata(untrusted).get(section, name, default) | ||||
Matt Mackall
|
r8139 | if self.debugflag and not untrusted: | ||
Matt Mackall
|
r8144 | uvalue = self.ucdata.get(section, name) | ||
Alexis S. L. Carvalho
|
r3552 | if uvalue is not None and uvalue != value: | ||
self.warn(_("Ignoring untrusted configuration option " | ||||
"%s.%s = %s\n") % (section, name, uvalue)) | ||||
return value | ||||
Alexis S. L. Carvalho
|
r3341 | |||
Alexis S. L. Carvalho
|
r3552 | def configbool(self, section, name, default=False, untrusted=False): | ||
Matt Mackall
|
r8144 | v = self.config(section, name, None, untrusted) | ||
if v == None: | ||||
return default | ||||
if v.lower() not in _booleans: | ||||
raise error.ConfigError(_("%s.%s not a boolean ('%s')") | ||||
% (section, name, v)) | ||||
return _booleans[v.lower()] | ||||
Alexis S. L. Carvalho
|
r3552 | |||
def configlist(self, section, name, default=None, untrusted=False): | ||||
Thomas Arendsen Hein
|
r2499 | """Return a list of comma/space separated strings""" | ||
Alexis S. L. Carvalho
|
r3552 | result = self.config(section, name, untrusted=untrusted) | ||
Thomas Arendsen Hein
|
r2499 | if result is None: | ||
Thomas Arendsen Hein
|
r2502 | result = default or [] | ||
if isinstance(result, basestring): | ||||
result = result.replace(",", " ").split() | ||||
return result | ||||
Thomas Arendsen Hein
|
r2499 | |||
Bryan O'Sullivan
|
r4487 | def has_section(self, section, untrusted=False): | ||
Vadim Gelfer
|
r2343 | '''tell whether section exists in config.''' | ||
Matt Mackall
|
r8144 | return section in self._get_cdata(untrusted) | ||
Alexis S. L. Carvalho
|
r3552 | |||
def configitems(self, section, untrusted=False): | ||||
Matt Mackall
|
r8144 | items = self._get_cdata(untrusted).items(section) | ||
Matt Mackall
|
r8139 | if self.debugflag and not untrusted: | ||
Matt Mackall
|
r8144 | for k,v in self.ucdata.items(section): | ||
if self.cdata.get(section, k) != v: | ||||
Alexis S. L. Carvalho
|
r3552 | self.warn(_("Ignoring untrusted configuration option " | ||
Matt Mackall
|
r8144 | "%s.%s = %s\n") % (section, k, v)) | ||
return items | ||||
mpm@selenic.com
|
r285 | |||
Alexis S. L. Carvalho
|
r3552 | def walkconfig(self, untrusted=False): | ||
cdata = self._get_cdata(untrusted) | ||||
Matt Mackall
|
r8144 | for section in cdata.sections(): | ||
Alexis S. L. Carvalho
|
r3552 | for name, value in self.configitems(section, untrusted): | ||
Alexis S. L. Carvalho
|
r4085 | yield section, name, str(value).replace('\n', '\\n') | ||
Bryan O'Sullivan
|
r1028 | |||
Matt Mackall
|
r608 | def username(self): | ||
Thomas Arendsen Hein
|
r1985 | """Return default username to be used in commits. | ||
Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL | ||||
and stop searching if one of these is set. | ||||
Benoit Boissinot
|
r6862 | If not found and ui.askusername is True, ask the user, else use | ||
($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname". | ||||
Thomas Arendsen Hein
|
r1985 | """ | ||
user = os.environ.get("HGUSER") | ||||
if user is None: | ||||
user = self.config("ui", "username") | ||||
if user is None: | ||||
user = os.environ.get("EMAIL") | ||||
Benoit Boissinot
|
r6862 | if user is None and self.configbool("ui", "askusername"): | ||
Martin Geisler
|
r7600 | user = self.prompt(_("enter a commit username:"), default=None) | ||
Thomas Arendsen Hein
|
r4044 | if user is None: | ||
Benoit Boissinot
|
r3721 | try: | ||
user = '%s@%s' % (util.getuser(), socket.getfqdn()) | ||||
Thomas Arendsen Hein
|
r4044 | self.warn(_("No username found, using '%s' instead\n") % user) | ||
Benoit Boissinot
|
r3721 | except KeyError: | ||
Thomas Arendsen Hein
|
r4044 | pass | ||
if not user: | ||||
raise util.Abort(_("Please specify a username.")) | ||||
Matt Mackall
|
r6351 | if "\n" in user: | ||
Benoit Boissinot
|
r7470 | raise util.Abort(_("username %s contains a newline\n") % repr(user)) | ||
Thomas Arendsen Hein
|
r1985 | return user | ||
Matt Mackall
|
r608 | |||
Thomas Arendsen Hein
|
r1129 | def shortuser(self, user): | ||
"""Return a short representation of a user name or email address.""" | ||||
Vadim Gelfer
|
r1903 | if not self.verbose: user = util.shortuser(user) | ||
Thomas Arendsen Hein
|
r1129 | return user | ||
Matt Mackall
|
r8196 | def _path(self, loc): | ||
p = self.config('paths', loc) | ||||
if p and '%%' in p: | ||||
ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' % | ||||
(loc, p, self.configsource('paths', loc))) | ||||
Matt Mackall
|
r8197 | p = p.replace('%%', '%') | ||
Matt Mackall
|
r8196 | return p | ||
Vadim Gelfer
|
r2494 | def expandpath(self, loc, default=None): | ||
Thomas Arendsen Hein
|
r1892 | """Return repository location relative to cwd or from [paths]""" | ||
Thomas Arendsen Hein
|
r4216 | if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')): | ||
Thomas Arendsen Hein
|
r1892 | return loc | ||
Matt Mackall
|
r8196 | path = self._path(loc) | ||
Vadim Gelfer
|
r2495 | if not path and default is not None: | ||
Matt Mackall
|
r8196 | path = self._path(default) | ||
Thomas Arendsen Hein
|
r2498 | return path or loc | ||
mpm@selenic.com
|
r506 | |||
Matt Mackall
|
r3737 | def pushbuffer(self): | ||
self.buffers.append([]) | ||||
def popbuffer(self): | ||||
return "".join(self.buffers.pop()) | ||||
mpm@selenic.com
|
r207 | def write(self, *args): | ||
Matt Mackall
|
r3737 | if self.buffers: | ||
self.buffers[-1].extend([str(a) for a in args]) | ||||
else: | ||||
for a in args: | ||||
sys.stdout.write(str(a)) | ||||
mpm@selenic.com
|
r565 | |||
def write_err(self, *args): | ||||
Benoit Boissinot
|
r1989 | try: | ||
if not sys.stdout.closed: sys.stdout.flush() | ||||
for a in args: | ||||
sys.stderr.write(str(a)) | ||||
Patrick Mezard
|
r4023 | # stderr may be buffered under win32 when redirected to files, | ||
# including stdout. | ||||
if not sys.stderr.closed: sys.stderr.flush() | ||||
Benoit Boissinot
|
r1989 | except IOError, inst: | ||
if inst.errno != errno.EPIPE: | ||||
raise | ||||
mpm@selenic.com
|
r565 | |||
Vadim Gelfer
|
r1837 | def flush(self): | ||
Eung-Ju Park
|
r2013 | try: sys.stdout.flush() | ||
except: pass | ||||
try: sys.stderr.flush() | ||||
except: pass | ||||
Vadim Gelfer
|
r1837 | |||
Dirkjan Ochtman
|
r5337 | def _readline(self, prompt=''): | ||
Bryan O'Sullivan
|
r5036 | if self.isatty(): | ||
try: | ||||
# magically add command line editing support, where | ||||
# available | ||||
import readline | ||||
# force demandimport to really load the module | ||||
readline.read_history_file | ||||
Brendan Cully
|
r7496 | # windows sometimes raises something other than ImportError | ||
except Exception: | ||||
Bryan O'Sullivan
|
r5036 | pass | ||
Steve Borho
|
r5613 | line = raw_input(prompt) | ||
# When stdin is in binary mode on Windows, it can cause | ||||
# raw_input() to emit an extra trailing carriage return | ||||
if os.linesep == '\r\n' and line and line[-1] == '\r': | ||||
line = line[:-1] | ||||
return line | ||||
Bryan O'Sullivan
|
r5036 | |||
Kirill Smelkov
|
r5751 | def prompt(self, msg, pat=None, default="y"): | ||
"""Prompt user with msg, read response, and ensure it matches pat | ||||
If not interactive -- the default is returned | ||||
""" | ||||
Peter Arrenbrecht
|
r7320 | if not self.interactive: | ||
self.note(msg, ' ', default, "\n") | ||||
return default | ||||
Thomas Arendsen Hein
|
r5671 | while True: | ||
try: | ||||
r = self._readline(msg + ' ') | ||||
Matt Mackall
|
r5709 | if not r: | ||
return default | ||||
Kirill Smelkov
|
r5751 | if not pat or re.match(pat, r): | ||
Thomas Arendsen Hein
|
r5671 | return r | ||
else: | ||||
self.write(_("unrecognized response\n")) | ||||
except EOFError: | ||||
raise util.Abort(_('response expected')) | ||||
Bryan O'Sullivan
|
r5036 | |||
Vadim Gelfer
|
r2281 | def getpass(self, prompt=None, default=None): | ||
if not self.interactive: return default | ||||
Steve Borho
|
r7798 | try: | ||
return getpass.getpass(prompt or _('password: ')) | ||||
except EOFError: | ||||
raise util.Abort(_('response expected')) | ||||
mpm@selenic.com
|
r207 | def status(self, *msg): | ||
if not self.quiet: self.write(*msg) | ||||
Thomas Arendsen Hein
|
r234 | def warn(self, *msg): | ||
mpm@selenic.com
|
r565 | self.write_err(*msg) | ||
mpm@selenic.com
|
r207 | def note(self, *msg): | ||
if self.verbose: self.write(*msg) | ||||
def debug(self, *msg): | ||||
if self.debugflag: self.write(*msg) | ||||
Thomas Arendsen Hein
|
r1983 | def edit(self, text, user): | ||
Stephen Darnell
|
r2206 | (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt", | ||
text=True) | ||||
Thomas Arendsen Hein
|
r1984 | try: | ||
f = os.fdopen(fd, "w") | ||||
f.write(text) | ||||
f.close() | ||||
Osku Salerma
|
r5660 | editor = self.geteditor() | ||
mpm@selenic.com
|
r207 | |||
Thomas Arendsen Hein
|
r1984 | util.system("%s \"%s\"" % (editor, name), | ||
environ={'HGUSER': user}, | ||||
onerr=util.Abort, errprefix=_("edit failed")) | ||||
Matt Mackall
|
r608 | |||
Thomas Arendsen Hein
|
r1984 | f = open(name) | ||
t = f.read() | ||||
f.close() | ||||
t = re.sub("(?m)^HG:.*\n", "", t) | ||||
finally: | ||||
os.unlink(name) | ||||
Radoslaw "AstralStorm" Szkodzinski
|
r662 | |||
mpm@selenic.com
|
r207 | return t | ||
Vadim Gelfer
|
r2200 | |||
Vadim Gelfer
|
r2335 | def print_exc(self): | ||
'''print exception traceback if traceback printing enabled. | ||||
only to call in exception handler. returns true if traceback | ||||
printed.''' | ||||
if self.traceback: | ||||
traceback.print_exc() | ||||
return self.traceback | ||||
Osku Salerma
|
r5660 | |||
def geteditor(self): | ||||
'''return editor to use''' | ||||
return (os.environ.get("HGEDITOR") or | ||||
self.config("ui", "editor") or | ||||
os.environ.get("VISUAL") or | ||||
os.environ.get("EDITOR", "vi")) | ||||