ui.py
397 lines
| 14.3 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 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
# GNU General Public License version 2, incorporated herein by reference. | ||||
mpm@selenic.com
|
r207 | |||
Matt Mackall
|
r3891 | from i18n import _ | ||
Martin Geisler
|
r8656 | import errno, getpass, os, socket, sys, tempfile, traceback | ||
Simon Heimberg
|
r8312 | import config, util, error | ||
mpm@selenic.com
|
r207 | |||
Dirkjan Ochtman
|
r8222 | _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
|
r8202 | self._buffers = [] | ||
Bryan O'Sullivan
|
r9851 | self.quiet = self.verbose = self.debugflag = self.tracebackflag = False | ||
Matt Mackall
|
r8208 | self._reportuntrusted = True | ||
Matt Mackall
|
r8203 | self._ocfg = config.config() # overlay | ||
self._tcfg = config.config() # trusted | ||||
self._ucfg = config.config() # untrusted | ||||
Martin Geisler
|
r8478 | self._trustusers = set() | ||
self._trustgroups = set() | ||||
Matt Mackall
|
r8136 | |||
Matt Mackall
|
r8190 | if src: | ||
Matt Mackall
|
r8203 | self._tcfg = src._tcfg.copy() | ||
self._ucfg = src._ucfg.copy() | ||||
self._ocfg = src._ocfg.copy() | ||||
Matt Mackall
|
r8201 | self._trustusers = src._trustusers.copy() | ||
self._trustgroups = src._trustgroups.copy() | ||||
Sune Foldager
|
r9887 | self.environ = src.environ | ||
Matt Mackall
|
r8143 | self.fixconfig() | ||
else: | ||||
Sune Foldager
|
r9887 | # shared read-only environment | ||
self.environ = os.environ | ||||
Alexis S. L. Carvalho
|
r3676 | # we always trust global config files | ||
Matt Mackall
|
r8142 | for f in util.rcpath(): | ||
Matt Mackall
|
r8200 | self.readconfig(f, trust=True) | ||
Dirkjan Ochtman
|
r8222 | |||
Matt Mackall
|
r8189 | def copy(self): | ||
Ronny Pfannschmidt
|
r8220 | return self.__class__(self) | ||
Thomas Arendsen Hein
|
r1839 | |||
Matt Mackall
|
r8141 | def _is_trusted(self, fp, f): | ||
Alexis S. L. Carvalho
|
r3677 | st = util.fstat(fp) | ||
Martin Geisler
|
r8657 | if util.isowner(st): | ||
Alexis S. L. Carvalho
|
r3677 | return True | ||
Matt Mackall
|
r8141 | |||
Matt Mackall
|
r8201 | tusers, tgroups = self._trustusers, self._trustgroups | ||
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 | ||||
Matt Mackall
|
r8204 | if self._reportuntrusted: | ||
Matt Mackall
|
r8141 | 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
|
r8200 | def readconfig(self, filename, root=None, trust=False, | ||
Alexander Solovyov
|
r8345 | sections=None, remap=None): | ||
Matt Mackall
|
r8142 | try: | ||
fp = open(filename) | ||||
except IOError: | ||||
if not sections: # ignore unless we were looking for something | ||||
return | ||||
raise | ||||
Matt Mackall
|
r8139 | |||
Matt Mackall
|
r8203 | cfg = config.config() | ||
Matt Mackall
|
r8200 | trusted = sections or trust or self._is_trusted(fp, filename) | ||
Alexis S. L. Carvalho
|
r3552 | |||
Matt Mackall
|
r8142 | try: | ||
Alexander Solovyov
|
r8345 | cfg.read(filename, fp, sections=sections, remap=remap) | ||
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
|
r8203 | self._tcfg.update(cfg) | ||
self._tcfg.update(self._ocfg) | ||||
self._ucfg.update(cfg) | ||||
self._ucfg.update(self._ocfg) | ||||
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() | ||
Matt Mackall
|
r8203 | for c in self._tcfg, self._ucfg, self._ocfg: | ||
Matt Mackall
|
r8197 | 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 | ||||
Matt Mackall
|
r8204 | self._reportuntrusted = self.configbool("ui", "report_untrusted", True) | ||
Bryan O'Sullivan
|
r9851 | self.tracebackflag = self.configbool('ui', 'traceback', False) | ||
Alexis S. L. Carvalho
|
r3350 | |||
Alexis S. L. Carvalho
|
r3551 | # update trust information | ||
Martin Geisler
|
r8478 | self._trustusers.update(self.configlist('trusted', 'users')) | ||
self._trustgroups.update(self.configlist('trusted', 'groups')) | ||||
Alexis S. L. Carvalho
|
r3551 | |||
Alexis S. L. Carvalho
|
r3344 | def setconfig(self, section, name, value): | ||
Matt Mackall
|
r8203 | for cfg in (self._ocfg, self._tcfg, self._ucfg): | ||
cfg.set(section, name, value) | ||||
Matt Mackall
|
r8197 | self.fixconfig() | ||
mpm@selenic.com
|
r960 | |||
Matt Mackall
|
r8199 | def _data(self, untrusted): | ||
Matt Mackall
|
r8203 | return untrusted and self._ucfg or self._tcfg | ||
Alexis S. L. Carvalho
|
r3552 | |||
Matt Mackall
|
r8182 | def configsource(self, section, name, untrusted=False): | ||
Matt Mackall
|
r8199 | return self._data(untrusted).source(section, name) or 'none' | ||
Matt Mackall
|
r8182 | |||
Matt Mackall
|
r8144 | def config(self, section, name, default=None, untrusted=False): | ||
Matt Mackall
|
r8199 | value = self._data(untrusted).get(section, name, default) | ||
Matt Mackall
|
r8204 | if self.debugflag and not untrusted and self._reportuntrusted: | ||
Matt Mackall
|
r8203 | uvalue = self._ucfg.get(section, name) | ||
Alexis S. L. Carvalho
|
r3552 | if uvalue is not None and uvalue != value: | ||
Matt Mackall
|
r8204 | self.debug(_("ignoring untrusted configuration option " | ||
Dirkjan Ochtman
|
r8222 | "%s.%s = %s\n") % (section, name, uvalue)) | ||
Alexis S. L. Carvalho
|
r3552 | 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) | ||
Martin Geisler
|
r8527 | if v is None: | ||
Matt Mackall
|
r8144 | return default | ||
Dirkjan Ochtman
|
r10243 | if isinstance(v, bool): | ||
return v | ||||
Matt Mackall
|
r8144 | 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
|
r8199 | return section in self._data(untrusted) | ||
Alexis S. L. Carvalho
|
r3552 | |||
def configitems(self, section, untrusted=False): | ||||
Matt Mackall
|
r8199 | items = self._data(untrusted).items(section) | ||
Matt Mackall
|
r8204 | if self.debugflag and not untrusted and self._reportuntrusted: | ||
Dirkjan Ochtman
|
r8222 | for k, v in self._ucfg.items(section): | ||
Matt Mackall
|
r8203 | if self._tcfg.get(section, k) != v: | ||
Matt Mackall
|
r8204 | self.debug(_("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): | ||
Matt Mackall
|
r8203 | cfg = self._data(untrusted) | ||
for section in cfg.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) | ||
Martin Geisler
|
r9613 | if user is None and not self.interactive(): | ||
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: | ||||
Martin Geisler
|
r9786 | raise util.Abort(_('no username supplied (see "hg help config")')) | ||
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) | ||||
Alexander Solovyov
|
r9610 | if p: | ||
if '%%' in p: | ||||
self.warn("(deprecated '%%' in path %s=%s from %s)\n" % | ||||
(loc, p, self.configsource('paths', loc))) | ||||
p = p.replace('%%', '%') | ||||
p = util.expandpath(p) | ||||
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): | ||
Matt Mackall
|
r8202 | self._buffers.append([]) | ||
Matt Mackall
|
r3737 | |||
def popbuffer(self): | ||||
Matt Mackall
|
r8202 | return "".join(self._buffers.pop()) | ||
Matt Mackall
|
r3737 | |||
mpm@selenic.com
|
r207 | def write(self, *args): | ||
Matt Mackall
|
r8202 | if self._buffers: | ||
self._buffers[-1].extend([str(a) for a in args]) | ||||
Matt Mackall
|
r3737 | 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 | |||
Matt Mackall
|
r8208 | def interactive(self): | ||
Patrick Mezard
|
r8538 | i = self.configbool("ui", "interactive", None) | ||
if i is None: | ||||
Ronny Pfannschmidt
|
r10077 | try: | ||
return sys.stdin.isatty() | ||||
except AttributeError: | ||||
# some environments replace stdin without implementing isatty | ||||
# usually those are non-interactive | ||||
return False | ||||
Patrick Mezard
|
r8538 | return i | ||
Matt Mackall
|
r8208 | |||
Dirkjan Ochtman
|
r5337 | def _readline(self, prompt=''): | ||
Matt Mackall
|
r8208 | if sys.stdin.isatty(): | ||
Bryan O'Sullivan
|
r5036 | 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 | |||
Simon Heimberg
|
r9048 | def prompt(self, msg, default="y"): | ||
"""Prompt user with msg, read response. | ||||
If ui is not interactive, the default is returned. | ||||
Kirill Smelkov
|
r5751 | """ | ||
Matt Mackall
|
r8208 | if not self.interactive(): | ||
Mads Kiilerich
|
r8940 | self.write(msg, ' ', default, "\n") | ||
Peter Arrenbrecht
|
r7320 | return default | ||
Simon Heimberg
|
r9048 | try: | ||
r = self._readline(msg + ' ') | ||||
if not r: | ||||
return default | ||||
return r | ||||
except EOFError: | ||||
raise util.Abort(_('response expected')) | ||||
def promptchoice(self, msg, choices, default=0): | ||||
"""Prompt user with msg, read response, and ensure it matches | ||||
one of the provided choices. The index of the choice is returned. | ||||
choices is a sequence of acceptable responses with the format: | ||||
Dirkjan Ochtman
|
r9312 | ('&None', 'E&xec', 'Sym&link') Responses are case insensitive. | ||
Simon Heimberg
|
r9048 | If ui is not interactive, the default is returned. | ||
""" | ||||
resps = [s[s.index('&')+1].lower() for s in choices] | ||||
Thomas Arendsen Hein
|
r5671 | while True: | ||
Simon Heimberg
|
r9048 | r = self.prompt(msg, resps[default]) | ||
if r.lower() in resps: | ||||
return resps.index(r.lower()) | ||||
self.write(_("unrecognized response\n")) | ||||
Bryan O'Sullivan
|
r5036 | |||
Vadim Gelfer
|
r2281 | def getpass(self, prompt=None, default=None): | ||
Matt Mackall
|
r8208 | 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() | ||||
finally: | ||||
os.unlink(name) | ||||
Radoslaw "AstralStorm" Szkodzinski
|
r662 | |||
mpm@selenic.com
|
r207 | return t | ||
Vadim Gelfer
|
r2200 | |||
Bryan O'Sullivan
|
r9851 | def traceback(self, exc=None): | ||
Vadim Gelfer
|
r2335 | '''print exception traceback if traceback printing enabled. | ||
only to call in exception handler. returns true if traceback | ||||
printed.''' | ||||
Bryan O'Sullivan
|
r9851 | if self.tracebackflag: | ||
if exc: | ||||
traceback.print_exception(exc[0], exc[1], exc[2]) | ||||
else: | ||||
traceback.print_exc() | ||||
return self.tracebackflag | ||||
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")) | ||||
Matt Mackall
|
r9153 | |||
def progress(self, topic, pos, item="", unit="", total=None): | ||||
'''show a progress message | ||||
With stock hg, this is simply a debug message that is hidden | ||||
by default, but with extensions or GUI tools it may be | ||||
visible. 'topic' is the current operation, 'item' is a | ||||
non-numeric marker of the current position (ie the currently | ||||
in-process file), 'pos' is the current numeric position (ie | ||||
Brodie Rao
|
r9398 | revision, bytes, etc.), unit is a corresponding unit label, | ||
Matt Mackall
|
r9153 | and total is the highest expected pos. | ||
Multiple nested topics may be active at a time. All topics | ||||
should be marked closed by setting pos to None at termination. | ||||
''' | ||||
if pos == None or not self.debugflag: | ||||
return | ||||
Brodie Rao
|
r9398 | if unit: | ||
unit = ' ' + unit | ||||
Matt Mackall
|
r9153 | if item: | ||
item = ' ' + item | ||||
if total: | ||||
pct = 100.0 * pos / total | ||||
Patrick Mezard
|
r10220 | self.debug('%s:%s %s/%s%s (%4.2f%%)\n' | ||
Brodie Rao
|
r9398 | % (topic, item, pos, total, unit, pct)) | ||
Matt Mackall
|
r9153 | else: | ||
Jesse Glick
|
r9749 | self.debug('%s:%s %s%s\n' % (topic, item, pos, unit)) | ||