summaryrefslogtreecommitdiff
path: root/mercurial/revset.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/revset.py')
-rw-r--r--mercurial/revset.py1123
1 files changed, 148 insertions, 975 deletions
diff --git a/mercurial/revset.py b/mercurial/revset.py
index a7e9d07..cb089d7 100644
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -6,76 +6,10 @@
# GNU General Public License version 2 or any later version.
import re
-import parser, util, error, discovery, hbisect, phases
-import node
+import parser, util, error, discovery, hbisect
import bookmarks as bookmarksmod
import match as matchmod
from i18n import _
-import encoding
-
-def _revancestors(repo, revs, followfirst):
- """Like revlog.ancestors(), but supports followfirst."""
- cut = followfirst and 1 or None
- cl = repo.changelog
- visit = util.deque(revs)
- seen = set([node.nullrev])
- while visit:
- for parent in cl.parentrevs(visit.popleft())[:cut]:
- if parent not in seen:
- visit.append(parent)
- seen.add(parent)
- yield parent
-
-def _revdescendants(repo, revs, followfirst):
- """Like revlog.descendants() but supports followfirst."""
- cut = followfirst and 1 or None
- cl = repo.changelog
- first = min(revs)
- nullrev = node.nullrev
- if first == nullrev:
- # Are there nodes with a null first parent and a non-null
- # second one? Maybe. Do we care? Probably not.
- for i in cl:
- yield i
- return
-
- seen = set(revs)
- for i in xrange(first + 1, len(cl)):
- for x in cl.parentrevs(i)[:cut]:
- if x != nullrev and x in seen:
- seen.add(i)
- yield i
- break
-
-def _revsbetween(repo, roots, heads):
- """Return all paths between roots and heads, inclusive of both endpoint
- sets."""
- if not roots:
- return []
- parentrevs = repo.changelog.parentrevs
- visit = heads[:]
- reachable = set()
- seen = {}
- minroot = min(roots)
- roots = set(roots)
- # open-code the post-order traversal due to the tiny size of
- # sys.getrecursionlimit()
- while visit:
- rev = visit.pop()
- if rev in roots:
- reachable.add(rev)
- parents = parentrevs(rev)
- seen[rev] = parents
- for parent in parents:
- if parent >= minroot and parent not in seen:
- visit.append(parent)
- if not reachable:
- return []
- for rev in sorted(seen):
- for parent in seen[rev]:
- if parent in reachable:
- reachable.add(rev)
- return sorted(reachable)
elements = {
"(": (20, ("group", 1, ")"), ("func", 1, ")")),
@@ -138,13 +72,12 @@ def tokenize(program):
pos += 1
else:
raise error.ParseError(_("unterminated string"), s)
- # gather up a symbol/keyword
- elif c.isalnum() or c in '._' or ord(c) > 127:
+ elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
s = pos
pos += 1
while pos < l: # find end of symbol
d = program[pos]
- if not (d.isalnum() or d in "._/" or ord(d) > 127):
+ if not (d.isalnum() or d in "._" or ord(d) > 127):
break
if d == '.' and program[pos - 1] == '.': # special case for ..
pos -= 1
@@ -177,7 +110,7 @@ def getlist(x):
def getargs(x, min, max, err):
l = getlist(x)
- if len(l) < min or (max >= 0 and len(l) > max):
+ if len(l) < min or len(l) > max:
raise error.ParseError(err)
return l
@@ -186,16 +119,6 @@ def getset(repo, subset, x):
raise error.ParseError(_("missing argument"))
return methods[x[0]](repo, subset, *x[1:])
-def _getrevsource(repo, r):
- extra = repo[r].extra()
- for label in ('source', 'transplant_source', 'rebase_source'):
- if label in extra:
- try:
- return repo[extra[label]].rev()
- except error.RepoLookupError:
- pass
- return None
-
# operator methods
def stringset(repo, subset, x):
@@ -231,14 +154,6 @@ def rangeset(repo, subset, x, y):
s = set(subset)
return [x for x in r if x in s]
-def dagrange(repo, subset, x, y):
- if subset:
- r = range(len(repo))
- xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
- s = set(subset)
- return [r for r in xs if r in s]
- return []
-
def andset(repo, subset, x, y):
return getset(repo, getset(repo, subset, x), y)
@@ -286,28 +201,19 @@ def ancestor(repo, subset, x):
return [r for r in an if r in subset]
-def _ancestors(repo, subset, x, followfirst=False):
- args = getset(repo, range(len(repo)), x)
- if not args:
- return []
- s = set(_revancestors(repo, args, followfirst)) | set(args)
- return [r for r in subset if r in s]
-
def ancestors(repo, subset, x):
"""``ancestors(set)``
Changesets that are ancestors of a changeset in set.
"""
- return _ancestors(repo, subset, x)
-
-def _firstancestors(repo, subset, x):
- # ``_firstancestors(set)``
- # Like ``ancestors(set)`` but follows only the first parents.
- return _ancestors(repo, subset, x, followfirst=True)
+ args = getset(repo, range(len(repo)), x)
+ if not args:
+ return []
+ s = set(repo.changelog.ancestors(*args)) | set(args)
+ return [r for r in subset if r in s]
def ancestorspec(repo, subset, x, n):
"""``set~n``
- Changesets that are the Nth ancestor (first parents only) of a changeset
- in set.
+ Changesets that are the Nth ancestor (first parents only) of a changeset in set.
"""
try:
n = int(n[1])
@@ -326,39 +232,22 @@ def author(repo, subset, x):
Alias for ``user(string)``.
"""
# i18n: "author" is a keyword
- n = encoding.lower(getstring(x, _("author requires a string")))
- kind, pattern, matcher = _substringmatcher(n)
- return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
-
-def bisect(repo, subset, x):
- """``bisect(string)``
- Changesets marked in the specified bisect status:
-
- - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
- - ``goods``, ``bads`` : csets topologicaly good/bad
- - ``range`` : csets taking part in the bisection
- - ``pruned`` : csets that are goods, bads or skipped
- - ``untested`` : csets whose fate is yet unknown
- - ``ignored`` : csets ignored due to DAG topology
- - ``current`` : the cset currently being bisected
- """
- # i18n: "bisect" is a keyword
- status = getstring(x, _("bisect requires a string")).lower()
- state = set(hbisect.get(repo, status))
- return [r for r in subset if r in state]
+ n = getstring(x, _("author requires a string")).lower()
+ return [r for r in subset if n in repo[r].user().lower()]
-# Backward-compatibility
-# - no help entry so that we do not advertise it any more
def bisected(repo, subset, x):
- return bisect(repo, subset, x)
+ """``bisected(string)``
+ Changesets marked in the specified bisect state (good, bad, skip).
+ """
+ state = getstring(x, _("bisect requires a string")).lower()
+ if state not in ('good', 'bad', 'skip', 'unknown'):
+ raise error.ParseError(_('invalid bisect state'))
+ marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
+ return [r for r in subset if r in marked]
def bookmark(repo, subset, x):
"""``bookmark([name])``
The named bookmark or all bookmarks.
-
- If `name` starts with `re:`, the remainder of the name is treated as
- a regular expression. To match a bookmark that actually starts with `re:`,
- use the prefix `literal:`.
"""
# i18n: "bookmark" is a keyword
args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
@@ -366,26 +255,11 @@ def bookmark(repo, subset, x):
bm = getstring(args[0],
# i18n: "bookmark" is a keyword
_('the argument to bookmark must be a string'))
- kind, pattern, matcher = _stringmatcher(bm)
- if kind == 'literal':
- bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
- if not bmrev:
- raise util.Abort(_("bookmark '%s' does not exist") % bm)
- bmrev = repo[bmrev].rev()
- return [r for r in subset if r == bmrev]
- else:
- matchrevs = set()
- for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
- if matcher(name):
- matchrevs.add(bmrev)
- if not matchrevs:
- raise util.Abort(_("no bookmarks exist that match '%s'")
- % pattern)
- bmrevs = set()
- for bmrev in matchrevs:
- bmrevs.add(repo[bmrev].rev())
- return [r for r in subset if r in bmrevs]
-
+ bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
+ if not bmrev:
+ raise util.Abort(_("bookmark '%s' does not exist") % bm)
+ bmrev = repo[bmrev].rev()
+ return [r for r in subset if r == bmrev]
bms = set([repo[r].rev()
for r in bookmarksmod.listbookmarks(repo).values()])
return [r for r in subset if r in bms]
@@ -394,25 +268,14 @@ def branch(repo, subset, x):
"""``branch(string or set)``
All changesets belonging to the given branch or the branches of the given
changesets.
-
- If `string` starts with `re:`, the remainder of the name is treated as
- a regular expression. To match a branch that actually starts with `re:`,
- use the prefix `literal:`.
"""
try:
b = getstring(x, '')
+ if b in repo.branchmap():
+ return [r for r in subset if repo[r].branch() == b]
except error.ParseError:
# not a string, but another revspec, e.g. tip()
pass
- else:
- kind, pattern, matcher = _stringmatcher(b)
- if kind == 'literal':
- # note: falls through to the revspec case if no branch with
- # this name exists
- if pattern in repo.branchmap():
- return [r for r in subset if matcher(repo[r].branch())]
- else:
- return [r for r in subset if matcher(repo[r].branch())]
s = getset(repo, range(len(repo)), x)
b = set()
@@ -422,18 +285,13 @@ def branch(repo, subset, x):
return [r for r in subset if r in s or repo[r].branch() in b]
def checkstatus(repo, subset, pat, field):
- m = None
+ m = matchmod.match(repo.root, repo.getcwd(), [pat])
s = []
- hasset = matchmod.patkind(pat) == 'set'
- fname = None
+ fast = (m.files() == [pat])
for r in subset:
c = repo[r]
- if not m or hasset:
- m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
- if not m.anypats() and len(m.files()) == 1:
- fname = m.files()[0]
- if fname is not None:
- if fname not in c.files():
+ if fast:
+ if pat not in c.files():
continue
else:
for f in c.files():
@@ -442,8 +300,8 @@ def checkstatus(repo, subset, pat, field):
else:
continue
files = repo.status(c.p1().node(), c.node())[field]
- if fname is not None:
- if fname in files:
+ if fast:
+ if pat in files:
s.append(r)
else:
for f in files:
@@ -452,21 +310,17 @@ def checkstatus(repo, subset, pat, field):
break
return s
-def _children(repo, narrow, parentset):
- cs = set()
- pr = repo.changelog.parentrevs
- for r in narrow:
- for p in pr(r):
- if p in parentset:
- cs.add(r)
- return cs
-
def children(repo, subset, x):
"""``children(set)``
Child changesets of changesets in set.
"""
+ cs = set()
+ cl = repo.changelog
s = set(getset(repo, range(len(repo)), x))
- cs = _children(repo, subset, s)
+ for r in xrange(0, len(repo)):
+ for p in cl.parentrevs(r):
+ if p in s:
+ cs.add(r)
return [r for r in subset if r in cs]
def closed(repo, subset, x):
@@ -475,7 +329,7 @@ def closed(repo, subset, x):
"""
# i18n: "closed" is a keyword
getargs(x, 0, 0, _("closed takes no arguments"))
- return [r for r in subset if repo[r].closesbranch()]
+ return [r for r in subset if repo[r].extra().get('close')]
def contains(repo, subset, x):
"""``contains(pattern)``
@@ -484,45 +338,20 @@ def contains(repo, subset, x):
"""
# i18n: "contains" is a keyword
pat = getstring(x, _("contains requires a pattern"))
- m = None
+ m = matchmod.match(repo.root, repo.getcwd(), [pat])
s = []
- if not matchmod.patkind(pat):
+ if m.files() == [pat]:
for r in subset:
if pat in repo[r]:
s.append(r)
else:
for r in subset:
- c = repo[r]
- if not m or matchmod.patkind(pat) == 'set':
- m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
- for f in c.manifest():
+ for f in repo[r].manifest():
if m(f):
s.append(r)
break
return s
-def converted(repo, subset, x):
- """``converted([id])``
- Changesets converted from the given identifier in the old repository if
- present, or all converted changesets if no identifier is specified.
- """
-
- # There is exactly no chance of resolving the revision, so do a simple
- # string compare and hope for the best
-
- rev = None
- # i18n: "converted" is a keyword
- l = getargs(x, 0, 1, _('converted takes one or no arguments'))
- if l:
- # i18n: "converted" is a keyword
- rev = getstring(l[0], _('converted requires a revision'))
-
- def _matchvalue(r):
- source = repo[r].extra().get('convert_revision', None)
- return source is not None and (rev is None or source.startswith(rev))
-
- return [r for r in subset if _matchvalue(r)]
-
def date(repo, subset, x):
"""``date(interval)``
Changesets within the interval, see :hg:`help dates`.
@@ -537,136 +366,34 @@ def desc(repo, subset, x):
Search commit message for string. The match is case-insensitive.
"""
# i18n: "desc" is a keyword
- ds = encoding.lower(getstring(x, _("desc requires a string")))
+ ds = getstring(x, _("desc requires a string")).lower()
l = []
for r in subset:
c = repo[r]
- if ds in encoding.lower(c.description()):
+ if ds in c.description().lower():
l.append(r)
return l
-def _descendants(repo, subset, x, followfirst=False):
- args = getset(repo, range(len(repo)), x)
- if not args:
- return []
- s = set(_revdescendants(repo, args, followfirst)) | set(args)
- return [r for r in subset if r in s]
-
def descendants(repo, subset, x):
"""``descendants(set)``
Changesets which are descendants of changesets in set.
"""
- return _descendants(repo, subset, x)
-
-def _firstdescendants(repo, subset, x):
- # ``_firstdescendants(set)``
- # Like ``descendants(set)`` but follows only the first parents.
- return _descendants(repo, subset, x, followfirst=True)
-
-def destination(repo, subset, x):
- """``destination([set])``
- Changesets that were created by a graft, transplant or rebase operation,
- with the given revisions specified as the source. Omitting the optional set
- is the same as passing all().
- """
- if x is not None:
- args = set(getset(repo, range(len(repo)), x))
- else:
- args = set(getall(repo, range(len(repo)), x))
-
- dests = set()
-
- # subset contains all of the possible destinations that can be returned, so
- # iterate over them and see if their source(s) were provided in the args.
- # Even if the immediate src of r is not in the args, src's source (or
- # further back) may be. Scanning back further than the immediate src allows
- # transitive transplants and rebases to yield the same results as transitive
- # grafts.
- for r in subset:
- src = _getrevsource(repo, r)
- lineage = None
-
- while src is not None:
- if lineage is None:
- lineage = list()
-
- lineage.append(r)
-
- # The visited lineage is a match if the current source is in the arg
- # set. Since every candidate dest is visited by way of iterating
- # subset, any dests futher back in the lineage will be tested by a
- # different iteration over subset. Likewise, if the src was already
- # selected, the current lineage can be selected without going back
- # further.
- if src in args or src in dests:
- dests.update(lineage)
- break
-
- r = src
- src = _getrevsource(repo, r)
-
- return [r for r in subset if r in dests]
-
-def draft(repo, subset, x):
- """``draft()``
- Changeset in draft phase."""
- # i18n: "draft" is a keyword
- getargs(x, 0, 0, _("draft takes no arguments"))
- pc = repo._phasecache
- return [r for r in subset if pc.phase(repo, r) == phases.draft]
-
-def extinct(repo, subset, x):
- """``extinct()``
- Obsolete changesets with obsolete descendants only.
- """
- # i18n: "extinct" is a keyword
- getargs(x, 0, 0, _("extinct takes no arguments"))
- extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))'))
- return [r for r in subset if r in extinctset]
-
-def extra(repo, subset, x):
- """``extra(label, [value])``
- Changesets with the given label in the extra metadata, with the given
- optional value.
-
- If `value` starts with `re:`, the remainder of the value is treated as
- a regular expression. To match a value that actually starts with `re:`,
- use the prefix `literal:`.
- """
-
- # i18n: "extra" is a keyword
- l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
- # i18n: "extra" is a keyword
- label = getstring(l[0], _('first argument to extra must be a string'))
- value = None
-
- if len(l) > 1:
- # i18n: "extra" is a keyword
- value = getstring(l[1], _('second argument to extra must be a string'))
- kind, value, matcher = _stringmatcher(value)
-
- def _matchvalue(r):
- extra = repo[r].extra()
- return label in extra and (value is None or matcher(extra[label]))
-
- return [r for r in subset if _matchvalue(r)]
+ args = getset(repo, range(len(repo)), x)
+ if not args:
+ return []
+ s = set(repo.changelog.descendants(*args)) | set(args)
+ return [r for r in subset if r in s]
def filelog(repo, subset, x):
"""``filelog(pattern)``
Changesets connected to the specified filelog.
-
- For performance reasons, ``filelog()`` does not show every changeset
- that affects the requested file(s). See :hg:`help log` for details. For
- a slower, more accurate result, use ``file()``.
"""
- # i18n: "filelog" is a keyword
pat = getstring(x, _("filelog requires a pattern"))
- m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
- ctx=repo[None])
+ m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
s = set()
- if not matchmod.patkind(pat):
+ if not m.anypats():
for f in m.files():
fl = repo.file(f)
for fr in fl:
@@ -680,42 +407,33 @@ def filelog(repo, subset, x):
return [r for r in subset if r in s]
-def first(repo, subset, x):
- """``first(set, [n])``
- An alias for limit().
+def follow(repo, subset, x):
+ """``follow([file])``
+ An alias for ``::.`` (ancestors of the working copy's first parent).
+ If a filename is specified, the history of the given file is followed,
+ including copies.
"""
- return limit(repo, subset, x)
-
-def _follow(repo, subset, x, name, followfirst=False):
- l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
- c = repo['.']
+ # i18n: "follow" is a keyword
+ l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
+ p = repo['.'].rev()
if l:
- x = getstring(l[0], _("%s expected a filename") % name)
- if x in c:
- cx = c[x]
- s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
- # include the revision responsible for the most recent version
- s.add(cx.linkrev())
- else:
- return []
+ x = getstring(l[0], _("follow expected a filename"))
+ s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
else:
- s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
+ s = set(repo.changelog.ancestors(p))
+ s |= set([p])
return [r for r in subset if r in s]
-def follow(repo, subset, x):
- """``follow([file])``
+def followfile(repo, subset, x):
+ """``follow()``
An alias for ``::.`` (ancestors of the working copy's first parent).
- If a filename is specified, the history of the given file is followed,
- including copies.
"""
- return _follow(repo, subset, x, 'follow')
-
-def _followfirst(repo, subset, x):
- # ``followfirst([file])``
- # Like ``follow([file])`` but follows only the first parent of
- # every revision or file revision.
- return _follow(repo, subset, x, '_followfirst', followfirst=True)
+ # i18n: "follow" is a keyword
+ getargs(x, 0, 0, _("follow takes no arguments"))
+ p = repo['.'].rev()
+ s = set(repo.changelog.ancestors(p)) | set([p])
+ return [r for r in subset if r in s]
def getall(repo, subset, x):
"""``all()``
@@ -745,79 +463,20 @@ def grep(repo, subset, x):
break
return l
-def _matchfiles(repo, subset, x):
- # _matchfiles takes a revset list of prefixed arguments:
- #
- # [p:foo, i:bar, x:baz]
- #
- # builds a match object from them and filters subset. Allowed
- # prefixes are 'p:' for regular patterns, 'i:' for include
- # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
- # a revision identifier, or the empty string to reference the
- # working directory, from which the match object is
- # initialized. Use 'd:' to set the default matching mode, default
- # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
-
- # i18n: "_matchfiles" is a keyword
- l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
- pats, inc, exc = [], [], []
- hasset = False
- rev, default = None, None
- for arg in l:
- # i18n: "_matchfiles" is a keyword
- s = getstring(arg, _("_matchfiles requires string arguments"))
- prefix, value = s[:2], s[2:]
- if prefix == 'p:':
- pats.append(value)
- elif prefix == 'i:':
- inc.append(value)
- elif prefix == 'x:':
- exc.append(value)
- elif prefix == 'r:':
- if rev is not None:
- # i18n: "_matchfiles" is a keyword
- raise error.ParseError(_('_matchfiles expected at most one '
- 'revision'))
- rev = value
- elif prefix == 'd:':
- if default is not None:
- # i18n: "_matchfiles" is a keyword
- raise error.ParseError(_('_matchfiles expected at most one '
- 'default mode'))
- default = value
- else:
- # i18n: "_matchfiles" is a keyword
- raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
- if not hasset and matchmod.patkind(value) == 'set':
- hasset = True
- if not default:
- default = 'glob'
- m = None
- s = []
- for r in subset:
- c = repo[r]
- if not m or (hasset and rev is None):
- ctx = c
- if rev is not None:
- ctx = repo[rev or None]
- m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
- exclude=exc, ctx=ctx, default=default)
- for f in c.files():
- if m(f):
- s.append(r)
- break
- return s
-
def hasfile(repo, subset, x):
"""``file(pattern)``
Changesets affecting files matched by pattern.
-
- For a faster but less accurate result, consider using ``filelog()``
- instead.
"""
# i18n: "file" is a keyword
pat = getstring(x, _("file requires a pattern"))
- return _matchfiles(repo, subset, ('string', 'p:' + pat))
+ m = matchmod.match(repo.root, repo.getcwd(), [pat])
+ s = []
+ for r in subset:
+ for f in repo[r].files():
+ if m(f):
+ s.append(r)
+ break
+ return s
def head(repo, subset, x):
"""``head()``
@@ -844,26 +503,24 @@ def keyword(repo, subset, x):
string. The match is case-insensitive.
"""
# i18n: "keyword" is a keyword
- kw = encoding.lower(getstring(x, _("keyword requires a string")))
+ kw = getstring(x, _("keyword requires a string")).lower()
l = []
for r in subset:
c = repo[r]
t = " ".join(c.files() + [c.user(), c.description()])
- if kw in encoding.lower(t):
+ if kw in t.lower():
l.append(r)
return l
def limit(repo, subset, x):
- """``limit(set, [n])``
- First n members of set, defaulting to 1.
+ """``limit(set, n)``
+ First n members of set.
"""
# i18n: "limit" is a keyword
- l = getargs(x, 1, 2, _("limit requires one or two arguments"))
+ l = getargs(x, 2, 2, _("limit requires two arguments"))
try:
- lim = 1
- if len(l) == 2:
- # i18n: "limit" is a keyword
- lim = int(getstring(l[1], _("limit requires a number")))
+ # i18n: "limit" is a keyword
+ lim = int(getstring(l[1], _("limit requires a number")))
except (TypeError, ValueError):
# i18n: "limit" is a keyword
raise error.ParseError(_("limit expects a number"))
@@ -872,16 +529,14 @@ def limit(repo, subset, x):
return [r for r in os if r in ss]
def last(repo, subset, x):
- """``last(set, [n])``
- Last n members of set, defaulting to 1.
+ """``last(set, n)``
+ Last n members of set.
"""
# i18n: "last" is a keyword
- l = getargs(x, 1, 2, _("last requires one or two arguments"))
+ l = getargs(x, 2, 2, _("last requires two arguments"))
try:
- lim = 1
- if len(l) == 2:
- # i18n: "last" is a keyword
- lim = int(getstring(l[1], _("last requires a number")))
+ # i18n: "last" is a keyword
+ lim = int(getstring(l[1], _("last requires a number")))
except (TypeError, ValueError):
# i18n: "last" is a keyword
raise error.ParseError(_("last expects a number"))
@@ -928,7 +583,7 @@ def modifies(repo, subset, x):
pat = getstring(x, _("modifies requires a pattern"))
return checkstatus(repo, subset, pat, 0)
-def node_(repo, subset, x):
+def node(repo, subset, x):
"""``id(string)``
Revision non-ambiguously specified by the given hex string prefix.
"""
@@ -939,48 +594,9 @@ def node_(repo, subset, x):
if len(n) == 40:
rn = repo[n].rev()
else:
- rn = None
- pm = repo.changelog._partialmatch(n)
- if pm is not None:
- rn = repo.changelog.rev(pm)
-
+ rn = repo.changelog.rev(repo.changelog._partialmatch(n))
return [r for r in subset if r == rn]
-def obsolete(repo, subset, x):
- """``obsolete()``
- Mutable changeset with a newer version."""
- # i18n: "obsolete" is a keyword
- getargs(x, 0, 0, _("obsolete takes no arguments"))
- return [r for r in subset if repo[r].obsolete()]
-
-def origin(repo, subset, x):
- """``origin([set])``
- Changesets that were specified as a source for the grafts, transplants or
- rebases that created the given revisions. Omitting the optional set is the
- same as passing all(). If a changeset created by these operations is itself
- specified as a source for one of these operations, only the source changeset
- for the first operation is selected.
- """
- if x is not None:
- args = set(getset(repo, range(len(repo)), x))
- else:
- args = set(getall(repo, range(len(repo)), x))
-
- def _firstsrc(rev):
- src = _getrevsource(repo, rev)
- if src is None:
- return None
-
- while True:
- prev = _getrevsource(repo, src)
-
- if prev is None:
- return src
- src = prev
-
- o = set([_firstsrc(r) for r in args])
- return [r for r in subset if r in o]
-
def outgoing(repo, subset, x):
"""``outgoing([path])``
Changesets not found in the specified destination repository, or the
@@ -998,10 +614,10 @@ def outgoing(repo, subset, x):
revs = [repo.lookup(rev) for rev in revs]
other = hg.peer(repo, {}, dest)
repo.ui.pushbuffer()
- outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
+ common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
repo.ui.popbuffer()
cl = repo.changelog
- o = set([cl.rev(r) for r in outgoing.missing])
+ o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
return [r for r in subset if r in o]
def p1(repo, subset, x):
@@ -1079,59 +695,12 @@ def present(repo, subset, x):
"""``present(set)``
An empty set, if any revision in set isn't found; otherwise,
all revisions in set.
-
- If any of specified revisions is not present in the local repository,
- the query is normally aborted. But this predicate allows the query
- to continue even in such cases.
"""
try:
return getset(repo, subset, x)
except error.RepoLookupError:
return []
-def public(repo, subset, x):
- """``public()``
- Changeset in public phase."""
- # i18n: "public" is a keyword
- getargs(x, 0, 0, _("public takes no arguments"))
- pc = repo._phasecache
- return [r for r in subset if pc.phase(repo, r) == phases.public]
-
-def remote(repo, subset, x):
- """``remote([id [,path]])``
- Local revision that corresponds to the given identifier in a
- remote repository, if present. Here, the '.' identifier is a
- synonym for the current local branch.
- """
-
- import hg # avoid start-up nasties
- # i18n: "remote" is a keyword
- l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
-
- q = '.'
- if len(l) > 0:
- # i18n: "remote" is a keyword
- q = getstring(l[0], _("remote requires a string id"))
- if q == '.':
- q = repo['.'].branch()
-
- dest = ''
- if len(l) > 1:
- # i18n: "remote" is a keyword
- dest = getstring(l[1], _("remote requires a repository path"))
- dest = repo.ui.expandpath(dest or 'default')
- dest, branches = hg.parseurl(dest)
- revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
- if revs:
- revs = [repo.lookup(rev) for rev in revs]
- other = hg.peer(repo, {}, dest)
- n = other.lookup(q)
- if n in repo:
- r = repo[n].rev()
- if r in subset:
- return [r]
- return []
-
def removes(repo, subset, x):
"""``removes(pattern)``
Changesets which remove files matching pattern.
@@ -1154,144 +723,21 @@ def rev(repo, subset, x):
raise error.ParseError(_("rev expects a number"))
return [r for r in subset if r == l]
-def matching(repo, subset, x):
- """``matching(revision [, field])``
- Changesets in which a given set of fields match the set of fields in the
- selected revision or set.
-
- To match more than one field pass the list of fields to match separated
- by spaces (e.g. ``author description``).
-
- Valid fields are most regular revision fields and some special fields.
-
- Regular revision fields are ``description``, ``author``, ``branch``,
- ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
- and ``diff``.
- Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
- contents of the revision. Two revisions matching their ``diff`` will
- also match their ``files``.
-
- Special fields are ``summary`` and ``metadata``:
- ``summary`` matches the first line of the description.
- ``metadata`` is equivalent to matching ``description user date``
- (i.e. it matches the main metadata fields).
-
- ``metadata`` is the default field which is used when no fields are
- specified. You can match more than one field at a time.
- """
- # i18n: "matching" is a keyword
- l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
-
- revs = getset(repo, xrange(len(repo)), l[0])
-
- fieldlist = ['metadata']
- if len(l) > 1:
- fieldlist = getstring(l[1],
- # i18n: "matching" is a keyword
- _("matching requires a string "
- "as its second argument")).split()
-
- # Make sure that there are no repeated fields,
- # expand the 'special' 'metadata' field type
- # and check the 'files' whenever we check the 'diff'
- fields = []
- for field in fieldlist:
- if field == 'metadata':
- fields += ['user', 'description', 'date']
- elif field == 'diff':
- # a revision matching the diff must also match the files
- # since matching the diff is very costly, make sure to
- # also match the files first
- fields += ['files', 'diff']
- else:
- if field == 'author':
- field = 'user'
- fields.append(field)
- fields = set(fields)
- if 'summary' in fields and 'description' in fields:
- # If a revision matches its description it also matches its summary
- fields.discard('summary')
-
- # We may want to match more than one field
- # Not all fields take the same amount of time to be matched
- # Sort the selected fields in order of increasing matching cost
- fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
- 'files', 'description', 'substate', 'diff']
- def fieldkeyfunc(f):
- try:
- return fieldorder.index(f)
- except ValueError:
- # assume an unknown field is very costly
- return len(fieldorder)
- fields = list(fields)
- fields.sort(key=fieldkeyfunc)
-
- # Each field will be matched with its own "getfield" function
- # which will be added to the getfieldfuncs array of functions
- getfieldfuncs = []
- _funcs = {
- 'user': lambda r: repo[r].user(),
- 'branch': lambda r: repo[r].branch(),
- 'date': lambda r: repo[r].date(),
- 'description': lambda r: repo[r].description(),
- 'files': lambda r: repo[r].files(),
- 'parents': lambda r: repo[r].parents(),
- 'phase': lambda r: repo[r].phase(),
- 'substate': lambda r: repo[r].substate,
- 'summary': lambda r: repo[r].description().splitlines()[0],
- 'diff': lambda r: list(repo[r].diff(git=True),)
- }
- for info in fields:
- getfield = _funcs.get(info, None)
- if getfield is None:
- raise error.ParseError(
- # i18n: "matching" is a keyword
- _("unexpected field name passed to matching: %s") % info)
- getfieldfuncs.append(getfield)
- # convert the getfield array of functions into a "getinfo" function
- # which returns an array of field values (or a single value if there
- # is only one field to match)
- getinfo = lambda r: [f(r) for f in getfieldfuncs]
-
- matches = set()
- for rev in revs:
- target = getinfo(rev)
- for r in subset:
- match = True
- for n, f in enumerate(getfieldfuncs):
- if target[n] != f(r):
- match = False
- break
- if match:
- matches.add(r)
- return [r for r in subset if r in matches]
-
def reverse(repo, subset, x):
"""``reverse(set)``
Reverse order of set.
"""
l = getset(repo, subset, x)
- if not isinstance(l, list):
- l = list(l)
l.reverse()
return l
def roots(repo, subset, x):
"""``roots(set)``
- Changesets in set with no parent changeset in set.
+ Changesets with no parent changeset in set.
"""
- s = set(getset(repo, xrange(len(repo)), x))
- subset = [r for r in subset if r in s]
- cs = _children(repo, subset, s)
- return [r for r in subset if r not in cs]
-
-def secret(repo, subset, x):
- """``secret()``
- Changeset in secret phase."""
- # i18n: "secret" is a keyword
- getargs(x, 0, 0, _("secret takes no arguments"))
- pc = repo._phasecache
- return [r for r in subset if pc.phase(repo, r) == phases.secret]
+ s = getset(repo, subset, x)
+ cs = set(children(repo, subset, x))
+ return [r for r in s if r not in cs]
def sort(repo, subset, x):
"""``sort(set[, [-]key...])``
@@ -1310,7 +756,6 @@ def sort(repo, subset, x):
l = getargs(x, 1, 2, _("sort requires one or two arguments"))
keys = "rev"
if len(l) == 2:
- # i18n: "sort" is a keyword
keys = getstring(l[1], _("sort spec must be a string"))
s = l[0]
@@ -1349,51 +794,6 @@ def sort(repo, subset, x):
l.sort()
return [e[-1] for e in l]
-def _stringmatcher(pattern):
- """
- accepts a string, possibly starting with 're:' or 'literal:' prefix.
- returns the matcher name, pattern, and matcher function.
- missing or unknown prefixes are treated as literal matches.
-
- helper for tests:
- >>> def test(pattern, *tests):
- ... kind, pattern, matcher = _stringmatcher(pattern)
- ... return (kind, pattern, [bool(matcher(t)) for t in tests])
-
- exact matching (no prefix):
- >>> test('abcdefg', 'abc', 'def', 'abcdefg')
- ('literal', 'abcdefg', [False, False, True])
-
- regex matching ('re:' prefix)
- >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
- ('re', 'a.+b', [False, False, True])
-
- force exact matches ('literal:' prefix)
- >>> test('literal:re:foobar', 'foobar', 're:foobar')
- ('literal', 're:foobar', [False, True])
-
- unknown prefixes are ignored and treated as literals
- >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
- ('literal', 'foo:bar', [False, False, True])
- """
- if pattern.startswith('re:'):
- pattern = pattern[3:]
- try:
- regex = re.compile(pattern)
- except re.error, e:
- raise error.ParseError(_('invalid regular expression: %s')
- % e)
- return 're', pattern, regex.search
- elif pattern.startswith('literal:'):
- pattern = pattern[8:]
- return 'literal', pattern, pattern.__eq__
-
-def _substringmatcher(pattern):
- kind, pattern, matcher = _stringmatcher(pattern)
- if kind == 'literal':
- matcher = lambda s: pattern in s
- return kind, pattern, matcher
-
def tag(repo, subset, x):
"""``tag([name])``
The specified tag by name, or all tagged revisions if no name is given.
@@ -1402,20 +802,12 @@ def tag(repo, subset, x):
args = getargs(x, 0, 1, _("tag takes one or no arguments"))
cl = repo.changelog
if args:
- pattern = getstring(args[0],
- # i18n: "tag" is a keyword
- _('the argument to tag must be a string'))
- kind, pattern, matcher = _stringmatcher(pattern)
- if kind == 'literal':
- # avoid resolving all tags
- tn = repo._tagscache.tags.get(pattern, None)
- if tn is None:
- raise util.Abort(_("tag '%s' does not exist") % pattern)
- s = set([repo[tn].rev()])
- else:
- s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
- if not s:
- raise util.Abort(_("no tags exist that match '%s'") % pattern)
+ tn = getstring(args[0],
+ # i18n: "tag" is a keyword
+ _('the argument to tag must be a string'))
+ if not repo.tags().get(tn, None):
+ raise util.Abort(_("tag '%s' does not exist") % tn)
+ s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
else:
s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
return [r for r in subset if r in s]
@@ -1423,102 +815,58 @@ def tag(repo, subset, x):
def tagged(repo, subset, x):
return tag(repo, subset, x)
-def unstable(repo, subset, x):
- """``unstable()``
- Non-obsolete changesets with obsolete ancestors.
- """
- # i18n: "unstable" is a keyword
- getargs(x, 0, 0, _("unstable takes no arguments"))
- unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
- return [r for r in subset if r in unstableset]
-
-
def user(repo, subset, x):
"""``user(string)``
User name contains string. The match is case-insensitive.
-
- If `string` starts with `re:`, the remainder of the string is treated as
- a regular expression. To match a user that actually contains `re:`, use
- the prefix `literal:`.
"""
return author(repo, subset, x)
-# for internal use
-def _list(repo, subset, x):
- s = getstring(x, "internal error")
- if not s:
- return []
- if not isinstance(subset, set):
- subset = set(subset)
- ls = [repo[r].rev() for r in s.split('\0')]
- return [r for r in ls if r in subset]
-
symbols = {
"adds": adds,
"all": getall,
"ancestor": ancestor,
"ancestors": ancestors,
- "_firstancestors": _firstancestors,
"author": author,
- "bisect": bisect,
"bisected": bisected,
"bookmark": bookmark,
"branch": branch,
"children": children,
"closed": closed,
"contains": contains,
- "converted": converted,
"date": date,
"desc": desc,
"descendants": descendants,
- "_firstdescendants": _firstdescendants,
- "destination": destination,
- "draft": draft,
- "extinct": extinct,
- "extra": extra,
"file": hasfile,
"filelog": filelog,
- "first": first,
"follow": follow,
- "_followfirst": _followfirst,
"grep": grep,
"head": head,
"heads": heads,
- "id": node_,
+ "id": node,
"keyword": keyword,
"last": last,
"limit": limit,
- "_matchfiles": _matchfiles,
"max": maxrev,
"merge": merge,
"min": minrev,
"modifies": modifies,
- "obsolete": obsolete,
- "origin": origin,
"outgoing": outgoing,
"p1": p1,
"p2": p2,
"parents": parents,
"present": present,
- "public": public,
- "remote": remote,
"removes": removes,
"rev": rev,
"reverse": reverse,
"roots": roots,
"sort": sort,
- "secret": secret,
- "matching": matching,
"tag": tag,
"tagged": tagged,
"user": user,
- "unstable": unstable,
- "_list": _list,
}
methods = {
"range": rangeset,
- "dagrange": dagrange,
"string": stringset,
"symbol": symbolset,
"and": andset,
@@ -1542,6 +890,9 @@ def optimize(x, small):
op = x[0]
if op == 'minus':
return optimize(('and', x[1], ('not', x[2])), small)
+ elif op == 'dagrange':
+ return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
+ ('func', ('symbol', 'ancestors'), x[2])), small)
elif op == 'dagrangepre':
return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
elif op == 'dagrangepost':
@@ -1555,7 +906,7 @@ def optimize(x, small):
'-' + getstring(x[1], _("can't negate that"))), small)
elif op in 'string symbol negate':
return smallbonus, x # single revisions are small
- elif op == 'and':
+ elif op == 'and' or op == 'dagrange':
wa, ta = optimize(x[1], True)
wb, tb = optimize(x[2], True)
w = min(wa, wb)
@@ -1576,7 +927,7 @@ def optimize(x, small):
return o[0], (op, o[1])
elif op == 'group':
return optimize(x[1], small)
- elif op in 'dagrange range list parent ancestorspec':
+ elif op in 'range list parent ancestorspec':
if op == 'parent':
# x^:y means (x^) : y, not x ^ (:y)
post = ('parentpost', x[1])
@@ -1600,7 +951,7 @@ def optimize(x, small):
w = 100 # very slow
elif f == "ancestor":
w = 1 * smallbonus
- elif f in "reverse limit first":
+ elif f in "reverse limit":
w = 0
elif f in "sort":
w = 10 # assume most sorts look at changelog
@@ -1609,27 +960,6 @@ def optimize(x, small):
return w + wa, (op, x[1], ta)
return 1, x
-_aliasarg = ('func', ('symbol', '_aliasarg'))
-def _getaliasarg(tree):
- """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
- return X, None otherwise.
- """
- if (len(tree) == 3 and tree[:2] == _aliasarg
- and tree[2][0] == 'string'):
- return tree[2][1]
- return None
-
-def _checkaliasarg(tree, known=None):
- """Check tree contains no _aliasarg construct or only ones which
- value is in known. Used to avoid alias placeholders injection.
- """
- if isinstance(tree, tuple):
- arg = _getaliasarg(tree)
- if arg is not None and (not known or arg not in known):
- raise error.ParseError(_("not a function: %s") % '_aliasarg')
- for t in tree:
- _checkaliasarg(t, known)
-
class revsetalias(object):
funcre = re.compile('^([^(]+)\(([^)]+)\)$')
args = None
@@ -1640,93 +970,46 @@ class revsetalias(object):
h = heads(default)
b($1) = ancestors($1) - ancestors(default)
'''
- m = self.funcre.search(name)
- if m:
- self.name = m.group(1)
- self.tree = ('func', ('symbol', m.group(1)))
- self.args = [x.strip() for x in m.group(2).split(',')]
- for arg in self.args:
- # _aliasarg() is an unknown symbol only used separate
- # alias argument placeholders from regular strings.
- value = value.replace(arg, '_aliasarg(%r)' % (arg,))
- else:
- self.name = name
- self.tree = ('symbol', name)
-
- self.replacement, pos = parse(value)
- if pos != len(value):
- raise error.ParseError(_('invalid token'), pos)
- # Check for placeholder injection
- _checkaliasarg(self.replacement, self.args)
-
-def _getalias(aliases, tree):
- """If tree looks like an unexpanded alias, return it. Return None
- otherwise.
- """
- if isinstance(tree, tuple) and tree:
- if tree[0] == 'symbol' and len(tree) == 2:
- name = tree[1]
- alias = aliases.get(name)
- if alias and alias.args is None and alias.tree == tree:
- return alias
- if tree[0] == 'func' and len(tree) > 1:
- if tree[1][0] == 'symbol' and len(tree[1]) == 2:
- name = tree[1][1]
- alias = aliases.get(name)
- if alias and alias.args is not None and alias.tree == tree[:2]:
- return alias
- return None
-
-def _expandargs(tree, args):
- """Replace _aliasarg instances with the substitution value of the
- same name in args, recursively.
- """
- if not tree or not isinstance(tree, tuple):
- return tree
- arg = _getaliasarg(tree)
- if arg is not None:
- return args[arg]
- return tuple(_expandargs(t, args) for t in tree)
-
-def _expandaliases(aliases, tree, expanding, cache):
- """Expand aliases in tree, recursively.
-
- 'aliases' is a dictionary mapping user defined aliases to
- revsetalias objects.
- """
- if not isinstance(tree, tuple):
- # Do not expand raw strings
+ if isinstance(name, tuple): # parameter substitution
+ self.tree = name
+ self.replacement = value
+ else: # alias definition
+ m = self.funcre.search(name)
+ if m:
+ self.tree = ('func', ('symbol', m.group(1)))
+ self.args = [x.strip() for x in m.group(2).split(',')]
+ for arg in self.args:
+ value = value.replace(arg, repr(arg))
+ else:
+ self.tree = ('symbol', name)
+
+ self.replacement, pos = parse(value)
+ if pos != len(value):
+ raise error.ParseError(_('invalid token'), pos)
+
+ def process(self, tree):
+ if isinstance(tree, tuple):
+ if self.args is None:
+ if tree == self.tree:
+ return self.replacement
+ elif tree[:2] == self.tree:
+ l = getlist(tree[2])
+ if len(l) != len(self.args):
+ raise error.ParseError(
+ _('invalid number of arguments: %s') % len(l))
+ result = self.replacement
+ for a, v in zip(self.args, l):
+ valalias = revsetalias(('string', a), v)
+ result = valalias.process(result)
+ return result
+ return tuple(map(self.process, tree))
return tree
- alias = _getalias(aliases, tree)
- if alias is not None:
- if alias in expanding:
- raise error.ParseError(_('infinite expansion of revset alias "%s" '
- 'detected') % alias.name)
- expanding.append(alias)
- if alias.name not in cache:
- cache[alias.name] = _expandaliases(aliases, alias.replacement,
- expanding, cache)
- result = cache[alias.name]
- expanding.pop()
- if alias.args is not None:
- l = getlist(tree[2])
- if len(l) != len(alias.args):
- raise error.ParseError(
- _('invalid number of arguments: %s') % len(l))
- l = [_expandaliases(aliases, a, [], cache) for a in l]
- result = _expandargs(result, dict(zip(alias.args, l)))
- else:
- result = tuple(_expandaliases(aliases, t, expanding, cache)
- for t in tree)
- return result
def findaliases(ui, tree):
- _checkaliasarg(tree)
- aliases = {}
for k, v in ui.configitems('revsetalias'):
alias = revsetalias(k, v)
- aliases[alias.name] = alias
- return _expandaliases(aliases, tree, [], {})
+ tree = alias.process(tree)
+ return tree
parse = parser.parser(tokenize, elements).parse
@@ -1736,121 +1019,11 @@ def match(ui, spec):
tree, pos = parse(spec)
if (pos != len(spec)):
raise error.ParseError(_("invalid token"), pos)
- if ui:
- tree = findaliases(ui, tree)
+ tree = findaliases(ui, tree)
weight, tree = optimize(tree, True)
def mfunc(repo, subset):
return getset(repo, subset, tree)
return mfunc
-def formatspec(expr, *args):
- '''
- This is a convenience function for using revsets internally, and
- escapes arguments appropriately. Aliases are intentionally ignored
- so that intended expression behavior isn't accidentally subverted.
-
- Supported arguments:
-
- %r = revset expression, parenthesized
- %d = int(arg), no quoting
- %s = string(arg), escaped and single-quoted
- %b = arg.branch(), escaped and single-quoted
- %n = hex(arg), single-quoted
- %% = a literal '%'
-
- Prefixing the type with 'l' specifies a parenthesized list of that type.
-
- >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
- '(10 or 11):: and ((this()) or (that()))'
- >>> formatspec('%d:: and not %d::', 10, 20)
- '10:: and not 20::'
- >>> formatspec('%ld or %ld', [], [1])
- "_list('') or 1"
- >>> formatspec('keyword(%s)', 'foo\\xe9')
- "keyword('foo\\\\xe9')"
- >>> b = lambda: 'default'
- >>> b.branch = b
- >>> formatspec('branch(%b)', b)
- "branch('default')"
- >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
- "root(_list('a\\x00b\\x00c\\x00d'))"
- '''
-
- def quote(s):
- return repr(str(s))
-
- def argtype(c, arg):
- if c == 'd':
- return str(int(arg))
- elif c == 's':
- return quote(arg)
- elif c == 'r':
- parse(arg) # make sure syntax errors are confined
- return '(%s)' % arg
- elif c == 'n':
- return quote(node.hex(arg))
- elif c == 'b':
- return quote(arg.branch())
-
- def listexp(s, t):
- l = len(s)
- if l == 0:
- return "_list('')"
- elif l == 1:
- return argtype(t, s[0])
- elif t == 'd':
- return "_list('%s')" % "\0".join(str(int(a)) for a in s)
- elif t == 's':
- return "_list('%s')" % "\0".join(s)
- elif t == 'n':
- return "_list('%s')" % "\0".join(node.hex(a) for a in s)
- elif t == 'b':
- return "_list('%s')" % "\0".join(a.branch() for a in s)
-
- m = l // 2
- return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
-
- ret = ''
- pos = 0
- arg = 0
- while pos < len(expr):
- c = expr[pos]
- if c == '%':
- pos += 1
- d = expr[pos]
- if d == '%':
- ret += d
- elif d in 'dsnbr':
- ret += argtype(d, args[arg])
- arg += 1
- elif d == 'l':
- # a list of some type
- pos += 1
- d = expr[pos]
- ret += listexp(list(args[arg]), d)
- arg += 1
- else:
- raise util.Abort('unexpected revspec format character %s' % d)
- else:
- ret += c
- pos += 1
-
- return ret
-
-def prettyformat(tree):
- def _prettyformat(tree, level, lines):
- if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
- lines.append((level, str(tree)))
- else:
- lines.append((level, '(%s' % tree[0]))
- for s in tree[1:]:
- _prettyformat(s, level + 1, lines)
- lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
-
- lines = []
- _prettyformat(tree, 0, lines)
- output = '\n'.join((' '*l + s) for l, s in lines)
- return output
-
# tell hggettext to extract docstrings from these functions:
i18nfunctions = symbols.values()