mercurial/namespaces.py
author Martin von Zweigbergk <martinvonz@google.com>
Thu, 11 Dec 2014 22:51:29 -0800
changeset 23642 7fd1a6c27e60
parent 23610 9266d1dd6a6e
child 23715 eee55c09010a
permissions -rw-r--r--
largefiles: don't duplicate 'actions' into 'actionbyfile'

from i18n import _
from mercurial import util
import templatekw

def tolist(val):
    """
    a convenience method to return an empty list instead of None
    """
    if val is None:
        return []
    else:
        return [val]

class namespaces(object):
    """
    provides an interface to register a generic many-to-many mapping between
    some (namespaced) names and nodes. The goal here is to control the
    pollution of jamming things into tags or bookmarks (in extension-land) and
    to simplify internal bits of mercurial: log output, tab completion, etc.

    More precisely, we define a list of names (the namespace), a mapping of
    names to nodes, and a mapping from nodes to names. Each mapping
    returns a list of nodes.

    Furthermore, each name mapping will be passed a name to lookup which might
    not be in its domain. In this case, each method should return an empty list
    and not raise an error.

    We'll have a dictionary '_names' where each key is a namespace and
    its value is a dictionary of functions:
      'templatename': name to use for templating (usually the singular form
                      of the plural namespace name)
      'namemap': function that takes a name and returns a list of nodes
      'nodemap': function that takes a node and returns a list of names
    """

    _names_version = 0

    def __init__(self):
        self._names = util.sortdict()

        addns = self.addnamespace

        # we need current mercurial named objects (bookmarks, tags, and
        # branches) to be initialized somewhere, so that place is here
        addns("bookmarks", "bookmark",
              lambda repo, name: tolist(repo._bookmarks.get(name)),
              lambda repo, name: repo.nodebookmarks(name))

        addns("tags", "tag",
              lambda repo, name: tolist(repo._tagscache.tags.get(name)),
              lambda repo, name: repo.nodetags(name))

        addns("branches", "branch",
              lambda repo, name: tolist(repo.branchtip(name)),
              lambda repo, node: [repo[node].branch()])

    def addnamespace(self, namespace, templatename, namemap, nodemap,
                     order=None):
        """
        register a namespace

        namespace: the name to be registered (in plural form)
        templatename: the name to use for templating
        namemap: function that inputs a node, output name(s)
        nodemap: function that inputs a name, output node(s)
        order: optional argument to specify the order of namespaces
               (e.g. 'branches' should be listed before 'bookmarks')
        """
        val = {'templatename': templatename,
               'namemap': namemap,
               'nodemap': nodemap}
        if order is not None:
            self._names.insert(order, namespace, val)
        else:
            self._names[namespace] = val

        # we only generate a template keyword if one does not already exist
        if namespace not in templatekw.keywords:
            def generatekw(**args):
                return templatekw.shownames(namespace, **args)

            templatekw.keywords[namespace] = generatekw

    def singlenode(self, repo, name):
        """
        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
        in the defined precedence order.

        Raises a KeyError if there is no such node.
        """
        for ns, v in self._names.iteritems():
            n = v['namemap'](repo, name)
            if 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]
        raise KeyError(_('no such name: %s') % name)

    def templatename(self, namespace):
        """method that returns the template name of a namespace"""
        return self._names[namespace]['templatename']

    def names(self, repo, namespace, node):
        """method that returns a (sorted) list of names in a namespace that
        match a given node"""
        return sorted(self._names[namespace]['nodemap'](repo, node))