diff mercurial/fileset.py @ 14511:30506b894359

filesets: introduce basic fileset expression parser
author Matt Mackall <mpm@selenic.com>
date Wed, 01 Jun 2011 19:12:18 -0500 (2011-06-02)
parents mercurial/revset.py@4f695345979c
children 85fe676c27e9
line wrap: on
line diff
copy from mercurial/revset.py
copy to mercurial/fileset.py
--- a/mercurial/revset.py
+++ b/mercurial/fileset.py
@@ -1,26 +1,16 @@
-# revset.py - revision set queries for mercurial
+# fileset.py - file set queries for mercurial
 #
 # Copyright 2010 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import re
-import parser, util, error, discovery, hbisect
-import bookmarks as bookmarksmod
-import match as matchmod
+import parser, error
 from i18n import _
 
 elements = {
     "(": (20, ("group", 1, ")"), ("func", 1, ")")),
-    "~": (18, None, ("ancestor", 18)),
-    "^": (18, None, ("parent", 18), ("parentpost", 18)),
     "-": (5, ("negate", 19), ("minus", 5)),
-    "::": (17, ("dagrangepre", 17), ("dagrange", 17),
-           ("dagrangepost", 17)),
-    "..": (17, ("dagrangepre", 17), ("dagrange", 17),
-           ("dagrangepost", 17)),
-    ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
     "not": (10, ("not", 10)),
     "!": (10, ("not", 10)),
     "and": (5, None, ("and", 5)),
@@ -43,13 +33,7 @@
         c = program[pos]
         if c.isspace(): # skip inter-token whitespace
             pass
-        elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
-            yield ('::', None, pos)
-            pos += 1 # skip ahead
-        elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
-            yield ('..', None, pos)
-            pos += 1 # skip ahead
-        elif c in "():,-|&+!~^": # handle simple operators
+        elif c in "(),-|&+!": # handle simple operators
             yield (c, None, pos)
         elif (c in '"\'' or c == 'r' and
               program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
@@ -72,15 +56,12 @@
                 pos += 1
             else:
                 raise error.ParseError(_("unterminated string"), s)
-        elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
+        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):
-                    break
-                if d == '.' and program[pos - 1] == '.': # special case for ..
-                    pos -= 1
+                if not (d.isalnum() or d in ".*{}[]?," or ord(d) > 127):
                     break
                 pos += 1
             sym = program[s:pos]
@@ -94,936 +75,5 @@
         pos += 1
     yield ('end', None, pos)
 
-# helpers
-
-def getstring(x, err):
-    if x and (x[0] == 'string' or x[0] == 'symbol'):
-        return x[1]
-    raise error.ParseError(err)
-
-def getlist(x):
-    if not x:
-        return []
-    if x[0] == 'list':
-        return getlist(x[1]) + [x[2]]
-    return [x]
-
-def getargs(x, min, max, err):
-    l = getlist(x)
-    if len(l) < min or len(l) > max:
-        raise error.ParseError(err)
-    return l
-
-def getset(repo, subset, x):
-    if not x:
-        raise error.ParseError(_("missing argument"))
-    return methods[x[0]](repo, subset, *x[1:])
-
-# operator methods
-
-def stringset(repo, subset, x):
-    x = repo[x].rev()
-    if x == -1 and len(subset) == len(repo):
-        return [-1]
-    if len(subset) == len(repo) or x in subset:
-        return [x]
-    return []
-
-def symbolset(repo, subset, x):
-    if x in symbols:
-        raise error.ParseError(_("can't use %s here") % x)
-    return stringset(repo, subset, x)
-
-def rangeset(repo, subset, x, y):
-    m = getset(repo, subset, x)
-    if not m:
-        m = getset(repo, range(len(repo)), x)
-
-    n = getset(repo, subset, y)
-    if not n:
-        n = getset(repo, range(len(repo)), y)
-
-    if not m or not n:
-        return []
-    m, n = m[0], n[-1]
-
-    if m < n:
-        r = range(m, n + 1)
-    else:
-        r = range(m, n - 1, -1)
-    s = set(subset)
-    return [x for x in r if x in s]
-
-def andset(repo, subset, x, y):
-    return getset(repo, getset(repo, subset, x), y)
-
-def orset(repo, subset, x, y):
-    xl = getset(repo, subset, x)
-    s = set(xl)
-    yl = getset(repo, [r for r in subset if r not in s], y)
-    return xl + yl
-
-def notset(repo, subset, x):
-    s = set(getset(repo, subset, x))
-    return [r for r in subset if r not in s]
-
-def listset(repo, subset, a, b):
-    raise error.ParseError(_("can't use a list in this context"))
-
-def func(repo, subset, a, b):
-    if a[0] == 'symbol' and a[1] in symbols:
-        return symbols[a[1]](repo, subset, b)
-    raise error.ParseError(_("not a function: %s") % a[1])
-
-# functions
-
-def adds(repo, subset, x):
-    """``adds(pattern)``
-    Changesets that add a file matching pattern.
-    """
-    # i18n: "adds" is a keyword
-    pat = getstring(x, _("adds requires a pattern"))
-    return checkstatus(repo, subset, pat, 1)
-
-def ancestor(repo, subset, x):
-    """``ancestor(single, single)``
-    Greatest common ancestor of the two changesets.
-    """
-    # i18n: "ancestor" is a keyword
-    l = getargs(x, 2, 2, _("ancestor requires two arguments"))
-    r = range(len(repo))
-    a = getset(repo, r, l[0])
-    b = getset(repo, r, l[1])
-    if len(a) != 1 or len(b) != 1:
-        # i18n: "ancestor" is a keyword
-        raise error.ParseError(_("ancestor arguments must be single revisions"))
-    an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
-
-    return [r for r in an if r in subset]
-
-def ancestors(repo, subset, x):
-    """``ancestors(set)``
-    Changesets that are ancestors of a changeset in set.
-    """
-    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.
-    """
-    try:
-        n = int(n[1])
-    except ValueError:
-        raise error.ParseError(_("~ expects a number"))
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, subset, x):
-        for i in range(n):
-            r = cl.parentrevs(r)[0]
-        ps.add(r)
-    return [r for r in subset if r in ps]
-
-def author(repo, subset, x):
-    """``author(string)``
-    Alias for ``user(string)``.
-    """
-    # i18n: "author" is a keyword
-    n = getstring(x, _("author requires a string")).lower()
-    return [r for r in subset if n in repo[r].user().lower()]
-
-def bisected(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.
-    """
-    # i18n: "bookmark" is a keyword
-    args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
-    if args:
-        bm = getstring(args[0],
-                       # i18n: "bookmark" is a keyword
-                       _('the argument to bookmark must be a string'))
-        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]
-
-def branch(repo, subset, x):
-    """``branch(string or set)``
-    All changesets belonging to the given branch or the branches of the given
-    changesets.
-    """
-    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
-
-    s = getset(repo, range(len(repo)), x)
-    b = set()
-    for r in s:
-        b.add(repo[r].branch())
-    s = set(s)
-    return [r for r in subset if r in s or repo[r].branch() in b]
-
-def checkstatus(repo, subset, pat, field):
-    m = matchmod.match(repo.root, repo.getcwd(), [pat])
-    s = []
-    fast = (m.files() == [pat])
-    for r in subset:
-        c = repo[r]
-        if fast:
-            if pat not in c.files():
-                continue
-        else:
-            for f in c.files():
-                if m(f):
-                    break
-            else:
-                continue
-        files = repo.status(c.p1().node(), c.node())[field]
-        if fast:
-            if pat in files:
-                s.append(r)
-        else:
-            for f in files:
-                if m(f):
-                    s.append(r)
-                    break
-    return s
-
-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))
-    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):
-    """``closed()``
-    Changeset is closed.
-    """
-    # i18n: "closed" is a keyword
-    getargs(x, 0, 0, _("closed takes no arguments"))
-    return [r for r in subset if repo[r].extra().get('close')]
-
-def contains(repo, subset, x):
-    """``contains(pattern)``
-    Revision contains a file matching pattern. See :hg:`help patterns`
-    for information about file patterns.
-    """
-    # i18n: "contains" is a keyword
-    pat = getstring(x, _("contains requires a pattern"))
-    m = matchmod.match(repo.root, repo.getcwd(), [pat])
-    s = []
-    if m.files() == [pat]:
-        for r in subset:
-            if pat in repo[r]:
-                s.append(r)
-    else:
-        for r in subset:
-            for f in repo[r].manifest():
-                if m(f):
-                    s.append(r)
-                    break
-    return s
-
-def date(repo, subset, x):
-    """``date(interval)``
-    Changesets within the interval, see :hg:`help dates`.
-    """
-    # i18n: "date" is a keyword
-    ds = getstring(x, _("date requires a string"))
-    dm = util.matchdate(ds)
-    return [r for r in subset if dm(repo[r].date()[0])]
-
-def descendants(repo, subset, x):
-    """``descendants(set)``
-    Changesets which are descendants of changesets in set.
-    """
-    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.
-    """
-
-    pat = getstring(x, _("filelog requires a pattern"))
-    m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
-    s = set()
-
-    if not m.anypats():
-        for f in m.files():
-            fl = repo.file(f)
-            for fr in fl:
-                s.add(fl.linkrev(fr))
-    else:
-        for f in repo[None]:
-            if m(f):
-                fl = repo.file(f)
-                for fr in fl:
-                    s.add(fl.linkrev(fr))
-
-    return [r for r in subset if r in s]
-
-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.
-    """
-    # 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], "follow expected a filename")
-        s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
-    else:
-        s = set(repo.changelog.ancestors(p))
-
-    s |= set([p])
-    return [r for r in subset if r in s]
-
-def followfile(repo, subset, f):
-    """``follow()``
-    An alias for ``::.`` (ancestors of the working copy's first parent).
-    """
-    # 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()``
-    All changesets, the same as ``0:tip``.
-    """
-    # i18n: "all" is a keyword
-    getargs(x, 0, 0, _("all takes no arguments"))
-    return subset
-
-def grep(repo, subset, x):
-    """``grep(regex)``
-    Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
-    to ensure special escape characters are handled correctly. Unlike
-    ``keyword(string)``, the match is case-sensitive.
-    """
-    try:
-        # i18n: "grep" is a keyword
-        gr = re.compile(getstring(x, _("grep requires a string")))
-    except re.error, e:
-        raise error.ParseError(_('invalid match pattern: %s') % e)
-    l = []
-    for r in subset:
-        c = repo[r]
-        for e in c.files() + [c.user(), c.description()]:
-            if gr.search(e):
-                l.append(r)
-                break
-    return l
-
-def hasfile(repo, subset, x):
-    """``file(pattern)``
-    Changesets affecting files matched by pattern.
-    """
-    # i18n: "file" is a keyword
-    pat = getstring(x, _("file requires a pattern"))
-    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()``
-    Changeset is a named branch head.
-    """
-    # i18n: "head" is a keyword
-    getargs(x, 0, 0, _("head takes no arguments"))
-    hs = set()
-    for b, ls in repo.branchmap().iteritems():
-        hs.update(repo[h].rev() for h in ls)
-    return [r for r in subset if r in hs]
-
-def heads(repo, subset, x):
-    """``heads(set)``
-    Members of set with no children in set.
-    """
-    s = getset(repo, subset, x)
-    ps = set(parents(repo, subset, x))
-    return [r for r in s if r not in ps]
-
-def keyword(repo, subset, x):
-    """``keyword(string)``
-    Search commit message, user name, and names of changed files for
-    string. The match is case-insensitive.
-    """
-    # i18n: "keyword" is a keyword
-    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 t.lower():
-            l.append(r)
-    return l
-
-def limit(repo, subset, x):
-    """``limit(set, n)``
-    First n members of set.
-    """
-    # i18n: "limit" is a keyword
-    l = getargs(x, 2, 2, _("limit requires two arguments"))
-    try:
-        # i18n: "limit" is a keyword
-        lim = int(getstring(l[1], _("limit requires a number")))
-    except ValueError:
-        # i18n: "limit" is a keyword
-        raise error.ParseError(_("limit expects a number"))
-    ss = set(subset)
-    os = getset(repo, range(len(repo)), l[0])[:lim]
-    return [r for r in os if r in ss]
-
-def last(repo, subset, x):
-    """``last(set, n)``
-    Last n members of set.
-    """
-    # i18n: "last" is a keyword
-    l = getargs(x, 2, 2, _("last requires two arguments"))
-    try:
-        # i18n: "last" is a keyword
-        lim = int(getstring(l[1], _("last requires a number")))
-    except ValueError:
-        # i18n: "last" is a keyword
-        raise error.ParseError(_("last expects a number"))
-    ss = set(subset)
-    os = getset(repo, range(len(repo)), l[0])[-lim:]
-    return [r for r in os if r in ss]
-
-def maxrev(repo, subset, x):
-    """``max(set)``
-    Changeset with highest revision number in set.
-    """
-    os = getset(repo, range(len(repo)), x)
-    if os:
-        m = max(os)
-        if m in subset:
-            return [m]
-    return []
-
-def merge(repo, subset, x):
-    """``merge()``
-    Changeset is a merge changeset.
-    """
-    # i18n: "merge" is a keyword
-    getargs(x, 0, 0, _("merge takes no arguments"))
-    cl = repo.changelog
-    return [r for r in subset if cl.parentrevs(r)[1] != -1]
-
-def minrev(repo, subset, x):
-    """``min(set)``
-    Changeset with lowest revision number in set.
-    """
-    os = getset(repo, range(len(repo)), x)
-    if os:
-        m = min(os)
-        if m in subset:
-            return [m]
-    return []
-
-def modifies(repo, subset, x):
-    """``modifies(pattern)``
-    Changesets modifying files matched by pattern.
-    """
-    # i18n: "modifies" is a keyword
-    pat = getstring(x, _("modifies requires a pattern"))
-    return checkstatus(repo, subset, pat, 0)
-
-def node(repo, subset, x):
-    """``id(string)``
-    Revision non-ambiguously specified by the given hex string prefix.
-    """
-    # i18n: "id" is a keyword
-    l = getargs(x, 1, 1, _("id requires one argument"))
-    # i18n: "id" is a keyword
-    n = getstring(l[0], _("id requires a string"))
-    if len(n) == 40:
-        rn = repo[n].rev()
-    else:
-        rn = repo.changelog.rev(repo.changelog._partialmatch(n))
-    return [r for r in subset if r == rn]
-
-def outgoing(repo, subset, x):
-    """``outgoing([path])``
-    Changesets not found in the specified destination repository, or the
-    default push location.
-    """
-    import hg # avoid start-up nasties
-    # i18n: "outgoing" is a keyword
-    l = getargs(x, 0, 1, _("outgoing requires a repository path"))
-    # i18n: "outgoing" is a keyword
-    dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
-    dest = repo.ui.expandpath(dest or 'default-push', 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.repository(hg.remoteui(repo, {}), dest)
-    repo.ui.pushbuffer()
-    common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
-    repo.ui.popbuffer()
-    cl = repo.changelog
-    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):
-    """``p1([set])``
-    First parent of changesets in set, or the working directory.
-    """
-    if x is None:
-        p = repo[x].p1().rev()
-        return [r for r in subset if r == p]
-
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, range(len(repo)), x):
-        ps.add(cl.parentrevs(r)[0])
-    return [r for r in subset if r in ps]
-
-def p2(repo, subset, x):
-    """``p2([set])``
-    Second parent of changesets in set, or the working directory.
-    """
-    if x is None:
-        ps = repo[x].parents()
-        try:
-            p = ps[1].rev()
-            return [r for r in subset if r == p]
-        except IndexError:
-            return []
-
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, range(len(repo)), x):
-        ps.add(cl.parentrevs(r)[1])
-    return [r for r in subset if r in ps]
-
-def parents(repo, subset, x):
-    """``parents([set])``
-    The set of all parents for all changesets in set, or the working directory.
-    """
-    if x is None:
-        ps = tuple(p.rev() for p in repo[x].parents())
-        return [r for r in subset if r in ps]
-
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, range(len(repo)), x):
-        ps.update(cl.parentrevs(r))
-    return [r for r in subset if r in ps]
-
-def parentspec(repo, subset, x, n):
-    """``set^0``
-    The set.
-    ``set^1`` (or ``set^``), ``set^2``
-    First or second parent, respectively, of all changesets in set.
-    """
-    try:
-        n = int(n[1])
-        if n not in (0, 1, 2):
-            raise ValueError
-    except ValueError:
-        raise error.ParseError(_("^ expects a number 0, 1, or 2"))
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, subset, x):
-        if n == 0:
-            ps.add(r)
-        elif n == 1:
-            ps.add(cl.parentrevs(r)[0])
-        elif n == 2:
-            parents = cl.parentrevs(r)
-            if len(parents) > 1:
-                ps.add(parents[1])
-    return [r for r in subset if r in ps]
-
-def present(repo, subset, x):
-    """``present(set)``
-    An empty set, if any revision in set isn't found; otherwise,
-    all revisions in set.
-    """
-    try:
-        return getset(repo, subset, x)
-    except error.RepoLookupError:
-        return []
-
-def removes(repo, subset, x):
-    """``removes(pattern)``
-    Changesets which remove files matching pattern.
-    """
-    # i18n: "removes" is a keyword
-    pat = getstring(x, _("removes requires a pattern"))
-    return checkstatus(repo, subset, pat, 2)
-
-def rev(repo, subset, x):
-    """``rev(number)``
-    Revision with the given numeric identifier.
-    """
-    # i18n: "rev" is a keyword
-    l = getargs(x, 1, 1, _("rev requires one argument"))
-    try:
-        # i18n: "rev" is a keyword
-        l = int(getstring(l[0], _("rev requires a number")))
-    except ValueError:
-        # i18n: "rev" is a keyword
-        raise error.ParseError(_("rev expects a number"))
-    return [r for r in subset if r == l]
-
-def reverse(repo, subset, x):
-    """``reverse(set)``
-    Reverse order of set.
-    """
-    l = getset(repo, subset, x)
-    l.reverse()
-    return l
-
-def roots(repo, subset, x):
-    """``roots(set)``
-    Changesets with no parent changeset in set.
-    """
-    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...])``
-    Sort set by keys. The default sort order is ascending, specify a key
-    as ``-key`` to sort in descending order.
-
-    The keys can be:
-
-    - ``rev`` for the revision number,
-    - ``branch`` for the branch name,
-    - ``desc`` for the commit message (description),
-    - ``user`` for user name (``author`` can be used as an alias),
-    - ``date`` for the commit date
-    """
-    # i18n: "sort" is a keyword
-    l = getargs(x, 1, 2, _("sort requires one or two arguments"))
-    keys = "rev"
-    if len(l) == 2:
-        keys = getstring(l[1], _("sort spec must be a string"))
-
-    s = l[0]
-    keys = keys.split()
-    l = []
-    def invert(s):
-        return "".join(chr(255 - ord(c)) for c in s)
-    for r in getset(repo, subset, s):
-        c = repo[r]
-        e = []
-        for k in keys:
-            if k == 'rev':
-                e.append(r)
-            elif k == '-rev':
-                e.append(-r)
-            elif k == 'branch':
-                e.append(c.branch())
-            elif k == '-branch':
-                e.append(invert(c.branch()))
-            elif k == 'desc':
-                e.append(c.description())
-            elif k == '-desc':
-                e.append(invert(c.description()))
-            elif k in 'user author':
-                e.append(c.user())
-            elif k in '-user -author':
-                e.append(invert(c.user()))
-            elif k == 'date':
-                e.append(c.date()[0])
-            elif k == '-date':
-                e.append(-c.date()[0])
-            else:
-                raise error.ParseError(_("unknown sort key %r") % k)
-        e.append(r)
-        l.append(e)
-    l.sort()
-    return [e[-1] for e in l]
-
-def tag(repo, subset, x):
-    """``tag([name])``
-    The specified tag by name, or all tagged revisions if no name is given.
-    """
-    # i18n: "tag" is a keyword
-    args = getargs(x, 0, 1, _("tag takes one or no arguments"))
-    cl = repo.changelog
-    if args:
-        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]
-
-def tagged(repo, subset, x):
-    return tag(repo, subset, x)
-
-def user(repo, subset, x):
-    """``user(string)``
-    User name contains string. The match is case-insensitive.
-    """
-    return author(repo, subset, x)
-
-symbols = {
-    "adds": adds,
-    "all": getall,
-    "ancestor": ancestor,
-    "ancestors": ancestors,
-    "author": author,
-    "bisected": bisected,
-    "bookmark": bookmark,
-    "branch": branch,
-    "children": children,
-    "closed": closed,
-    "contains": contains,
-    "date": date,
-    "descendants": descendants,
-    "file": hasfile,
-    "filelog": filelog,
-    "follow": follow,
-    "grep": grep,
-    "head": head,
-    "heads": heads,
-    "keyword": keyword,
-    "last": last,
-    "limit": limit,
-    "max": maxrev,
-    "min": minrev,
-    "merge": merge,
-    "modifies": modifies,
-    "id": node,
-    "outgoing": outgoing,
-    "p1": p1,
-    "p2": p2,
-    "parents": parents,
-    "present": present,
-    "removes": removes,
-    "reverse": reverse,
-    "rev": rev,
-    "roots": roots,
-    "sort": sort,
-    "tag": tag,
-    "tagged": tagged,
-    "user": user,
-}
-
-methods = {
-    "range": rangeset,
-    "string": stringset,
-    "symbol": symbolset,
-    "and": andset,
-    "or": orset,
-    "not": notset,
-    "list": listset,
-    "func": func,
-    "ancestor": ancestorspec,
-    "parent": parentspec,
-    "parentpost": p1,
-}
-
-def optimize(x, small):
-    if x is None:
-        return 0, x
-
-    smallbonus = 1
-    if small:
-        smallbonus = .5
-
-    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':
-        return optimize(('func', ('symbol', 'descendants'), x[1]), small)
-    elif op == 'rangepre':
-        return optimize(('range', ('string', '0'), x[1]), small)
-    elif op == 'rangepost':
-        return optimize(('range', x[1], ('string', 'tip')), small)
-    elif op == 'negate':
-        return optimize(('string',
-                         '-' + getstring(x[1], _("can't negate that"))), small)
-    elif op in 'string symbol negate':
-        return smallbonus, x # single revisions are small
-    elif op == 'and' or op == 'dagrange':
-        wa, ta = optimize(x[1], True)
-        wb, tb = optimize(x[2], True)
-        w = min(wa, wb)
-        if wa > wb:
-            return w, (op, tb, ta)
-        return w, (op, ta, tb)
-    elif op == 'or':
-        wa, ta = optimize(x[1], False)
-        wb, tb = optimize(x[2], False)
-        if wb < wa:
-            wb, wa = wa, wb
-        return max(wa, wb), (op, ta, tb)
-    elif op == 'not':
-        o = optimize(x[1], not small)
-        return o[0], (op, o[1])
-    elif op == 'parentpost':
-        o = optimize(x[1], small)
-        return o[0], (op, o[1])
-    elif op == 'group':
-        return optimize(x[1], small)
-    elif op in 'range list parent ancestorspec':
-        wa, ta = optimize(x[1], small)
-        wb, tb = optimize(x[2], small)
-        return wa + wb, (op, ta, tb)
-    elif op == 'func':
-        f = getstring(x[1], _("not a symbol"))
-        wa, ta = optimize(x[2], small)
-        if f in "grep date user author keyword branch file outgoing closed":
-            w = 10 # slow
-        elif f in "modifies adds removes":
-            w = 30 # slower
-        elif f == "contains":
-            w = 100 # very slow
-        elif f == "ancestor":
-            w = 1 * smallbonus
-        elif f in "reverse limit":
-            w = 0
-        elif f in "sort":
-            w = 10 # assume most sorts look at changelog
-        else:
-            w = 1
-        return w + wa, (op, x[1], ta)
-    return 1, x
-
-class revsetalias(object):
-    funcre = re.compile('^([^(]+)\(([^)]+)\)$')
-    args = ()
-
-    def __init__(self, token, value):
-        '''Aliases like:
-
-        h = heads(default)
-        b($1) = ancestors($1) - ancestors(default)
-        '''
-        if isinstance(token, tuple):
-            self.type, self.name = token
-        else:
-            m = self.funcre.search(token)
-            if m:
-                self.type = 'func'
-                self.name = m.group(1)
-                self.args = [x.strip() for x in m.group(2).split(',')]
-            else:
-                self.type = 'symbol'
-                self.name = token
-
-        if isinstance(value, str):
-            for arg in self.args:
-                value = value.replace(arg, repr(arg))
-            self.replacement, pos = parse(value)
-            if pos != len(value):
-                raise error.ParseError('invalid token', pos)
-        else:
-            self.replacement = value
-
-    def match(self, tree):
-        if not tree:
-            return False
-        if tree == (self.type, self.name):
-            return True
-        if tree[0] != self.type:
-            return False
-        if len(tree) > 1 and tree[1] != ('symbol', self.name):
-            return False
-        # 'func' + funcname + args
-        if ((self.args and len(tree) != 3) or
-            (len(self.args) == 1 and tree[2][0] == 'list') or
-            (len(self.args) > 1 and (tree[2][0] != 'list' or
-                                     len(tree[2]) - 1 != len(self.args)))):
-            raise error.ParseError('invalid amount of arguments', len(tree) - 2)
-        return True
-
-    def replace(self, tree):
-        if tree == (self.type, self.name):
-            return self.replacement
-        result = self.replacement
-        def getsubtree(i):
-            if tree[2][0] == 'list':
-                return tree[2][i + 1]
-            return tree[i + 2]
-        for i, v in enumerate(self.args):
-            valalias = revsetalias(('string', v), getsubtree(i))
-            result = valalias.process(result)
-        return result
-
-    def process(self, tree):
-        if self.match(tree):
-            return self.replace(tree)
-        if isinstance(tree, tuple):
-            return tuple(map(self.process, tree))
-        return tree
-
-def findaliases(ui, tree):
-    for k, v in ui.configitems('revsetalias'):
-        alias = revsetalias(k, v)
-        tree = alias.process(tree)
-    return tree
-
 parse = parser.parser(tokenize, elements).parse
 
-def match(ui, spec):
-    if not spec:
-        raise error.ParseError(_("empty query"))
-    tree, pos = parse(spec)
-    if (pos != len(spec)):
-        raise error.ParseError("invalid token", pos)
-    tree = findaliases(ui, tree)
-    weight, tree = optimize(tree, True)
-    def mfunc(repo, subset):
-        return getset(repo, subset, tree)
-    return mfunc
-
-# tell hggettext to extract docstrings from these functions:
-i18nfunctions = symbols.values()