D3715: namespaces: allow namespaces whose symbols resolve to many nodes (API)
martinvonz (Martin von Zweigbergk)
phabricator at mercurial-scm.org
Mon Jun 11 23:11:04 UTC 2018
martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.
REVISION SUMMARY
The goal of this commit is to allow namespaces that support multiple
nodes per name to also have the name resolve to those multiple nodes
in the revset. For example, the "topics" namespace seems to be a
natural candidate for this (I don't know if the extension's maintainer
agrees). I view a topic as having multiple nodes and I would therefore
expect `hg log -r my-topic` to list all those nodes. Note that the
topics extension already supports multiple nodes per name, but we
don't expose that to the user in a consistent way (each namespace has
to define its own revset).
This commit adds an option to namespaces to indicate that `hg log -r
<name in the namespace>` and similar should resolve to the nodes that
the namespace says and not just the highest revnum among them.
Marked (API) because I repurposed singlenode() to nodes().
Note: I think branches should also resolve to multiple nodes (so
e.g. `hg log -r stable` lists all nodes on stable), but it's obviously
too late to change that now (and perhaps BC is the reason it even
behaves the way it does). I also realize that any namespace that were
to use the new mechanism would be inconsistent with how branches
work. I think the convenience and intuitiveness outweighs the cost of
that inconsistency.
REPOSITORY
rHG Mercurial
REVISION DETAIL
https://phab.mercurial-scm.org/D3715
AFFECTED FILES
mercurial/namespaces.py
mercurial/revset.py
mercurial/scmutil.py
tests/paritynamesext.py
tests/test-revset2.t
CHANGE DETAILS
diff --git a/tests/test-revset2.t b/tests/test-revset2.t
--- a/tests/test-revset2.t
+++ b/tests/test-revset2.t
@@ -675,6 +675,12 @@
6
$ log 'named("tags")'
6
+ $ hg --config extensions.revnamesext=$TESTDIR/paritynamesext.py log --template '{rev}\n' -r even
+ 0
+ 2
+ 4
+ 6
+ 8
issue2437
diff --git a/tests/paritynamesext.py b/tests/paritynamesext.py
new file mode 100644
--- /dev/null
+++ b/tests/paritynamesext.py
@@ -0,0 +1,26 @@
+# Defines a namespace with names 'even' and 'odd'
+
+from __future__ import absolute_import
+
+from mercurial import (
+ namespaces,
+)
+
+def reposetup(ui, repo):
+ def namemap(repo, name):
+ if name == 'even':
+ parity = 0
+ elif name == 'odd':
+ parity = 1
+ else:
+ return []
+ return [repo[rev].node() for rev in repo if rev % 2 == parity]
+ def nodemap(repo, node):
+ return [repo[node].rev() % 2]
+
+ ns = namespaces.namespace(b'parity', templatename=b'parity',
+ logname=b'parity',
+ listnames=lambda r: ['even', 'odd'],
+ namemap=namemap, nodemap=nodemap,
+ multinode=True)
+ repo.names.addnamespace(ns)
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -488,7 +488,7 @@
except error.RepoLookupError:
return False
-def _revsymbol(repo, symbol):
+def _revsymbol(repo, symbol, allowmultiple):
if not isinstance(symbol, bytes):
msg = ("symbol (%s of type %s) was not a string, did you mean "
"repo[symbol]?" % (symbol, type(symbol)))
@@ -524,9 +524,9 @@
# look up bookmarks through the name interface
try:
- node = repo.names.singlenode(repo, symbol)
- rev = repo.changelog.rev(node)
- return [repo[rev]]
+ nodes = repo.names.nodes(repo, symbol, allowmultiple)
+ revs = [repo.changelog.rev(n) for n in nodes]
+ return [repo[r2] for r2 in revs] # "r2" to keep pyflakes happy
except KeyError:
pass
@@ -550,10 +550,19 @@
i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
not "max(public())".
"""
- ctxs = _revsymbol(repo, symbol)
+ ctxs = _revsymbol(repo, symbol, allowmultiple=False)
assert len(ctxs) == 1
return ctxs[0]
+def revsymbolmultiple(repo, symbol):
+ """Returns all contexts given a single revision symbol (as string).
+
+ This is similar to revsymbol(), but if "symbol" is a namespace symbol,
+ then this may return many context (if the namespace maps the symbol to
+ many revisions).
+ """
+ return _revsymbol(repo, symbol, allowmultiple=True)
+
def _filterederror(repo, changeid):
"""build an exception to be raised about a filtered changeid
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -118,11 +118,14 @@
def stringset(repo, subset, x, order):
if not x:
raise error.ParseError(_("empty string is not a valid revision"))
- x = scmutil.intrev(scmutil.revsymbol(repo, x))
- if (x in subset
- or x == node.nullrev and isinstance(subset, fullreposet)):
- return baseset([x])
- return baseset()
+ ctxs = scmutil.revsymbolmultiple(repo, x)
+ if len(ctxs) == 1:
+ x = scmutil.intrev(ctxs[0])
+ if (x in subset
+ or x == node.nullrev and isinstance(subset, fullreposet)):
+ return baseset([x])
+ return baseset()
+ return subset & baseset(sorted(ctx.rev() for ctx in ctxs))
def rangeset(repo, subset, x, y, order):
m = getset(repo, fullreposet(repo), x)
diff --git a/mercurial/namespaces.py b/mercurial/namespaces.py
--- a/mercurial/namespaces.py
+++ b/mercurial/namespaces.py
@@ -93,7 +93,7 @@
def generatekw(context, mapping):
return templatekw.shownames(context, mapping, namespace.name)
- def singlenode(self, repo, name):
+ def nodes(self, repo, name, allowmultiple=False):
"""
Return the 'best' node for the given name. Best means the first node
in the first nonempty list returned by a name-to-nodes mapping function
@@ -104,12 +104,12 @@
for ns, v in self._names.iteritems():
n = v.namemap(repo, name)
if n:
+ if (allowmultiple and v.multinode) or len(n) == 1:
+ return n
# return max revision number
- if len(n) > 1:
- cl = repo.changelog
- maxrev = max(cl.rev(node) for node in n)
- return cl.node(maxrev)
- return n[0]
+ cl = repo.changelog
+ maxrev = max(cl.rev(node) for node in n)
+ return [cl.node(maxrev)]
raise KeyError(_('no such name: %s') % name)
class namespace(object):
@@ -142,7 +142,7 @@
def __init__(self, name, templatename=None, logname=None, colorname=None,
logfmt=None, listnames=None, namemap=None, nodemap=None,
- deprecated=None, builtin=False):
+ deprecated=None, builtin=False, multinode=False):
"""create a namespace
name: the namespace to be registered (in plural form)
@@ -158,6 +158,9 @@
nodemap: function that inputs a node, output name(s)
deprecated: set of names to be masked for ordinary use
builtin: whether namespace is implemented by core Mercurial
+ multinode: whether namespace can have multiple nodes associated with
+ one name (`hg log -r that-name` will then list multiple
+ nodes)
"""
self.name = name
self.templatename = templatename
@@ -187,6 +190,7 @@
self.deprecated = deprecated
self.builtin = builtin
+ self.multinode = multinode
def names(self, repo, node):
"""method that returns a (sorted) list of names in a namespace that
To: martinvonz, #hg-reviewers
Cc: mercurial-devel
More information about the Mercurial-devel
mailing list