Patchwork smartset: move set classes and related functions from revset module (API)

login
register
mail settings
Submitter Yuya Nishihara
Date Feb. 5, 2017, 9:13 a.m.
Message ID <a555e941ebe982cc22e1.1486285987@mimosa>
Download mbox | patch
Permalink /patch/18332/
State Accepted
Headers show

Comments

Yuya Nishihara - Feb. 5, 2017, 9:13 a.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1476606531 -32400
#      Sun Oct 16 17:28:51 2016 +0900
# Node ID a555e941ebe982cc22e15670bfe125017b3121e5
# Parent  8d7e40524ae467b3201e264e3548681c52bb6492
smartset: move set classes and related functions from revset module (API)

These classes are pretty large and independent from revset computation.

  2961 mercurial/revset.py
   973 mercurial/smartset.py
  3934 total

revset.prettyformatset() is renamed to smartset.prettyformat(). Smartset
classes are aliased since they are quite common in revset.py.
Kostia Balytskyi - Feb. 6, 2017, 11 a.m.
Yay for refactoring! I love how you first copied revset.py into 
smartset.py and then cleaned it up. This looks good to me.

On 05/02/2017 09:13, Yuya Nishihara wrote:
> # HG changeset patch

> # User Yuya Nishihara <yuya@tcha.org>

> # Date 1476606531 -32400

> #      Sun Oct 16 17:28:51 2016 +0900

> # Node ID a555e941ebe982cc22e15670bfe125017b3121e5

> # Parent  8d7e40524ae467b3201e264e3548681c52bb6492

> smartset: move set classes and related functions from revset module (API)

>

> These classes are pretty large and independent from revset computation.

>

>    2961 mercurial/revset.py

>     973 mercurial/smartset.py

>    3934 total

>

> revset.prettyformatset() is renamed to smartset.prettyformat(). Smartset

> classes are aliased since they are quite common in revset.py.

>

> diff --git a/mercurial/commands.py b/mercurial/commands.py

> --- a/mercurial/commands.py

> +++ b/mercurial/commands.py

> @@ -59,6 +59,7 @@ from . import (

>       revset,

>       scmutil,

>       server,

> +    smartset,

>       sshserver,

>       sslutil,

>       streamclone,

> @@ -2804,8 +2805,8 @@ def debugrevspec(ui, repo, expr, **opts)

>           arevs = revset.makematcher(treebystage['analyzed'])(repo)

>           brevs = revset.makematcher(treebystage['optimized'])(repo)

>           if ui.verbose:

> -            ui.note(("* analyzed set:\n"), revset.prettyformatset(arevs), "\n")

> -            ui.note(("* optimized set:\n"), revset.prettyformatset(brevs), "\n")

> +            ui.note(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")

> +            ui.note(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")

>           arevs = list(arevs)

>           brevs = list(brevs)

>           if arevs == brevs:

> @@ -2828,7 +2829,7 @@ def debugrevspec(ui, repo, expr, **opts)

>       func = revset.makematcher(tree)

>       revs = func(repo)

>       if ui.verbose:

> -        ui.note(("* set:\n"), revset.prettyformatset(revs), "\n")

> +        ui.note(("* set:\n"), smartset.prettyformat(revs), "\n")

>       for c in revs:

>           ui.write("%s\n" % c)

>   

> diff --git a/mercurial/revset.py b/mercurial/revset.py

> --- a/mercurial/revset.py

> +++ b/mercurial/revset.py

> @@ -26,9 +26,15 @@ from . import (

>       pycompat,

>       registrar,

>       repoview,

> +    smartset,

>       util,

>   )

>   

> +baseset = smartset.baseset

> +generatorset = smartset.generatorset

> +spanset = smartset.spanset

> +fullreposet = smartset.fullreposet

> +

>   def _revancestors(repo, revs, followfirst):

>       """Like revlog.ancestors(), but supports followfirst."""

>       if followfirst:

> @@ -2940,967 +2946,6 @@ def funcsused(tree):

>               funcs.add(tree[1][1])

>           return funcs

>   

> -def _formatsetrepr(r):

> -    """Format an optional printable representation of a set

> -

> -    ========  =================================

> -    type(r)   example

> -    ========  =================================

> -    tuple     ('<not %r>', other)

> -    str       '<branch closed>'

> -    callable  lambda: '<branch %r>' % sorted(b)

> -    object    other

> -    ========  =================================

> -    """

> -    if r is None:

> -        return ''

> -    elif isinstance(r, tuple):

> -        return r[0] % r[1:]

> -    elif isinstance(r, str):

> -        return r

> -    elif callable(r):

> -        return r()

> -    else:

> -        return repr(r)

> -

> -class abstractsmartset(object):

> -

> -    def __nonzero__(self):

> -        """True if the smartset is not empty"""

> -        raise NotImplementedError()

> -

> -    def __contains__(self, rev):

> -        """provide fast membership testing"""

> -        raise NotImplementedError()

> -

> -    def __iter__(self):

> -        """iterate the set in the order it is supposed to be iterated"""

> -        raise NotImplementedError()

> -

> -    # Attributes containing a function to perform a fast iteration in a given

> -    # direction. A smartset can have none, one, or both defined.

> -    #

> -    # Default value is None instead of a function returning None to avoid

> -    # initializing an iterator just for testing if a fast method exists.

> -    fastasc = None

> -    fastdesc = None

> -

> -    def isascending(self):

> -        """True if the set will iterate in ascending order"""

> -        raise NotImplementedError()

> -

> -    def isdescending(self):

> -        """True if the set will iterate in descending order"""

> -        raise NotImplementedError()

> -

> -    def istopo(self):

> -        """True if the set will iterate in topographical order"""

> -        raise NotImplementedError()

> -

> -    def min(self):

> -        """return the minimum element in the set"""

> -        if self.fastasc is None:

> -            v = min(self)

> -        else:

> -            for v in self.fastasc():

> -                break

> -            else:

> -                raise ValueError('arg is an empty sequence')

> -        self.min = lambda: v

> -        return v

> -

> -    def max(self):

> -        """return the maximum element in the set"""

> -        if self.fastdesc is None:

> -            return max(self)

> -        else:

> -            for v in self.fastdesc():

> -                break

> -            else:

> -                raise ValueError('arg is an empty sequence')

> -        self.max = lambda: v

> -        return v

> -

> -    def first(self):

> -        """return the first element in the set (user iteration perspective)

> -

> -        Return None if the set is empty"""

> -        raise NotImplementedError()

> -

> -    def last(self):

> -        """return the last element in the set (user iteration perspective)

> -

> -        Return None if the set is empty"""

> -        raise NotImplementedError()

> -

> -    def __len__(self):

> -        """return the length of the smartsets

> -

> -        This can be expensive on smartset that could be lazy otherwise."""

> -        raise NotImplementedError()

> -

> -    def reverse(self):

> -        """reverse the expected iteration order"""

> -        raise NotImplementedError()

> -

> -    def sort(self, reverse=True):

> -        """get the set to iterate in an ascending or descending order"""

> -        raise NotImplementedError()

> -

> -    def __and__(self, other):

> -        """Returns a new object with the intersection of the two collections.

> -

> -        This is part of the mandatory API for smartset."""

> -        if isinstance(other, fullreposet):

> -            return self

> -        return self.filter(other.__contains__, condrepr=other, cache=False)

> -

> -    def __add__(self, other):

> -        """Returns a new object with the union of the two collections.

> -

> -        This is part of the mandatory API for smartset."""

> -        return addset(self, other)

> -

> -    def __sub__(self, other):

> -        """Returns a new object with the substraction of the two collections.

> -

> -        This is part of the mandatory API for smartset."""

> -        c = other.__contains__

> -        return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),

> -                           cache=False)

> -

> -    def filter(self, condition, condrepr=None, cache=True):

> -        """Returns this smartset filtered by condition as a new smartset.

> -

> -        `condition` is a callable which takes a revision number and returns a

> -        boolean. Optional `condrepr` provides a printable representation of

> -        the given `condition`.

> -

> -        This is part of the mandatory API for smartset."""

> -        # builtin cannot be cached. but do not needs to

> -        if cache and util.safehasattr(condition, 'func_code'):

> -            condition = util.cachefunc(condition)

> -        return filteredset(self, condition, condrepr)

> -

> -class baseset(abstractsmartset):

> -    """Basic data structure that represents a revset and contains the basic

> -    operation that it should be able to perform.

> -

> -    Every method in this class should be implemented by any smartset class.

> -    """

> -    def __init__(self, data=(), datarepr=None, istopo=False):

> -        """

> -        datarepr: a tuple of (format, obj, ...), a function or an object that

> -                  provides a printable representation of the given data.

> -        """

> -        self._ascending = None

> -        self._istopo = istopo

> -        if not isinstance(data, list):

> -            if isinstance(data, set):

> -                self._set = data

> -                # set has no order we pick one for stability purpose

> -                self._ascending = True

> -            data = list(data)

> -        self._list = data

> -        self._datarepr = datarepr

> -

> -    @util.propertycache

> -    def _set(self):

> -        return set(self._list)

> -

> -    @util.propertycache

> -    def _asclist(self):

> -        asclist = self._list[:]

> -        asclist.sort()

> -        return asclist

> -

> -    def __iter__(self):

> -        if self._ascending is None:

> -            return iter(self._list)

> -        elif self._ascending:

> -            return iter(self._asclist)

> -        else:

> -            return reversed(self._asclist)

> -

> -    def fastasc(self):

> -        return iter(self._asclist)

> -

> -    def fastdesc(self):

> -        return reversed(self._asclist)

> -

> -    @util.propertycache

> -    def __contains__(self):

> -        return self._set.__contains__

> -

> -    def __nonzero__(self):

> -        return bool(self._list)

> -

> -    def sort(self, reverse=False):

> -        self._ascending = not bool(reverse)

> -        self._istopo = False

> -

> -    def reverse(self):

> -        if self._ascending is None:

> -            self._list.reverse()

> -        else:

> -            self._ascending = not self._ascending

> -        self._istopo = False

> -

> -    def __len__(self):

> -        return len(self._list)

> -

> -    def isascending(self):

> -        """Returns True if the collection is ascending order, False if not.

> -

> -        This is part of the mandatory API for smartset."""

> -        if len(self) <= 1:

> -            return True

> -        return self._ascending is not None and self._ascending

> -

> -    def isdescending(self):

> -        """Returns True if the collection is descending order, False if not.

> -

> -        This is part of the mandatory API for smartset."""

> -        if len(self) <= 1:

> -            return True

> -        return self._ascending is not None and not self._ascending

> -

> -    def istopo(self):

> -        """Is the collection is in topographical order or not.

> -

> -        This is part of the mandatory API for smartset."""

> -        if len(self) <= 1:

> -            return True

> -        return self._istopo

> -

> -    def first(self):

> -        if self:

> -            if self._ascending is None:

> -                return self._list[0]

> -            elif self._ascending:

> -                return self._asclist[0]

> -            else:

> -                return self._asclist[-1]

> -        return None

> -

> -    def last(self):

> -        if self:

> -            if self._ascending is None:

> -                return self._list[-1]

> -            elif self._ascending:

> -                return self._asclist[-1]

> -            else:

> -                return self._asclist[0]

> -        return None

> -

> -    def __repr__(self):

> -        d = {None: '', False: '-', True: '+'}[self._ascending]

> -        s = _formatsetrepr(self._datarepr)

> -        if not s:

> -            l = self._list

> -            # if _list has been built from a set, it might have a different

> -            # order from one python implementation to another.

> -            # We fallback to the sorted version for a stable output.

> -            if self._ascending is not None:

> -                l = self._asclist

> -            s = repr(l)

> -        return '<%s%s %s>' % (type(self).__name__, d, s)

> -

> -class filteredset(abstractsmartset):

> -    """Duck type for baseset class which iterates lazily over the revisions in

> -    the subset and contains a function which tests for membership in the

> -    revset

> -    """

> -    def __init__(self, subset, condition=lambda x: True, condrepr=None):

> -        """

> -        condition: a function that decide whether a revision in the subset

> -                   belongs to the revset or not.

> -        condrepr: a tuple of (format, obj, ...), a function or an object that

> -                  provides a printable representation of the given condition.

> -        """

> -        self._subset = subset

> -        self._condition = condition

> -        self._condrepr = condrepr

> -

> -    def __contains__(self, x):

> -        return x in self._subset and self._condition(x)

> -

> -    def __iter__(self):

> -        return self._iterfilter(self._subset)

> -

> -    def _iterfilter(self, it):

> -        cond = self._condition

> -        for x in it:

> -            if cond(x):

> -                yield x

> -

> -    @property

> -    def fastasc(self):

> -        it = self._subset.fastasc

> -        if it is None:

> -            return None

> -        return lambda: self._iterfilter(it())

> -

> -    @property

> -    def fastdesc(self):

> -        it = self._subset.fastdesc

> -        if it is None:

> -            return None

> -        return lambda: self._iterfilter(it())

> -

> -    def __nonzero__(self):

> -        fast = None

> -        candidates = [self.fastasc if self.isascending() else None,

> -                      self.fastdesc if self.isdescending() else None,

> -                      self.fastasc,

> -                      self.fastdesc]

> -        for candidate in candidates:

> -            if candidate is not None:

> -                fast = candidate

> -                break

> -

> -        if fast is not None:

> -            it = fast()

> -        else:

> -            it = self

> -

> -        for r in it:

> -            return True

> -        return False

> -

> -    def __len__(self):

> -        # Basic implementation to be changed in future patches.

> -        # until this gets improved, we use generator expression

> -        # here, since list comprehensions are free to call __len__ again

> -        # causing infinite recursion

> -        l = baseset(r for r in self)

> -        return len(l)

> -

> -    def sort(self, reverse=False):

> -        self._subset.sort(reverse=reverse)

> -

> -    def reverse(self):

> -        self._subset.reverse()

> -

> -    def isascending(self):

> -        return self._subset.isascending()

> -

> -    def isdescending(self):

> -        return self._subset.isdescending()

> -

> -    def istopo(self):

> -        return self._subset.istopo()

> -

> -    def first(self):

> -        for x in self:

> -            return x

> -        return None

> -

> -    def last(self):

> -        it = None

> -        if self.isascending():

> -            it = self.fastdesc

> -        elif self.isdescending():

> -            it = self.fastasc

> -        if it is not None:

> -            for x in it():

> -                return x

> -            return None #empty case

> -        else:

> -            x = None

> -            for x in self:

> -                pass

> -            return x

> -

> -    def __repr__(self):

> -        xs = [repr(self._subset)]

> -        s = _formatsetrepr(self._condrepr)

> -        if s:

> -            xs.append(s)

> -        return '<%s %s>' % (type(self).__name__, ', '.join(xs))

> -

> -def _iterordered(ascending, iter1, iter2):

> -    """produce an ordered iteration from two iterators with the same order

> -

> -    The ascending is used to indicated the iteration direction.

> -    """

> -    choice = max

> -    if ascending:

> -        choice = min

> -

> -    val1 = None

> -    val2 = None

> -    try:

> -        # Consume both iterators in an ordered way until one is empty

> -        while True:

> -            if val1 is None:

> -                val1 = next(iter1)

> -            if val2 is None:

> -                val2 = next(iter2)

> -            n = choice(val1, val2)

> -            yield n

> -            if val1 == n:

> -                val1 = None

> -            if val2 == n:

> -                val2 = None

> -    except StopIteration:

> -        # Flush any remaining values and consume the other one

> -        it = iter2

> -        if val1 is not None:

> -            yield val1

> -            it = iter1

> -        elif val2 is not None:

> -            # might have been equality and both are empty

> -            yield val2

> -        for val in it:

> -            yield val

> -

> -class addset(abstractsmartset):

> -    """Represent the addition of two sets

> -

> -    Wrapper structure for lazily adding two structures without losing much

> -    performance on the __contains__ method

> -

> -    If the ascending attribute is set, that means the two structures are

> -    ordered in either an ascending or descending way. Therefore, we can add

> -    them maintaining the order by iterating over both at the same time

> -

> -    >>> xs = baseset([0, 3, 2])

> -    >>> ys = baseset([5, 2, 4])

> -

> -    >>> rs = addset(xs, ys)

> -    >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()

> -    (True, True, False, True, 0, 4)

> -    >>> rs = addset(xs, baseset([]))

> -    >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()

> -    (True, True, False, 0, 2)

> -    >>> rs = addset(baseset([]), baseset([]))

> -    >>> bool(rs), 0 in rs, rs.first(), rs.last()

> -    (False, False, None, None)

> -

> -    iterate unsorted:

> -    >>> rs = addset(xs, ys)

> -    >>> # (use generator because pypy could call len())

> -    >>> list(x for x in rs)  # without _genlist

> -    [0, 3, 2, 5, 4]

> -    >>> assert not rs._genlist

> -    >>> len(rs)

> -    5

> -    >>> [x for x in rs]  # with _genlist

> -    [0, 3, 2, 5, 4]

> -    >>> assert rs._genlist

> -

> -    iterate ascending:

> -    >>> rs = addset(xs, ys, ascending=True)

> -    >>> # (use generator because pypy could call len())

> -    >>> list(x for x in rs), list(x for x in rs.fastasc())  # without _asclist

> -    ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])

> -    >>> assert not rs._asclist

> -    >>> len(rs)

> -    5

> -    >>> [x for x in rs], [x for x in rs.fastasc()]

> -    ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])

> -    >>> assert rs._asclist

> -

> -    iterate descending:

> -    >>> rs = addset(xs, ys, ascending=False)

> -    >>> # (use generator because pypy could call len())

> -    >>> list(x for x in rs), list(x for x in rs.fastdesc())  # without _asclist

> -    ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])

> -    >>> assert not rs._asclist

> -    >>> len(rs)

> -    5

> -    >>> [x for x in rs], [x for x in rs.fastdesc()]

> -    ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])

> -    >>> assert rs._asclist

> -

> -    iterate ascending without fastasc:

> -    >>> rs = addset(xs, generatorset(ys), ascending=True)

> -    >>> assert rs.fastasc is None

> -    >>> [x for x in rs]

> -    [0, 2, 3, 4, 5]

> -

> -    iterate descending without fastdesc:

> -    >>> rs = addset(generatorset(xs), ys, ascending=False)

> -    >>> assert rs.fastdesc is None

> -    >>> [x for x in rs]

> -    [5, 4, 3, 2, 0]

> -    """

> -    def __init__(self, revs1, revs2, ascending=None):

> -        self._r1 = revs1

> -        self._r2 = revs2

> -        self._iter = None

> -        self._ascending = ascending

> -        self._genlist = None

> -        self._asclist = None

> -

> -    def __len__(self):

> -        return len(self._list)

> -

> -    def __nonzero__(self):

> -        return bool(self._r1) or bool(self._r2)

> -

> -    @util.propertycache

> -    def _list(self):

> -        if not self._genlist:

> -            self._genlist = baseset(iter(self))

> -        return self._genlist

> -

> -    def __iter__(self):

> -        """Iterate over both collections without repeating elements

> -

> -        If the ascending attribute is not set, iterate over the first one and

> -        then over the second one checking for membership on the first one so we

> -        dont yield any duplicates.

> -

> -        If the ascending attribute is set, iterate over both collections at the

> -        same time, yielding only one value at a time in the given order.

> -        """

> -        if self._ascending is None:

> -            if self._genlist:

> -                return iter(self._genlist)

> -            def arbitraryordergen():

> -                for r in self._r1:

> -                    yield r

> -                inr1 = self._r1.__contains__

> -                for r in self._r2:

> -                    if not inr1(r):

> -                        yield r

> -            return arbitraryordergen()

> -        # try to use our own fast iterator if it exists

> -        self._trysetasclist()

> -        if self._ascending:

> -            attr = 'fastasc'

> -        else:

> -            attr = 'fastdesc'

> -        it = getattr(self, attr)

> -        if it is not None:

> -            return it()

> -        # maybe half of the component supports fast

> -        # get iterator for _r1

> -        iter1 = getattr(self._r1, attr)

> -        if iter1 is None:

> -            # let's avoid side effect (not sure it matters)

> -            iter1 = iter(sorted(self._r1, reverse=not self._ascending))

> -        else:

> -            iter1 = iter1()

> -        # get iterator for _r2

> -        iter2 = getattr(self._r2, attr)

> -        if iter2 is None:

> -            # let's avoid side effect (not sure it matters)

> -            iter2 = iter(sorted(self._r2, reverse=not self._ascending))

> -        else:

> -            iter2 = iter2()

> -        return _iterordered(self._ascending, iter1, iter2)

> -

> -    def _trysetasclist(self):

> -        """populate the _asclist attribute if possible and necessary"""

> -        if self._genlist is not None and self._asclist is None:

> -            self._asclist = sorted(self._genlist)

> -

> -    @property

> -    def fastasc(self):

> -        self._trysetasclist()

> -        if self._asclist is not None:

> -            return self._asclist.__iter__

> -        iter1 = self._r1.fastasc

> -        iter2 = self._r2.fastasc

> -        if None in (iter1, iter2):

> -            return None

> -        return lambda: _iterordered(True, iter1(), iter2())

> -

> -    @property

> -    def fastdesc(self):

> -        self._trysetasclist()

> -        if self._asclist is not None:

> -            return self._asclist.__reversed__

> -        iter1 = self._r1.fastdesc

> -        iter2 = self._r2.fastdesc

> -        if None in (iter1, iter2):

> -            return None

> -        return lambda: _iterordered(False, iter1(), iter2())

> -

> -    def __contains__(self, x):

> -        return x in self._r1 or x in self._r2

> -

> -    def sort(self, reverse=False):

> -        """Sort the added set

> -

> -        For this we use the cached list with all the generated values and if we

> -        know they are ascending or descending we can sort them in a smart way.

> -        """

> -        self._ascending = not reverse

> -

> -    def isascending(self):

> -        return self._ascending is not None and self._ascending

> -

> -    def isdescending(self):

> -        return self._ascending is not None and not self._ascending

> -

> -    def istopo(self):

> -        # not worth the trouble asserting if the two sets combined are still

> -        # in topographical order. Use the sort() predicate to explicitly sort

> -        # again instead.

> -        return False

> -

> -    def reverse(self):

> -        if self._ascending is None:

> -            self._list.reverse()

> -        else:

> -            self._ascending = not self._ascending

> -

> -    def first(self):

> -        for x in self:

> -            return x

> -        return None

> -

> -    def last(self):

> -        self.reverse()

> -        val = self.first()

> -        self.reverse()

> -        return val

> -

> -    def __repr__(self):

> -        d = {None: '', False: '-', True: '+'}[self._ascending]

> -        return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)

> -

> -class generatorset(abstractsmartset):

> -    """Wrap a generator for lazy iteration

> -

> -    Wrapper structure for generators that provides lazy membership and can

> -    be iterated more than once.

> -    When asked for membership it generates values until either it finds the

> -    requested one or has gone through all the elements in the generator

> -    """

> -    def __init__(self, gen, iterasc=None):

> -        """

> -        gen: a generator producing the values for the generatorset.

> -        """

> -        self._gen = gen

> -        self._asclist = None

> -        self._cache = {}

> -        self._genlist = []

> -        self._finished = False

> -        self._ascending = True

> -        if iterasc is not None:

> -            if iterasc:

> -                self.fastasc = self._iterator

> -                self.__contains__ = self._asccontains

> -            else:

> -                self.fastdesc = self._iterator

> -                self.__contains__ = self._desccontains

> -

> -    def __nonzero__(self):

> -        # Do not use 'for r in self' because it will enforce the iteration

> -        # order (default ascending), possibly unrolling a whole descending

> -        # iterator.

> -        if self._genlist:

> -            return True

> -        for r in self._consumegen():

> -            return True

> -        return False

> -

> -    def __contains__(self, x):

> -        if x in self._cache:

> -            return self._cache[x]

> -

> -        # Use new values only, as existing values would be cached.

> -        for l in self._consumegen():

> -            if l == x:

> -                return True

> -

> -        self._cache[x] = False

> -        return False

> -

> -    def _asccontains(self, x):

> -        """version of contains optimised for ascending generator"""

> -        if x in self._cache:

> -            return self._cache[x]

> -

> -        # Use new values only, as existing values would be cached.

> -        for l in self._consumegen():

> -            if l == x:

> -                return True

> -            if l > x:

> -                break

> -

> -        self._cache[x] = False

> -        return False

> -

> -    def _desccontains(self, x):

> -        """version of contains optimised for descending generator"""

> -        if x in self._cache:

> -            return self._cache[x]

> -

> -        # Use new values only, as existing values would be cached.

> -        for l in self._consumegen():

> -            if l == x:

> -                return True

> -            if l < x:

> -                break

> -

> -        self._cache[x] = False

> -        return False

> -

> -    def __iter__(self):

> -        if self._ascending:

> -            it = self.fastasc

> -        else:

> -            it = self.fastdesc

> -        if it is not None:

> -            return it()

> -        # we need to consume the iterator

> -        for x in self._consumegen():

> -            pass

> -        # recall the same code

> -        return iter(self)

> -

> -    def _iterator(self):

> -        if self._finished:

> -            return iter(self._genlist)

> -

> -        # We have to use this complex iteration strategy to allow multiple

> -        # iterations at the same time. We need to be able to catch revision

> -        # removed from _consumegen and added to genlist in another instance.

> -        #

> -        # Getting rid of it would provide an about 15% speed up on this

> -        # iteration.

> -        genlist = self._genlist

> -        nextrev = self._consumegen().next

> -        _len = len # cache global lookup

> -        def gen():

> -            i = 0

> -            while True:

> -                if i < _len(genlist):

> -                    yield genlist[i]

> -                else:

> -                    yield nextrev()

> -                i += 1

> -        return gen()

> -

> -    def _consumegen(self):

> -        cache = self._cache

> -        genlist = self._genlist.append

> -        for item in self._gen:

> -            cache[item] = True

> -            genlist(item)

> -            yield item

> -        if not self._finished:

> -            self._finished = True

> -            asc = self._genlist[:]

> -            asc.sort()

> -            self._asclist = asc

> -            self.fastasc = asc.__iter__

> -            self.fastdesc = asc.__reversed__

> -

> -    def __len__(self):

> -        for x in self._consumegen():

> -            pass

> -        return len(self._genlist)

> -

> -    def sort(self, reverse=False):

> -        self._ascending = not reverse

> -

> -    def reverse(self):

> -        self._ascending = not self._ascending

> -

> -    def isascending(self):

> -        return self._ascending

> -

> -    def isdescending(self):

> -        return not self._ascending

> -

> -    def istopo(self):

> -        # not worth the trouble asserting if the two sets combined are still

> -        # in topographical order. Use the sort() predicate to explicitly sort

> -        # again instead.

> -        return False

> -

> -    def first(self):

> -        if self._ascending:

> -            it = self.fastasc

> -        else:

> -            it = self.fastdesc

> -        if it is None:

> -            # we need to consume all and try again

> -            for x in self._consumegen():

> -                pass

> -            return self.first()

> -        return next(it(), None)

> -

> -    def last(self):

> -        if self._ascending:

> -            it = self.fastdesc

> -        else:

> -            it = self.fastasc

> -        if it is None:

> -            # we need to consume all and try again

> -            for x in self._consumegen():

> -                pass

> -            return self.first()

> -        return next(it(), None)

> -

> -    def __repr__(self):

> -        d = {False: '-', True: '+'}[self._ascending]

> -        return '<%s%s>' % (type(self).__name__, d)

> -

> -class spanset(abstractsmartset):

> -    """Duck type for baseset class which represents a range of revisions and

> -    can work lazily and without having all the range in memory

> -

> -    Note that spanset(x, y) behave almost like xrange(x, y) except for two

> -    notable points:

> -    - when x < y it will be automatically descending,

> -    - revision filtered with this repoview will be skipped.

> -

> -    """

> -    def __init__(self, repo, start=0, end=None):

> -        """

> -        start: first revision included the set

> -               (default to 0)

> -        end:   first revision excluded (last+1)

> -               (default to len(repo)

> -

> -        Spanset will be descending if `end` < `start`.

> -        """

> -        if end is None:

> -            end = len(repo)

> -        self._ascending = start <= end

> -        if not self._ascending:

> -            start, end = end + 1, start +1

> -        self._start = start

> -        self._end = end

> -        self._hiddenrevs = repo.changelog.filteredrevs

> -

> -    def sort(self, reverse=False):

> -        self._ascending = not reverse

> -

> -    def reverse(self):

> -        self._ascending = not self._ascending

> -

> -    def istopo(self):

> -        # not worth the trouble asserting if the two sets combined are still

> -        # in topographical order. Use the sort() predicate to explicitly sort

> -        # again instead.

> -        return False

> -

> -    def _iterfilter(self, iterrange):

> -        s = self._hiddenrevs

> -        for r in iterrange:

> -            if r not in s:

> -                yield r

> -

> -    def __iter__(self):

> -        if self._ascending:

> -            return self.fastasc()

> -        else:

> -            return self.fastdesc()

> -

> -    def fastasc(self):

> -        iterrange = xrange(self._start, self._end)

> -        if self._hiddenrevs:

> -            return self._iterfilter(iterrange)

> -        return iter(iterrange)

> -

> -    def fastdesc(self):

> -        iterrange = xrange(self._end - 1, self._start - 1, -1)

> -        if self._hiddenrevs:

> -            return self._iterfilter(iterrange)

> -        return iter(iterrange)

> -

> -    def __contains__(self, rev):

> -        hidden = self._hiddenrevs

> -        return ((self._start <= rev < self._end)

> -                and not (hidden and rev in hidden))

> -

> -    def __nonzero__(self):

> -        for r in self:

> -            return True

> -        return False

> -

> -    def __len__(self):

> -        if not self._hiddenrevs:

> -            return abs(self._end - self._start)

> -        else:

> -            count = 0

> -            start = self._start

> -            end = self._end

> -            for rev in self._hiddenrevs:

> -                if (end < rev <= start) or (start <= rev < end):

> -                    count += 1

> -            return abs(self._end - self._start) - count

> -

> -    def isascending(self):

> -        return self._ascending

> -

> -    def isdescending(self):

> -        return not self._ascending

> -

> -    def first(self):

> -        if self._ascending:

> -            it = self.fastasc

> -        else:

> -            it = self.fastdesc

> -        for x in it():

> -            return x

> -        return None

> -

> -    def last(self):

> -        if self._ascending:

> -            it = self.fastdesc

> -        else:

> -            it = self.fastasc

> -        for x in it():

> -            return x

> -        return None

> -

> -    def __repr__(self):

> -        d = {False: '-', True: '+'}[self._ascending]

> -        return '<%s%s %d:%d>' % (type(self).__name__, d,

> -                                 self._start, self._end - 1)

> -

> -class fullreposet(spanset):

> -    """a set containing all revisions in the repo

> -

> -    This class exists to host special optimization and magic to handle virtual

> -    revisions such as "null".

> -    """

> -

> -    def __init__(self, repo):

> -        super(fullreposet, self).__init__(repo)

> -

> -    def __and__(self, other):

> -        """As self contains the whole repo, all of the other set should also be

> -        in self. Therefore `self & other = other`.

> -

> -        This boldly assumes the other contains valid revs only.

> -        """

> -        # other not a smartset, make is so

> -        if not util.safehasattr(other, 'isascending'):

> -            # filter out hidden revision

> -            # (this boldly assumes all smartset are pure)

> -            #

> -            # `other` was used with "&", let's assume this is a set like

> -            # object.

> -            other = baseset(other - self._hiddenrevs)

> -

> -        other.sort(reverse=self.isdescending())

> -        return other

> -

> -def prettyformatset(revs):

> -    lines = []

> -    rs = repr(revs)

> -    p = 0

> -    while p < len(rs):

> -        q = rs.find('<', p + 1)

> -        if q < 0:

> -            q = len(rs)

> -        l = rs.count('<', 0, p) - rs.count('>', 0, p)

> -        assert l >= 0

> -        lines.append((l, rs[p:q].rstrip()))

> -        p = q

> -    return '\n'.join('  ' * l + s for l, s in lines)

> -

>   def loadpredicate(ui, extname, registrarobj):

>       """Load revset predicates from specified registrarobj

>       """

> diff --git a/mercurial/revset.py b/mercurial/smartset.py

> copy from mercurial/revset.py

> copy to mercurial/smartset.py

> --- a/mercurial/revset.py

> +++ b/mercurial/smartset.py

> @@ -1,4 +1,4 @@

> -# revset.py - revision set queries for mercurial

> +# smartset.py - data structure for revision set

>   #

>   # Copyright 2010 Matt Mackall <mpm@selenic.com>

>   #

> @@ -7,2939 +7,10 @@

>   

>   from __future__ import absolute_import

>   

> -import heapq

> -import re

> -import string

> -

> -from .i18n import _

>   from . import (

> -    destutil,

> -    encoding,

> -    error,

> -    hbisect,

> -    match as matchmod,

> -    node,

> -    obsolete as obsmod,

> -    parser,

> -    pathutil,

> -    phases,

> -    pycompat,

> -    registrar,

> -    repoview,

>       util,

>   )

>   

> -def _revancestors(repo, revs, followfirst):

> -    """Like revlog.ancestors(), but supports followfirst."""

> -    if followfirst:

> -        cut = 1

> -    else:

> -        cut = None

> -    cl = repo.changelog

> -

> -    def iterate():

> -        revs.sort(reverse=True)

> -        irevs = iter(revs)

> -        h = []

> -

> -        inputrev = next(irevs, None)

> -        if inputrev is not None:

> -            heapq.heappush(h, -inputrev)

> -

> -        seen = set()

> -        while h:

> -            current = -heapq.heappop(h)

> -            if current == inputrev:

> -                inputrev = next(irevs, None)

> -                if inputrev is not None:

> -                    heapq.heappush(h, -inputrev)

> -            if current not in seen:

> -                seen.add(current)

> -                yield current

> -                for parent in cl.parentrevs(current)[:cut]:

> -                    if parent != node.nullrev:

> -                        heapq.heappush(h, -parent)

> -

> -    return generatorset(iterate(), iterasc=False)

> -

> -def _revdescendants(repo, revs, followfirst):

> -    """Like revlog.descendants() but supports followfirst."""

> -    if followfirst:

> -        cut = 1

> -    else:

> -        cut = None

> -

> -    def iterate():

> -        cl = repo.changelog

> -        # XXX this should be 'parentset.min()' assuming 'parentset' is a

> -        # smartset (and if it is not, it should.)

> -        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

> -        else:

> -            seen = set(revs)

> -            for i in cl.revs(first + 1):

> -                for x in cl.parentrevs(i)[:cut]:

> -                    if x != nullrev and x in seen:

> -                        seen.add(i)

> -                        yield i

> -                        break

> -

> -    return generatorset(iterate(), iterasc=True)

> -

> -def _reachablerootspure(repo, minroot, roots, heads, includepath):

> -    """return (heads(::<roots> and ::<heads>))

> -

> -    If includepath is True, return (<roots>::<heads>)."""

> -    if not roots:

> -        return []

> -    parentrevs = repo.changelog.parentrevs

> -    roots = set(roots)

> -    visit = list(heads)

> -    reachable = set()

> -    seen = {}

> -    # prefetch all the things! (because python is slow)

> -    reached = reachable.add

> -    dovisit = visit.append

> -    nextvisit = visit.pop

> -    # open-code the post-order traversal due to the tiny size of

> -    # sys.getrecursionlimit()

> -    while visit:

> -        rev = nextvisit()

> -        if rev in roots:

> -            reached(rev)

> -            if not includepath:

> -                continue

> -        parents = parentrevs(rev)

> -        seen[rev] = parents

> -        for parent in parents:

> -            if parent >= minroot and parent not in seen:

> -                dovisit(parent)

> -    if not reachable:

> -        return baseset()

> -    if not includepath:

> -        return reachable

> -    for rev in sorted(seen):

> -        for parent in seen[rev]:

> -            if parent in reachable:

> -                reached(rev)

> -    return reachable

> -

> -def reachableroots(repo, roots, heads, includepath=False):

> -    """return (heads(::<roots> and ::<heads>))

> -

> -    If includepath is True, return (<roots>::<heads>)."""

> -    if not roots:

> -        return baseset()

> -    minroot = roots.min()

> -    roots = list(roots)

> -    heads = list(heads)

> -    try:

> -        revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)

> -    except AttributeError:

> -        revs = _reachablerootspure(repo, minroot, roots, heads, includepath)

> -    revs = baseset(revs)

> -    revs.sort()

> -    return revs

> -

> -elements = {

> -    # token-type: binding-strength, primary, prefix, infix, suffix

> -    "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),

> -    "##": (20, None, None, ("_concat", 20), None),

> -    "~": (18, None, None, ("ancestor", 18), None),

> -    "^": (18, None, None, ("parent", 18), "parentpost"),

> -    "-": (5, None, ("negate", 19), ("minus", 5), None),

> -    "::": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),

> -    "..": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),

> -    ":": (15, "rangeall", ("rangepre", 15), ("range", 15), "rangepost"),

> -    "not": (10, None, ("not", 10), None, None),

> -    "!": (10, None, ("not", 10), None, None),

> -    "and": (5, None, None, ("and", 5), None),

> -    "&": (5, None, None, ("and", 5), None),

> -    "%": (5, None, None, ("only", 5), "onlypost"),

> -    "or": (4, None, None, ("or", 4), None),

> -    "|": (4, None, None, ("or", 4), None),

> -    "+": (4, None, None, ("or", 4), None),

> -    "=": (3, None, None, ("keyvalue", 3), None),

> -    ",": (2, None, None, ("list", 2), None),

> -    ")": (0, None, None, None, None),

> -    "symbol": (0, "symbol", None, None, None),

> -    "string": (0, "string", None, None, None),

> -    "end": (0, None, None, None, None),

> -}

> -

> -keywords = set(['and', 'or', 'not'])

> -

> -# default set of valid characters for the initial letter of symbols

> -_syminitletters = set(

> -    string.ascii_letters +

> -    string.digits + pycompat.sysstr('._@')) | set(map(chr, xrange(128, 256)))

> -

> -# default set of valid characters for non-initial letters of symbols

> -_symletters = _syminitletters | set(pycompat.sysstr('-/'))

> -

> -def tokenize(program, lookup=None, syminitletters=None, symletters=None):

> -    '''

> -    Parse a revset statement into a stream of tokens

> -

> -    ``syminitletters`` is the set of valid characters for the initial

> -    letter of symbols.

> -

> -    By default, character ``c`` is recognized as valid for initial

> -    letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.

> -

> -    ``symletters`` is the set of valid characters for non-initial

> -    letters of symbols.

> -

> -    By default, character ``c`` is recognized as valid for non-initial

> -    letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.

> -

> -    Check that @ is a valid unquoted token character (issue3686):

> -    >>> list(tokenize("@::"))

> -    [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]

> -

> -    '''

> -    if syminitletters is None:

> -        syminitletters = _syminitletters

> -    if symletters is None:

> -        symletters = _symletters

> -

> -    if program and lookup:

> -        # attempt to parse old-style ranges first to deal with

> -        # things like old-tag which contain query metacharacters

> -        parts = program.split(':', 1)

> -        if all(lookup(sym) for sym in parts if sym):

> -            if parts[0]:

> -                yield ('symbol', parts[0], 0)

> -            if len(parts) > 1:

> -                s = len(parts[0])

> -                yield (':', None, s)

> -                if parts[1]:

> -                    yield ('symbol', parts[1], s + 1)

> -            yield ('end', None, len(program))

> -            return

> -

> -    pos, l = 0, len(program)

> -    while pos < l:

> -        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 == '#' and program[pos:pos + 2] == '##': # look ahead carefully

> -            yield ('##', None, pos)

> -            pos += 1 # skip ahead

> -        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

> -            if c == 'r':

> -                pos += 1

> -                c = program[pos]

> -                decode = lambda x: x

> -            else:

> -                decode = parser.unescapestr

> -            pos += 1

> -            s = pos

> -            while pos < l: # find closing quote

> -                d = program[pos]

> -                if d == '\\': # skip over escaped characters

> -                    pos += 2

> -                    continue

> -                if d == c:

> -                    yield ('string', decode(program[s:pos]), s)

> -                    break

> -                pos += 1

> -            else:

> -                raise error.ParseError(_("unterminated string"), s)

> -        # gather up a symbol/keyword

> -        elif c in syminitletters:

> -            s = pos

> -            pos += 1

> -            while pos < l: # find end of symbol

> -                d = program[pos]

> -                if d not in symletters:

> -                    break

> -                if d == '.' and program[pos - 1] == '.': # special case for ..

> -                    pos -= 1

> -                    break

> -                pos += 1

> -            sym = program[s:pos]

> -            if sym in keywords: # operator keywords

> -                yield (sym, None, s)

> -            elif '-' in sym:

> -                # some jerk gave us foo-bar-baz, try to check if it's a symbol

> -                if lookup and lookup(sym):

> -                    # looks like a real symbol

> -                    yield ('symbol', sym, s)

> -                else:

> -                    # looks like an expression

> -                    parts = sym.split('-')

> -                    for p in parts[:-1]:

> -                        if p: # possible consecutive -

> -                            yield ('symbol', p, s)

> -                        s += len(p)

> -                        yield ('-', None, pos)

> -                        s += 1

> -                    if parts[-1]: # possible trailing -

> -                        yield ('symbol', parts[-1], s)

> -            else:

> -                yield ('symbol', sym, s)

> -            pos -= 1

> -        else:

> -            raise error.ParseError(_("syntax error in revset '%s'") %

> -                                   program, pos)

> -        pos += 1

> -    yield ('end', None, pos)

> -

> -# helpers

> -

> -_notset = object()

> -

> -def getsymbol(x):

> -    if x and x[0] == 'symbol':

> -        return x[1]

> -    raise error.ParseError(_('not a symbol'))

> -

> -def getstring(x, err):

> -    if x and (x[0] == 'string' or x[0] == 'symbol'):

> -        return x[1]

> -    raise error.ParseError(err)

> -

> -def getinteger(x, err, default=_notset):

> -    if not x and default is not _notset:

> -        return default

> -    try:

> -        return int(getstring(x, err))

> -    except ValueError:

> -        raise error.ParseError(err)

> -

> -def getlist(x):

> -    if not x:

> -        return []

> -    if x[0] == 'list':

> -        return list(x[1:])

> -    return [x]

> -

> -def getrange(x, err):

> -    if not x:

> -        raise error.ParseError(err)

> -    op = x[0]

> -    if op == 'range':

> -        return x[1], x[2]

> -    elif op == 'rangepre':

> -        return None, x[1]

> -    elif op == 'rangepost':

> -        return x[1], None

> -    elif op == 'rangeall':

> -        return None, None

> -    raise error.ParseError(err)

> -

> -def getargs(x, min, max, err):

> -    l = getlist(x)

> -    if len(l) < min or (max >= 0 and len(l) > max):

> -        raise error.ParseError(err)

> -    return l

> -

> -def getargsdict(x, funcname, keys):

> -    return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),

> -                                keyvaluenode='keyvalue', keynode='symbol')

> -

> -def getset(repo, subset, x):

> -    if not x:

> -        raise error.ParseError(_("missing argument"))

> -    s = methods[x[0]](repo, subset, *x[1:])

> -    if util.safehasattr(s, 'isascending'):

> -        return s

> -    # else case should not happen, because all non-func are internal,

> -    # ignoring for now.

> -    if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:

> -        repo.ui.deprecwarn('revset "%s" uses list instead of smartset'

> -                           % x[1][1],

> -                           '3.9')

> -    return baseset(s)

> -

> -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):

> -    x = repo[x].rev()

> -    if (x in subset

> -        or x == node.nullrev and isinstance(subset, fullreposet)):

> -        return baseset([x])

> -    return baseset()

> -

> -def rangeset(repo, subset, x, y, order):

> -    m = getset(repo, fullreposet(repo), x)

> -    n = getset(repo, fullreposet(repo), y)

> -

> -    if not m or not n:

> -        return baseset()

> -    return _makerangeset(repo, subset, m.first(), n.last(), order)

> -

> -def rangeall(repo, subset, x, order):

> -    assert x is None

> -    return _makerangeset(repo, subset, 0, len(repo) - 1, order)

> -

> -def rangepre(repo, subset, y, order):

> -    # ':y' can't be rewritten to '0:y' since '0' may be hidden

> -    n = getset(repo, fullreposet(repo), y)

> -    if not n:

> -        return baseset()

> -    return _makerangeset(repo, subset, 0, n.last(), order)

> -

> -def rangepost(repo, subset, x, order):

> -    m = getset(repo, fullreposet(repo), x)

> -    if not m:

> -        return baseset()

> -    return _makerangeset(repo, subset, m.first(), len(repo) - 1, order)

> -

> -def _makerangeset(repo, subset, m, n, order):

> -    if m == n:

> -        r = baseset([m])

> -    elif n == node.wdirrev:

> -        r = spanset(repo, m, len(repo)) + baseset([n])

> -    elif m == node.wdirrev:

> -        r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)

> -    elif m < n:

> -        r = spanset(repo, m, n + 1)

> -    else:

> -        r = spanset(repo, m, n - 1)

> -

> -    if order == defineorder:

> -        return r & subset

> -    else:

> -        # carrying the sorting over when possible would be more efficient

> -        return subset & r

> -

> -def dagrange(repo, subset, x, y, order):

> -    r = fullreposet(repo)

> -    xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),

> -                         includepath=True)

> -    return subset & xs

> -

> -def andset(repo, subset, x, y, order):

> -    return getset(repo, getset(repo, subset, x), y)

> -

> -def differenceset(repo, subset, x, y, order):

> -    return getset(repo, subset, x) - getset(repo, subset, y)

> -

> -def _orsetlist(repo, subset, xs):

> -    assert xs

> -    if len(xs) == 1:

> -        return getset(repo, subset, xs[0])

> -    p = len(xs) // 2

> -    a = _orsetlist(repo, subset, xs[:p])

> -    b = _orsetlist(repo, subset, xs[p:])

> -    return a + b

> -

> -def orset(repo, subset, x, order):

> -    xs = getlist(x)

> -    if order == followorder:

> -        # slow path to take the subset order

> -        return subset & _orsetlist(repo, fullreposet(repo), xs)

> -    else:

> -        return _orsetlist(repo, subset, xs)

> -

> -def notset(repo, subset, x, order):

> -    return subset - getset(repo, subset, x)

> -

> -def listset(repo, subset, *xs):

> -    raise error.ParseError(_("can't use a list in this context"),

> -                           hint=_('see hg help "revsets.x or y"'))

> -

> -def keyvaluepair(repo, subset, k, v):

> -    raise error.ParseError(_("can't use a key-value pair in this context"))

> -

> -def func(repo, subset, a, b, order):

> -    f = getsymbol(a)

> -    if f in symbols:

> -        func = symbols[f]

> -        if getattr(func, '_takeorder', False):

> -            return func(repo, subset, b, order)

> -        return func(repo, subset, b)

> -

> -    keep = lambda fn: getattr(fn, '__doc__', None) is not None

> -

> -    syms = [s for (s, fn) in symbols.items() if keep(fn)]

> -    raise error.UnknownIdentifier(f, syms)

> -

> -# functions

> -

> -# symbols are callables like:

> -#   fn(repo, subset, x)

> -# with:

> -#   repo - current repository instance

> -#   subset - of revisions to be examined

> -#   x - argument in tree form

> -symbols = {}

> -

> -# symbols which can't be used for a DoS attack for any given input

> -# (e.g. those which accept regexes as plain strings shouldn't be included)

> -# functions that just return a lot of changesets (like all) don't count here

> -safesymbols = set()

> -

> -predicate = registrar.revsetpredicate()

> -

> -@predicate('_destupdate')

> -def _destupdate(repo, subset, x):

> -    # experimental revset for update destination

> -    args = getargsdict(x, 'limit', 'clean check')

> -    return subset & baseset([destutil.destupdate(repo, **args)[0]])

> -

> -@predicate('_destmerge')

> -def _destmerge(repo, subset, x):

> -    # experimental revset for merge destination

> -    sourceset = None

> -    if x is not None:

> -        sourceset = getset(repo, fullreposet(repo), x)

> -    return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])

> -

> -@predicate('adds(pattern)', safe=True)

> -def adds(repo, subset, x):

> -    """Changesets that add a file matching pattern.

> -

> -    The pattern without explicit kind like ``glob:`` is expected to be

> -    relative to the current directory and match against a file or a

> -    directory.

> -    """

> -    # i18n: "adds" is a keyword

> -    pat = getstring(x, _("adds requires a pattern"))

> -    return checkstatus(repo, subset, pat, 1)

> -

> -@predicate('ancestor(*changeset)', safe=True)

> -def ancestor(repo, subset, x):

> -    """A greatest common ancestor of the changesets.

> -

> -    Accepts 0 or more changesets.

> -    Will return empty list when passed no args.

> -    Greatest common ancestor of a single changeset is that changeset.

> -    """

> -    # i18n: "ancestor" is a keyword

> -    l = getlist(x)

> -    rl = fullreposet(repo)

> -    anc = None

> -

> -    # (getset(repo, rl, i) for i in l) generates a list of lists

> -    for revs in (getset(repo, rl, i) for i in l):

> -        for r in revs:

> -            if anc is None:

> -                anc = repo[r]

> -            else:

> -                anc = anc.ancestor(repo[r])

> -

> -    if anc is not None and anc.rev() in subset:

> -        return baseset([anc.rev()])

> -    return baseset()

> -

> -def _ancestors(repo, subset, x, followfirst=False):

> -    heads = getset(repo, fullreposet(repo), x)

> -    if not heads:

> -        return baseset()

> -    s = _revancestors(repo, heads, followfirst)

> -    return subset & s

> -

> -@predicate('ancestors(set)', safe=True)

> -def ancestors(repo, subset, x):

> -    """Changesets that are ancestors of a changeset in set.

> -    """

> -    return _ancestors(repo, subset, x)

> -

> -@predicate('_firstancestors', safe=True)

> -def _firstancestors(repo, subset, x):

> -    # ``_firstancestors(set)``

> -    # Like ``ancestors(set)`` but follows only the first parents.

> -    return _ancestors(repo, subset, x, followfirst=True)

> -

> -def ancestorspec(repo, subset, x, n, order):

> -    """``set~n``

> -    Changesets that are the Nth ancestor (first parents only) of a changeset

> -    in set.

> -    """

> -    n = getinteger(n, _("~ expects a number"))

> -    ps = set()

> -    cl = repo.changelog

> -    for r in getset(repo, fullreposet(repo), x):

> -        for i in range(n):

> -            r = cl.parentrevs(r)[0]

> -        ps.add(r)

> -    return subset & ps

> -

> -@predicate('author(string)', safe=True)

> -def author(repo, subset, x):

> -    """Alias for ``user(string)``.

> -    """

> -    # i18n: "author" is a keyword

> -    n = getstring(x, _("author requires a string"))

> -    kind, pattern, matcher = _substringmatcher(n, casesensitive=False)

> -    return subset.filter(lambda x: matcher(repo[x].user()),

> -                         condrepr=('<user %r>', n))

> -

> -@predicate('bisect(string)', safe=True)

> -def bisect(repo, subset, x):

> -    """Changesets marked in the specified bisect status:

> -

> -    - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip

> -    - ``goods``, ``bads``      : csets topologically 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 subset & state

> -

> -# Backward-compatibility

> -# - no help entry so that we do not advertise it any more

> -@predicate('bisected', safe=True)

> -def bisected(repo, subset, x):

> -    return bisect(repo, subset, x)

> -

> -@predicate('bookmark([name])', safe=True)

> -def bookmark(repo, subset, x):

> -    """The named bookmark or all bookmarks.

> -

> -    Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.

> -    """

> -    # 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'))

> -        kind, pattern, matcher = util.stringmatcher(bm)

> -        bms = set()

> -        if kind == 'literal':

> -            bmrev = repo._bookmarks.get(pattern, None)

> -            if not bmrev:

> -                raise error.RepoLookupError(_("bookmark '%s' does not exist")

> -                                            % pattern)

> -            bms.add(repo[bmrev].rev())

> -        else:

> -            matchrevs = set()

> -            for name, bmrev in repo._bookmarks.iteritems():

> -                if matcher(name):

> -                    matchrevs.add(bmrev)

> -            if not matchrevs:

> -                raise error.RepoLookupError(_("no bookmarks exist"

> -                                              " that match '%s'") % pattern)

> -            for bmrev in matchrevs:

> -                bms.add(repo[bmrev].rev())

> -    else:

> -        bms = set([repo[r].rev()

> -                   for r in repo._bookmarks.values()])

> -    bms -= set([node.nullrev])

> -    return subset & bms

> -

> -@predicate('branch(string or set)', safe=True)

> -def branch(repo, subset, x):

> -    """

> -    All changesets belonging to the given branch or the branches of the given

> -    changesets.

> -

> -    Pattern matching is supported for `string`. See

> -    :hg:`help revisions.patterns`.

> -    """

> -    getbi = repo.revbranchcache().branchinfo

> -

> -    try:

> -        b = getstring(x, '')

> -    except error.ParseError:

> -        # not a string, but another revspec, e.g. tip()

> -        pass

> -    else:

> -        kind, pattern, matcher = util.stringmatcher(b)

> -        if kind == 'literal':

> -            # note: falls through to the revspec case if no branch with

> -            # this name exists and pattern kind is not specified explicitly

> -            if pattern in repo.branchmap():

> -                return subset.filter(lambda r: matcher(getbi(r)[0]),

> -                                     condrepr=('<branch %r>', b))

> -            if b.startswith('literal:'):

> -                raise error.RepoLookupError(_("branch '%s' does not exist")

> -                                            % pattern)

> -        else:

> -            return subset.filter(lambda r: matcher(getbi(r)[0]),

> -                                 condrepr=('<branch %r>', b))

> -

> -    s = getset(repo, fullreposet(repo), x)

> -    b = set()

> -    for r in s:

> -        b.add(getbi(r)[0])

> -    c = s.__contains__

> -    return subset.filter(lambda r: c(r) or getbi(r)[0] in b,

> -                         condrepr=lambda: '<branch %r>' % sorted(b))

> -

> -@predicate('bumped()', safe=True)

> -def bumped(repo, subset, x):

> -    """Mutable changesets marked as successors of public changesets.

> -

> -    Only non-public and non-obsolete changesets can be `bumped`.

> -    """

> -    # i18n: "bumped" is a keyword

> -    getargs(x, 0, 0, _("bumped takes no arguments"))

> -    bumped = obsmod.getrevs(repo, 'bumped')

> -    return subset & bumped

> -

> -@predicate('bundle()', safe=True)

> -def bundle(repo, subset, x):

> -    """Changesets in the bundle.

> -

> -    Bundle must be specified by the -R option."""

> -

> -    try:

> -        bundlerevs = repo.changelog.bundlerevs

> -    except AttributeError:

> -        raise error.Abort(_("no bundle provided - specify with -R"))

> -    return subset & bundlerevs

> -

> -def checkstatus(repo, subset, pat, field):

> -    hasset = matchmod.patkind(pat) == 'set'

> -

> -    mcache = [None]

> -    def matches(x):

> -        c = repo[x]

> -        if not mcache[0] or hasset:

> -            mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)

> -        m = mcache[0]

> -        fname = None

> -        if not m.anypats() and len(m.files()) == 1:

> -            fname = m.files()[0]

> -        if fname is not None:

> -            if fname not in c.files():

> -                return False

> -        else:

> -            for f in c.files():

> -                if m(f):

> -                    break

> -            else:

> -                return False

> -        files = repo.status(c.p1().node(), c.node())[field]

> -        if fname is not None:

> -            if fname in files:

> -                return True

> -        else:

> -            for f in files:

> -                if m(f):

> -                    return True

> -

> -    return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))

> -

> -def _children(repo, subset, parentset):

> -    if not parentset:

> -        return baseset()

> -    cs = set()

> -    pr = repo.changelog.parentrevs

> -    minrev = parentset.min()

> -    nullrev = node.nullrev

> -    for r in subset:

> -        if r <= minrev:

> -            continue

> -        p1, p2 = pr(r)

> -        if p1 in parentset:

> -            cs.add(r)

> -        if p2 != nullrev and p2 in parentset:

> -            cs.add(r)

> -    return baseset(cs)

> -

> -@predicate('children(set)', safe=True)

> -def children(repo, subset, x):

> -    """Child changesets of changesets in set.

> -    """

> -    s = getset(repo, fullreposet(repo), x)

> -    cs = _children(repo, subset, s)

> -    return subset & cs

> -

> -@predicate('closed()', safe=True)

> -def closed(repo, subset, x):

> -    """Changeset is closed.

> -    """

> -    # i18n: "closed" is a keyword

> -    getargs(x, 0, 0, _("closed takes no arguments"))

> -    return subset.filter(lambda r: repo[r].closesbranch(),

> -                         condrepr='<branch closed>')

> -

> -@predicate('contains(pattern)')

> -def contains(repo, subset, x):

> -    """The revision's manifest contains a file matching pattern (but might not

> -    modify it). See :hg:`help patterns` for information about file patterns.

> -

> -    The pattern without explicit kind like ``glob:`` is expected to be

> -    relative to the current directory and match against a file exactly

> -    for efficiency.

> -    """

> -    # i18n: "contains" is a keyword

> -    pat = getstring(x, _("contains requires a pattern"))

> -

> -    def matches(x):

> -        if not matchmod.patkind(pat):

> -            pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)

> -            if pats in repo[x]:

> -                return True

> -        else:

> -            c = repo[x]

> -            m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)

> -            for f in c.manifest():

> -                if m(f):

> -                    return True

> -        return False

> -

> -    return subset.filter(matches, condrepr=('<contains %r>', pat))

> -

> -@predicate('converted([id])', safe=True)

> -def converted(repo, subset, x):

> -    """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 subset.filter(lambda r: _matchvalue(r),

> -                         condrepr=('<converted %r>', rev))

> -

> -@predicate('date(interval)', safe=True)

> -def date(repo, subset, x):

> -    """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 subset.filter(lambda x: dm(repo[x].date()[0]),

> -                         condrepr=('<date %r>', ds))

> -

> -@predicate('desc(string)', safe=True)

> -def desc(repo, subset, x):

> -    """Search commit message for string. The match is case-insensitive.

> -

> -    Pattern matching is supported for `string`. See

> -    :hg:`help revisions.patterns`.

> -    """

> -    # i18n: "desc" is a keyword

> -    ds = getstring(x, _("desc requires a string"))

> -

> -    kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)

> -

> -    return subset.filter(lambda r: matcher(repo[r].description()),

> -                         condrepr=('<desc %r>', ds))

> -

> -def _descendants(repo, subset, x, followfirst=False):

> -    roots = getset(repo, fullreposet(repo), x)

> -    if not roots:

> -        return baseset()

> -    s = _revdescendants(repo, roots, followfirst)

> -

> -    # Both sets need to be ascending in order to lazily return the union

> -    # in the correct order.

> -    base = subset & roots

> -    desc = subset & s

> -    result = base + desc

> -    if subset.isascending():

> -        result.sort()

> -    elif subset.isdescending():

> -        result.sort(reverse=True)

> -    else:

> -        result = subset & result

> -    return result

> -

> -@predicate('descendants(set)', safe=True)

> -def descendants(repo, subset, x):

> -    """Changesets which are descendants of changesets in set.

> -    """

> -    return _descendants(repo, subset, x)

> -

> -@predicate('_firstdescendants', safe=True)

> -def _firstdescendants(repo, subset, x):

> -    # ``_firstdescendants(set)``

> -    # Like ``descendants(set)`` but follows only the first parents.

> -    return _descendants(repo, subset, x, followfirst=True)

> -

> -@predicate('destination([set])', safe=True)

> -def destination(repo, subset, x):

> -    """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:

> -        sources = getset(repo, fullreposet(repo), x)

> -    else:

> -        sources = fullreposet(repo)

> -

> -    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 arg set.

> -    # Even if the immediate src of r is not in the arg set, 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 further 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 sources or src in dests:

> -                dests.update(lineage)

> -                break

> -

> -            r = src

> -            src = _getrevsource(repo, r)

> -

> -    return subset.filter(dests.__contains__,

> -                         condrepr=lambda: '<destination %r>' % sorted(dests))

> -

> -@predicate('divergent()', safe=True)

> -def divergent(repo, subset, x):

> -    """

> -    Final successors of changesets with an alternative set of final successors.

> -    """

> -    # i18n: "divergent" is a keyword

> -    getargs(x, 0, 0, _("divergent takes no arguments"))

> -    divergent = obsmod.getrevs(repo, 'divergent')

> -    return subset & divergent

> -

> -@predicate('extinct()', safe=True)

> -def extinct(repo, subset, x):

> -    """Obsolete changesets with obsolete descendants only.

> -    """

> -    # i18n: "extinct" is a keyword

> -    getargs(x, 0, 0, _("extinct takes no arguments"))

> -    extincts = obsmod.getrevs(repo, 'extinct')

> -    return subset & extincts

> -

> -@predicate('extra(label, [value])', safe=True)

> -def extra(repo, subset, x):

> -    """Changesets with the given label in the extra metadata, with the given

> -    optional value.

> -

> -    Pattern matching is supported for `value`. See

> -    :hg:`help revisions.patterns`.

> -    """

> -    args = getargsdict(x, 'extra', 'label value')

> -    if 'label' not in args:

> -        # i18n: "extra" is a keyword

> -        raise error.ParseError(_('extra takes at least 1 argument'))

> -    # i18n: "extra" is a keyword

> -    label = getstring(args['label'], _('first argument to extra must be '

> -                                       'a string'))

> -    value = None

> -

> -    if 'value' in args:

> -        # i18n: "extra" is a keyword

> -        value = getstring(args['value'], _('second argument to extra must be '

> -                                           'a string'))

> -        kind, value, matcher = util.stringmatcher(value)

> -

> -    def _matchvalue(r):

> -        extra = repo[r].extra()

> -        return label in extra and (value is None or matcher(extra[label]))

> -

> -    return subset.filter(lambda r: _matchvalue(r),

> -                         condrepr=('<extra[%r] %r>', label, value))

> -

> -@predicate('filelog(pattern)', safe=True)

> -def filelog(repo, subset, x):

> -    """Changesets connected to the specified filelog.

> -

> -    For performance reasons, visits only revisions mentioned in the file-level

> -    filelog, rather than filtering through all changesets (much faster, but

> -    doesn't include deletes or duplicate changes). For a slower, more accurate

> -    result, use ``file()``.

> -

> -    The pattern without explicit kind like ``glob:`` is expected to be

> -    relative to the current directory and match against a file exactly

> -    for efficiency.

> -

> -    If some linkrev points to revisions filtered by the current repoview, we'll

> -    work around it to return a non-filtered value.

> -    """

> -

> -    # i18n: "filelog" is a keyword

> -    pat = getstring(x, _("filelog requires a pattern"))

> -    s = set()

> -    cl = repo.changelog

> -

> -    if not matchmod.patkind(pat):

> -        f = pathutil.canonpath(repo.root, repo.getcwd(), pat)

> -        files = [f]

> -    else:

> -        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])

> -        files = (f for f in repo[None] if m(f))

> -

> -    for f in files:

> -        fl = repo.file(f)

> -        known = {}

> -        scanpos = 0

> -        for fr in list(fl):

> -            fn = fl.node(fr)

> -            if fn in known:

> -                s.add(known[fn])

> -                continue

> -

> -            lr = fl.linkrev(fr)

> -            if lr in cl:

> -                s.add(lr)

> -            elif scanpos is not None:

> -                # lowest matching changeset is filtered, scan further

> -                # ahead in changelog

> -                start = max(lr, scanpos) + 1

> -                scanpos = None

> -                for r in cl.revs(start):

> -                    # minimize parsing of non-matching entries

> -                    if f in cl.revision(r) and f in cl.readfiles(r):

> -                        try:

> -                            # try to use manifest delta fastpath

> -                            n = repo[r].filenode(f)

> -                            if n not in known:

> -                                if n == fn:

> -                                    s.add(r)

> -                                    scanpos = r

> -                                    break

> -                                else:

> -                                    known[n] = r

> -                        except error.ManifestLookupError:

> -                            # deletion in changelog

> -                            continue

> -

> -    return subset & s

> -

> -@predicate('first(set, [n])', safe=True)

> -def first(repo, subset, x):

> -    """An alias for limit().

> -    """

> -    return limit(repo, subset, x)

> -

> -def _follow(repo, subset, x, name, followfirst=False):

> -    l = getargs(x, 0, 2, _("%s takes no arguments or a pattern "

> -                           "and an optional revset") % name)

> -    c = repo['.']

> -    if l:

> -        x = getstring(l[0], _("%s expected a pattern") % name)

> -        rev = None

> -        if len(l) >= 2:

> -            revs = getset(repo, fullreposet(repo), l[1])

> -            if len(revs) != 1:

> -                raise error.RepoLookupError(

> -                        _("%s expected one starting revision") % name)

> -            rev = revs.last()

> -            c = repo[rev]

> -        matcher = matchmod.match(repo.root, repo.getcwd(), [x],

> -                                 ctx=repo[rev], default='path')

> -

> -        files = c.manifest().walk(matcher)

> -

> -        s = set()

> -        for fname in files:

> -            fctx = c[fname]

> -            s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))

> -            # include the revision responsible for the most recent version

> -            s.add(fctx.introrev())

> -    else:

> -        s = _revancestors(repo, baseset([c.rev()]), followfirst)

> -

> -    return subset & s

> -

> -@predicate('follow([pattern[, startrev]])', safe=True)

> -def follow(repo, subset, x):

> -    """

> -    An alias for ``::.`` (ancestors of the working directory's first parent).

> -    If pattern is specified, the histories of files matching given

> -    pattern in the revision given by startrev are followed, including copies.

> -    """

> -    return _follow(repo, subset, x, 'follow')

> -

> -@predicate('_followfirst', safe=True)

> -def _followfirst(repo, subset, x):

> -    # ``followfirst([pattern[, startrev]])``

> -    # Like ``follow([pattern[, startrev]])`` but follows only the first parent

> -    # of every revisions or files revisions.

> -    return _follow(repo, subset, x, '_followfirst', followfirst=True)

> -

> -@predicate('followlines(file, fromline:toline[, startrev=.])', safe=True)

> -def followlines(repo, subset, x):

> -    """Changesets modifying `file` in line range ('fromline', 'toline').

> -

> -    Line range corresponds to 'file' content at 'startrev' and should hence be

> -    consistent with file size. If startrev is not specified, working directory's

> -    parent is used.

> -    """

> -    from . import context  # avoid circular import issues

> -

> -    args = getargsdict(x, 'followlines', 'file *lines startrev')

> -    if len(args['lines']) != 1:

> -        raise error.ParseError(_("followlines requires a line range"))

> -

> -    rev = '.'

> -    if 'startrev' in args:

> -        revs = getset(repo, fullreposet(repo), args['startrev'])

> -        if len(revs) != 1:

> -            raise error.ParseError(

> -                _("followlines expects exactly one revision"))

> -        rev = revs.last()

> -

> -    pat = getstring(args['file'], _("followlines requires a pattern"))

> -    if not matchmod.patkind(pat):

> -        fname = pathutil.canonpath(repo.root, repo.getcwd(), pat)

> -    else:

> -        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[rev])

> -        files = [f for f in repo[rev] if m(f)]

> -        if len(files) != 1:

> -            raise error.ParseError(_("followlines expects exactly one file"))

> -        fname = files[0]

> -

> -    lr = getrange(args['lines'][0], _("followlines expects a line range"))

> -    fromline, toline = [getinteger(a, _("line range bounds must be integers"))

> -                        for a in lr]

> -    if toline - fromline < 0:

> -        raise error.ParseError(_("line range must be positive"))

> -    if fromline < 1:

> -        raise error.ParseError(_("fromline must be strictly positive"))

> -    fromline -= 1

> -

> -    fctx = repo[rev].filectx(fname)

> -    revs = (c.rev() for c in context.blockancestors(fctx, fromline, toline))

> -    return subset & generatorset(revs, iterasc=False)

> -

> -@predicate('all()', safe=True)

> -def getall(repo, subset, x):

> -    """All changesets, the same as ``0:tip``.

> -    """

> -    # i18n: "all" is a keyword

> -    getargs(x, 0, 0, _("all takes no arguments"))

> -    return subset & spanset(repo)  # drop "null" if any

> -

> -@predicate('grep(regex)')

> -def grep(repo, subset, x):

> -    """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 as e:

> -        raise error.ParseError(_('invalid match pattern: %s') % e)

> -

> -    def matches(x):

> -        c = repo[x]

> -        for e in c.files() + [c.user(), c.description()]:

> -            if gr.search(e):

> -                return True

> -        return False

> -

> -    return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))

> -

> -@predicate('_matchfiles', safe=True)

> -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.

> -

> -    l = getargs(x, 1, -1, "_matchfiles requires at least one argument")

> -    pats, inc, exc = [], [], []

> -    rev, default = None, None

> -    for arg in l:

> -        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:

> -                raise error.ParseError('_matchfiles expected at most one '

> -                                       'revision')

> -            if value != '': # empty means working directory; leave rev as None

> -                rev = value

> -        elif prefix == 'd:':

> -            if default is not None:

> -                raise error.ParseError('_matchfiles expected at most one '

> -                                       'default mode')

> -            default = value

> -        else:

> -            raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)

> -    if not default:

> -        default = 'glob'

> -

> -    m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,

> -                       exclude=exc, ctx=repo[rev], default=default)

> -

> -    # This directly read the changelog data as creating changectx for all

> -    # revisions is quite expensive.

> -    getfiles = repo.changelog.readfiles

> -    wdirrev = node.wdirrev

> -    def matches(x):

> -        if x == wdirrev:

> -            files = repo[x].files()

> -        else:

> -            files = getfiles(x)

> -        for f in files:

> -            if m(f):

> -                return True

> -        return False

> -

> -    return subset.filter(matches,

> -                         condrepr=('<matchfiles patterns=%r, include=%r '

> -                                   'exclude=%r, default=%r, rev=%r>',

> -                                   pats, inc, exc, default, rev))

> -

> -@predicate('file(pattern)', safe=True)

> -def hasfile(repo, subset, x):

> -    """Changesets affecting files matched by pattern.

> -

> -    For a faster but less accurate result, consider using ``filelog()``

> -    instead.

> -

> -    This predicate uses ``glob:`` as the default kind of pattern.

> -    """

> -    # i18n: "file" is a keyword

> -    pat = getstring(x, _("file requires a pattern"))

> -    return _matchfiles(repo, subset, ('string', 'p:' + pat))

> -

> -@predicate('head()', safe=True)

> -def head(repo, subset, x):

> -    """Changeset is a named branch head.

> -    """

> -    # i18n: "head" is a keyword

> -    getargs(x, 0, 0, _("head takes no arguments"))

> -    hs = set()

> -    cl = repo.changelog

> -    for ls in repo.branchmap().itervalues():

> -        hs.update(cl.rev(h) for h in ls)

> -    return subset & baseset(hs)

> -

> -@predicate('heads(set)', safe=True)

> -def heads(repo, subset, x):

> -    """Members of set with no children in set.

> -    """

> -    s = getset(repo, subset, x)

> -    ps = parents(repo, subset, x)

> -    return s - ps

> -

> -@predicate('hidden()', safe=True)

> -def hidden(repo, subset, x):

> -    """Hidden changesets.

> -    """

> -    # i18n: "hidden" is a keyword

> -    getargs(x, 0, 0, _("hidden takes no arguments"))

> -    hiddenrevs = repoview.filterrevs(repo, 'visible')

> -    return subset & hiddenrevs

> -

> -@predicate('keyword(string)', safe=True)

> -def keyword(repo, subset, x):

> -    """Search commit message, user name, and names of changed files for

> -    string. The match is case-insensitive.

> -

> -    For a regular expression or case sensitive search of these fields, use

> -    ``grep(regex)``.

> -    """

> -    # i18n: "keyword" is a keyword

> -    kw = encoding.lower(getstring(x, _("keyword requires a string")))

> -

> -    def matches(r):

> -        c = repo[r]

> -        return any(kw in encoding.lower(t)

> -                   for t in c.files() + [c.user(), c.description()])

> -

> -    return subset.filter(matches, condrepr=('<keyword %r>', kw))

> -

> -@predicate('limit(set[, n[, offset]])', safe=True)

> -def limit(repo, subset, x):

> -    """First n members of set, defaulting to 1, starting from offset.

> -    """

> -    args = getargsdict(x, 'limit', 'set n offset')

> -    if 'set' not in args:

> -        # i18n: "limit" is a keyword

> -        raise error.ParseError(_("limit requires one to three arguments"))

> -    # i18n: "limit" is a keyword

> -    lim = getinteger(args.get('n'), _("limit expects a number"), default=1)

> -    # i18n: "limit" is a keyword

> -    ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)

> -    if ofs < 0:

> -        raise error.ParseError(_("negative offset"))

> -    os = getset(repo, fullreposet(repo), args['set'])

> -    result = []

> -    it = iter(os)

> -    for x in xrange(ofs):

> -        y = next(it, None)

> -        if y is None:

> -            break

> -    for x in xrange(lim):

> -        y = next(it, None)

> -        if y is None:

> -            break

> -        elif y in subset:

> -            result.append(y)

> -    return baseset(result, datarepr=('<limit n=%d, offset=%d, %r, %r>',

> -                                     lim, ofs, subset, os))

> -

> -@predicate('last(set, [n])', safe=True)

> -def last(repo, subset, x):

> -    """Last n members of set, defaulting to 1.

> -    """

> -    # i18n: "last" is a keyword

> -    l = getargs(x, 1, 2, _("last requires one or two arguments"))

> -    lim = 1

> -    if len(l) == 2:

> -        # i18n: "last" is a keyword

> -        lim = getinteger(l[1], _("last expects a number"))

> -    os = getset(repo, fullreposet(repo), l[0])

> -    os.reverse()

> -    result = []

> -    it = iter(os)

> -    for x in xrange(lim):

> -        y = next(it, None)

> -        if y is None:

> -            break

> -        elif y in subset:

> -            result.append(y)

> -    return baseset(result, datarepr=('<last n=%d, %r, %r>', lim, subset, os))

> -

> -@predicate('max(set)', safe=True)

> -def maxrev(repo, subset, x):

> -    """Changeset with highest revision number in set.

> -    """

> -    os = getset(repo, fullreposet(repo), x)

> -    try:

> -        m = os.max()

> -        if m in subset:

> -            return baseset([m], datarepr=('<max %r, %r>', subset, os))

> -    except ValueError:

> -        # os.max() throws a ValueError when the collection is empty.

> -        # Same as python's max().

> -        pass

> -    return baseset(datarepr=('<max %r, %r>', subset, os))

> -

> -@predicate('merge()', safe=True)

> -def merge(repo, subset, x):

> -    """Changeset is a merge changeset.

> -    """

> -    # i18n: "merge" is a keyword

> -    getargs(x, 0, 0, _("merge takes no arguments"))

> -    cl = repo.changelog

> -    return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,

> -                         condrepr='<merge>')

> -

> -@predicate('branchpoint()', safe=True)

> -def branchpoint(repo, subset, x):

> -    """Changesets with more than one child.

> -    """

> -    # i18n: "branchpoint" is a keyword

> -    getargs(x, 0, 0, _("branchpoint takes no arguments"))

> -    cl = repo.changelog

> -    if not subset:

> -        return baseset()

> -    # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset

> -    # (and if it is not, it should.)

> -    baserev = min(subset)

> -    parentscount = [0]*(len(repo) - baserev)

> -    for r in cl.revs(start=baserev + 1):

> -        for p in cl.parentrevs(r):

> -            if p >= baserev:

> -                parentscount[p - baserev] += 1

> -    return subset.filter(lambda r: parentscount[r - baserev] > 1,

> -                         condrepr='<branchpoint>')

> -

> -@predicate('min(set)', safe=True)

> -def minrev(repo, subset, x):

> -    """Changeset with lowest revision number in set.

> -    """

> -    os = getset(repo, fullreposet(repo), x)

> -    try:

> -        m = os.min()

> -        if m in subset:

> -            return baseset([m], datarepr=('<min %r, %r>', subset, os))

> -    except ValueError:

> -        # os.min() throws a ValueError when the collection is empty.

> -        # Same as python's min().

> -        pass

> -    return baseset(datarepr=('<min %r, %r>', subset, os))

> -

> -@predicate('modifies(pattern)', safe=True)

> -def modifies(repo, subset, x):

> -    """Changesets modifying files matched by pattern.

> -

> -    The pattern without explicit kind like ``glob:`` is expected to be

> -    relative to the current directory and match against a file or a

> -    directory.

> -    """

> -    # i18n: "modifies" is a keyword

> -    pat = getstring(x, _("modifies requires a pattern"))

> -    return checkstatus(repo, subset, pat, 0)

> -

> -@predicate('named(namespace)')

> -def named(repo, subset, x):

> -    """The changesets in a given namespace.

> -

> -    Pattern matching is supported for `namespace`. See

> -    :hg:`help revisions.patterns`.

> -    """

> -    # i18n: "named" is a keyword

> -    args = getargs(x, 1, 1, _('named requires a namespace argument'))

> -

> -    ns = getstring(args[0],

> -                   # i18n: "named" is a keyword

> -                   _('the argument to named must be a string'))

> -    kind, pattern, matcher = util.stringmatcher(ns)

> -    namespaces = set()

> -    if kind == 'literal':

> -        if pattern not in repo.names:

> -            raise error.RepoLookupError(_("namespace '%s' does not exist")

> -                                        % ns)

> -        namespaces.add(repo.names[pattern])

> -    else:

> -        for name, ns in repo.names.iteritems():

> -            if matcher(name):

> -                namespaces.add(ns)

> -        if not namespaces:

> -            raise error.RepoLookupError(_("no namespace exists"

> -                                          " that match '%s'") % pattern)

> -

> -    names = set()

> -    for ns in namespaces:

> -        for name in ns.listnames(repo):

> -            if name not in ns.deprecated:

> -                names.update(repo[n].rev() for n in ns.nodes(repo, name))

> -

> -    names -= set([node.nullrev])

> -    return subset & names

> -

> -@predicate('id(string)', safe=True)

> -def node_(repo, subset, x):

> -    """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:

> -        try:

> -            rn = repo.changelog.rev(node.bin(n))

> -        except (LookupError, TypeError):

> -            rn = None

> -    else:

> -        rn = None

> -        pm = repo.changelog._partialmatch(n)

> -        if pm is not None:

> -            rn = repo.changelog.rev(pm)

> -

> -    if rn is None:

> -        return baseset()

> -    result = baseset([rn])

> -    return result & subset

> -

> -@predicate('obsolete()', safe=True)

> -def obsolete(repo, subset, x):

> -    """Mutable changeset with a newer version."""

> -    # i18n: "obsolete" is a keyword

> -    getargs(x, 0, 0, _("obsolete takes no arguments"))

> -    obsoletes = obsmod.getrevs(repo, 'obsolete')

> -    return subset & obsoletes

> -

> -@predicate('only(set, [set])', safe=True)

> -def only(repo, subset, x):

> -    """Changesets that are ancestors of the first set that are not ancestors

> -    of any other head in the repo. If a second set is specified, the result

> -    is ancestors of the first set that are not ancestors of the second set

> -    (i.e. ::<set1> - ::<set2>).

> -    """

> -    cl = repo.changelog

> -    # i18n: "only" is a keyword

> -    args = getargs(x, 1, 2, _('only takes one or two arguments'))

> -    include = getset(repo, fullreposet(repo), args[0])

> -    if len(args) == 1:

> -        if not include:

> -            return baseset()

> -

> -        descendants = set(_revdescendants(repo, include, False))

> -        exclude = [rev for rev in cl.headrevs()

> -            if not rev in descendants and not rev in include]

> -    else:

> -        exclude = getset(repo, fullreposet(repo), args[1])

> -

> -    results = set(cl.findmissingrevs(common=exclude, heads=include))

> -    # XXX we should turn this into a baseset instead of a set, smartset may do

> -    # some optimizations from the fact this is a baseset.

> -    return subset & results

> -

> -@predicate('origin([set])', safe=True)

> -def origin(repo, subset, x):

> -    """

> -    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:

> -        dests = getset(repo, fullreposet(repo), x)

> -    else:

> -        dests = fullreposet(repo)

> -

> -    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 dests])

> -    o -= set([None])

> -    # XXX we should turn this into a baseset instead of a set, smartset may do

> -    # some optimizations from the fact this is a baseset.

> -    return subset & o

> -

> -@predicate('outgoing([path])', safe=False)

> -def outgoing(repo, subset, x):

> -    """Changesets not found in the specified destination repository, or the

> -    default push location.

> -    """

> -    # Avoid cycles.

> -    from . import (

> -        discovery,

> -        hg,

> -    )

> -    # i18n: "outgoing" is a keyword

> -    l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))

> -    # 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.peer(repo, {}, dest)

> -    repo.ui.pushbuffer()

> -    outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)

> -    repo.ui.popbuffer()

> -    cl = repo.changelog

> -    o = set([cl.rev(r) for r in outgoing.missing])

> -    return subset & o

> -

> -@predicate('p1([set])', safe=True)

> -def p1(repo, subset, x):

> -    """First parent of changesets in set, or the working directory.

> -    """

> -    if x is None:

> -        p = repo[x].p1().rev()

> -        if p >= 0:

> -            return subset & baseset([p])

> -        return baseset()

> -

> -    ps = set()

> -    cl = repo.changelog

> -    for r in getset(repo, fullreposet(repo), x):

> -        ps.add(cl.parentrevs(r)[0])

> -    ps -= set([node.nullrev])

> -    # XXX we should turn this into a baseset instead of a set, smartset may do

> -    # some optimizations from the fact this is a baseset.

> -    return subset & ps

> -

> -@predicate('p2([set])', safe=True)

> -def p2(repo, subset, x):

> -    """Second parent of changesets in set, or the working directory.

> -    """

> -    if x is None:

> -        ps = repo[x].parents()

> -        try:

> -            p = ps[1].rev()

> -            if p >= 0:

> -                return subset & baseset([p])

> -            return baseset()

> -        except IndexError:

> -            return baseset()

> -

> -    ps = set()

> -    cl = repo.changelog

> -    for r in getset(repo, fullreposet(repo), x):

> -        ps.add(cl.parentrevs(r)[1])

> -    ps -= set([node.nullrev])

> -    # XXX we should turn this into a baseset instead of a set, smartset may do

> -    # some optimizations from the fact this is a baseset.

> -    return subset & ps

> -

> -def parentpost(repo, subset, x, order):

> -    return p1(repo, subset, x)

> -

> -@predicate('parents([set])', safe=True)

> -def parents(repo, subset, x):

> -    """

> -    The set of all parents for all changesets in set, or the working directory.

> -    """

> -    if x is None:

> -        ps = set(p.rev() for p in repo[x].parents())

> -    else:

> -        ps = set()

> -        cl = repo.changelog

> -        up = ps.update

> -        parentrevs = cl.parentrevs

> -        for r in getset(repo, fullreposet(repo), x):

> -            if r == node.wdirrev:

> -                up(p.rev() for p in repo[r].parents())

> -            else:

> -                up(parentrevs(r))

> -    ps -= set([node.nullrev])

> -    return subset & ps

> -

> -def _phase(repo, subset, target):

> -    """helper to select all rev in phase <target>"""

> -    repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded

> -    if repo._phasecache._phasesets:

> -        s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs

> -        s = baseset(s)

> -        s.sort() # set are non ordered, so we enforce ascending

> -        return subset & s

> -    else:

> -        phase = repo._phasecache.phase

> -        condition = lambda r: phase(repo, r) == target

> -        return subset.filter(condition, condrepr=('<phase %r>', target),

> -                             cache=False)

> -

> -@predicate('draft()', safe=True)

> -def draft(repo, subset, x):

> -    """Changeset in draft phase."""

> -    # i18n: "draft" is a keyword

> -    getargs(x, 0, 0, _("draft takes no arguments"))

> -    target = phases.draft

> -    return _phase(repo, subset, target)

> -

> -@predicate('secret()', safe=True)

> -def secret(repo, subset, x):

> -    """Changeset in secret phase."""

> -    # i18n: "secret" is a keyword

> -    getargs(x, 0, 0, _("secret takes no arguments"))

> -    target = phases.secret

> -    return _phase(repo, subset, target)

> -

> -def parentspec(repo, subset, x, n, order):

> -    """``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 (TypeError, ValueError):

> -        raise error.ParseError(_("^ expects a number 0, 1, or 2"))

> -    ps = set()

> -    cl = repo.changelog

> -    for r in getset(repo, fullreposet(repo), x):

> -        if n == 0:

> -            ps.add(r)

> -        elif n == 1:

> -            ps.add(cl.parentrevs(r)[0])

> -        elif n == 2:

> -            parents = cl.parentrevs(r)

> -            if parents[1] != node.nullrev:

> -                ps.add(parents[1])

> -    return subset & ps

> -

> -@predicate('present(set)', safe=True)

> -def present(repo, subset, x):

> -    """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 baseset()

> -

> -# for internal use

> -@predicate('_notpublic', safe=True)

> -def _notpublic(repo, subset, x):

> -    getargs(x, 0, 0, "_notpublic takes no arguments")

> -    repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded

> -    if repo._phasecache._phasesets:

> -        s = set()

> -        for u in repo._phasecache._phasesets[1:]:

> -            s.update(u)

> -        s = baseset(s - repo.changelog.filteredrevs)

> -        s.sort()

> -        return subset & s

> -    else:

> -        phase = repo._phasecache.phase

> -        target = phases.public

> -        condition = lambda r: phase(repo, r) != target

> -        return subset.filter(condition, condrepr=('<phase %r>', target),

> -                             cache=False)

> -

> -@predicate('public()', safe=True)

> -def public(repo, subset, x):

> -    """Changeset in public phase."""

> -    # i18n: "public" is a keyword

> -    getargs(x, 0, 0, _("public takes no arguments"))

> -    phase = repo._phasecache.phase

> -    target = phases.public

> -    condition = lambda r: phase(repo, r) == target

> -    return subset.filter(condition, condrepr=('<phase %r>', target),

> -                         cache=False)

> -

> -@predicate('remote([id [,path]])', safe=False)

> -def remote(repo, subset, x):

> -    """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.

> -    """

> -

> -    from . import hg # avoid start-up nasties

> -    # i18n: "remote" is a keyword

> -    l = getargs(x, 0, 2, _("remote takes zero, one, or two 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 baseset([r])

> -    return baseset()

> -

> -@predicate('removes(pattern)', safe=True)

> -def removes(repo, subset, x):

> -    """Changesets which remove files matching pattern.

> -

> -    The pattern without explicit kind like ``glob:`` is expected to be

> -    relative to the current directory and match against a file or a

> -    directory.

> -    """

> -    # i18n: "removes" is a keyword

> -    pat = getstring(x, _("removes requires a pattern"))

> -    return checkstatus(repo, subset, pat, 2)

> -

> -@predicate('rev(number)', safe=True)

> -def rev(repo, subset, x):

> -    """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 (TypeError, ValueError):

> -        # i18n: "rev" is a keyword

> -        raise error.ParseError(_("rev expects a number"))

> -    if l not in repo.changelog and l != node.nullrev:

> -        return baseset()

> -    return subset & baseset([l])

> -

> -@predicate('matching(revision [, field])', safe=True)

> -def matching(repo, subset, x):

> -    """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, fullreposet(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]

> -

> -    def matches(x):

> -        for rev in revs:

> -            target = getinfo(rev)

> -            match = True

> -            for n, f in enumerate(getfieldfuncs):

> -                if target[n] != f(x):

> -                    match = False

> -            if match:

> -                return True

> -        return False

> -

> -    return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))

> -

> -@predicate('reverse(set)', safe=True, takeorder=True)

> -def reverse(repo, subset, x, order):

> -    """Reverse order of set.

> -    """

> -    l = getset(repo, subset, x)

> -    if order == defineorder:

> -        l.reverse()

> -    return l

> -

> -@predicate('roots(set)', safe=True)

> -def roots(repo, subset, x):

> -    """Changesets in set with no parent changeset in set.

> -    """

> -    s = getset(repo, fullreposet(repo), x)

> -    parents = repo.changelog.parentrevs

> -    def filter(r):

> -        for p in parents(r):

> -            if 0 <= p and p in s:

> -                return False

> -        return True

> -    return subset & s.filter(filter, condrepr='<roots>')

> -

> -_sortkeyfuncs = {

> -    'rev': lambda c: c.rev(),

> -    'branch': lambda c: c.branch(),

> -    'desc': lambda c: c.description(),

> -    'user': lambda c: c.user(),

> -    'author': lambda c: c.user(),

> -    'date': lambda c: c.date()[0],

> -}

> -

> -def _getsortargs(x):

> -    """Parse sort options into (set, [(key, reverse)], opts)"""

> -    args = getargsdict(x, 'sort', 'set keys topo.firstbranch')

> -    if 'set' not in args:

> -        # i18n: "sort" is a keyword

> -        raise error.ParseError(_('sort requires one or two arguments'))

> -    keys = "rev"

> -    if 'keys' in args:

> -        # i18n: "sort" is a keyword

> -        keys = getstring(args['keys'], _("sort spec must be a string"))

> -

> -    keyflags = []

> -    for k in keys.split():

> -        fk = k

> -        reverse = (k[0] == '-')

> -        if reverse:

> -            k = k[1:]

> -        if k not in _sortkeyfuncs and k != 'topo':

> -            raise error.ParseError(_("unknown sort key %r") % fk)

> -        keyflags.append((k, reverse))

> -

> -    if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):

> -        # i18n: "topo" is a keyword

> -        raise error.ParseError(_('topo sort order cannot be combined '

> -                                 'with other sort keys'))

> -

> -    opts = {}

> -    if 'topo.firstbranch' in args:

> -        if any(k == 'topo' for k, reverse in keyflags):

> -            opts['topo.firstbranch'] = args['topo.firstbranch']

> -        else:

> -            # i18n: "topo" and "topo.firstbranch" are keywords

> -            raise error.ParseError(_('topo.firstbranch can only be used '

> -                                     'when using the topo sort key'))

> -

> -    return args['set'], keyflags, opts

> -

> -@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True)

> -def sort(repo, subset, x, order):

> -    """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

> -    - ``topo`` for a reverse topographical sort

> -

> -    The ``topo`` sort order cannot be combined with other sort keys. This sort

> -    takes one optional argument, ``topo.firstbranch``, which takes a revset that

> -    specifies what topographical branches to prioritize in the sort.

> -

> -    """

> -    s, keyflags, opts = _getsortargs(x)

> -    revs = getset(repo, subset, s)

> -

> -    if not keyflags or order != defineorder:

> -        return revs

> -    if len(keyflags) == 1 and keyflags[0][0] == "rev":

> -        revs.sort(reverse=keyflags[0][1])

> -        return revs

> -    elif keyflags[0][0] == "topo":

> -        firstbranch = ()

> -        if 'topo.firstbranch' in opts:

> -            firstbranch = getset(repo, subset, opts['topo.firstbranch'])

> -        revs = baseset(_toposort(revs, repo.changelog.parentrevs, firstbranch),

> -                       istopo=True)

> -        if keyflags[0][1]:

> -            revs.reverse()

> -        return revs

> -

> -    # sort() is guaranteed to be stable

> -    ctxs = [repo[r] for r in revs]

> -    for k, reverse in reversed(keyflags):

> -        ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)

> -    return baseset([c.rev() for c in ctxs])

> -

> -def _toposort(revs, parentsfunc, firstbranch=()):

> -    """Yield revisions from heads to roots one (topo) branch at a time.

> -

> -    This function aims to be used by a graph generator that wishes to minimize

> -    the number of parallel branches and their interleaving.

> -

> -    Example iteration order (numbers show the "true" order in a changelog):

> -

> -      o  4

> -      |

> -      o  1

> -      |

> -      | o  3

> -      | |

> -      | o  2

> -      |/

> -      o  0

> -

> -    Note that the ancestors of merges are understood by the current

> -    algorithm to be on the same branch. This means no reordering will

> -    occur behind a merge.

> -    """

> -

> -    ### Quick summary of the algorithm

> -    #

> -    # This function is based around a "retention" principle. We keep revisions

> -    # in memory until we are ready to emit a whole branch that immediately

> -    # "merges" into an existing one. This reduces the number of parallel

> -    # branches with interleaved revisions.

> -    #

> -    # During iteration revs are split into two groups:

> -    # A) revision already emitted

> -    # B) revision in "retention". They are stored as different subgroups.

> -    #

> -    # for each REV, we do the following logic:

> -    #

> -    #   1) if REV is a parent of (A), we will emit it. If there is a

> -    #   retention group ((B) above) that is blocked on REV being

> -    #   available, we emit all the revisions out of that retention

> -    #   group first.

> -    #

> -    #   2) else, we'll search for a subgroup in (B) awaiting for REV to be

> -    #   available, if such subgroup exist, we add REV to it and the subgroup is

> -    #   now awaiting for REV.parents() to be available.

> -    #

> -    #   3) finally if no such group existed in (B), we create a new subgroup.

> -    #

> -    #

> -    # To bootstrap the algorithm, we emit the tipmost revision (which

> -    # puts it in group (A) from above).

> -

> -    revs.sort(reverse=True)

> -

> -    # Set of parents of revision that have been emitted. They can be considered

> -    # unblocked as the graph generator is already aware of them so there is no

> -    # need to delay the revisions that reference them.

> -    #

> -    # If someone wants to prioritize a branch over the others, pre-filling this

> -    # set will force all other branches to wait until this branch is ready to be

> -    # emitted.

> -    unblocked = set(firstbranch)

> -

> -    # list of groups waiting to be displayed, each group is defined by:

> -    #

> -    #   (revs:    lists of revs waiting to be displayed,

> -    #    blocked: set of that cannot be displayed before those in 'revs')

> -    #

> -    # The second value ('blocked') correspond to parents of any revision in the

> -    # group ('revs') that is not itself contained in the group. The main idea

> -    # of this algorithm is to delay as much as possible the emission of any

> -    # revision.  This means waiting for the moment we are about to display

> -    # these parents to display the revs in a group.

> -    #

> -    # This first implementation is smart until it encounters a merge: it will

> -    # emit revs as soon as any parent is about to be emitted and can grow an

> -    # arbitrary number of revs in 'blocked'. In practice this mean we properly

> -    # retains new branches but gives up on any special ordering for ancestors

> -    # of merges. The implementation can be improved to handle this better.

> -    #

> -    # The first subgroup is special. It corresponds to all the revision that

> -    # were already emitted. The 'revs' lists is expected to be empty and the

> -    # 'blocked' set contains the parents revisions of already emitted revision.

> -    #

> -    # You could pre-seed the <parents> set of groups[0] to a specific

> -    # changesets to select what the first emitted branch should be.

> -    groups = [([], unblocked)]

> -    pendingheap = []

> -    pendingset = set()

> -

> -    heapq.heapify(pendingheap)

> -    heappop = heapq.heappop

> -    heappush = heapq.heappush

> -    for currentrev in revs:

> -        # Heap works with smallest element, we want highest so we invert

> -        if currentrev not in pendingset:

> -            heappush(pendingheap, -currentrev)

> -            pendingset.add(currentrev)

> -        # iterates on pending rev until after the current rev have been

> -        # processed.

> -        rev = None

> -        while rev != currentrev:

> -            rev = -heappop(pendingheap)

> -            pendingset.remove(rev)

> -

> -            # Seek for a subgroup blocked, waiting for the current revision.

> -            matching = [i for i, g in enumerate(groups) if rev in g[1]]

> -

> -            if matching:

> -                # The main idea is to gather together all sets that are blocked

> -                # on the same revision.

> -                #

> -                # Groups are merged when a common blocking ancestor is

> -                # observed. For example, given two groups:

> -                #

> -                # revs [5, 4] waiting for 1

> -                # revs [3, 2] waiting for 1

> -                #

> -                # These two groups will be merged when we process

> -                # 1. In theory, we could have merged the groups when

> -                # we added 2 to the group it is now in (we could have

> -                # noticed the groups were both blocked on 1 then), but

> -                # the way it works now makes the algorithm simpler.

> -                #

> -                # We also always keep the oldest subgroup first. We can

> -                # probably improve the behavior by having the longest set

> -                # first. That way, graph algorithms could minimise the length

> -                # of parallel lines their drawing. This is currently not done.

> -                targetidx = matching.pop(0)

> -                trevs, tparents = groups[targetidx]

> -                for i in matching:

> -                    gr = groups[i]

> -                    trevs.extend(gr[0])

> -                    tparents |= gr[1]

> -                # delete all merged subgroups (except the one we kept)

> -                # (starting from the last subgroup for performance and

> -                # sanity reasons)

> -                for i in reversed(matching):

> -                    del groups[i]

> -            else:

> -                # This is a new head. We create a new subgroup for it.

> -                targetidx = len(groups)

> -                groups.append(([], set([rev])))

> -

> -            gr = groups[targetidx]

> -

> -            # We now add the current nodes to this subgroups. This is done

> -            # after the subgroup merging because all elements from a subgroup

> -            # that relied on this rev must precede it.

> -            #

> -            # we also update the <parents> set to include the parents of the

> -            # new nodes.

> -            if rev == currentrev: # only display stuff in rev

> -                gr[0].append(rev)

> -            gr[1].remove(rev)

> -            parents = [p for p in parentsfunc(rev) if p > node.nullrev]

> -            gr[1].update(parents)

> -            for p in parents:

> -                if p not in pendingset:

> -                    pendingset.add(p)

> -                    heappush(pendingheap, -p)

> -

> -            # Look for a subgroup to display

> -            #

> -            # When unblocked is empty (if clause), we were not waiting for any

> -            # revisions during the first iteration (if no priority was given) or

> -            # if we emitted a whole disconnected set of the graph (reached a

> -            # root).  In that case we arbitrarily take the oldest known

> -            # subgroup. The heuristic could probably be better.

> -            #

> -            # Otherwise (elif clause) if the subgroup is blocked on

> -            # a revision we just emitted, we can safely emit it as

> -            # well.

> -            if not unblocked:

> -                if len(groups) > 1:  # display other subset

> -                    targetidx = 1

> -                    gr = groups[1]

> -            elif not gr[1] & unblocked:

> -                gr = None

> -

> -            if gr is not None:

> -                # update the set of awaited revisions with the one from the

> -                # subgroup

> -                unblocked |= gr[1]

> -                # output all revisions in the subgroup

> -                for r in gr[0]:

> -                    yield r

> -                # delete the subgroup that you just output

> -                # unless it is groups[0] in which case you just empty it.

> -                if targetidx:

> -                    del groups[targetidx]

> -                else:

> -                    gr[0][:] = []

> -    # Check if we have some subgroup waiting for revisions we are not going to

> -    # iterate over

> -    for g in groups:

> -        for r in g[0]:

> -            yield r

> -

> -@predicate('subrepo([pattern])')

> -def subrepo(repo, subset, x):

> -    """Changesets that add, modify or remove the given subrepo.  If no subrepo

> -    pattern is named, any subrepo changes are returned.

> -    """

> -    # i18n: "subrepo" is a keyword

> -    args = getargs(x, 0, 1, _('subrepo takes at most one argument'))

> -    pat = None

> -    if len(args) != 0:

> -        pat = getstring(args[0], _("subrepo requires a pattern"))

> -

> -    m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])

> -

> -    def submatches(names):

> -        k, p, m = util.stringmatcher(pat)

> -        for name in names:

> -            if m(name):

> -                yield name

> -

> -    def matches(x):

> -        c = repo[x]

> -        s = repo.status(c.p1().node(), c.node(), match=m)

> -

> -        if pat is None:

> -            return s.added or s.modified or s.removed

> -

> -        if s.added:

> -            return any(submatches(c.substate.keys()))

> -

> -        if s.modified:

> -            subs = set(c.p1().substate.keys())

> -            subs.update(c.substate.keys())

> -

> -            for path in submatches(subs):

> -                if c.p1().substate.get(path) != c.substate.get(path):

> -                    return True

> -

> -        if s.removed:

> -            return any(submatches(c.p1().substate.keys()))

> -

> -        return False

> -

> -    return subset.filter(matches, condrepr=('<subrepo %r>', pat))

> -

> -def _substringmatcher(pattern, casesensitive=True):

> -    kind, pattern, matcher = util.stringmatcher(pattern,

> -                                                casesensitive=casesensitive)

> -    if kind == 'literal':

> -        if not casesensitive:

> -            pattern = encoding.lower(pattern)

> -            matcher = lambda s: pattern in encoding.lower(s)

> -        else:

> -            matcher = lambda s: pattern in s

> -    return kind, pattern, matcher

> -

> -@predicate('tag([name])', safe=True)

> -def tag(repo, subset, x):

> -    """The specified tag by name, or all tagged revisions if no name is given.

> -

> -    Pattern matching is supported for `name`. See

> -    :hg:`help revisions.patterns`.

> -    """

> -    # i18n: "tag" is a keyword

> -    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 = util.stringmatcher(pattern)

> -        if kind == 'literal':

> -            # avoid resolving all tags

> -            tn = repo._tagscache.tags.get(pattern, None)

> -            if tn is None:

> -                raise error.RepoLookupError(_("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)])

> -    else:

> -        s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])

> -    return subset & s

> -

> -@predicate('tagged', safe=True)

> -def tagged(repo, subset, x):

> -    return tag(repo, subset, x)

> -

> -@predicate('unstable()', safe=True)

> -def unstable(repo, subset, x):

> -    """Non-obsolete changesets with obsolete ancestors.

> -    """

> -    # i18n: "unstable" is a keyword

> -    getargs(x, 0, 0, _("unstable takes no arguments"))

> -    unstables = obsmod.getrevs(repo, 'unstable')

> -    return subset & unstables

> -

> -

> -@predicate('user(string)', safe=True)

> -def user(repo, subset, x):

> -    """User name contains string. The match is case-insensitive.

> -

> -    Pattern matching is supported for `string`. See

> -    :hg:`help revisions.patterns`.

> -    """

> -    return author(repo, subset, x)

> -

> -@predicate('wdir', safe=True)

> -def wdir(repo, subset, x):

> -    """Working directory. (EXPERIMENTAL)"""

> -    # i18n: "wdir" is a keyword

> -    getargs(x, 0, 0, _("wdir takes no arguments"))

> -    if node.wdirrev in subset or isinstance(subset, fullreposet):

> -        return baseset([node.wdirrev])

> -    return baseset()

> -

> -def _orderedlist(repo, subset, x):

> -    s = getstring(x, "internal error")

> -    if not s:

> -        return baseset()

> -    # remove duplicates here. it's difficult for caller to deduplicate sets

> -    # because different symbols can point to the same rev.

> -    cl = repo.changelog

> -    ls = []

> -    seen = set()

> -    for t in s.split('\0'):

> -        try:

> -            # fast path for integer revision

> -            r = int(t)

> -            if str(r) != t or r not in cl:

> -                raise ValueError

> -            revs = [r]

> -        except ValueError:

> -            revs = stringset(repo, subset, t)

> -

> -        for r in revs:

> -            if r in seen:

> -                continue

> -            if (r in subset

> -                or r == node.nullrev and isinstance(subset, fullreposet)):

> -                ls.append(r)

> -            seen.add(r)

> -    return baseset(ls)

> -

> -# for internal use

> -@predicate('_list', safe=True, takeorder=True)

> -def _list(repo, subset, x, order):

> -    if order == followorder:

> -        # slow path to take the subset order

> -        return subset & _orderedlist(repo, fullreposet(repo), x)

> -    else:

> -        return _orderedlist(repo, subset, x)

> -

> -def _orderedintlist(repo, subset, x):

> -    s = getstring(x, "internal error")

> -    if not s:

> -        return baseset()

> -    ls = [int(r) for r in s.split('\0')]

> -    s = subset

> -    return baseset([r for r in ls if r in s])

> -

> -# for internal use

> -@predicate('_intlist', safe=True, takeorder=True)

> -def _intlist(repo, subset, x, order):

> -    if order == followorder:

> -        # slow path to take the subset order

> -        return subset & _orderedintlist(repo, fullreposet(repo), x)

> -    else:

> -        return _orderedintlist(repo, subset, x)

> -

> -def _orderedhexlist(repo, subset, x):

> -    s = getstring(x, "internal error")

> -    if not s:

> -        return baseset()

> -    cl = repo.changelog

> -    ls = [cl.rev(node.bin(r)) for r in s.split('\0')]

> -    s = subset

> -    return baseset([r for r in ls if r in s])

> -

> -# for internal use

> -@predicate('_hexlist', safe=True, takeorder=True)

> -def _hexlist(repo, subset, x, order):

> -    if order == followorder:

> -        # slow path to take the subset order

> -        return subset & _orderedhexlist(repo, fullreposet(repo), x)

> -    else:

> -        return _orderedhexlist(repo, subset, x)

> -

> -methods = {

> -    "range": rangeset,

> -    "rangeall": rangeall,

> -    "rangepre": rangepre,

> -    "rangepost": rangepost,

> -    "dagrange": dagrange,

> -    "string": stringset,

> -    "symbol": stringset,

> -    "and": andset,

> -    "or": orset,

> -    "not": notset,

> -    "difference": differenceset,

> -    "list": listset,

> -    "keyvalue": keyvaluepair,

> -    "func": func,

> -    "ancestor": ancestorspec,

> -    "parent": parentspec,

> -    "parentpost": parentpost,

> -}

> -

> -# Constants for ordering requirement, used in _analyze():

> -#

> -# If 'define', any nested functions and operations can change the ordering of

> -# the entries in the set. If 'follow', any nested functions and operations

> -# should take the ordering specified by the first operand to the '&' operator.

> -#

> -# For instance,

> -#

> -#   X & (Y | Z)

> -#   ^   ^^^^^^^

> -#   |   follow

> -#   define

> -#

> -# will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order

> -# of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.

> -#

> -# 'any' means the order doesn't matter. For instance,

> -#

> -#   X & !Y

> -#        ^

> -#        any

> -#

> -# 'y()' can either enforce its ordering requirement or take the ordering

> -# specified by 'x()' because 'not()' doesn't care the order.

> -#

> -# Transition of ordering requirement:

> -#

> -# 1. starts with 'define'

> -# 2. shifts to 'follow' by 'x & y'

> -# 3. changes back to 'define' on function call 'f(x)' or function-like

> -#    operation 'x (f) y' because 'f' may have its own ordering requirement

> -#    for 'x' and 'y' (e.g. 'first(x)')

> -#

> -anyorder = 'any'        # don't care the order

> -defineorder = 'define'  # should define the order

> -followorder = 'follow'  # must follow the current order

> -

> -# transition table for 'x & y', from the current expression 'x' to 'y'

> -_tofolloworder = {

> -    anyorder: anyorder,

> -    defineorder: followorder,

> -    followorder: followorder,

> -}

> -

> -def _matchonly(revs, bases):

> -    """

> -    >>> f = lambda *args: _matchonly(*map(parse, args))

> -    >>> f('ancestors(A)', 'not ancestors(B)')

> -    ('list', ('symbol', 'A'), ('symbol', 'B'))

> -    """

> -    if (revs is not None

> -        and revs[0] == 'func'

> -        and getsymbol(revs[1]) == 'ancestors'

> -        and bases is not None

> -        and bases[0] == 'not'

> -        and bases[1][0] == 'func'

> -        and getsymbol(bases[1][1]) == 'ancestors'):

> -        return ('list', revs[2], bases[1][2])

> -

> -def _fixops(x):

> -    """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be

> -    handled well by our simple top-down parser"""

> -    if not isinstance(x, tuple):

> -        return x

> -

> -    op = x[0]

> -    if op == 'parent':

> -        # x^:y means (x^) : y, not x ^ (:y)

> -        # x^:  means (x^) :,   not x ^ (:)

> -        post = ('parentpost', x[1])

> -        if x[2][0] == 'dagrangepre':

> -            return _fixops(('dagrange', post, x[2][1]))

> -        elif x[2][0] == 'rangepre':

> -            return _fixops(('range', post, x[2][1]))

> -        elif x[2][0] == 'rangeall':

> -            return _fixops(('rangepost', post))

> -    elif op == 'or':

> -        # make number of arguments deterministic:

> -        # x + y + z -> (or x y z) -> (or (list x y z))

> -        return (op, _fixops(('list',) + x[1:]))

> -

> -    return (op,) + tuple(_fixops(y) for y in x[1:])

> -

> -def _analyze(x, order):

> -    if x is None:

> -        return x

> -

> -    op = x[0]

> -    if op == 'minus':

> -        return _analyze(('and', x[1], ('not', x[2])), order)

> -    elif op == 'only':

> -        t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))

> -        return _analyze(t, order)

> -    elif op == 'onlypost':

> -        return _analyze(('func', ('symbol', 'only'), x[1]), order)

> -    elif op == 'dagrangepre':

> -        return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)

> -    elif op == 'dagrangepost':

> -        return _analyze(('func', ('symbol', 'descendants'), x[1]), order)

> -    elif op == 'negate':

> -        s = getstring(x[1], _("can't negate that"))

> -        return _analyze(('string', '-' + s), order)

> -    elif op in ('string', 'symbol'):

> -        return x

> -    elif op == 'and':

> -        ta = _analyze(x[1], order)

> -        tb = _analyze(x[2], _tofolloworder[order])

> -        return (op, ta, tb, order)

> -    elif op == 'or':

> -        return (op, _analyze(x[1], order), order)

> -    elif op == 'not':

> -        return (op, _analyze(x[1], anyorder), order)

> -    elif op == 'rangeall':

> -        return (op, None, order)

> -    elif op in ('rangepre', 'rangepost', 'parentpost'):

> -        return (op, _analyze(x[1], defineorder), order)

> -    elif op == 'group':

> -        return _analyze(x[1], order)

> -    elif op in ('dagrange', 'range', 'parent', 'ancestor'):

> -        ta = _analyze(x[1], defineorder)

> -        tb = _analyze(x[2], defineorder)

> -        return (op, ta, tb, order)

> -    elif op == 'list':

> -        return (op,) + tuple(_analyze(y, order) for y in x[1:])

> -    elif op == 'keyvalue':

> -        return (op, x[1], _analyze(x[2], order))

> -    elif op == 'func':

> -        f = getsymbol(x[1])

> -        d = defineorder

> -        if f == 'present':

> -            # 'present(set)' is known to return the argument set with no

> -            # modification, so forward the current order to its argument

> -            d = order

> -        return (op, x[1], _analyze(x[2], d), order)

> -    raise ValueError('invalid operator %r' % op)

> -

> -def analyze(x, order=defineorder):

> -    """Transform raw parsed tree to evaluatable tree which can be fed to

> -    optimize() or getset()

> -

> -    All pseudo operations should be mapped to real operations or functions

> -    defined in methods or symbols table respectively.

> -

> -    'order' specifies how the current expression 'x' is ordered (see the

> -    constants defined above.)

> -    """

> -    return _analyze(x, order)

> -

> -def _optimize(x, small):

> -    if x is None:

> -        return 0, x

> -

> -    smallbonus = 1

> -    if small:

> -        smallbonus = .5

> -

> -    op = x[0]

> -    if op in ('string', 'symbol'):

> -        return smallbonus, x # single revisions are small

> -    elif op == 'and':

> -        wa, ta = _optimize(x[1], True)

> -        wb, tb = _optimize(x[2], True)

> -        order = x[3]

> -        w = min(wa, wb)

> -

> -        # (::x and not ::y)/(not ::y and ::x) have a fast path

> -        tm = _matchonly(ta, tb) or _matchonly(tb, ta)

> -        if tm:

> -            return w, ('func', ('symbol', 'only'), tm, order)

> -

> -        if tb is not None and tb[0] == 'not':

> -            return wa, ('difference', ta, tb[1], order)

> -

> -        if wa > wb:

> -            return w, (op, tb, ta, order)

> -        return w, (op, ta, tb, order)

> -    elif op == 'or':

> -        # fast path for machine-generated expression, that is likely to have

> -        # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'

> -        order = x[2]

> -        ws, ts, ss = [], [], []

> -        def flushss():

> -            if not ss:

> -                return

> -            if len(ss) == 1:

> -                w, t = ss[0]

> -            else:

> -                s = '\0'.join(t[1] for w, t in ss)

> -                y = ('func', ('symbol', '_list'), ('string', s), order)

> -                w, t = _optimize(y, False)

> -            ws.append(w)

> -            ts.append(t)

> -            del ss[:]

> -        for y in getlist(x[1]):

> -            w, t = _optimize(y, False)

> -            if t is not None and (t[0] == 'string' or t[0] == 'symbol'):

> -                ss.append((w, t))

> -                continue

> -            flushss()

> -            ws.append(w)

> -            ts.append(t)

> -        flushss()

> -        if len(ts) == 1:

> -            return ws[0], ts[0] # 'or' operation is fully optimized out

> -        # we can't reorder trees by weight because it would change the order.

> -        # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")

> -        #   ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))

> -        return max(ws), (op, ('list',) + tuple(ts), order)

> -    elif op == 'not':

> -        # Optimize not public() to _notpublic() because we have a fast version

> -        if x[1][:3] == ('func', ('symbol', 'public'), None):

> -            order = x[1][3]

> -            newsym = ('func', ('symbol', '_notpublic'), None, order)

> -            o = _optimize(newsym, not small)

> -            return o[0], o[1]

> -        else:

> -            o = _optimize(x[1], not small)

> -            order = x[2]

> -            return o[0], (op, o[1], order)

> -    elif op == 'rangeall':

> -        return smallbonus, x

> -    elif op in ('rangepre', 'rangepost', 'parentpost'):

> -        o = _optimize(x[1], small)

> -        order = x[2]

> -        return o[0], (op, o[1], order)

> -    elif op in ('dagrange', 'range', 'parent', 'ancestor'):

> -        wa, ta = _optimize(x[1], small)

> -        wb, tb = _optimize(x[2], small)

> -        order = x[3]

> -        return wa + wb, (op, ta, tb, order)

> -    elif op == 'list':

> -        ws, ts = zip(*(_optimize(y, small) for y in x[1:]))

> -        return sum(ws), (op,) + ts

> -    elif op == 'keyvalue':

> -        w, t = _optimize(x[2], small)

> -        return w, (op, x[1], t)

> -    elif op == 'func':

> -        f = getsymbol(x[1])

> -        wa, ta = _optimize(x[2], small)

> -        if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',

> -                 'keyword', 'outgoing', 'user', 'destination'):

> -            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', 'first', 'wdir', '_intlist'):

> -            w = 0

> -        elif f == "sort":

> -            w = 10 # assume most sorts look at changelog

> -        else:

> -            w = 1

> -        order = x[3]

> -        return w + wa, (op, x[1], ta, order)

> -    raise ValueError('invalid operator %r' % op)

> -

> -def optimize(tree):

> -    """Optimize evaluatable tree

> -

> -    All pseudo operations should be transformed beforehand.

> -    """

> -    _weight, newtree = _optimize(tree, small=True)

> -    return newtree

> -

> -# the set of valid characters for the initial letter of symbols in

> -# alias declarations and definitions

> -_aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))

> -

> -def _parsewith(spec, lookup=None, syminitletters=None):

> -    """Generate a parse tree of given spec with given tokenizing options

> -

> -    >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)

> -    ('func', ('symbol', 'foo'), ('symbol', '$1'))

> -    >>> _parsewith('$1')

> -    Traceback (most recent call last):

> -      ...

> -    ParseError: ("syntax error in revset '$1'", 0)

> -    >>> _parsewith('foo bar')

> -    Traceback (most recent call last):

> -      ...

> -    ParseError: ('invalid token', 4)

> -    """

> -    p = parser.parser(elements)

> -    tree, pos = p.parse(tokenize(spec, lookup=lookup,

> -                                 syminitletters=syminitletters))

> -    if pos != len(spec):

> -        raise error.ParseError(_('invalid token'), pos)

> -    return _fixops(parser.simplifyinfixops(tree, ('list', 'or')))

> -

> -class _aliasrules(parser.basealiasrules):

> -    """Parsing and expansion rule set of revset aliases"""

> -    _section = _('revset alias')

> -

> -    @staticmethod

> -    def _parse(spec):

> -        """Parse alias declaration/definition ``spec``

> -

> -        This allows symbol names to use also ``$`` as an initial letter

> -        (for backward compatibility), and callers of this function should

> -        examine whether ``$`` is used also for unexpected symbols or not.

> -        """

> -        return _parsewith(spec, syminitletters=_aliassyminitletters)

> -

> -    @staticmethod

> -    def _trygetfunc(tree):

> -        if tree[0] == 'func' and tree[1][0] == 'symbol':

> -            return tree[1][1], getlist(tree[2])

> -

> -def expandaliases(ui, tree):

> -    aliases = _aliasrules.buildmap(ui.configitems('revsetalias'))

> -    tree = _aliasrules.expand(aliases, tree)

> -    # warn about problematic (but not referred) aliases

> -    for name, alias in sorted(aliases.iteritems()):

> -        if alias.error and not alias.warned:

> -            ui.warn(_('warning: %s\n') % (alias.error))

> -            alias.warned = True

> -    return tree

> -

> -def foldconcat(tree):

> -    """Fold elements to be concatenated by `##`

> -    """

> -    if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):

> -        return tree

> -    if tree[0] == '_concat':

> -        pending = [tree]

> -        l = []

> -        while pending:

> -            e = pending.pop()

> -            if e[0] == '_concat':

> -                pending.extend(reversed(e[1:]))

> -            elif e[0] in ('string', 'symbol'):

> -                l.append(e[1])

> -            else:

> -                msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])

> -                raise error.ParseError(msg)

> -        return ('string', ''.join(l))

> -    else:

> -        return tuple(foldconcat(t) for t in tree)

> -

> -def parse(spec, lookup=None):

> -    return _parsewith(spec, lookup=lookup)

> -

> -def posttreebuilthook(tree, repo):

> -    # hook for extensions to execute code on the optimized tree

> -    pass

> -

> -def match(ui, spec, repo=None, order=defineorder):

> -    """Create a matcher for a single revision spec

> -

> -    If order=followorder, a matcher takes the ordering specified by the input

> -    set.

> -    """

> -    return matchany(ui, [spec], repo=repo, order=order)

> -

> -def matchany(ui, specs, repo=None, order=defineorder):

> -    """Create a matcher that will include any revisions matching one of the

> -    given specs

> -

> -    If order=followorder, a matcher takes the ordering specified by the input

> -    set.

> -    """

> -    if not specs:

> -        def mfunc(repo, subset=None):

> -            return baseset()

> -        return mfunc

> -    if not all(specs):

> -        raise error.ParseError(_("empty query"))

> -    lookup = None

> -    if repo:

> -        lookup = repo.__contains__

> -    if len(specs) == 1:

> -        tree = parse(specs[0], lookup)

> -    else:

> -        tree = ('or', ('list',) + tuple(parse(s, lookup) for s in specs))

> -

> -    if ui:

> -        tree = expandaliases(ui, tree)

> -    tree = foldconcat(tree)

> -    tree = analyze(tree, order)

> -    tree = optimize(tree)

> -    posttreebuilthook(tree, repo)

> -    return makematcher(tree)

> -

> -def makematcher(tree):

> -    """Create a matcher from an evaluatable tree"""

> -    def mfunc(repo, subset=None):

> -        if subset is None:

> -            subset = fullreposet(repo)

> -        if util.safehasattr(subset, 'isascending'):

> -            result = getset(repo, subset, tree)

> -        else:

> -            result = getset(repo, baseset(subset), tree)

> -        return result

> -    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 "_intlist('%s')" % "\0".join(str(int(a)) for a in s)

> -        elif t == 's':

> -            return "_list('%s')" % "\0".join(s)

> -        elif t == 'n':

> -            return "_hexlist('%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 error.Abort(_('unexpected revspec format character %s')

> -                                  % d)

> -        else:

> -            ret += c

> -        pos += 1

> -

> -    return ret

> -

> -def prettyformat(tree):

> -    return parser.prettyformat(tree, ('string', 'symbol'))

> -

> -def depth(tree):

> -    if isinstance(tree, tuple):

> -        return max(map(depth, tree)) + 1

> -    else:

> -        return 0

> -

> -def funcsused(tree):

> -    if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):

> -        return set()

> -    else:

> -        funcs = set()

> -        for s in tree[1:]:

> -            funcs |= funcsused(s)

> -        if tree[0] == 'func':

> -            funcs.add(tree[1][1])

> -        return funcs

> -

>   def _formatsetrepr(r):

>       """Format an optional printable representation of a set

>   

> @@ -3887,7 +958,7 @@ class fullreposet(spanset):

>           other.sort(reverse=self.isdescending())

>           return other

>   

> -def prettyformatset(revs):

> +def prettyformat(revs):

>       lines = []

>       rs = repr(revs)

>       p = 0

> @@ -3900,17 +971,3 @@ def prettyformatset(revs):

>           lines.append((l, rs[p:q].rstrip()))

>           p = q

>       return '\n'.join('  ' * l + s for l, s in lines)

> -

> -def loadpredicate(ui, extname, registrarobj):

> -    """Load revset predicates from specified registrarobj

> -    """

> -    for name, func in registrarobj._table.iteritems():

> -        symbols[name] = func

> -        if func._safe:

> -            safesymbols.add(name)

> -

> -# load built-in predicates explicitly to setup safesymbols

> -loadpredicate(None, None, predicate)

> -

> -# tell hggettext to extract docstrings from these functions:

> -i18nfunctions = symbols.values()

> diff --git a/tests/test-doctest.py b/tests/test-doctest.py

> --- a/tests/test-doctest.py

> +++ b/tests/test-doctest.py

> @@ -29,6 +29,7 @@ testmod('mercurial.patch')

>   testmod('mercurial.pathutil')

>   testmod('mercurial.parser')

>   testmod('mercurial.revset')

> +testmod('mercurial.smartset')

>   testmod('mercurial.store')

>   testmod('mercurial.subrepo')

>   testmod('mercurial.templatefilters')

> diff --git a/tests/test-revset.t b/tests/test-revset.t

> --- a/tests/test-revset.t

> +++ b/tests/test-revset.t

> @@ -40,6 +40,7 @@ these predicates use '\0' as a separator

>     >     cmdutil,

>     >     node as nodemod,

>     >     revset,

> +  >     smartset,

>     > )

>     > cmdtable = {}

>     > command = cmdutil.command(cmdtable)

> @@ -59,7 +60,7 @@ these predicates use '\0' as a separator

>     >     func = revset.match(ui, expr, repo)

>     >     revs = func(repo)

>     >     if ui.verbose:

> -  >         ui.note("* set:\n", revset.prettyformatset(revs), "\n")

> +  >         ui.note("* set:\n", smartset.prettyformat(revs), "\n")

>     >     for c in revs:

>     >         ui.write("%s\n" % c)

>     > EOF

>

> _______________________________________________

> Mercurial-devel mailing list

> Mercurial-devel@mercurial-scm.org

> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Sean Farley - Feb. 6, 2017, 7:27 p.m.
Yuya Nishihara <yuya@tcha.org> writes:

> # HG changeset patch
> # User Yuya Nishihara <yuya@tcha.org>
> # Date 1476606531 -32400
> #      Sun Oct 16 17:28:51 2016 +0900
> # Node ID a555e941ebe982cc22e15670bfe125017b3121e5
> # Parent  8d7e40524ae467b3201e264e3548681c52bb6492
> smartset: move set classes and related functions from revset module (API)
>
> These classes are pretty large and independent from revset computation.
>
>   2961 mercurial/revset.py
>    973 mercurial/smartset.py
>   3934 total
>
> revset.prettyformatset() is renamed to smartset.prettyformat(). Smartset
> classes are aliased since they are quite common in revset.py.

Love it. Plz moar cleanup!
via Mercurial-devel - Feb. 7, 2017, 10:53 p.m.
On Mon, Feb 6, 2017 at 11:27 AM, Sean Farley <sean@farley.io> wrote:
> Yuya Nishihara <yuya@tcha.org> writes:
>
>> # HG changeset patch
>> # User Yuya Nishihara <yuya@tcha.org>
>> # Date 1476606531 -32400
>> #      Sun Oct 16 17:28:51 2016 +0900
>> # Node ID a555e941ebe982cc22e15670bfe125017b3121e5
>> # Parent  8d7e40524ae467b3201e264e3548681c52bb6492
>> smartset: move set classes and related functions from revset module (API)

Queued (by Augie, I think), thanks.


>>
>> These classes are pretty large and independent from revset computation.
>>
>>   2961 mercurial/revset.py
>>    973 mercurial/smartset.py
>>   3934 total
>>
>> revset.prettyformatset() is renamed to smartset.prettyformat(). Smartset
>> classes are aliased since they are quite common in revset.py.
>
> Love it. Plz moar cleanup!
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Patch

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -59,6 +59,7 @@  from . import (
     revset,
     scmutil,
     server,
+    smartset,
     sshserver,
     sslutil,
     streamclone,
@@ -2804,8 +2805,8 @@  def debugrevspec(ui, repo, expr, **opts)
         arevs = revset.makematcher(treebystage['analyzed'])(repo)
         brevs = revset.makematcher(treebystage['optimized'])(repo)
         if ui.verbose:
-            ui.note(("* analyzed set:\n"), revset.prettyformatset(arevs), "\n")
-            ui.note(("* optimized set:\n"), revset.prettyformatset(brevs), "\n")
+            ui.note(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
+            ui.note(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
         arevs = list(arevs)
         brevs = list(brevs)
         if arevs == brevs:
@@ -2828,7 +2829,7 @@  def debugrevspec(ui, repo, expr, **opts)
     func = revset.makematcher(tree)
     revs = func(repo)
     if ui.verbose:
-        ui.note(("* set:\n"), revset.prettyformatset(revs), "\n")
+        ui.note(("* set:\n"), smartset.prettyformat(revs), "\n")
     for c in revs:
         ui.write("%s\n" % c)
 
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -26,9 +26,15 @@  from . import (
     pycompat,
     registrar,
     repoview,
+    smartset,
     util,
 )
 
+baseset = smartset.baseset
+generatorset = smartset.generatorset
+spanset = smartset.spanset
+fullreposet = smartset.fullreposet
+
 def _revancestors(repo, revs, followfirst):
     """Like revlog.ancestors(), but supports followfirst."""
     if followfirst:
@@ -2940,967 +2946,6 @@  def funcsused(tree):
             funcs.add(tree[1][1])
         return funcs
 
-def _formatsetrepr(r):
-    """Format an optional printable representation of a set
-
-    ========  =================================
-    type(r)   example
-    ========  =================================
-    tuple     ('<not %r>', other)
-    str       '<branch closed>'
-    callable  lambda: '<branch %r>' % sorted(b)
-    object    other
-    ========  =================================
-    """
-    if r is None:
-        return ''
-    elif isinstance(r, tuple):
-        return r[0] % r[1:]
-    elif isinstance(r, str):
-        return r
-    elif callable(r):
-        return r()
-    else:
-        return repr(r)
-
-class abstractsmartset(object):
-
-    def __nonzero__(self):
-        """True if the smartset is not empty"""
-        raise NotImplementedError()
-
-    def __contains__(self, rev):
-        """provide fast membership testing"""
-        raise NotImplementedError()
-
-    def __iter__(self):
-        """iterate the set in the order it is supposed to be iterated"""
-        raise NotImplementedError()
-
-    # Attributes containing a function to perform a fast iteration in a given
-    # direction. A smartset can have none, one, or both defined.
-    #
-    # Default value is None instead of a function returning None to avoid
-    # initializing an iterator just for testing if a fast method exists.
-    fastasc = None
-    fastdesc = None
-
-    def isascending(self):
-        """True if the set will iterate in ascending order"""
-        raise NotImplementedError()
-
-    def isdescending(self):
-        """True if the set will iterate in descending order"""
-        raise NotImplementedError()
-
-    def istopo(self):
-        """True if the set will iterate in topographical order"""
-        raise NotImplementedError()
-
-    def min(self):
-        """return the minimum element in the set"""
-        if self.fastasc is None:
-            v = min(self)
-        else:
-            for v in self.fastasc():
-                break
-            else:
-                raise ValueError('arg is an empty sequence')
-        self.min = lambda: v
-        return v
-
-    def max(self):
-        """return the maximum element in the set"""
-        if self.fastdesc is None:
-            return max(self)
-        else:
-            for v in self.fastdesc():
-                break
-            else:
-                raise ValueError('arg is an empty sequence')
-        self.max = lambda: v
-        return v
-
-    def first(self):
-        """return the first element in the set (user iteration perspective)
-
-        Return None if the set is empty"""
-        raise NotImplementedError()
-
-    def last(self):
-        """return the last element in the set (user iteration perspective)
-
-        Return None if the set is empty"""
-        raise NotImplementedError()
-
-    def __len__(self):
-        """return the length of the smartsets
-
-        This can be expensive on smartset that could be lazy otherwise."""
-        raise NotImplementedError()
-
-    def reverse(self):
-        """reverse the expected iteration order"""
-        raise NotImplementedError()
-
-    def sort(self, reverse=True):
-        """get the set to iterate in an ascending or descending order"""
-        raise NotImplementedError()
-
-    def __and__(self, other):
-        """Returns a new object with the intersection of the two collections.
-
-        This is part of the mandatory API for smartset."""
-        if isinstance(other, fullreposet):
-            return self
-        return self.filter(other.__contains__, condrepr=other, cache=False)
-
-    def __add__(self, other):
-        """Returns a new object with the union of the two collections.
-
-        This is part of the mandatory API for smartset."""
-        return addset(self, other)
-
-    def __sub__(self, other):
-        """Returns a new object with the substraction of the two collections.
-
-        This is part of the mandatory API for smartset."""
-        c = other.__contains__
-        return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
-                           cache=False)
-
-    def filter(self, condition, condrepr=None, cache=True):
-        """Returns this smartset filtered by condition as a new smartset.
-
-        `condition` is a callable which takes a revision number and returns a
-        boolean. Optional `condrepr` provides a printable representation of
-        the given `condition`.
-
-        This is part of the mandatory API for smartset."""
-        # builtin cannot be cached. but do not needs to
-        if cache and util.safehasattr(condition, 'func_code'):
-            condition = util.cachefunc(condition)
-        return filteredset(self, condition, condrepr)
-
-class baseset(abstractsmartset):
-    """Basic data structure that represents a revset and contains the basic
-    operation that it should be able to perform.
-
-    Every method in this class should be implemented by any smartset class.
-    """
-    def __init__(self, data=(), datarepr=None, istopo=False):
-        """
-        datarepr: a tuple of (format, obj, ...), a function or an object that
-                  provides a printable representation of the given data.
-        """
-        self._ascending = None
-        self._istopo = istopo
-        if not isinstance(data, list):
-            if isinstance(data, set):
-                self._set = data
-                # set has no order we pick one for stability purpose
-                self._ascending = True
-            data = list(data)
-        self._list = data
-        self._datarepr = datarepr
-
-    @util.propertycache
-    def _set(self):
-        return set(self._list)
-
-    @util.propertycache
-    def _asclist(self):
-        asclist = self._list[:]
-        asclist.sort()
-        return asclist
-
-    def __iter__(self):
-        if self._ascending is None:
-            return iter(self._list)
-        elif self._ascending:
-            return iter(self._asclist)
-        else:
-            return reversed(self._asclist)
-
-    def fastasc(self):
-        return iter(self._asclist)
-
-    def fastdesc(self):
-        return reversed(self._asclist)
-
-    @util.propertycache
-    def __contains__(self):
-        return self._set.__contains__
-
-    def __nonzero__(self):
-        return bool(self._list)
-
-    def sort(self, reverse=False):
-        self._ascending = not bool(reverse)
-        self._istopo = False
-
-    def reverse(self):
-        if self._ascending is None:
-            self._list.reverse()
-        else:
-            self._ascending = not self._ascending
-        self._istopo = False
-
-    def __len__(self):
-        return len(self._list)
-
-    def isascending(self):
-        """Returns True if the collection is ascending order, False if not.
-
-        This is part of the mandatory API for smartset."""
-        if len(self) <= 1:
-            return True
-        return self._ascending is not None and self._ascending
-
-    def isdescending(self):
-        """Returns True if the collection is descending order, False if not.
-
-        This is part of the mandatory API for smartset."""
-        if len(self) <= 1:
-            return True
-        return self._ascending is not None and not self._ascending
-
-    def istopo(self):
-        """Is the collection is in topographical order or not.
-
-        This is part of the mandatory API for smartset."""
-        if len(self) <= 1:
-            return True
-        return self._istopo
-
-    def first(self):
-        if self:
-            if self._ascending is None:
-                return self._list[0]
-            elif self._ascending:
-                return self._asclist[0]
-            else:
-                return self._asclist[-1]
-        return None
-
-    def last(self):
-        if self:
-            if self._ascending is None:
-                return self._list[-1]
-            elif self._ascending:
-                return self._asclist[-1]
-            else:
-                return self._asclist[0]
-        return None
-
-    def __repr__(self):
-        d = {None: '', False: '-', True: '+'}[self._ascending]
-        s = _formatsetrepr(self._datarepr)
-        if not s:
-            l = self._list
-            # if _list has been built from a set, it might have a different
-            # order from one python implementation to another.
-            # We fallback to the sorted version for a stable output.
-            if self._ascending is not None:
-                l = self._asclist
-            s = repr(l)
-        return '<%s%s %s>' % (type(self).__name__, d, s)
-
-class filteredset(abstractsmartset):
-    """Duck type for baseset class which iterates lazily over the revisions in
-    the subset and contains a function which tests for membership in the
-    revset
-    """
-    def __init__(self, subset, condition=lambda x: True, condrepr=None):
-        """
-        condition: a function that decide whether a revision in the subset
-                   belongs to the revset or not.
-        condrepr: a tuple of (format, obj, ...), a function or an object that
-                  provides a printable representation of the given condition.
-        """
-        self._subset = subset
-        self._condition = condition
-        self._condrepr = condrepr
-
-    def __contains__(self, x):
-        return x in self._subset and self._condition(x)
-
-    def __iter__(self):
-        return self._iterfilter(self._subset)
-
-    def _iterfilter(self, it):
-        cond = self._condition
-        for x in it:
-            if cond(x):
-                yield x
-
-    @property
-    def fastasc(self):
-        it = self._subset.fastasc
-        if it is None:
-            return None
-        return lambda: self._iterfilter(it())
-
-    @property
-    def fastdesc(self):
-        it = self._subset.fastdesc
-        if it is None:
-            return None
-        return lambda: self._iterfilter(it())
-
-    def __nonzero__(self):
-        fast = None
-        candidates = [self.fastasc if self.isascending() else None,
-                      self.fastdesc if self.isdescending() else None,
-                      self.fastasc,
-                      self.fastdesc]
-        for candidate in candidates:
-            if candidate is not None:
-                fast = candidate
-                break
-
-        if fast is not None:
-            it = fast()
-        else:
-            it = self
-
-        for r in it:
-            return True
-        return False
-
-    def __len__(self):
-        # Basic implementation to be changed in future patches.
-        # until this gets improved, we use generator expression
-        # here, since list comprehensions are free to call __len__ again
-        # causing infinite recursion
-        l = baseset(r for r in self)
-        return len(l)
-
-    def sort(self, reverse=False):
-        self._subset.sort(reverse=reverse)
-
-    def reverse(self):
-        self._subset.reverse()
-
-    def isascending(self):
-        return self._subset.isascending()
-
-    def isdescending(self):
-        return self._subset.isdescending()
-
-    def istopo(self):
-        return self._subset.istopo()
-
-    def first(self):
-        for x in self:
-            return x
-        return None
-
-    def last(self):
-        it = None
-        if self.isascending():
-            it = self.fastdesc
-        elif self.isdescending():
-            it = self.fastasc
-        if it is not None:
-            for x in it():
-                return x
-            return None #empty case
-        else:
-            x = None
-            for x in self:
-                pass
-            return x
-
-    def __repr__(self):
-        xs = [repr(self._subset)]
-        s = _formatsetrepr(self._condrepr)
-        if s:
-            xs.append(s)
-        return '<%s %s>' % (type(self).__name__, ', '.join(xs))
-
-def _iterordered(ascending, iter1, iter2):
-    """produce an ordered iteration from two iterators with the same order
-
-    The ascending is used to indicated the iteration direction.
-    """
-    choice = max
-    if ascending:
-        choice = min
-
-    val1 = None
-    val2 = None
-    try:
-        # Consume both iterators in an ordered way until one is empty
-        while True:
-            if val1 is None:
-                val1 = next(iter1)
-            if val2 is None:
-                val2 = next(iter2)
-            n = choice(val1, val2)
-            yield n
-            if val1 == n:
-                val1 = None
-            if val2 == n:
-                val2 = None
-    except StopIteration:
-        # Flush any remaining values and consume the other one
-        it = iter2
-        if val1 is not None:
-            yield val1
-            it = iter1
-        elif val2 is not None:
-            # might have been equality and both are empty
-            yield val2
-        for val in it:
-            yield val
-
-class addset(abstractsmartset):
-    """Represent the addition of two sets
-
-    Wrapper structure for lazily adding two structures without losing much
-    performance on the __contains__ method
-
-    If the ascending attribute is set, that means the two structures are
-    ordered in either an ascending or descending way. Therefore, we can add
-    them maintaining the order by iterating over both at the same time
-
-    >>> xs = baseset([0, 3, 2])
-    >>> ys = baseset([5, 2, 4])
-
-    >>> rs = addset(xs, ys)
-    >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
-    (True, True, False, True, 0, 4)
-    >>> rs = addset(xs, baseset([]))
-    >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
-    (True, True, False, 0, 2)
-    >>> rs = addset(baseset([]), baseset([]))
-    >>> bool(rs), 0 in rs, rs.first(), rs.last()
-    (False, False, None, None)
-
-    iterate unsorted:
-    >>> rs = addset(xs, ys)
-    >>> # (use generator because pypy could call len())
-    >>> list(x for x in rs)  # without _genlist
-    [0, 3, 2, 5, 4]
-    >>> assert not rs._genlist
-    >>> len(rs)
-    5
-    >>> [x for x in rs]  # with _genlist
-    [0, 3, 2, 5, 4]
-    >>> assert rs._genlist
-
-    iterate ascending:
-    >>> rs = addset(xs, ys, ascending=True)
-    >>> # (use generator because pypy could call len())
-    >>> list(x for x in rs), list(x for x in rs.fastasc())  # without _asclist
-    ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
-    >>> assert not rs._asclist
-    >>> len(rs)
-    5
-    >>> [x for x in rs], [x for x in rs.fastasc()]
-    ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
-    >>> assert rs._asclist
-
-    iterate descending:
-    >>> rs = addset(xs, ys, ascending=False)
-    >>> # (use generator because pypy could call len())
-    >>> list(x for x in rs), list(x for x in rs.fastdesc())  # without _asclist
-    ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
-    >>> assert not rs._asclist
-    >>> len(rs)
-    5
-    >>> [x for x in rs], [x for x in rs.fastdesc()]
-    ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
-    >>> assert rs._asclist
-
-    iterate ascending without fastasc:
-    >>> rs = addset(xs, generatorset(ys), ascending=True)
-    >>> assert rs.fastasc is None
-    >>> [x for x in rs]
-    [0, 2, 3, 4, 5]
-
-    iterate descending without fastdesc:
-    >>> rs = addset(generatorset(xs), ys, ascending=False)
-    >>> assert rs.fastdesc is None
-    >>> [x for x in rs]
-    [5, 4, 3, 2, 0]
-    """
-    def __init__(self, revs1, revs2, ascending=None):
-        self._r1 = revs1
-        self._r2 = revs2
-        self._iter = None
-        self._ascending = ascending
-        self._genlist = None
-        self._asclist = None
-
-    def __len__(self):
-        return len(self._list)
-
-    def __nonzero__(self):
-        return bool(self._r1) or bool(self._r2)
-
-    @util.propertycache
-    def _list(self):
-        if not self._genlist:
-            self._genlist = baseset(iter(self))
-        return self._genlist
-
-    def __iter__(self):
-        """Iterate over both collections without repeating elements
-
-        If the ascending attribute is not set, iterate over the first one and
-        then over the second one checking for membership on the first one so we
-        dont yield any duplicates.
-
-        If the ascending attribute is set, iterate over both collections at the
-        same time, yielding only one value at a time in the given order.
-        """
-        if self._ascending is None:
-            if self._genlist:
-                return iter(self._genlist)
-            def arbitraryordergen():
-                for r in self._r1:
-                    yield r
-                inr1 = self._r1.__contains__
-                for r in self._r2:
-                    if not inr1(r):
-                        yield r
-            return arbitraryordergen()
-        # try to use our own fast iterator if it exists
-        self._trysetasclist()
-        if self._ascending:
-            attr = 'fastasc'
-        else:
-            attr = 'fastdesc'
-        it = getattr(self, attr)
-        if it is not None:
-            return it()
-        # maybe half of the component supports fast
-        # get iterator for _r1
-        iter1 = getattr(self._r1, attr)
-        if iter1 is None:
-            # let's avoid side effect (not sure it matters)
-            iter1 = iter(sorted(self._r1, reverse=not self._ascending))
-        else:
-            iter1 = iter1()
-        # get iterator for _r2
-        iter2 = getattr(self._r2, attr)
-        if iter2 is None:
-            # let's avoid side effect (not sure it matters)
-            iter2 = iter(sorted(self._r2, reverse=not self._ascending))
-        else:
-            iter2 = iter2()
-        return _iterordered(self._ascending, iter1, iter2)
-
-    def _trysetasclist(self):
-        """populate the _asclist attribute if possible and necessary"""
-        if self._genlist is not None and self._asclist is None:
-            self._asclist = sorted(self._genlist)
-
-    @property
-    def fastasc(self):
-        self._trysetasclist()
-        if self._asclist is not None:
-            return self._asclist.__iter__
-        iter1 = self._r1.fastasc
-        iter2 = self._r2.fastasc
-        if None in (iter1, iter2):
-            return None
-        return lambda: _iterordered(True, iter1(), iter2())
-
-    @property
-    def fastdesc(self):
-        self._trysetasclist()
-        if self._asclist is not None:
-            return self._asclist.__reversed__
-        iter1 = self._r1.fastdesc
-        iter2 = self._r2.fastdesc
-        if None in (iter1, iter2):
-            return None
-        return lambda: _iterordered(False, iter1(), iter2())
-
-    def __contains__(self, x):
-        return x in self._r1 or x in self._r2
-
-    def sort(self, reverse=False):
-        """Sort the added set
-
-        For this we use the cached list with all the generated values and if we
-        know they are ascending or descending we can sort them in a smart way.
-        """
-        self._ascending = not reverse
-
-    def isascending(self):
-        return self._ascending is not None and self._ascending
-
-    def isdescending(self):
-        return self._ascending is not None and not self._ascending
-
-    def istopo(self):
-        # not worth the trouble asserting if the two sets combined are still
-        # in topographical order. Use the sort() predicate to explicitly sort
-        # again instead.
-        return False
-
-    def reverse(self):
-        if self._ascending is None:
-            self._list.reverse()
-        else:
-            self._ascending = not self._ascending
-
-    def first(self):
-        for x in self:
-            return x
-        return None
-
-    def last(self):
-        self.reverse()
-        val = self.first()
-        self.reverse()
-        return val
-
-    def __repr__(self):
-        d = {None: '', False: '-', True: '+'}[self._ascending]
-        return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
-
-class generatorset(abstractsmartset):
-    """Wrap a generator for lazy iteration
-
-    Wrapper structure for generators that provides lazy membership and can
-    be iterated more than once.
-    When asked for membership it generates values until either it finds the
-    requested one or has gone through all the elements in the generator
-    """
-    def __init__(self, gen, iterasc=None):
-        """
-        gen: a generator producing the values for the generatorset.
-        """
-        self._gen = gen
-        self._asclist = None
-        self._cache = {}
-        self._genlist = []
-        self._finished = False
-        self._ascending = True
-        if iterasc is not None:
-            if iterasc:
-                self.fastasc = self._iterator
-                self.__contains__ = self._asccontains
-            else:
-                self.fastdesc = self._iterator
-                self.__contains__ = self._desccontains
-
-    def __nonzero__(self):
-        # Do not use 'for r in self' because it will enforce the iteration
-        # order (default ascending), possibly unrolling a whole descending
-        # iterator.
-        if self._genlist:
-            return True
-        for r in self._consumegen():
-            return True
-        return False
-
-    def __contains__(self, x):
-        if x in self._cache:
-            return self._cache[x]
-
-        # Use new values only, as existing values would be cached.
-        for l in self._consumegen():
-            if l == x:
-                return True
-
-        self._cache[x] = False
-        return False
-
-    def _asccontains(self, x):
-        """version of contains optimised for ascending generator"""
-        if x in self._cache:
-            return self._cache[x]
-
-        # Use new values only, as existing values would be cached.
-        for l in self._consumegen():
-            if l == x:
-                return True
-            if l > x:
-                break
-
-        self._cache[x] = False
-        return False
-
-    def _desccontains(self, x):
-        """version of contains optimised for descending generator"""
-        if x in self._cache:
-            return self._cache[x]
-
-        # Use new values only, as existing values would be cached.
-        for l in self._consumegen():
-            if l == x:
-                return True
-            if l < x:
-                break
-
-        self._cache[x] = False
-        return False
-
-    def __iter__(self):
-        if self._ascending:
-            it = self.fastasc
-        else:
-            it = self.fastdesc
-        if it is not None:
-            return it()
-        # we need to consume the iterator
-        for x in self._consumegen():
-            pass
-        # recall the same code
-        return iter(self)
-
-    def _iterator(self):
-        if self._finished:
-            return iter(self._genlist)
-
-        # We have to use this complex iteration strategy to allow multiple
-        # iterations at the same time. We need to be able to catch revision
-        # removed from _consumegen and added to genlist in another instance.
-        #
-        # Getting rid of it would provide an about 15% speed up on this
-        # iteration.
-        genlist = self._genlist
-        nextrev = self._consumegen().next
-        _len = len # cache global lookup
-        def gen():
-            i = 0
-            while True:
-                if i < _len(genlist):
-                    yield genlist[i]
-                else:
-                    yield nextrev()
-                i += 1
-        return gen()
-
-    def _consumegen(self):
-        cache = self._cache
-        genlist = self._genlist.append
-        for item in self._gen:
-            cache[item] = True
-            genlist(item)
-            yield item
-        if not self._finished:
-            self._finished = True
-            asc = self._genlist[:]
-            asc.sort()
-            self._asclist = asc
-            self.fastasc = asc.__iter__
-            self.fastdesc = asc.__reversed__
-
-    def __len__(self):
-        for x in self._consumegen():
-            pass
-        return len(self._genlist)
-
-    def sort(self, reverse=False):
-        self._ascending = not reverse
-
-    def reverse(self):
-        self._ascending = not self._ascending
-
-    def isascending(self):
-        return self._ascending
-
-    def isdescending(self):
-        return not self._ascending
-
-    def istopo(self):
-        # not worth the trouble asserting if the two sets combined are still
-        # in topographical order. Use the sort() predicate to explicitly sort
-        # again instead.
-        return False
-
-    def first(self):
-        if self._ascending:
-            it = self.fastasc
-        else:
-            it = self.fastdesc
-        if it is None:
-            # we need to consume all and try again
-            for x in self._consumegen():
-                pass
-            return self.first()
-        return next(it(), None)
-
-    def last(self):
-        if self._ascending:
-            it = self.fastdesc
-        else:
-            it = self.fastasc
-        if it is None:
-            # we need to consume all and try again
-            for x in self._consumegen():
-                pass
-            return self.first()
-        return next(it(), None)
-
-    def __repr__(self):
-        d = {False: '-', True: '+'}[self._ascending]
-        return '<%s%s>' % (type(self).__name__, d)
-
-class spanset(abstractsmartset):
-    """Duck type for baseset class which represents a range of revisions and
-    can work lazily and without having all the range in memory
-
-    Note that spanset(x, y) behave almost like xrange(x, y) except for two
-    notable points:
-    - when x < y it will be automatically descending,
-    - revision filtered with this repoview will be skipped.
-
-    """
-    def __init__(self, repo, start=0, end=None):
-        """
-        start: first revision included the set
-               (default to 0)
-        end:   first revision excluded (last+1)
-               (default to len(repo)
-
-        Spanset will be descending if `end` < `start`.
-        """
-        if end is None:
-            end = len(repo)
-        self._ascending = start <= end
-        if not self._ascending:
-            start, end = end + 1, start +1
-        self._start = start
-        self._end = end
-        self._hiddenrevs = repo.changelog.filteredrevs
-
-    def sort(self, reverse=False):
-        self._ascending = not reverse
-
-    def reverse(self):
-        self._ascending = not self._ascending
-
-    def istopo(self):
-        # not worth the trouble asserting if the two sets combined are still
-        # in topographical order. Use the sort() predicate to explicitly sort
-        # again instead.
-        return False
-
-    def _iterfilter(self, iterrange):
-        s = self._hiddenrevs
-        for r in iterrange:
-            if r not in s:
-                yield r
-
-    def __iter__(self):
-        if self._ascending:
-            return self.fastasc()
-        else:
-            return self.fastdesc()
-
-    def fastasc(self):
-        iterrange = xrange(self._start, self._end)
-        if self._hiddenrevs:
-            return self._iterfilter(iterrange)
-        return iter(iterrange)
-
-    def fastdesc(self):
-        iterrange = xrange(self._end - 1, self._start - 1, -1)
-        if self._hiddenrevs:
-            return self._iterfilter(iterrange)
-        return iter(iterrange)
-
-    def __contains__(self, rev):
-        hidden = self._hiddenrevs
-        return ((self._start <= rev < self._end)
-                and not (hidden and rev in hidden))
-
-    def __nonzero__(self):
-        for r in self:
-            return True
-        return False
-
-    def __len__(self):
-        if not self._hiddenrevs:
-            return abs(self._end - self._start)
-        else:
-            count = 0
-            start = self._start
-            end = self._end
-            for rev in self._hiddenrevs:
-                if (end < rev <= start) or (start <= rev < end):
-                    count += 1
-            return abs(self._end - self._start) - count
-
-    def isascending(self):
-        return self._ascending
-
-    def isdescending(self):
-        return not self._ascending
-
-    def first(self):
-        if self._ascending:
-            it = self.fastasc
-        else:
-            it = self.fastdesc
-        for x in it():
-            return x
-        return None
-
-    def last(self):
-        if self._ascending:
-            it = self.fastdesc
-        else:
-            it = self.fastasc
-        for x in it():
-            return x
-        return None
-
-    def __repr__(self):
-        d = {False: '-', True: '+'}[self._ascending]
-        return '<%s%s %d:%d>' % (type(self).__name__, d,
-                                 self._start, self._end - 1)
-
-class fullreposet(spanset):
-    """a set containing all revisions in the repo
-
-    This class exists to host special optimization and magic to handle virtual
-    revisions such as "null".
-    """
-
-    def __init__(self, repo):
-        super(fullreposet, self).__init__(repo)
-
-    def __and__(self, other):
-        """As self contains the whole repo, all of the other set should also be
-        in self. Therefore `self & other = other`.
-
-        This boldly assumes the other contains valid revs only.
-        """
-        # other not a smartset, make is so
-        if not util.safehasattr(other, 'isascending'):
-            # filter out hidden revision
-            # (this boldly assumes all smartset are pure)
-            #
-            # `other` was used with "&", let's assume this is a set like
-            # object.
-            other = baseset(other - self._hiddenrevs)
-
-        other.sort(reverse=self.isdescending())
-        return other
-
-def prettyformatset(revs):
-    lines = []
-    rs = repr(revs)
-    p = 0
-    while p < len(rs):
-        q = rs.find('<', p + 1)
-        if q < 0:
-            q = len(rs)
-        l = rs.count('<', 0, p) - rs.count('>', 0, p)
-        assert l >= 0
-        lines.append((l, rs[p:q].rstrip()))
-        p = q
-    return '\n'.join('  ' * l + s for l, s in lines)
-
 def loadpredicate(ui, extname, registrarobj):
     """Load revset predicates from specified registrarobj
     """
diff --git a/mercurial/revset.py b/mercurial/smartset.py
copy from mercurial/revset.py
copy to mercurial/smartset.py
--- a/mercurial/revset.py
+++ b/mercurial/smartset.py
@@ -1,4 +1,4 @@ 
-# revset.py - revision set queries for mercurial
+# smartset.py - data structure for revision set
 #
 # Copyright 2010 Matt Mackall <mpm@selenic.com>
 #
@@ -7,2939 +7,10 @@ 
 
 from __future__ import absolute_import
 
-import heapq
-import re
-import string
-
-from .i18n import _
 from . import (
-    destutil,
-    encoding,
-    error,
-    hbisect,
-    match as matchmod,
-    node,
-    obsolete as obsmod,
-    parser,
-    pathutil,
-    phases,
-    pycompat,
-    registrar,
-    repoview,
     util,
 )
 
-def _revancestors(repo, revs, followfirst):
-    """Like revlog.ancestors(), but supports followfirst."""
-    if followfirst:
-        cut = 1
-    else:
-        cut = None
-    cl = repo.changelog
-
-    def iterate():
-        revs.sort(reverse=True)
-        irevs = iter(revs)
-        h = []
-
-        inputrev = next(irevs, None)
-        if inputrev is not None:
-            heapq.heappush(h, -inputrev)
-
-        seen = set()
-        while h:
-            current = -heapq.heappop(h)
-            if current == inputrev:
-                inputrev = next(irevs, None)
-                if inputrev is not None:
-                    heapq.heappush(h, -inputrev)
-            if current not in seen:
-                seen.add(current)
-                yield current
-                for parent in cl.parentrevs(current)[:cut]:
-                    if parent != node.nullrev:
-                        heapq.heappush(h, -parent)
-
-    return generatorset(iterate(), iterasc=False)
-
-def _revdescendants(repo, revs, followfirst):
-    """Like revlog.descendants() but supports followfirst."""
-    if followfirst:
-        cut = 1
-    else:
-        cut = None
-
-    def iterate():
-        cl = repo.changelog
-        # XXX this should be 'parentset.min()' assuming 'parentset' is a
-        # smartset (and if it is not, it should.)
-        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
-        else:
-            seen = set(revs)
-            for i in cl.revs(first + 1):
-                for x in cl.parentrevs(i)[:cut]:
-                    if x != nullrev and x in seen:
-                        seen.add(i)
-                        yield i
-                        break
-
-    return generatorset(iterate(), iterasc=True)
-
-def _reachablerootspure(repo, minroot, roots, heads, includepath):
-    """return (heads(::<roots> and ::<heads>))
-
-    If includepath is True, return (<roots>::<heads>)."""
-    if not roots:
-        return []
-    parentrevs = repo.changelog.parentrevs
-    roots = set(roots)
-    visit = list(heads)
-    reachable = set()
-    seen = {}
-    # prefetch all the things! (because python is slow)
-    reached = reachable.add
-    dovisit = visit.append
-    nextvisit = visit.pop
-    # open-code the post-order traversal due to the tiny size of
-    # sys.getrecursionlimit()
-    while visit:
-        rev = nextvisit()
-        if rev in roots:
-            reached(rev)
-            if not includepath:
-                continue
-        parents = parentrevs(rev)
-        seen[rev] = parents
-        for parent in parents:
-            if parent >= minroot and parent not in seen:
-                dovisit(parent)
-    if not reachable:
-        return baseset()
-    if not includepath:
-        return reachable
-    for rev in sorted(seen):
-        for parent in seen[rev]:
-            if parent in reachable:
-                reached(rev)
-    return reachable
-
-def reachableroots(repo, roots, heads, includepath=False):
-    """return (heads(::<roots> and ::<heads>))
-
-    If includepath is True, return (<roots>::<heads>)."""
-    if not roots:
-        return baseset()
-    minroot = roots.min()
-    roots = list(roots)
-    heads = list(heads)
-    try:
-        revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
-    except AttributeError:
-        revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
-    revs = baseset(revs)
-    revs.sort()
-    return revs
-
-elements = {
-    # token-type: binding-strength, primary, prefix, infix, suffix
-    "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
-    "##": (20, None, None, ("_concat", 20), None),
-    "~": (18, None, None, ("ancestor", 18), None),
-    "^": (18, None, None, ("parent", 18), "parentpost"),
-    "-": (5, None, ("negate", 19), ("minus", 5), None),
-    "::": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
-    "..": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
-    ":": (15, "rangeall", ("rangepre", 15), ("range", 15), "rangepost"),
-    "not": (10, None, ("not", 10), None, None),
-    "!": (10, None, ("not", 10), None, None),
-    "and": (5, None, None, ("and", 5), None),
-    "&": (5, None, None, ("and", 5), None),
-    "%": (5, None, None, ("only", 5), "onlypost"),
-    "or": (4, None, None, ("or", 4), None),
-    "|": (4, None, None, ("or", 4), None),
-    "+": (4, None, None, ("or", 4), None),
-    "=": (3, None, None, ("keyvalue", 3), None),
-    ",": (2, None, None, ("list", 2), None),
-    ")": (0, None, None, None, None),
-    "symbol": (0, "symbol", None, None, None),
-    "string": (0, "string", None, None, None),
-    "end": (0, None, None, None, None),
-}
-
-keywords = set(['and', 'or', 'not'])
-
-# default set of valid characters for the initial letter of symbols
-_syminitletters = set(
-    string.ascii_letters +
-    string.digits + pycompat.sysstr('._@')) | set(map(chr, xrange(128, 256)))
-
-# default set of valid characters for non-initial letters of symbols
-_symletters = _syminitletters | set(pycompat.sysstr('-/'))
-
-def tokenize(program, lookup=None, syminitletters=None, symletters=None):
-    '''
-    Parse a revset statement into a stream of tokens
-
-    ``syminitletters`` is the set of valid characters for the initial
-    letter of symbols.
-
-    By default, character ``c`` is recognized as valid for initial
-    letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
-
-    ``symletters`` is the set of valid characters for non-initial
-    letters of symbols.
-
-    By default, character ``c`` is recognized as valid for non-initial
-    letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
-
-    Check that @ is a valid unquoted token character (issue3686):
-    >>> list(tokenize("@::"))
-    [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
-
-    '''
-    if syminitletters is None:
-        syminitletters = _syminitletters
-    if symletters is None:
-        symletters = _symletters
-
-    if program and lookup:
-        # attempt to parse old-style ranges first to deal with
-        # things like old-tag which contain query metacharacters
-        parts = program.split(':', 1)
-        if all(lookup(sym) for sym in parts if sym):
-            if parts[0]:
-                yield ('symbol', parts[0], 0)
-            if len(parts) > 1:
-                s = len(parts[0])
-                yield (':', None, s)
-                if parts[1]:
-                    yield ('symbol', parts[1], s + 1)
-            yield ('end', None, len(program))
-            return
-
-    pos, l = 0, len(program)
-    while pos < l:
-        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 == '#' and program[pos:pos + 2] == '##': # look ahead carefully
-            yield ('##', None, pos)
-            pos += 1 # skip ahead
-        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
-            if c == 'r':
-                pos += 1
-                c = program[pos]
-                decode = lambda x: x
-            else:
-                decode = parser.unescapestr
-            pos += 1
-            s = pos
-            while pos < l: # find closing quote
-                d = program[pos]
-                if d == '\\': # skip over escaped characters
-                    pos += 2
-                    continue
-                if d == c:
-                    yield ('string', decode(program[s:pos]), s)
-                    break
-                pos += 1
-            else:
-                raise error.ParseError(_("unterminated string"), s)
-        # gather up a symbol/keyword
-        elif c in syminitletters:
-            s = pos
-            pos += 1
-            while pos < l: # find end of symbol
-                d = program[pos]
-                if d not in symletters:
-                    break
-                if d == '.' and program[pos - 1] == '.': # special case for ..
-                    pos -= 1
-                    break
-                pos += 1
-            sym = program[s:pos]
-            if sym in keywords: # operator keywords
-                yield (sym, None, s)
-            elif '-' in sym:
-                # some jerk gave us foo-bar-baz, try to check if it's a symbol
-                if lookup and lookup(sym):
-                    # looks like a real symbol
-                    yield ('symbol', sym, s)
-                else:
-                    # looks like an expression
-                    parts = sym.split('-')
-                    for p in parts[:-1]:
-                        if p: # possible consecutive -
-                            yield ('symbol', p, s)
-                        s += len(p)
-                        yield ('-', None, pos)
-                        s += 1
-                    if parts[-1]: # possible trailing -
-                        yield ('symbol', parts[-1], s)
-            else:
-                yield ('symbol', sym, s)
-            pos -= 1
-        else:
-            raise error.ParseError(_("syntax error in revset '%s'") %
-                                   program, pos)
-        pos += 1
-    yield ('end', None, pos)
-
-# helpers
-
-_notset = object()
-
-def getsymbol(x):
-    if x and x[0] == 'symbol':
-        return x[1]
-    raise error.ParseError(_('not a symbol'))
-
-def getstring(x, err):
-    if x and (x[0] == 'string' or x[0] == 'symbol'):
-        return x[1]
-    raise error.ParseError(err)
-
-def getinteger(x, err, default=_notset):
-    if not x and default is not _notset:
-        return default
-    try:
-        return int(getstring(x, err))
-    except ValueError:
-        raise error.ParseError(err)
-
-def getlist(x):
-    if not x:
-        return []
-    if x[0] == 'list':
-        return list(x[1:])
-    return [x]
-
-def getrange(x, err):
-    if not x:
-        raise error.ParseError(err)
-    op = x[0]
-    if op == 'range':
-        return x[1], x[2]
-    elif op == 'rangepre':
-        return None, x[1]
-    elif op == 'rangepost':
-        return x[1], None
-    elif op == 'rangeall':
-        return None, None
-    raise error.ParseError(err)
-
-def getargs(x, min, max, err):
-    l = getlist(x)
-    if len(l) < min or (max >= 0 and len(l) > max):
-        raise error.ParseError(err)
-    return l
-
-def getargsdict(x, funcname, keys):
-    return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
-                                keyvaluenode='keyvalue', keynode='symbol')
-
-def getset(repo, subset, x):
-    if not x:
-        raise error.ParseError(_("missing argument"))
-    s = methods[x[0]](repo, subset, *x[1:])
-    if util.safehasattr(s, 'isascending'):
-        return s
-    # else case should not happen, because all non-func are internal,
-    # ignoring for now.
-    if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
-        repo.ui.deprecwarn('revset "%s" uses list instead of smartset'
-                           % x[1][1],
-                           '3.9')
-    return baseset(s)
-
-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):
-    x = repo[x].rev()
-    if (x in subset
-        or x == node.nullrev and isinstance(subset, fullreposet)):
-        return baseset([x])
-    return baseset()
-
-def rangeset(repo, subset, x, y, order):
-    m = getset(repo, fullreposet(repo), x)
-    n = getset(repo, fullreposet(repo), y)
-
-    if not m or not n:
-        return baseset()
-    return _makerangeset(repo, subset, m.first(), n.last(), order)
-
-def rangeall(repo, subset, x, order):
-    assert x is None
-    return _makerangeset(repo, subset, 0, len(repo) - 1, order)
-
-def rangepre(repo, subset, y, order):
-    # ':y' can't be rewritten to '0:y' since '0' may be hidden
-    n = getset(repo, fullreposet(repo), y)
-    if not n:
-        return baseset()
-    return _makerangeset(repo, subset, 0, n.last(), order)
-
-def rangepost(repo, subset, x, order):
-    m = getset(repo, fullreposet(repo), x)
-    if not m:
-        return baseset()
-    return _makerangeset(repo, subset, m.first(), len(repo) - 1, order)
-
-def _makerangeset(repo, subset, m, n, order):
-    if m == n:
-        r = baseset([m])
-    elif n == node.wdirrev:
-        r = spanset(repo, m, len(repo)) + baseset([n])
-    elif m == node.wdirrev:
-        r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
-    elif m < n:
-        r = spanset(repo, m, n + 1)
-    else:
-        r = spanset(repo, m, n - 1)
-
-    if order == defineorder:
-        return r & subset
-    else:
-        # carrying the sorting over when possible would be more efficient
-        return subset & r
-
-def dagrange(repo, subset, x, y, order):
-    r = fullreposet(repo)
-    xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
-                         includepath=True)
-    return subset & xs
-
-def andset(repo, subset, x, y, order):
-    return getset(repo, getset(repo, subset, x), y)
-
-def differenceset(repo, subset, x, y, order):
-    return getset(repo, subset, x) - getset(repo, subset, y)
-
-def _orsetlist(repo, subset, xs):
-    assert xs
-    if len(xs) == 1:
-        return getset(repo, subset, xs[0])
-    p = len(xs) // 2
-    a = _orsetlist(repo, subset, xs[:p])
-    b = _orsetlist(repo, subset, xs[p:])
-    return a + b
-
-def orset(repo, subset, x, order):
-    xs = getlist(x)
-    if order == followorder:
-        # slow path to take the subset order
-        return subset & _orsetlist(repo, fullreposet(repo), xs)
-    else:
-        return _orsetlist(repo, subset, xs)
-
-def notset(repo, subset, x, order):
-    return subset - getset(repo, subset, x)
-
-def listset(repo, subset, *xs):
-    raise error.ParseError(_("can't use a list in this context"),
-                           hint=_('see hg help "revsets.x or y"'))
-
-def keyvaluepair(repo, subset, k, v):
-    raise error.ParseError(_("can't use a key-value pair in this context"))
-
-def func(repo, subset, a, b, order):
-    f = getsymbol(a)
-    if f in symbols:
-        func = symbols[f]
-        if getattr(func, '_takeorder', False):
-            return func(repo, subset, b, order)
-        return func(repo, subset, b)
-
-    keep = lambda fn: getattr(fn, '__doc__', None) is not None
-
-    syms = [s for (s, fn) in symbols.items() if keep(fn)]
-    raise error.UnknownIdentifier(f, syms)
-
-# functions
-
-# symbols are callables like:
-#   fn(repo, subset, x)
-# with:
-#   repo - current repository instance
-#   subset - of revisions to be examined
-#   x - argument in tree form
-symbols = {}
-
-# symbols which can't be used for a DoS attack for any given input
-# (e.g. those which accept regexes as plain strings shouldn't be included)
-# functions that just return a lot of changesets (like all) don't count here
-safesymbols = set()
-
-predicate = registrar.revsetpredicate()
-
-@predicate('_destupdate')
-def _destupdate(repo, subset, x):
-    # experimental revset for update destination
-    args = getargsdict(x, 'limit', 'clean check')
-    return subset & baseset([destutil.destupdate(repo, **args)[0]])
-
-@predicate('_destmerge')
-def _destmerge(repo, subset, x):
-    # experimental revset for merge destination
-    sourceset = None
-    if x is not None:
-        sourceset = getset(repo, fullreposet(repo), x)
-    return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
-
-@predicate('adds(pattern)', safe=True)
-def adds(repo, subset, x):
-    """Changesets that add a file matching pattern.
-
-    The pattern without explicit kind like ``glob:`` is expected to be
-    relative to the current directory and match against a file or a
-    directory.
-    """
-    # i18n: "adds" is a keyword
-    pat = getstring(x, _("adds requires a pattern"))
-    return checkstatus(repo, subset, pat, 1)
-
-@predicate('ancestor(*changeset)', safe=True)
-def ancestor(repo, subset, x):
-    """A greatest common ancestor of the changesets.
-
-    Accepts 0 or more changesets.
-    Will return empty list when passed no args.
-    Greatest common ancestor of a single changeset is that changeset.
-    """
-    # i18n: "ancestor" is a keyword
-    l = getlist(x)
-    rl = fullreposet(repo)
-    anc = None
-
-    # (getset(repo, rl, i) for i in l) generates a list of lists
-    for revs in (getset(repo, rl, i) for i in l):
-        for r in revs:
-            if anc is None:
-                anc = repo[r]
-            else:
-                anc = anc.ancestor(repo[r])
-
-    if anc is not None and anc.rev() in subset:
-        return baseset([anc.rev()])
-    return baseset()
-
-def _ancestors(repo, subset, x, followfirst=False):
-    heads = getset(repo, fullreposet(repo), x)
-    if not heads:
-        return baseset()
-    s = _revancestors(repo, heads, followfirst)
-    return subset & s
-
-@predicate('ancestors(set)', safe=True)
-def ancestors(repo, subset, x):
-    """Changesets that are ancestors of a changeset in set.
-    """
-    return _ancestors(repo, subset, x)
-
-@predicate('_firstancestors', safe=True)
-def _firstancestors(repo, subset, x):
-    # ``_firstancestors(set)``
-    # Like ``ancestors(set)`` but follows only the first parents.
-    return _ancestors(repo, subset, x, followfirst=True)
-
-def ancestorspec(repo, subset, x, n, order):
-    """``set~n``
-    Changesets that are the Nth ancestor (first parents only) of a changeset
-    in set.
-    """
-    n = getinteger(n, _("~ expects a number"))
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, fullreposet(repo), x):
-        for i in range(n):
-            r = cl.parentrevs(r)[0]
-        ps.add(r)
-    return subset & ps
-
-@predicate('author(string)', safe=True)
-def author(repo, subset, x):
-    """Alias for ``user(string)``.
-    """
-    # i18n: "author" is a keyword
-    n = getstring(x, _("author requires a string"))
-    kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
-    return subset.filter(lambda x: matcher(repo[x].user()),
-                         condrepr=('<user %r>', n))
-
-@predicate('bisect(string)', safe=True)
-def bisect(repo, subset, x):
-    """Changesets marked in the specified bisect status:
-
-    - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
-    - ``goods``, ``bads``      : csets topologically 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 subset & state
-
-# Backward-compatibility
-# - no help entry so that we do not advertise it any more
-@predicate('bisected', safe=True)
-def bisected(repo, subset, x):
-    return bisect(repo, subset, x)
-
-@predicate('bookmark([name])', safe=True)
-def bookmark(repo, subset, x):
-    """The named bookmark or all bookmarks.
-
-    Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
-    """
-    # 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'))
-        kind, pattern, matcher = util.stringmatcher(bm)
-        bms = set()
-        if kind == 'literal':
-            bmrev = repo._bookmarks.get(pattern, None)
-            if not bmrev:
-                raise error.RepoLookupError(_("bookmark '%s' does not exist")
-                                            % pattern)
-            bms.add(repo[bmrev].rev())
-        else:
-            matchrevs = set()
-            for name, bmrev in repo._bookmarks.iteritems():
-                if matcher(name):
-                    matchrevs.add(bmrev)
-            if not matchrevs:
-                raise error.RepoLookupError(_("no bookmarks exist"
-                                              " that match '%s'") % pattern)
-            for bmrev in matchrevs:
-                bms.add(repo[bmrev].rev())
-    else:
-        bms = set([repo[r].rev()
-                   for r in repo._bookmarks.values()])
-    bms -= set([node.nullrev])
-    return subset & bms
-
-@predicate('branch(string or set)', safe=True)
-def branch(repo, subset, x):
-    """
-    All changesets belonging to the given branch or the branches of the given
-    changesets.
-
-    Pattern matching is supported for `string`. See
-    :hg:`help revisions.patterns`.
-    """
-    getbi = repo.revbranchcache().branchinfo
-
-    try:
-        b = getstring(x, '')
-    except error.ParseError:
-        # not a string, but another revspec, e.g. tip()
-        pass
-    else:
-        kind, pattern, matcher = util.stringmatcher(b)
-        if kind == 'literal':
-            # note: falls through to the revspec case if no branch with
-            # this name exists and pattern kind is not specified explicitly
-            if pattern in repo.branchmap():
-                return subset.filter(lambda r: matcher(getbi(r)[0]),
-                                     condrepr=('<branch %r>', b))
-            if b.startswith('literal:'):
-                raise error.RepoLookupError(_("branch '%s' does not exist")
-                                            % pattern)
-        else:
-            return subset.filter(lambda r: matcher(getbi(r)[0]),
-                                 condrepr=('<branch %r>', b))
-
-    s = getset(repo, fullreposet(repo), x)
-    b = set()
-    for r in s:
-        b.add(getbi(r)[0])
-    c = s.__contains__
-    return subset.filter(lambda r: c(r) or getbi(r)[0] in b,
-                         condrepr=lambda: '<branch %r>' % sorted(b))
-
-@predicate('bumped()', safe=True)
-def bumped(repo, subset, x):
-    """Mutable changesets marked as successors of public changesets.
-
-    Only non-public and non-obsolete changesets can be `bumped`.
-    """
-    # i18n: "bumped" is a keyword
-    getargs(x, 0, 0, _("bumped takes no arguments"))
-    bumped = obsmod.getrevs(repo, 'bumped')
-    return subset & bumped
-
-@predicate('bundle()', safe=True)
-def bundle(repo, subset, x):
-    """Changesets in the bundle.
-
-    Bundle must be specified by the -R option."""
-
-    try:
-        bundlerevs = repo.changelog.bundlerevs
-    except AttributeError:
-        raise error.Abort(_("no bundle provided - specify with -R"))
-    return subset & bundlerevs
-
-def checkstatus(repo, subset, pat, field):
-    hasset = matchmod.patkind(pat) == 'set'
-
-    mcache = [None]
-    def matches(x):
-        c = repo[x]
-        if not mcache[0] or hasset:
-            mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
-        m = mcache[0]
-        fname = None
-        if not m.anypats() and len(m.files()) == 1:
-            fname = m.files()[0]
-        if fname is not None:
-            if fname not in c.files():
-                return False
-        else:
-            for f in c.files():
-                if m(f):
-                    break
-            else:
-                return False
-        files = repo.status(c.p1().node(), c.node())[field]
-        if fname is not None:
-            if fname in files:
-                return True
-        else:
-            for f in files:
-                if m(f):
-                    return True
-
-    return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
-
-def _children(repo, subset, parentset):
-    if not parentset:
-        return baseset()
-    cs = set()
-    pr = repo.changelog.parentrevs
-    minrev = parentset.min()
-    nullrev = node.nullrev
-    for r in subset:
-        if r <= minrev:
-            continue
-        p1, p2 = pr(r)
-        if p1 in parentset:
-            cs.add(r)
-        if p2 != nullrev and p2 in parentset:
-            cs.add(r)
-    return baseset(cs)
-
-@predicate('children(set)', safe=True)
-def children(repo, subset, x):
-    """Child changesets of changesets in set.
-    """
-    s = getset(repo, fullreposet(repo), x)
-    cs = _children(repo, subset, s)
-    return subset & cs
-
-@predicate('closed()', safe=True)
-def closed(repo, subset, x):
-    """Changeset is closed.
-    """
-    # i18n: "closed" is a keyword
-    getargs(x, 0, 0, _("closed takes no arguments"))
-    return subset.filter(lambda r: repo[r].closesbranch(),
-                         condrepr='<branch closed>')
-
-@predicate('contains(pattern)')
-def contains(repo, subset, x):
-    """The revision's manifest contains a file matching pattern (but might not
-    modify it). See :hg:`help patterns` for information about file patterns.
-
-    The pattern without explicit kind like ``glob:`` is expected to be
-    relative to the current directory and match against a file exactly
-    for efficiency.
-    """
-    # i18n: "contains" is a keyword
-    pat = getstring(x, _("contains requires a pattern"))
-
-    def matches(x):
-        if not matchmod.patkind(pat):
-            pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
-            if pats in repo[x]:
-                return True
-        else:
-            c = repo[x]
-            m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
-            for f in c.manifest():
-                if m(f):
-                    return True
-        return False
-
-    return subset.filter(matches, condrepr=('<contains %r>', pat))
-
-@predicate('converted([id])', safe=True)
-def converted(repo, subset, x):
-    """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 subset.filter(lambda r: _matchvalue(r),
-                         condrepr=('<converted %r>', rev))
-
-@predicate('date(interval)', safe=True)
-def date(repo, subset, x):
-    """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 subset.filter(lambda x: dm(repo[x].date()[0]),
-                         condrepr=('<date %r>', ds))
-
-@predicate('desc(string)', safe=True)
-def desc(repo, subset, x):
-    """Search commit message for string. The match is case-insensitive.
-
-    Pattern matching is supported for `string`. See
-    :hg:`help revisions.patterns`.
-    """
-    # i18n: "desc" is a keyword
-    ds = getstring(x, _("desc requires a string"))
-
-    kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
-
-    return subset.filter(lambda r: matcher(repo[r].description()),
-                         condrepr=('<desc %r>', ds))
-
-def _descendants(repo, subset, x, followfirst=False):
-    roots = getset(repo, fullreposet(repo), x)
-    if not roots:
-        return baseset()
-    s = _revdescendants(repo, roots, followfirst)
-
-    # Both sets need to be ascending in order to lazily return the union
-    # in the correct order.
-    base = subset & roots
-    desc = subset & s
-    result = base + desc
-    if subset.isascending():
-        result.sort()
-    elif subset.isdescending():
-        result.sort(reverse=True)
-    else:
-        result = subset & result
-    return result
-
-@predicate('descendants(set)', safe=True)
-def descendants(repo, subset, x):
-    """Changesets which are descendants of changesets in set.
-    """
-    return _descendants(repo, subset, x)
-
-@predicate('_firstdescendants', safe=True)
-def _firstdescendants(repo, subset, x):
-    # ``_firstdescendants(set)``
-    # Like ``descendants(set)`` but follows only the first parents.
-    return _descendants(repo, subset, x, followfirst=True)
-
-@predicate('destination([set])', safe=True)
-def destination(repo, subset, x):
-    """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:
-        sources = getset(repo, fullreposet(repo), x)
-    else:
-        sources = fullreposet(repo)
-
-    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 arg set.
-    # Even if the immediate src of r is not in the arg set, 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 further 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 sources or src in dests:
-                dests.update(lineage)
-                break
-
-            r = src
-            src = _getrevsource(repo, r)
-
-    return subset.filter(dests.__contains__,
-                         condrepr=lambda: '<destination %r>' % sorted(dests))
-
-@predicate('divergent()', safe=True)
-def divergent(repo, subset, x):
-    """
-    Final successors of changesets with an alternative set of final successors.
-    """
-    # i18n: "divergent" is a keyword
-    getargs(x, 0, 0, _("divergent takes no arguments"))
-    divergent = obsmod.getrevs(repo, 'divergent')
-    return subset & divergent
-
-@predicate('extinct()', safe=True)
-def extinct(repo, subset, x):
-    """Obsolete changesets with obsolete descendants only.
-    """
-    # i18n: "extinct" is a keyword
-    getargs(x, 0, 0, _("extinct takes no arguments"))
-    extincts = obsmod.getrevs(repo, 'extinct')
-    return subset & extincts
-
-@predicate('extra(label, [value])', safe=True)
-def extra(repo, subset, x):
-    """Changesets with the given label in the extra metadata, with the given
-    optional value.
-
-    Pattern matching is supported for `value`. See
-    :hg:`help revisions.patterns`.
-    """
-    args = getargsdict(x, 'extra', 'label value')
-    if 'label' not in args:
-        # i18n: "extra" is a keyword
-        raise error.ParseError(_('extra takes at least 1 argument'))
-    # i18n: "extra" is a keyword
-    label = getstring(args['label'], _('first argument to extra must be '
-                                       'a string'))
-    value = None
-
-    if 'value' in args:
-        # i18n: "extra" is a keyword
-        value = getstring(args['value'], _('second argument to extra must be '
-                                           'a string'))
-        kind, value, matcher = util.stringmatcher(value)
-
-    def _matchvalue(r):
-        extra = repo[r].extra()
-        return label in extra and (value is None or matcher(extra[label]))
-
-    return subset.filter(lambda r: _matchvalue(r),
-                         condrepr=('<extra[%r] %r>', label, value))
-
-@predicate('filelog(pattern)', safe=True)
-def filelog(repo, subset, x):
-    """Changesets connected to the specified filelog.
-
-    For performance reasons, visits only revisions mentioned in the file-level
-    filelog, rather than filtering through all changesets (much faster, but
-    doesn't include deletes or duplicate changes). For a slower, more accurate
-    result, use ``file()``.
-
-    The pattern without explicit kind like ``glob:`` is expected to be
-    relative to the current directory and match against a file exactly
-    for efficiency.
-
-    If some linkrev points to revisions filtered by the current repoview, we'll
-    work around it to return a non-filtered value.
-    """
-
-    # i18n: "filelog" is a keyword
-    pat = getstring(x, _("filelog requires a pattern"))
-    s = set()
-    cl = repo.changelog
-
-    if not matchmod.patkind(pat):
-        f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
-        files = [f]
-    else:
-        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
-        files = (f for f in repo[None] if m(f))
-
-    for f in files:
-        fl = repo.file(f)
-        known = {}
-        scanpos = 0
-        for fr in list(fl):
-            fn = fl.node(fr)
-            if fn in known:
-                s.add(known[fn])
-                continue
-
-            lr = fl.linkrev(fr)
-            if lr in cl:
-                s.add(lr)
-            elif scanpos is not None:
-                # lowest matching changeset is filtered, scan further
-                # ahead in changelog
-                start = max(lr, scanpos) + 1
-                scanpos = None
-                for r in cl.revs(start):
-                    # minimize parsing of non-matching entries
-                    if f in cl.revision(r) and f in cl.readfiles(r):
-                        try:
-                            # try to use manifest delta fastpath
-                            n = repo[r].filenode(f)
-                            if n not in known:
-                                if n == fn:
-                                    s.add(r)
-                                    scanpos = r
-                                    break
-                                else:
-                                    known[n] = r
-                        except error.ManifestLookupError:
-                            # deletion in changelog
-                            continue
-
-    return subset & s
-
-@predicate('first(set, [n])', safe=True)
-def first(repo, subset, x):
-    """An alias for limit().
-    """
-    return limit(repo, subset, x)
-
-def _follow(repo, subset, x, name, followfirst=False):
-    l = getargs(x, 0, 2, _("%s takes no arguments or a pattern "
-                           "and an optional revset") % name)
-    c = repo['.']
-    if l:
-        x = getstring(l[0], _("%s expected a pattern") % name)
-        rev = None
-        if len(l) >= 2:
-            revs = getset(repo, fullreposet(repo), l[1])
-            if len(revs) != 1:
-                raise error.RepoLookupError(
-                        _("%s expected one starting revision") % name)
-            rev = revs.last()
-            c = repo[rev]
-        matcher = matchmod.match(repo.root, repo.getcwd(), [x],
-                                 ctx=repo[rev], default='path')
-
-        files = c.manifest().walk(matcher)
-
-        s = set()
-        for fname in files:
-            fctx = c[fname]
-            s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))
-            # include the revision responsible for the most recent version
-            s.add(fctx.introrev())
-    else:
-        s = _revancestors(repo, baseset([c.rev()]), followfirst)
-
-    return subset & s
-
-@predicate('follow([pattern[, startrev]])', safe=True)
-def follow(repo, subset, x):
-    """
-    An alias for ``::.`` (ancestors of the working directory's first parent).
-    If pattern is specified, the histories of files matching given
-    pattern in the revision given by startrev are followed, including copies.
-    """
-    return _follow(repo, subset, x, 'follow')
-
-@predicate('_followfirst', safe=True)
-def _followfirst(repo, subset, x):
-    # ``followfirst([pattern[, startrev]])``
-    # Like ``follow([pattern[, startrev]])`` but follows only the first parent
-    # of every revisions or files revisions.
-    return _follow(repo, subset, x, '_followfirst', followfirst=True)
-
-@predicate('followlines(file, fromline:toline[, startrev=.])', safe=True)
-def followlines(repo, subset, x):
-    """Changesets modifying `file` in line range ('fromline', 'toline').
-
-    Line range corresponds to 'file' content at 'startrev' and should hence be
-    consistent with file size. If startrev is not specified, working directory's
-    parent is used.
-    """
-    from . import context  # avoid circular import issues
-
-    args = getargsdict(x, 'followlines', 'file *lines startrev')
-    if len(args['lines']) != 1:
-        raise error.ParseError(_("followlines requires a line range"))
-
-    rev = '.'
-    if 'startrev' in args:
-        revs = getset(repo, fullreposet(repo), args['startrev'])
-        if len(revs) != 1:
-            raise error.ParseError(
-                _("followlines expects exactly one revision"))
-        rev = revs.last()
-
-    pat = getstring(args['file'], _("followlines requires a pattern"))
-    if not matchmod.patkind(pat):
-        fname = pathutil.canonpath(repo.root, repo.getcwd(), pat)
-    else:
-        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[rev])
-        files = [f for f in repo[rev] if m(f)]
-        if len(files) != 1:
-            raise error.ParseError(_("followlines expects exactly one file"))
-        fname = files[0]
-
-    lr = getrange(args['lines'][0], _("followlines expects a line range"))
-    fromline, toline = [getinteger(a, _("line range bounds must be integers"))
-                        for a in lr]
-    if toline - fromline < 0:
-        raise error.ParseError(_("line range must be positive"))
-    if fromline < 1:
-        raise error.ParseError(_("fromline must be strictly positive"))
-    fromline -= 1
-
-    fctx = repo[rev].filectx(fname)
-    revs = (c.rev() for c in context.blockancestors(fctx, fromline, toline))
-    return subset & generatorset(revs, iterasc=False)
-
-@predicate('all()', safe=True)
-def getall(repo, subset, x):
-    """All changesets, the same as ``0:tip``.
-    """
-    # i18n: "all" is a keyword
-    getargs(x, 0, 0, _("all takes no arguments"))
-    return subset & spanset(repo)  # drop "null" if any
-
-@predicate('grep(regex)')
-def grep(repo, subset, x):
-    """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 as e:
-        raise error.ParseError(_('invalid match pattern: %s') % e)
-
-    def matches(x):
-        c = repo[x]
-        for e in c.files() + [c.user(), c.description()]:
-            if gr.search(e):
-                return True
-        return False
-
-    return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
-
-@predicate('_matchfiles', safe=True)
-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.
-
-    l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
-    pats, inc, exc = [], [], []
-    rev, default = None, None
-    for arg in l:
-        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:
-                raise error.ParseError('_matchfiles expected at most one '
-                                       'revision')
-            if value != '': # empty means working directory; leave rev as None
-                rev = value
-        elif prefix == 'd:':
-            if default is not None:
-                raise error.ParseError('_matchfiles expected at most one '
-                                       'default mode')
-            default = value
-        else:
-            raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
-    if not default:
-        default = 'glob'
-
-    m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
-                       exclude=exc, ctx=repo[rev], default=default)
-
-    # This directly read the changelog data as creating changectx for all
-    # revisions is quite expensive.
-    getfiles = repo.changelog.readfiles
-    wdirrev = node.wdirrev
-    def matches(x):
-        if x == wdirrev:
-            files = repo[x].files()
-        else:
-            files = getfiles(x)
-        for f in files:
-            if m(f):
-                return True
-        return False
-
-    return subset.filter(matches,
-                         condrepr=('<matchfiles patterns=%r, include=%r '
-                                   'exclude=%r, default=%r, rev=%r>',
-                                   pats, inc, exc, default, rev))
-
-@predicate('file(pattern)', safe=True)
-def hasfile(repo, subset, x):
-    """Changesets affecting files matched by pattern.
-
-    For a faster but less accurate result, consider using ``filelog()``
-    instead.
-
-    This predicate uses ``glob:`` as the default kind of pattern.
-    """
-    # i18n: "file" is a keyword
-    pat = getstring(x, _("file requires a pattern"))
-    return _matchfiles(repo, subset, ('string', 'p:' + pat))
-
-@predicate('head()', safe=True)
-def head(repo, subset, x):
-    """Changeset is a named branch head.
-    """
-    # i18n: "head" is a keyword
-    getargs(x, 0, 0, _("head takes no arguments"))
-    hs = set()
-    cl = repo.changelog
-    for ls in repo.branchmap().itervalues():
-        hs.update(cl.rev(h) for h in ls)
-    return subset & baseset(hs)
-
-@predicate('heads(set)', safe=True)
-def heads(repo, subset, x):
-    """Members of set with no children in set.
-    """
-    s = getset(repo, subset, x)
-    ps = parents(repo, subset, x)
-    return s - ps
-
-@predicate('hidden()', safe=True)
-def hidden(repo, subset, x):
-    """Hidden changesets.
-    """
-    # i18n: "hidden" is a keyword
-    getargs(x, 0, 0, _("hidden takes no arguments"))
-    hiddenrevs = repoview.filterrevs(repo, 'visible')
-    return subset & hiddenrevs
-
-@predicate('keyword(string)', safe=True)
-def keyword(repo, subset, x):
-    """Search commit message, user name, and names of changed files for
-    string. The match is case-insensitive.
-
-    For a regular expression or case sensitive search of these fields, use
-    ``grep(regex)``.
-    """
-    # i18n: "keyword" is a keyword
-    kw = encoding.lower(getstring(x, _("keyword requires a string")))
-
-    def matches(r):
-        c = repo[r]
-        return any(kw in encoding.lower(t)
-                   for t in c.files() + [c.user(), c.description()])
-
-    return subset.filter(matches, condrepr=('<keyword %r>', kw))
-
-@predicate('limit(set[, n[, offset]])', safe=True)
-def limit(repo, subset, x):
-    """First n members of set, defaulting to 1, starting from offset.
-    """
-    args = getargsdict(x, 'limit', 'set n offset')
-    if 'set' not in args:
-        # i18n: "limit" is a keyword
-        raise error.ParseError(_("limit requires one to three arguments"))
-    # i18n: "limit" is a keyword
-    lim = getinteger(args.get('n'), _("limit expects a number"), default=1)
-    # i18n: "limit" is a keyword
-    ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)
-    if ofs < 0:
-        raise error.ParseError(_("negative offset"))
-    os = getset(repo, fullreposet(repo), args['set'])
-    result = []
-    it = iter(os)
-    for x in xrange(ofs):
-        y = next(it, None)
-        if y is None:
-            break
-    for x in xrange(lim):
-        y = next(it, None)
-        if y is None:
-            break
-        elif y in subset:
-            result.append(y)
-    return baseset(result, datarepr=('<limit n=%d, offset=%d, %r, %r>',
-                                     lim, ofs, subset, os))
-
-@predicate('last(set, [n])', safe=True)
-def last(repo, subset, x):
-    """Last n members of set, defaulting to 1.
-    """
-    # i18n: "last" is a keyword
-    l = getargs(x, 1, 2, _("last requires one or two arguments"))
-    lim = 1
-    if len(l) == 2:
-        # i18n: "last" is a keyword
-        lim = getinteger(l[1], _("last expects a number"))
-    os = getset(repo, fullreposet(repo), l[0])
-    os.reverse()
-    result = []
-    it = iter(os)
-    for x in xrange(lim):
-        y = next(it, None)
-        if y is None:
-            break
-        elif y in subset:
-            result.append(y)
-    return baseset(result, datarepr=('<last n=%d, %r, %r>', lim, subset, os))
-
-@predicate('max(set)', safe=True)
-def maxrev(repo, subset, x):
-    """Changeset with highest revision number in set.
-    """
-    os = getset(repo, fullreposet(repo), x)
-    try:
-        m = os.max()
-        if m in subset:
-            return baseset([m], datarepr=('<max %r, %r>', subset, os))
-    except ValueError:
-        # os.max() throws a ValueError when the collection is empty.
-        # Same as python's max().
-        pass
-    return baseset(datarepr=('<max %r, %r>', subset, os))
-
-@predicate('merge()', safe=True)
-def merge(repo, subset, x):
-    """Changeset is a merge changeset.
-    """
-    # i18n: "merge" is a keyword
-    getargs(x, 0, 0, _("merge takes no arguments"))
-    cl = repo.changelog
-    return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
-                         condrepr='<merge>')
-
-@predicate('branchpoint()', safe=True)
-def branchpoint(repo, subset, x):
-    """Changesets with more than one child.
-    """
-    # i18n: "branchpoint" is a keyword
-    getargs(x, 0, 0, _("branchpoint takes no arguments"))
-    cl = repo.changelog
-    if not subset:
-        return baseset()
-    # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
-    # (and if it is not, it should.)
-    baserev = min(subset)
-    parentscount = [0]*(len(repo) - baserev)
-    for r in cl.revs(start=baserev + 1):
-        for p in cl.parentrevs(r):
-            if p >= baserev:
-                parentscount[p - baserev] += 1
-    return subset.filter(lambda r: parentscount[r - baserev] > 1,
-                         condrepr='<branchpoint>')
-
-@predicate('min(set)', safe=True)
-def minrev(repo, subset, x):
-    """Changeset with lowest revision number in set.
-    """
-    os = getset(repo, fullreposet(repo), x)
-    try:
-        m = os.min()
-        if m in subset:
-            return baseset([m], datarepr=('<min %r, %r>', subset, os))
-    except ValueError:
-        # os.min() throws a ValueError when the collection is empty.
-        # Same as python's min().
-        pass
-    return baseset(datarepr=('<min %r, %r>', subset, os))
-
-@predicate('modifies(pattern)', safe=True)
-def modifies(repo, subset, x):
-    """Changesets modifying files matched by pattern.
-
-    The pattern without explicit kind like ``glob:`` is expected to be
-    relative to the current directory and match against a file or a
-    directory.
-    """
-    # i18n: "modifies" is a keyword
-    pat = getstring(x, _("modifies requires a pattern"))
-    return checkstatus(repo, subset, pat, 0)
-
-@predicate('named(namespace)')
-def named(repo, subset, x):
-    """The changesets in a given namespace.
-
-    Pattern matching is supported for `namespace`. See
-    :hg:`help revisions.patterns`.
-    """
-    # i18n: "named" is a keyword
-    args = getargs(x, 1, 1, _('named requires a namespace argument'))
-
-    ns = getstring(args[0],
-                   # i18n: "named" is a keyword
-                   _('the argument to named must be a string'))
-    kind, pattern, matcher = util.stringmatcher(ns)
-    namespaces = set()
-    if kind == 'literal':
-        if pattern not in repo.names:
-            raise error.RepoLookupError(_("namespace '%s' does not exist")
-                                        % ns)
-        namespaces.add(repo.names[pattern])
-    else:
-        for name, ns in repo.names.iteritems():
-            if matcher(name):
-                namespaces.add(ns)
-        if not namespaces:
-            raise error.RepoLookupError(_("no namespace exists"
-                                          " that match '%s'") % pattern)
-
-    names = set()
-    for ns in namespaces:
-        for name in ns.listnames(repo):
-            if name not in ns.deprecated:
-                names.update(repo[n].rev() for n in ns.nodes(repo, name))
-
-    names -= set([node.nullrev])
-    return subset & names
-
-@predicate('id(string)', safe=True)
-def node_(repo, subset, x):
-    """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:
-        try:
-            rn = repo.changelog.rev(node.bin(n))
-        except (LookupError, TypeError):
-            rn = None
-    else:
-        rn = None
-        pm = repo.changelog._partialmatch(n)
-        if pm is not None:
-            rn = repo.changelog.rev(pm)
-
-    if rn is None:
-        return baseset()
-    result = baseset([rn])
-    return result & subset
-
-@predicate('obsolete()', safe=True)
-def obsolete(repo, subset, x):
-    """Mutable changeset with a newer version."""
-    # i18n: "obsolete" is a keyword
-    getargs(x, 0, 0, _("obsolete takes no arguments"))
-    obsoletes = obsmod.getrevs(repo, 'obsolete')
-    return subset & obsoletes
-
-@predicate('only(set, [set])', safe=True)
-def only(repo, subset, x):
-    """Changesets that are ancestors of the first set that are not ancestors
-    of any other head in the repo. If a second set is specified, the result
-    is ancestors of the first set that are not ancestors of the second set
-    (i.e. ::<set1> - ::<set2>).
-    """
-    cl = repo.changelog
-    # i18n: "only" is a keyword
-    args = getargs(x, 1, 2, _('only takes one or two arguments'))
-    include = getset(repo, fullreposet(repo), args[0])
-    if len(args) == 1:
-        if not include:
-            return baseset()
-
-        descendants = set(_revdescendants(repo, include, False))
-        exclude = [rev for rev in cl.headrevs()
-            if not rev in descendants and not rev in include]
-    else:
-        exclude = getset(repo, fullreposet(repo), args[1])
-
-    results = set(cl.findmissingrevs(common=exclude, heads=include))
-    # XXX we should turn this into a baseset instead of a set, smartset may do
-    # some optimizations from the fact this is a baseset.
-    return subset & results
-
-@predicate('origin([set])', safe=True)
-def origin(repo, subset, x):
-    """
-    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:
-        dests = getset(repo, fullreposet(repo), x)
-    else:
-        dests = fullreposet(repo)
-
-    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 dests])
-    o -= set([None])
-    # XXX we should turn this into a baseset instead of a set, smartset may do
-    # some optimizations from the fact this is a baseset.
-    return subset & o
-
-@predicate('outgoing([path])', safe=False)
-def outgoing(repo, subset, x):
-    """Changesets not found in the specified destination repository, or the
-    default push location.
-    """
-    # Avoid cycles.
-    from . import (
-        discovery,
-        hg,
-    )
-    # i18n: "outgoing" is a keyword
-    l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
-    # 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.peer(repo, {}, dest)
-    repo.ui.pushbuffer()
-    outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
-    repo.ui.popbuffer()
-    cl = repo.changelog
-    o = set([cl.rev(r) for r in outgoing.missing])
-    return subset & o
-
-@predicate('p1([set])', safe=True)
-def p1(repo, subset, x):
-    """First parent of changesets in set, or the working directory.
-    """
-    if x is None:
-        p = repo[x].p1().rev()
-        if p >= 0:
-            return subset & baseset([p])
-        return baseset()
-
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, fullreposet(repo), x):
-        ps.add(cl.parentrevs(r)[0])
-    ps -= set([node.nullrev])
-    # XXX we should turn this into a baseset instead of a set, smartset may do
-    # some optimizations from the fact this is a baseset.
-    return subset & ps
-
-@predicate('p2([set])', safe=True)
-def p2(repo, subset, x):
-    """Second parent of changesets in set, or the working directory.
-    """
-    if x is None:
-        ps = repo[x].parents()
-        try:
-            p = ps[1].rev()
-            if p >= 0:
-                return subset & baseset([p])
-            return baseset()
-        except IndexError:
-            return baseset()
-
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, fullreposet(repo), x):
-        ps.add(cl.parentrevs(r)[1])
-    ps -= set([node.nullrev])
-    # XXX we should turn this into a baseset instead of a set, smartset may do
-    # some optimizations from the fact this is a baseset.
-    return subset & ps
-
-def parentpost(repo, subset, x, order):
-    return p1(repo, subset, x)
-
-@predicate('parents([set])', safe=True)
-def parents(repo, subset, x):
-    """
-    The set of all parents for all changesets in set, or the working directory.
-    """
-    if x is None:
-        ps = set(p.rev() for p in repo[x].parents())
-    else:
-        ps = set()
-        cl = repo.changelog
-        up = ps.update
-        parentrevs = cl.parentrevs
-        for r in getset(repo, fullreposet(repo), x):
-            if r == node.wdirrev:
-                up(p.rev() for p in repo[r].parents())
-            else:
-                up(parentrevs(r))
-    ps -= set([node.nullrev])
-    return subset & ps
-
-def _phase(repo, subset, target):
-    """helper to select all rev in phase <target>"""
-    repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
-    if repo._phasecache._phasesets:
-        s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
-        s = baseset(s)
-        s.sort() # set are non ordered, so we enforce ascending
-        return subset & s
-    else:
-        phase = repo._phasecache.phase
-        condition = lambda r: phase(repo, r) == target
-        return subset.filter(condition, condrepr=('<phase %r>', target),
-                             cache=False)
-
-@predicate('draft()', safe=True)
-def draft(repo, subset, x):
-    """Changeset in draft phase."""
-    # i18n: "draft" is a keyword
-    getargs(x, 0, 0, _("draft takes no arguments"))
-    target = phases.draft
-    return _phase(repo, subset, target)
-
-@predicate('secret()', safe=True)
-def secret(repo, subset, x):
-    """Changeset in secret phase."""
-    # i18n: "secret" is a keyword
-    getargs(x, 0, 0, _("secret takes no arguments"))
-    target = phases.secret
-    return _phase(repo, subset, target)
-
-def parentspec(repo, subset, x, n, order):
-    """``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 (TypeError, ValueError):
-        raise error.ParseError(_("^ expects a number 0, 1, or 2"))
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, fullreposet(repo), x):
-        if n == 0:
-            ps.add(r)
-        elif n == 1:
-            ps.add(cl.parentrevs(r)[0])
-        elif n == 2:
-            parents = cl.parentrevs(r)
-            if parents[1] != node.nullrev:
-                ps.add(parents[1])
-    return subset & ps
-
-@predicate('present(set)', safe=True)
-def present(repo, subset, x):
-    """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 baseset()
-
-# for internal use
-@predicate('_notpublic', safe=True)
-def _notpublic(repo, subset, x):
-    getargs(x, 0, 0, "_notpublic takes no arguments")
-    repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
-    if repo._phasecache._phasesets:
-        s = set()
-        for u in repo._phasecache._phasesets[1:]:
-            s.update(u)
-        s = baseset(s - repo.changelog.filteredrevs)
-        s.sort()
-        return subset & s
-    else:
-        phase = repo._phasecache.phase
-        target = phases.public
-        condition = lambda r: phase(repo, r) != target
-        return subset.filter(condition, condrepr=('<phase %r>', target),
-                             cache=False)
-
-@predicate('public()', safe=True)
-def public(repo, subset, x):
-    """Changeset in public phase."""
-    # i18n: "public" is a keyword
-    getargs(x, 0, 0, _("public takes no arguments"))
-    phase = repo._phasecache.phase
-    target = phases.public
-    condition = lambda r: phase(repo, r) == target
-    return subset.filter(condition, condrepr=('<phase %r>', target),
-                         cache=False)
-
-@predicate('remote([id [,path]])', safe=False)
-def remote(repo, subset, x):
-    """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.
-    """
-
-    from . import hg # avoid start-up nasties
-    # i18n: "remote" is a keyword
-    l = getargs(x, 0, 2, _("remote takes zero, one, or two 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 baseset([r])
-    return baseset()
-
-@predicate('removes(pattern)', safe=True)
-def removes(repo, subset, x):
-    """Changesets which remove files matching pattern.
-
-    The pattern without explicit kind like ``glob:`` is expected to be
-    relative to the current directory and match against a file or a
-    directory.
-    """
-    # i18n: "removes" is a keyword
-    pat = getstring(x, _("removes requires a pattern"))
-    return checkstatus(repo, subset, pat, 2)
-
-@predicate('rev(number)', safe=True)
-def rev(repo, subset, x):
-    """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 (TypeError, ValueError):
-        # i18n: "rev" is a keyword
-        raise error.ParseError(_("rev expects a number"))
-    if l not in repo.changelog and l != node.nullrev:
-        return baseset()
-    return subset & baseset([l])
-
-@predicate('matching(revision [, field])', safe=True)
-def matching(repo, subset, x):
-    """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, fullreposet(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]
-
-    def matches(x):
-        for rev in revs:
-            target = getinfo(rev)
-            match = True
-            for n, f in enumerate(getfieldfuncs):
-                if target[n] != f(x):
-                    match = False
-            if match:
-                return True
-        return False
-
-    return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
-
-@predicate('reverse(set)', safe=True, takeorder=True)
-def reverse(repo, subset, x, order):
-    """Reverse order of set.
-    """
-    l = getset(repo, subset, x)
-    if order == defineorder:
-        l.reverse()
-    return l
-
-@predicate('roots(set)', safe=True)
-def roots(repo, subset, x):
-    """Changesets in set with no parent changeset in set.
-    """
-    s = getset(repo, fullreposet(repo), x)
-    parents = repo.changelog.parentrevs
-    def filter(r):
-        for p in parents(r):
-            if 0 <= p and p in s:
-                return False
-        return True
-    return subset & s.filter(filter, condrepr='<roots>')
-
-_sortkeyfuncs = {
-    'rev': lambda c: c.rev(),
-    'branch': lambda c: c.branch(),
-    'desc': lambda c: c.description(),
-    'user': lambda c: c.user(),
-    'author': lambda c: c.user(),
-    'date': lambda c: c.date()[0],
-}
-
-def _getsortargs(x):
-    """Parse sort options into (set, [(key, reverse)], opts)"""
-    args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
-    if 'set' not in args:
-        # i18n: "sort" is a keyword
-        raise error.ParseError(_('sort requires one or two arguments'))
-    keys = "rev"
-    if 'keys' in args:
-        # i18n: "sort" is a keyword
-        keys = getstring(args['keys'], _("sort spec must be a string"))
-
-    keyflags = []
-    for k in keys.split():
-        fk = k
-        reverse = (k[0] == '-')
-        if reverse:
-            k = k[1:]
-        if k not in _sortkeyfuncs and k != 'topo':
-            raise error.ParseError(_("unknown sort key %r") % fk)
-        keyflags.append((k, reverse))
-
-    if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
-        # i18n: "topo" is a keyword
-        raise error.ParseError(_('topo sort order cannot be combined '
-                                 'with other sort keys'))
-
-    opts = {}
-    if 'topo.firstbranch' in args:
-        if any(k == 'topo' for k, reverse in keyflags):
-            opts['topo.firstbranch'] = args['topo.firstbranch']
-        else:
-            # i18n: "topo" and "topo.firstbranch" are keywords
-            raise error.ParseError(_('topo.firstbranch can only be used '
-                                     'when using the topo sort key'))
-
-    return args['set'], keyflags, opts
-
-@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True)
-def sort(repo, subset, x, order):
-    """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
-    - ``topo`` for a reverse topographical sort
-
-    The ``topo`` sort order cannot be combined with other sort keys. This sort
-    takes one optional argument, ``topo.firstbranch``, which takes a revset that
-    specifies what topographical branches to prioritize in the sort.
-
-    """
-    s, keyflags, opts = _getsortargs(x)
-    revs = getset(repo, subset, s)
-
-    if not keyflags or order != defineorder:
-        return revs
-    if len(keyflags) == 1 and keyflags[0][0] == "rev":
-        revs.sort(reverse=keyflags[0][1])
-        return revs
-    elif keyflags[0][0] == "topo":
-        firstbranch = ()
-        if 'topo.firstbranch' in opts:
-            firstbranch = getset(repo, subset, opts['topo.firstbranch'])
-        revs = baseset(_toposort(revs, repo.changelog.parentrevs, firstbranch),
-                       istopo=True)
-        if keyflags[0][1]:
-            revs.reverse()
-        return revs
-
-    # sort() is guaranteed to be stable
-    ctxs = [repo[r] for r in revs]
-    for k, reverse in reversed(keyflags):
-        ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
-    return baseset([c.rev() for c in ctxs])
-
-def _toposort(revs, parentsfunc, firstbranch=()):
-    """Yield revisions from heads to roots one (topo) branch at a time.
-
-    This function aims to be used by a graph generator that wishes to minimize
-    the number of parallel branches and their interleaving.
-
-    Example iteration order (numbers show the "true" order in a changelog):
-
-      o  4
-      |
-      o  1
-      |
-      | o  3
-      | |
-      | o  2
-      |/
-      o  0
-
-    Note that the ancestors of merges are understood by the current
-    algorithm to be on the same branch. This means no reordering will
-    occur behind a merge.
-    """
-
-    ### Quick summary of the algorithm
-    #
-    # This function is based around a "retention" principle. We keep revisions
-    # in memory until we are ready to emit a whole branch that immediately
-    # "merges" into an existing one. This reduces the number of parallel
-    # branches with interleaved revisions.
-    #
-    # During iteration revs are split into two groups:
-    # A) revision already emitted
-    # B) revision in "retention". They are stored as different subgroups.
-    #
-    # for each REV, we do the following logic:
-    #
-    #   1) if REV is a parent of (A), we will emit it. If there is a
-    #   retention group ((B) above) that is blocked on REV being
-    #   available, we emit all the revisions out of that retention
-    #   group first.
-    #
-    #   2) else, we'll search for a subgroup in (B) awaiting for REV to be
-    #   available, if such subgroup exist, we add REV to it and the subgroup is
-    #   now awaiting for REV.parents() to be available.
-    #
-    #   3) finally if no such group existed in (B), we create a new subgroup.
-    #
-    #
-    # To bootstrap the algorithm, we emit the tipmost revision (which
-    # puts it in group (A) from above).
-
-    revs.sort(reverse=True)
-
-    # Set of parents of revision that have been emitted. They can be considered
-    # unblocked as the graph generator is already aware of them so there is no
-    # need to delay the revisions that reference them.
-    #
-    # If someone wants to prioritize a branch over the others, pre-filling this
-    # set will force all other branches to wait until this branch is ready to be
-    # emitted.
-    unblocked = set(firstbranch)
-
-    # list of groups waiting to be displayed, each group is defined by:
-    #
-    #   (revs:    lists of revs waiting to be displayed,
-    #    blocked: set of that cannot be displayed before those in 'revs')
-    #
-    # The second value ('blocked') correspond to parents of any revision in the
-    # group ('revs') that is not itself contained in the group. The main idea
-    # of this algorithm is to delay as much as possible the emission of any
-    # revision.  This means waiting for the moment we are about to display
-    # these parents to display the revs in a group.
-    #
-    # This first implementation is smart until it encounters a merge: it will
-    # emit revs as soon as any parent is about to be emitted and can grow an
-    # arbitrary number of revs in 'blocked'. In practice this mean we properly
-    # retains new branches but gives up on any special ordering for ancestors
-    # of merges. The implementation can be improved to handle this better.
-    #
-    # The first subgroup is special. It corresponds to all the revision that
-    # were already emitted. The 'revs' lists is expected to be empty and the
-    # 'blocked' set contains the parents revisions of already emitted revision.
-    #
-    # You could pre-seed the <parents> set of groups[0] to a specific
-    # changesets to select what the first emitted branch should be.
-    groups = [([], unblocked)]
-    pendingheap = []
-    pendingset = set()
-
-    heapq.heapify(pendingheap)
-    heappop = heapq.heappop
-    heappush = heapq.heappush
-    for currentrev in revs:
-        # Heap works with smallest element, we want highest so we invert
-        if currentrev not in pendingset:
-            heappush(pendingheap, -currentrev)
-            pendingset.add(currentrev)
-        # iterates on pending rev until after the current rev have been
-        # processed.
-        rev = None
-        while rev != currentrev:
-            rev = -heappop(pendingheap)
-            pendingset.remove(rev)
-
-            # Seek for a subgroup blocked, waiting for the current revision.
-            matching = [i for i, g in enumerate(groups) if rev in g[1]]
-
-            if matching:
-                # The main idea is to gather together all sets that are blocked
-                # on the same revision.
-                #
-                # Groups are merged when a common blocking ancestor is
-                # observed. For example, given two groups:
-                #
-                # revs [5, 4] waiting for 1
-                # revs [3, 2] waiting for 1
-                #
-                # These two groups will be merged when we process
-                # 1. In theory, we could have merged the groups when
-                # we added 2 to the group it is now in (we could have
-                # noticed the groups were both blocked on 1 then), but
-                # the way it works now makes the algorithm simpler.
-                #
-                # We also always keep the oldest subgroup first. We can
-                # probably improve the behavior by having the longest set
-                # first. That way, graph algorithms could minimise the length
-                # of parallel lines their drawing. This is currently not done.
-                targetidx = matching.pop(0)
-                trevs, tparents = groups[targetidx]
-                for i in matching:
-                    gr = groups[i]
-                    trevs.extend(gr[0])
-                    tparents |= gr[1]
-                # delete all merged subgroups (except the one we kept)
-                # (starting from the last subgroup for performance and
-                # sanity reasons)
-                for i in reversed(matching):
-                    del groups[i]
-            else:
-                # This is a new head. We create a new subgroup for it.
-                targetidx = len(groups)
-                groups.append(([], set([rev])))
-
-            gr = groups[targetidx]
-
-            # We now add the current nodes to this subgroups. This is done
-            # after the subgroup merging because all elements from a subgroup
-            # that relied on this rev must precede it.
-            #
-            # we also update the <parents> set to include the parents of the
-            # new nodes.
-            if rev == currentrev: # only display stuff in rev
-                gr[0].append(rev)
-            gr[1].remove(rev)
-            parents = [p for p in parentsfunc(rev) if p > node.nullrev]
-            gr[1].update(parents)
-            for p in parents:
-                if p not in pendingset:
-                    pendingset.add(p)
-                    heappush(pendingheap, -p)
-
-            # Look for a subgroup to display
-            #
-            # When unblocked is empty (if clause), we were not waiting for any
-            # revisions during the first iteration (if no priority was given) or
-            # if we emitted a whole disconnected set of the graph (reached a
-            # root).  In that case we arbitrarily take the oldest known
-            # subgroup. The heuristic could probably be better.
-            #
-            # Otherwise (elif clause) if the subgroup is blocked on
-            # a revision we just emitted, we can safely emit it as
-            # well.
-            if not unblocked:
-                if len(groups) > 1:  # display other subset
-                    targetidx = 1
-                    gr = groups[1]
-            elif not gr[1] & unblocked:
-                gr = None
-
-            if gr is not None:
-                # update the set of awaited revisions with the one from the
-                # subgroup
-                unblocked |= gr[1]
-                # output all revisions in the subgroup
-                for r in gr[0]:
-                    yield r
-                # delete the subgroup that you just output
-                # unless it is groups[0] in which case you just empty it.
-                if targetidx:
-                    del groups[targetidx]
-                else:
-                    gr[0][:] = []
-    # Check if we have some subgroup waiting for revisions we are not going to
-    # iterate over
-    for g in groups:
-        for r in g[0]:
-            yield r
-
-@predicate('subrepo([pattern])')
-def subrepo(repo, subset, x):
-    """Changesets that add, modify or remove the given subrepo.  If no subrepo
-    pattern is named, any subrepo changes are returned.
-    """
-    # i18n: "subrepo" is a keyword
-    args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
-    pat = None
-    if len(args) != 0:
-        pat = getstring(args[0], _("subrepo requires a pattern"))
-
-    m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
-
-    def submatches(names):
-        k, p, m = util.stringmatcher(pat)
-        for name in names:
-            if m(name):
-                yield name
-
-    def matches(x):
-        c = repo[x]
-        s = repo.status(c.p1().node(), c.node(), match=m)
-
-        if pat is None:
-            return s.added or s.modified or s.removed
-
-        if s.added:
-            return any(submatches(c.substate.keys()))
-
-        if s.modified:
-            subs = set(c.p1().substate.keys())
-            subs.update(c.substate.keys())
-
-            for path in submatches(subs):
-                if c.p1().substate.get(path) != c.substate.get(path):
-                    return True
-
-        if s.removed:
-            return any(submatches(c.p1().substate.keys()))
-
-        return False
-
-    return subset.filter(matches, condrepr=('<subrepo %r>', pat))
-
-def _substringmatcher(pattern, casesensitive=True):
-    kind, pattern, matcher = util.stringmatcher(pattern,
-                                                casesensitive=casesensitive)
-    if kind == 'literal':
-        if not casesensitive:
-            pattern = encoding.lower(pattern)
-            matcher = lambda s: pattern in encoding.lower(s)
-        else:
-            matcher = lambda s: pattern in s
-    return kind, pattern, matcher
-
-@predicate('tag([name])', safe=True)
-def tag(repo, subset, x):
-    """The specified tag by name, or all tagged revisions if no name is given.
-
-    Pattern matching is supported for `name`. See
-    :hg:`help revisions.patterns`.
-    """
-    # i18n: "tag" is a keyword
-    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 = util.stringmatcher(pattern)
-        if kind == 'literal':
-            # avoid resolving all tags
-            tn = repo._tagscache.tags.get(pattern, None)
-            if tn is None:
-                raise error.RepoLookupError(_("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)])
-    else:
-        s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
-    return subset & s
-
-@predicate('tagged', safe=True)
-def tagged(repo, subset, x):
-    return tag(repo, subset, x)
-
-@predicate('unstable()', safe=True)
-def unstable(repo, subset, x):
-    """Non-obsolete changesets with obsolete ancestors.
-    """
-    # i18n: "unstable" is a keyword
-    getargs(x, 0, 0, _("unstable takes no arguments"))
-    unstables = obsmod.getrevs(repo, 'unstable')
-    return subset & unstables
-
-
-@predicate('user(string)', safe=True)
-def user(repo, subset, x):
-    """User name contains string. The match is case-insensitive.
-
-    Pattern matching is supported for `string`. See
-    :hg:`help revisions.patterns`.
-    """
-    return author(repo, subset, x)
-
-@predicate('wdir', safe=True)
-def wdir(repo, subset, x):
-    """Working directory. (EXPERIMENTAL)"""
-    # i18n: "wdir" is a keyword
-    getargs(x, 0, 0, _("wdir takes no arguments"))
-    if node.wdirrev in subset or isinstance(subset, fullreposet):
-        return baseset([node.wdirrev])
-    return baseset()
-
-def _orderedlist(repo, subset, x):
-    s = getstring(x, "internal error")
-    if not s:
-        return baseset()
-    # remove duplicates here. it's difficult for caller to deduplicate sets
-    # because different symbols can point to the same rev.
-    cl = repo.changelog
-    ls = []
-    seen = set()
-    for t in s.split('\0'):
-        try:
-            # fast path for integer revision
-            r = int(t)
-            if str(r) != t or r not in cl:
-                raise ValueError
-            revs = [r]
-        except ValueError:
-            revs = stringset(repo, subset, t)
-
-        for r in revs:
-            if r in seen:
-                continue
-            if (r in subset
-                or r == node.nullrev and isinstance(subset, fullreposet)):
-                ls.append(r)
-            seen.add(r)
-    return baseset(ls)
-
-# for internal use
-@predicate('_list', safe=True, takeorder=True)
-def _list(repo, subset, x, order):
-    if order == followorder:
-        # slow path to take the subset order
-        return subset & _orderedlist(repo, fullreposet(repo), x)
-    else:
-        return _orderedlist(repo, subset, x)
-
-def _orderedintlist(repo, subset, x):
-    s = getstring(x, "internal error")
-    if not s:
-        return baseset()
-    ls = [int(r) for r in s.split('\0')]
-    s = subset
-    return baseset([r for r in ls if r in s])
-
-# for internal use
-@predicate('_intlist', safe=True, takeorder=True)
-def _intlist(repo, subset, x, order):
-    if order == followorder:
-        # slow path to take the subset order
-        return subset & _orderedintlist(repo, fullreposet(repo), x)
-    else:
-        return _orderedintlist(repo, subset, x)
-
-def _orderedhexlist(repo, subset, x):
-    s = getstring(x, "internal error")
-    if not s:
-        return baseset()
-    cl = repo.changelog
-    ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
-    s = subset
-    return baseset([r for r in ls if r in s])
-
-# for internal use
-@predicate('_hexlist', safe=True, takeorder=True)
-def _hexlist(repo, subset, x, order):
-    if order == followorder:
-        # slow path to take the subset order
-        return subset & _orderedhexlist(repo, fullreposet(repo), x)
-    else:
-        return _orderedhexlist(repo, subset, x)
-
-methods = {
-    "range": rangeset,
-    "rangeall": rangeall,
-    "rangepre": rangepre,
-    "rangepost": rangepost,
-    "dagrange": dagrange,
-    "string": stringset,
-    "symbol": stringset,
-    "and": andset,
-    "or": orset,
-    "not": notset,
-    "difference": differenceset,
-    "list": listset,
-    "keyvalue": keyvaluepair,
-    "func": func,
-    "ancestor": ancestorspec,
-    "parent": parentspec,
-    "parentpost": parentpost,
-}
-
-# Constants for ordering requirement, used in _analyze():
-#
-# If 'define', any nested functions and operations can change the ordering of
-# the entries in the set. If 'follow', any nested functions and operations
-# should take the ordering specified by the first operand to the '&' operator.
-#
-# For instance,
-#
-#   X & (Y | Z)
-#   ^   ^^^^^^^
-#   |   follow
-#   define
-#
-# will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
-# of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
-#
-# 'any' means the order doesn't matter. For instance,
-#
-#   X & !Y
-#        ^
-#        any
-#
-# 'y()' can either enforce its ordering requirement or take the ordering
-# specified by 'x()' because 'not()' doesn't care the order.
-#
-# Transition of ordering requirement:
-#
-# 1. starts with 'define'
-# 2. shifts to 'follow' by 'x & y'
-# 3. changes back to 'define' on function call 'f(x)' or function-like
-#    operation 'x (f) y' because 'f' may have its own ordering requirement
-#    for 'x' and 'y' (e.g. 'first(x)')
-#
-anyorder = 'any'        # don't care the order
-defineorder = 'define'  # should define the order
-followorder = 'follow'  # must follow the current order
-
-# transition table for 'x & y', from the current expression 'x' to 'y'
-_tofolloworder = {
-    anyorder: anyorder,
-    defineorder: followorder,
-    followorder: followorder,
-}
-
-def _matchonly(revs, bases):
-    """
-    >>> f = lambda *args: _matchonly(*map(parse, args))
-    >>> f('ancestors(A)', 'not ancestors(B)')
-    ('list', ('symbol', 'A'), ('symbol', 'B'))
-    """
-    if (revs is not None
-        and revs[0] == 'func'
-        and getsymbol(revs[1]) == 'ancestors'
-        and bases is not None
-        and bases[0] == 'not'
-        and bases[1][0] == 'func'
-        and getsymbol(bases[1][1]) == 'ancestors'):
-        return ('list', revs[2], bases[1][2])
-
-def _fixops(x):
-    """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
-    handled well by our simple top-down parser"""
-    if not isinstance(x, tuple):
-        return x
-
-    op = x[0]
-    if op == 'parent':
-        # x^:y means (x^) : y, not x ^ (:y)
-        # x^:  means (x^) :,   not x ^ (:)
-        post = ('parentpost', x[1])
-        if x[2][0] == 'dagrangepre':
-            return _fixops(('dagrange', post, x[2][1]))
-        elif x[2][0] == 'rangepre':
-            return _fixops(('range', post, x[2][1]))
-        elif x[2][0] == 'rangeall':
-            return _fixops(('rangepost', post))
-    elif op == 'or':
-        # make number of arguments deterministic:
-        # x + y + z -> (or x y z) -> (or (list x y z))
-        return (op, _fixops(('list',) + x[1:]))
-
-    return (op,) + tuple(_fixops(y) for y in x[1:])
-
-def _analyze(x, order):
-    if x is None:
-        return x
-
-    op = x[0]
-    if op == 'minus':
-        return _analyze(('and', x[1], ('not', x[2])), order)
-    elif op == 'only':
-        t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
-        return _analyze(t, order)
-    elif op == 'onlypost':
-        return _analyze(('func', ('symbol', 'only'), x[1]), order)
-    elif op == 'dagrangepre':
-        return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)
-    elif op == 'dagrangepost':
-        return _analyze(('func', ('symbol', 'descendants'), x[1]), order)
-    elif op == 'negate':
-        s = getstring(x[1], _("can't negate that"))
-        return _analyze(('string', '-' + s), order)
-    elif op in ('string', 'symbol'):
-        return x
-    elif op == 'and':
-        ta = _analyze(x[1], order)
-        tb = _analyze(x[2], _tofolloworder[order])
-        return (op, ta, tb, order)
-    elif op == 'or':
-        return (op, _analyze(x[1], order), order)
-    elif op == 'not':
-        return (op, _analyze(x[1], anyorder), order)
-    elif op == 'rangeall':
-        return (op, None, order)
-    elif op in ('rangepre', 'rangepost', 'parentpost'):
-        return (op, _analyze(x[1], defineorder), order)
-    elif op == 'group':
-        return _analyze(x[1], order)
-    elif op in ('dagrange', 'range', 'parent', 'ancestor'):
-        ta = _analyze(x[1], defineorder)
-        tb = _analyze(x[2], defineorder)
-        return (op, ta, tb, order)
-    elif op == 'list':
-        return (op,) + tuple(_analyze(y, order) for y in x[1:])
-    elif op == 'keyvalue':
-        return (op, x[1], _analyze(x[2], order))
-    elif op == 'func':
-        f = getsymbol(x[1])
-        d = defineorder
-        if f == 'present':
-            # 'present(set)' is known to return the argument set with no
-            # modification, so forward the current order to its argument
-            d = order
-        return (op, x[1], _analyze(x[2], d), order)
-    raise ValueError('invalid operator %r' % op)
-
-def analyze(x, order=defineorder):
-    """Transform raw parsed tree to evaluatable tree which can be fed to
-    optimize() or getset()
-
-    All pseudo operations should be mapped to real operations or functions
-    defined in methods or symbols table respectively.
-
-    'order' specifies how the current expression 'x' is ordered (see the
-    constants defined above.)
-    """
-    return _analyze(x, order)
-
-def _optimize(x, small):
-    if x is None:
-        return 0, x
-
-    smallbonus = 1
-    if small:
-        smallbonus = .5
-
-    op = x[0]
-    if op in ('string', 'symbol'):
-        return smallbonus, x # single revisions are small
-    elif op == 'and':
-        wa, ta = _optimize(x[1], True)
-        wb, tb = _optimize(x[2], True)
-        order = x[3]
-        w = min(wa, wb)
-
-        # (::x and not ::y)/(not ::y and ::x) have a fast path
-        tm = _matchonly(ta, tb) or _matchonly(tb, ta)
-        if tm:
-            return w, ('func', ('symbol', 'only'), tm, order)
-
-        if tb is not None and tb[0] == 'not':
-            return wa, ('difference', ta, tb[1], order)
-
-        if wa > wb:
-            return w, (op, tb, ta, order)
-        return w, (op, ta, tb, order)
-    elif op == 'or':
-        # fast path for machine-generated expression, that is likely to have
-        # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
-        order = x[2]
-        ws, ts, ss = [], [], []
-        def flushss():
-            if not ss:
-                return
-            if len(ss) == 1:
-                w, t = ss[0]
-            else:
-                s = '\0'.join(t[1] for w, t in ss)
-                y = ('func', ('symbol', '_list'), ('string', s), order)
-                w, t = _optimize(y, False)
-            ws.append(w)
-            ts.append(t)
-            del ss[:]
-        for y in getlist(x[1]):
-            w, t = _optimize(y, False)
-            if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
-                ss.append((w, t))
-                continue
-            flushss()
-            ws.append(w)
-            ts.append(t)
-        flushss()
-        if len(ts) == 1:
-            return ws[0], ts[0] # 'or' operation is fully optimized out
-        # we can't reorder trees by weight because it would change the order.
-        # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
-        #   ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
-        return max(ws), (op, ('list',) + tuple(ts), order)
-    elif op == 'not':
-        # Optimize not public() to _notpublic() because we have a fast version
-        if x[1][:3] == ('func', ('symbol', 'public'), None):
-            order = x[1][3]
-            newsym = ('func', ('symbol', '_notpublic'), None, order)
-            o = _optimize(newsym, not small)
-            return o[0], o[1]
-        else:
-            o = _optimize(x[1], not small)
-            order = x[2]
-            return o[0], (op, o[1], order)
-    elif op == 'rangeall':
-        return smallbonus, x
-    elif op in ('rangepre', 'rangepost', 'parentpost'):
-        o = _optimize(x[1], small)
-        order = x[2]
-        return o[0], (op, o[1], order)
-    elif op in ('dagrange', 'range', 'parent', 'ancestor'):
-        wa, ta = _optimize(x[1], small)
-        wb, tb = _optimize(x[2], small)
-        order = x[3]
-        return wa + wb, (op, ta, tb, order)
-    elif op == 'list':
-        ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
-        return sum(ws), (op,) + ts
-    elif op == 'keyvalue':
-        w, t = _optimize(x[2], small)
-        return w, (op, x[1], t)
-    elif op == 'func':
-        f = getsymbol(x[1])
-        wa, ta = _optimize(x[2], small)
-        if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
-                 'keyword', 'outgoing', 'user', 'destination'):
-            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', 'first', 'wdir', '_intlist'):
-            w = 0
-        elif f == "sort":
-            w = 10 # assume most sorts look at changelog
-        else:
-            w = 1
-        order = x[3]
-        return w + wa, (op, x[1], ta, order)
-    raise ValueError('invalid operator %r' % op)
-
-def optimize(tree):
-    """Optimize evaluatable tree
-
-    All pseudo operations should be transformed beforehand.
-    """
-    _weight, newtree = _optimize(tree, small=True)
-    return newtree
-
-# the set of valid characters for the initial letter of symbols in
-# alias declarations and definitions
-_aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))
-
-def _parsewith(spec, lookup=None, syminitletters=None):
-    """Generate a parse tree of given spec with given tokenizing options
-
-    >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
-    ('func', ('symbol', 'foo'), ('symbol', '$1'))
-    >>> _parsewith('$1')
-    Traceback (most recent call last):
-      ...
-    ParseError: ("syntax error in revset '$1'", 0)
-    >>> _parsewith('foo bar')
-    Traceback (most recent call last):
-      ...
-    ParseError: ('invalid token', 4)
-    """
-    p = parser.parser(elements)
-    tree, pos = p.parse(tokenize(spec, lookup=lookup,
-                                 syminitletters=syminitletters))
-    if pos != len(spec):
-        raise error.ParseError(_('invalid token'), pos)
-    return _fixops(parser.simplifyinfixops(tree, ('list', 'or')))
-
-class _aliasrules(parser.basealiasrules):
-    """Parsing and expansion rule set of revset aliases"""
-    _section = _('revset alias')
-
-    @staticmethod
-    def _parse(spec):
-        """Parse alias declaration/definition ``spec``
-
-        This allows symbol names to use also ``$`` as an initial letter
-        (for backward compatibility), and callers of this function should
-        examine whether ``$`` is used also for unexpected symbols or not.
-        """
-        return _parsewith(spec, syminitletters=_aliassyminitletters)
-
-    @staticmethod
-    def _trygetfunc(tree):
-        if tree[0] == 'func' and tree[1][0] == 'symbol':
-            return tree[1][1], getlist(tree[2])
-
-def expandaliases(ui, tree):
-    aliases = _aliasrules.buildmap(ui.configitems('revsetalias'))
-    tree = _aliasrules.expand(aliases, tree)
-    # warn about problematic (but not referred) aliases
-    for name, alias in sorted(aliases.iteritems()):
-        if alias.error and not alias.warned:
-            ui.warn(_('warning: %s\n') % (alias.error))
-            alias.warned = True
-    return tree
-
-def foldconcat(tree):
-    """Fold elements to be concatenated by `##`
-    """
-    if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
-        return tree
-    if tree[0] == '_concat':
-        pending = [tree]
-        l = []
-        while pending:
-            e = pending.pop()
-            if e[0] == '_concat':
-                pending.extend(reversed(e[1:]))
-            elif e[0] in ('string', 'symbol'):
-                l.append(e[1])
-            else:
-                msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
-                raise error.ParseError(msg)
-        return ('string', ''.join(l))
-    else:
-        return tuple(foldconcat(t) for t in tree)
-
-def parse(spec, lookup=None):
-    return _parsewith(spec, lookup=lookup)
-
-def posttreebuilthook(tree, repo):
-    # hook for extensions to execute code on the optimized tree
-    pass
-
-def match(ui, spec, repo=None, order=defineorder):
-    """Create a matcher for a single revision spec
-
-    If order=followorder, a matcher takes the ordering specified by the input
-    set.
-    """
-    return matchany(ui, [spec], repo=repo, order=order)
-
-def matchany(ui, specs, repo=None, order=defineorder):
-    """Create a matcher that will include any revisions matching one of the
-    given specs
-
-    If order=followorder, a matcher takes the ordering specified by the input
-    set.
-    """
-    if not specs:
-        def mfunc(repo, subset=None):
-            return baseset()
-        return mfunc
-    if not all(specs):
-        raise error.ParseError(_("empty query"))
-    lookup = None
-    if repo:
-        lookup = repo.__contains__
-    if len(specs) == 1:
-        tree = parse(specs[0], lookup)
-    else:
-        tree = ('or', ('list',) + tuple(parse(s, lookup) for s in specs))
-
-    if ui:
-        tree = expandaliases(ui, tree)
-    tree = foldconcat(tree)
-    tree = analyze(tree, order)
-    tree = optimize(tree)
-    posttreebuilthook(tree, repo)
-    return makematcher(tree)
-
-def makematcher(tree):
-    """Create a matcher from an evaluatable tree"""
-    def mfunc(repo, subset=None):
-        if subset is None:
-            subset = fullreposet(repo)
-        if util.safehasattr(subset, 'isascending'):
-            result = getset(repo, subset, tree)
-        else:
-            result = getset(repo, baseset(subset), tree)
-        return result
-    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 "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
-        elif t == 's':
-            return "_list('%s')" % "\0".join(s)
-        elif t == 'n':
-            return "_hexlist('%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 error.Abort(_('unexpected revspec format character %s')
-                                  % d)
-        else:
-            ret += c
-        pos += 1
-
-    return ret
-
-def prettyformat(tree):
-    return parser.prettyformat(tree, ('string', 'symbol'))
-
-def depth(tree):
-    if isinstance(tree, tuple):
-        return max(map(depth, tree)) + 1
-    else:
-        return 0
-
-def funcsused(tree):
-    if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
-        return set()
-    else:
-        funcs = set()
-        for s in tree[1:]:
-            funcs |= funcsused(s)
-        if tree[0] == 'func':
-            funcs.add(tree[1][1])
-        return funcs
-
 def _formatsetrepr(r):
     """Format an optional printable representation of a set
 
@@ -3887,7 +958,7 @@  class fullreposet(spanset):
         other.sort(reverse=self.isdescending())
         return other
 
-def prettyformatset(revs):
+def prettyformat(revs):
     lines = []
     rs = repr(revs)
     p = 0
@@ -3900,17 +971,3 @@  def prettyformatset(revs):
         lines.append((l, rs[p:q].rstrip()))
         p = q
     return '\n'.join('  ' * l + s for l, s in lines)
-
-def loadpredicate(ui, extname, registrarobj):
-    """Load revset predicates from specified registrarobj
-    """
-    for name, func in registrarobj._table.iteritems():
-        symbols[name] = func
-        if func._safe:
-            safesymbols.add(name)
-
-# load built-in predicates explicitly to setup safesymbols
-loadpredicate(None, None, predicate)
-
-# tell hggettext to extract docstrings from these functions:
-i18nfunctions = symbols.values()
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -29,6 +29,7 @@  testmod('mercurial.patch')
 testmod('mercurial.pathutil')
 testmod('mercurial.parser')
 testmod('mercurial.revset')
+testmod('mercurial.smartset')
 testmod('mercurial.store')
 testmod('mercurial.subrepo')
 testmod('mercurial.templatefilters')
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -40,6 +40,7 @@  these predicates use '\0' as a separator
   >     cmdutil,
   >     node as nodemod,
   >     revset,
+  >     smartset,
   > )
   > cmdtable = {}
   > command = cmdutil.command(cmdtable)
@@ -59,7 +60,7 @@  these predicates use '\0' as a separator
   >     func = revset.match(ui, expr, repo)
   >     revs = func(repo)
   >     if ui.verbose:
-  >         ui.note("* set:\n", revset.prettyformatset(revs), "\n")
+  >         ui.note("* set:\n", smartset.prettyformat(revs), "\n")
   >     for c in revs:
   >         ui.write("%s\n" % c)
   > EOF