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 _______________________________________________ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel