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