##// END OF EJS Templates
further simplify stringify
further simplify stringify

File last commit:

r3645:b984dcb1 default
r3647:734e337c default
Show More
cmdutil.py
585 lines | 20.7 KiB | text/x-python | PythonLexer
# cmdutil.py - help for command processing in mercurial
#
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
from demandload import demandload
from node import *
from i18n import gettext as _
demandload(globals(), 'os sys')
demandload(globals(), 'mdiff util templater cStringIO patch')
revrangesep = ':'
def revpair(ui, repo, revs):
'''return pair of nodes, given list of revisions. second item can
be None, meaning use working dir.'''
def revfix(repo, val, defval):
if not val and val != 0:
val = defval
return repo.lookup(val)
if not revs:
return repo.dirstate.parents()[0], None
end = None
if len(revs) == 1:
if revrangesep in revs[0]:
start, end = revs[0].split(revrangesep, 1)
start = revfix(repo, start, 0)
end = revfix(repo, end, repo.changelog.count() - 1)
else:
start = revfix(repo, revs[0], None)
elif len(revs) == 2:
if revrangesep in revs[0] or revrangesep in revs[1]:
raise util.Abort(_('too many revisions specified'))
start = revfix(repo, revs[0], None)
end = revfix(repo, revs[1], None)
else:
raise util.Abort(_('too many revisions specified'))
return start, end
def revrange(ui, repo, revs):
"""Yield revision as strings from a list of revision specifications."""
def revfix(repo, val, defval):
if not val and val != 0:
return defval
return repo.changelog.rev(repo.lookup(val))
seen, l = {}, []
for spec in revs:
if revrangesep in spec:
start, end = spec.split(revrangesep, 1)
start = revfix(repo, start, 0)
end = revfix(repo, end, repo.changelog.count() - 1)
step = start > end and -1 or 1
for rev in xrange(start, end+step, step):
if rev in seen:
continue
seen[rev] = 1
l.append(rev)
else:
rev = revfix(repo, spec, None)
if rev in seen:
continue
seen[rev] = 1
l.append(rev)
return l
def make_filename(repo, pat, node,
total=None, seqno=None, revwidth=None, pathname=None):
node_expander = {
'H': lambda: hex(node),
'R': lambda: str(repo.changelog.rev(node)),
'h': lambda: short(node),
}
expander = {
'%': lambda: '%',
'b': lambda: os.path.basename(repo.root),
}
try:
if node:
expander.update(node_expander)
if node and revwidth is not None:
expander['r'] = (lambda:
str(repo.changelog.rev(node)).zfill(revwidth))
if total is not None:
expander['N'] = lambda: str(total)
if seqno is not None:
expander['n'] = lambda: str(seqno)
if total is not None and seqno is not None:
expander['n'] = lambda:str(seqno).zfill(len(str(total)))
if pathname is not None:
expander['s'] = lambda: os.path.basename(pathname)
expander['d'] = lambda: os.path.dirname(pathname) or '.'
expander['p'] = lambda: pathname
newname = []
patlen = len(pat)
i = 0
while i < patlen:
c = pat[i]
if c == '%':
i += 1
c = pat[i]
c = expander[c]()
newname.append(c)
i += 1
return ''.join(newname)
except KeyError, inst:
raise util.Abort(_("invalid format spec '%%%s' in output file name") %
inst.args[0])
def make_file(repo, pat, node=None,
total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
if not pat or pat == '-':
return 'w' in mode and sys.stdout or sys.stdin
if hasattr(pat, 'write') and 'w' in mode:
return pat
if hasattr(pat, 'read') and 'r' in mode:
return pat
return open(make_filename(repo, pat, node, total, seqno, revwidth,
pathname),
mode)
def matchpats(repo, pats=[], opts={}, head=''):
cwd = repo.getcwd()
if not pats and cwd:
opts['include'] = [os.path.join(cwd, i)
for i in opts.get('include', [])]
opts['exclude'] = [os.path.join(cwd, x)
for x in opts.get('exclude', [])]
cwd = ''
return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
opts.get('exclude'), head)
def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
files, matchfn, anypats = matchpats(repo, pats, opts, head)
exact = dict.fromkeys(files)
for src, fn in repo.walk(node=node, files=files, match=matchfn,
badmatch=badmatch):
yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
def findrenames(repo, added=None, removed=None, threshold=0.5):
if added is None or removed is None:
added, removed = repo.status()[1:3]
changes = repo.changelog.read(repo.dirstate.parents()[0])
mf = repo.manifest.read(changes[0])
for a in added:
aa = repo.wread(a)
bestscore, bestname = None, None
for r in removed:
rr = repo.file(r).read(mf[r])
delta = mdiff.textdiff(aa, rr)
if len(delta) < len(aa):
myscore = 1.0 - (float(len(delta)) / len(aa))
if bestscore is None or myscore > bestscore:
bestscore, bestname = myscore, r
if bestname and bestscore >= threshold:
yield bestname, a, bestscore
def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
similarity=None):
if dry_run is None:
dry_run = opts.get('dry_run')
if similarity is None:
similarity = float(opts.get('similarity') or 0)
add, remove = [], []
mapping = {}
for src, abs, rel, exact in walk(repo, pats, opts):
if src == 'f' and repo.dirstate.state(abs) == '?':
add.append(abs)
mapping[abs] = rel, exact
if repo.ui.verbose or not exact:
repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
remove.append(abs)
mapping[abs] = rel, exact
if repo.ui.verbose or not exact:
repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
if not dry_run:
repo.add(add, wlock=wlock)
repo.remove(remove, wlock=wlock)
if similarity > 0:
for old, new, score in findrenames(repo, add, remove, similarity):
oldrel, oldexact = mapping[old]
newrel, newexact = mapping[new]
if repo.ui.verbose or not oldexact or not newexact:
repo.ui.status(_('recording removal of %s as rename to %s '
'(%d%% similar)\n') %
(oldrel, newrel, score * 100))
if not dry_run:
repo.copy(old, new, wlock=wlock)
class uibuffer(object):
# Implement and delegate some ui protocol. Save hunks of
# output for later display in the desired order.
def __init__(self, ui):
self.ui = ui
self.hunk = {}
self.header = {}
self.quiet = ui.quiet
self.verbose = ui.verbose
self.debugflag = ui.debugflag
self.lastheader = None
def note(self, *args):
if self.verbose:
self.write(*args)
def status(self, *args):
if not self.quiet:
self.write(*args)
def debug(self, *args):
if self.debugflag:
self.write(*args)
def write(self, *args):
self.hunk.setdefault(self.rev, []).extend(args)
def write_header(self, *args):
self.header.setdefault(self.rev, []).extend(args)
def mark(self, rev):
self.rev = rev
def flush(self, rev):
if rev in self.header:
h = "".join(self.header[rev])
if h != self.lastheader:
self.lastheader = h
self.ui.write(h)
del self.header[rev]
if rev in self.hunk:
self.ui.write("".join(self.hunk[rev]))
del self.hunk[rev]
return 1
return 0
class changeset_printer(object):
'''show changeset information when templating not requested.'''
def __init__(self, ui, repo, patch, buffered):
self.ui = ui
self.repo = repo
self.buffered = buffered
self.patch = patch
if buffered:
self.ui = uibuffer(ui)
def flush(self, rev):
return self.ui.flush(rev)
def show(self, rev=0, changenode=None, brinfo=None, copies=None):
'''show a single changeset or file revision'''
if self.buffered:
self.ui.mark(rev)
log = self.repo.changelog
if changenode is None:
changenode = log.node(rev)
elif not rev:
rev = log.rev(changenode)
if self.ui.quiet:
self.ui.write("%d:%s\n" % (rev, short(changenode)))
return
changes = log.read(changenode)
date = util.datestr(changes[2])
extra = changes[5]
branch = extra.get("branch")
hexfunc = self.ui.debugflag and hex or short
parents = log.parentrevs(rev)
if not self.ui.debugflag:
if parents[1] == nullrev:
if parents[0] >= rev - 1:
parents = []
else:
parents = [parents[0]]
parents = [(p, hexfunc(log.node(p))) for p in parents]
self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
if branch:
self.ui.write(_("branch: %s\n") % branch)
for tag in self.repo.nodetags(changenode):
self.ui.write(_("tag: %s\n") % tag)
for parent in parents:
self.ui.write(_("parent: %d:%s\n") % parent)
if brinfo and changenode in brinfo:
br = brinfo[changenode]
self.ui.write(_("branch: %s\n") % " ".join(br))
if self.ui.debugflag:
self.ui.write(_("manifest: %d:%s\n") %
(self.repo.manifest.rev(changes[0]), hex(changes[0])))
self.ui.write(_("user: %s\n") % changes[1])
self.ui.write(_("date: %s\n") % date)
if self.ui.debugflag:
files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
for key, value in zip([_("files:"), _("files+:"), _("files-:")],
files):
if value:
self.ui.write("%-12s %s\n" % (key, " ".join(value)))
elif changes[3] and self.ui.verbose:
self.ui.write(_("files: %s\n") % " ".join(changes[3]))
if copies and self.ui.verbose:
copies = ['%s (%s)' % c for c in copies]
self.ui.write(_("copies: %s\n") % ' '.join(copies))
if extra and self.ui.debugflag:
extraitems = extra.items()
extraitems.sort()
for key, value in extraitems:
self.ui.write(_("extra: %s=%s\n")
% (key, value.encode('string_escape')))
description = changes[4].strip()
if description:
if self.ui.verbose:
self.ui.write(_("description:\n"))
self.ui.write(description)
self.ui.write("\n\n")
else:
self.ui.write(_("summary: %s\n") %
description.splitlines()[0])
self.ui.write("\n")
self.showpatch(changenode)
def showpatch(self, node):
if self.patch:
prev = self.repo.changelog.parents(node)[0]
patch.diff(self.repo, prev, node, fp=self.ui)
self.ui.write("\n")
class changeset_templater(changeset_printer):
'''format changeset information.'''
def __init__(self, ui, repo, patch, mapfile, buffered):
changeset_printer.__init__(self, ui, repo, patch, buffered)
self.t = templater.templater(mapfile, templater.common_filters,
cache={'parent': '{rev}:{node|short} ',
'manifest': '{rev}:{node|short}',
'filecopy': '{name} ({source})'})
def use_template(self, t):
'''set template string to use'''
self.t.cache['changeset'] = t
def show(self, rev=0, changenode=None, brinfo=None, copies=[], **props):
'''show a single changeset or file revision'''
if self.buffered:
self.ui.mark(rev)
log = self.repo.changelog
if changenode is None:
changenode = log.node(rev)
elif not rev:
rev = log.rev(changenode)
changes = log.read(changenode)
def showlist(name, values, plural=None, **args):
'''expand set of values.
name is name of key in template map.
values is list of strings or dicts.
plural is plural of name, if not simply name + 's'.
expansion works like this, given name 'foo'.
if values is empty, expand 'no_foos'.
if 'foo' not in template map, return values as a string,
joined by space.
expand 'start_foos'.
for each value, expand 'foo'. if 'last_foo' in template
map, expand it instead of 'foo' for last key.
expand 'end_foos'.
'''
if plural: names = plural
else: names = name + 's'
if not values:
noname = 'no_' + names
if noname in self.t:
yield self.t(noname, **args)
return
if name not in self.t:
if isinstance(values[0], str):
yield ' '.join(values)
else:
for v in values:
yield dict(v, **args)
return
startname = 'start_' + names
if startname in self.t:
yield self.t(startname, **args)
vargs = args.copy()
def one(v, tag=name):
try:
vargs.update(v)
except (AttributeError, ValueError):
try:
for a, b in v:
vargs[a] = b
except ValueError:
vargs[name] = v
return self.t(tag, **vargs)
lastname = 'last_' + name
if lastname in self.t:
last = values.pop()
else:
last = None
for v in values:
yield one(v)
if last is not None:
yield one(last, tag=lastname)
endname = 'end_' + names
if endname in self.t:
yield self.t(endname, **args)
def showbranches(**args):
branch = changes[5].get("branch")
if branch:
yield showlist('branch', [branch], plural='branches', **args)
# add old style branches if requested
if brinfo and changenode in brinfo:
yield showlist('branch', brinfo[changenode],
plural='branches', **args)
def showparents(**args):
parents = [[('rev', log.rev(p)), ('node', hex(p))]
for p in log.parents(changenode)
if self.ui.debugflag or p != nullid]
if (not self.ui.debugflag and len(parents) == 1 and
parents[0][0][1] == rev - 1):
return
return showlist('parent', parents, **args)
def showtags(**args):
return showlist('tag', self.repo.nodetags(changenode), **args)
def showextras(**args):
extras = changes[5].items()
extras.sort()
for key, value in extras:
args = args.copy()
args.update(dict(key=key, value=value))
yield self.t('extra', **args)
def showcopies(**args):
c = [{'name': x[0], 'source': x[1]} for x in copies]
return showlist('file_copy', c, plural='file_copies', **args)
if self.ui.debugflag:
files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
def showfiles(**args):
return showlist('file', files[0], **args)
def showadds(**args):
return showlist('file_add', files[1], **args)
def showdels(**args):
return showlist('file_del', files[2], **args)
def showmanifest(**args):
args = args.copy()
args.update(dict(rev=self.repo.manifest.rev(changes[0]),
node=hex(changes[0])))
return self.t('manifest', **args)
else:
def showfiles(**args):
yield showlist('file', changes[3], **args)
showadds = ''
showdels = ''
showmanifest = ''
defprops = {
'author': changes[1],
'branches': showbranches,
'date': changes[2],
'desc': changes[4],
'file_adds': showadds,
'file_dels': showdels,
'files': showfiles,
'file_copies': showcopies,
'manifest': showmanifest,
'node': hex(changenode),
'parents': showparents,
'rev': rev,
'tags': showtags,
'extras': showextras,
}
props = props.copy()
props.update(defprops)
try:
if self.ui.debugflag and 'header_debug' in self.t:
key = 'header_debug'
elif self.ui.quiet and 'header_quiet' in self.t:
key = 'header_quiet'
elif self.ui.verbose and 'header_verbose' in self.t:
key = 'header_verbose'
elif 'header' in self.t:
key = 'header'
else:
key = ''
if key:
h = templater.stringify(self.t(key, **props))
if self.buffered:
self.ui.write_header(h)
else:
self.ui.write(h)
if self.ui.debugflag and 'changeset_debug' in self.t:
key = 'changeset_debug'
elif self.ui.quiet and 'changeset_quiet' in self.t:
key = 'changeset_quiet'
elif self.ui.verbose and 'changeset_verbose' in self.t:
key = 'changeset_verbose'
else:
key = 'changeset'
self.ui.write(templater.stringify(self.t(key, **props)))
self.showpatch(changenode)
except KeyError, inst:
raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
inst.args[0]))
except SyntaxError, inst:
raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
class stringio(object):
'''wrap cStringIO for use by changeset_templater.'''
def __init__(self):
self.fp = cStringIO.StringIO()
def write(self, *args):
for a in args:
self.fp.write(a)
write_header = write
def __getattr__(self, key):
return getattr(self.fp, key)
def show_changeset(ui, repo, opts, buffered=False):
"""show one changeset using template or regular display.
Display format will be the first non-empty hit of:
1. option 'template'
2. option 'style'
3. [ui] setting 'logtemplate'
4. [ui] setting 'style'
If all of these values are either the unset or the empty string,
regular display via changeset_printer() is done.
"""
# options
patch = opts.get('patch')
tmpl = opts.get('template')
mapfile = None
if tmpl:
tmpl = templater.parsestring(tmpl, quoted=False)
else:
mapfile = opts.get('style')
# ui settings
if not mapfile:
tmpl = ui.config('ui', 'logtemplate')
if tmpl:
tmpl = templater.parsestring(tmpl)
else:
mapfile = ui.config('ui', 'style')
if tmpl or mapfile:
if mapfile:
if not os.path.split(mapfile)[0]:
mapname = (templater.templatepath('map-cmdline.' + mapfile)
or templater.templatepath(mapfile))
if mapname: mapfile = mapname
try:
t = changeset_templater(ui, repo, patch, mapfile, buffered)
except SyntaxError, inst:
raise util.Abort(inst.args[0])
if tmpl: t.use_template(tmpl)
return t
return changeset_printer(ui, repo, patch, buffered)