view hgext3rd/topic/revset.py @ 4061:ad4194399b47

topic: handle ambiguous arguments to topic() revset These arguments can be interpreted as either string or a revset. The decision is made based on existence of topic with such a name. This matches the behavior of branch() revset. The code needs to know all topics that ever existed in the repo, because some commands report "disappearance" of topics after certain operations, using this revset (e.g. via stack.stack or repo.revs).
author Anton Shestakov <av6@dwimlabs.net>
date Thu, 30 Aug 2018 21:05:17 +0800
parents 54eade86ac31
children 00c65abf99cd
line wrap: on
line source

from __future__ import absolute_import

from mercurial import (
    error,
    registrar,
    revset,
    util,
)

from . import (
    destination,
    stack,
)

try:
    mkmatcher = revset._stringmatcher
except AttributeError:
    try:
        from mercurial.utils import stringutil
        mkmatcher = stringutil.stringmatcher
    except (ImportError, AttributeError):
        mkmatcher = util.stringmatcher

revsetpredicate = registrar.revsetpredicate()

@revsetpredicate('topic([string or set])')
def topicset(repo, subset, x):
    """All changesets with the specified topic or the topics of the given
    changesets. Without the argument, all changesets with any topic specified.

    If `string` starts with `re:` the remainder of the name is treated
    as a regular expression.
    """
    args = revset.getargs(x, 0, 1, 'topic takes one or no arguments')

    mutable = revset._notpublic(repo, revset.fullreposet(repo), ())

    if not args:
        return (subset & mutable).filter(lambda r: bool(repo[r].topic()))

    try:
        topic = revset.getstring(args[0], '')
    except error.ParseError:
        # not a string, but another revset
        pass
    else:
        kind, pattern, matcher = mkmatcher(topic)

        def matches(r):
            topic = repo[r].topic()
            if not topic:
                return False
            return matcher(topic)

        if kind == 'literal':
            # note: falls through to the revset case if no topic with this name
            # exists and pattern kind is not specified explicitly

            alltopics = set([repo.currenttopic])
            for r in repo.unfiltered().set('all()'):
                alltopics.add(r.topic(force=True))
            alltopics.discard('')

            if pattern in alltopics:
                return (subset & mutable).filter(matches)

            if topic.startswith('literal:'):
                raise error.RepoLookupError("topic '%s' does not exist"
                                            % pattern)
        else:
            return (subset & mutable).filter(matches)

    s = revset.getset(repo, revset.fullreposet(repo), x)
    topics = set(repo[r].topic() for r in s)
    topics.discard('')

    def matches(r):
        if r in s:
            return True
        topic = repo[r].topic()
        if not topic:
            return False
        return topic in topics

    return (subset & mutable).filter(matches)

@revsetpredicate('ngtip([branch])')
def ngtipset(repo, subset, x):
    """The untopiced tip.

    Name is horrible so that people change it.
    """
    args = revset.getargs(x, 1, 1, 'topic takes one')
    # match a specific topic
    branch = revset.getstring(args[0], 'ngtip() argument must be a string')
    if branch == '.':
        branch = repo['.'].branch()
    return subset & revset.baseset(destination.ngtip(repo, branch))

@revsetpredicate('stack()')
def stackset(repo, subset, x):
    """All relevant changes in the current topic,

    This is roughly equivalent to 'topic(.) - obsolete' with a sorting moving
    unstable changeset after there future parent (as if evolve where already
    run)."""
    err = 'stack() takes no argument, it works on current topic'
    revset.getargs(x, 0, 0, err)
    topic = None
    branch = None
    if repo.currenttopic:
        topic = repo.currenttopic
    else:
        branch = repo[None].branch()
    return revset.baseset(stack.stack(repo, branch=branch, topic=topic)[1:]) & subset