mercurial/tags.py
author Greg Ward <greg-hg@gerg.ca>
Thu, 16 Jul 2009 10:39:41 -0400
changeset 9149 abb7d4d43a5f
child 9151 f528d1a93491
permissions -rw-r--r--
Factor tags module out of localrepo (issue548). Currently only handles reading tags, and will soon grow support for tag caching. Could eventually deal with updating tags too.

# tags.py - read tag info from local repository
#
# Copyright 2009 Matt Mackall <mpm@selenic.com>
# Copyright 2009 Greg Ward <greg@gerg.ca>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

# Currently this module only deals with reading tags.  Soon it will grow
# support for caching tag info.  Eventually, it could take care of
# updating (adding/removing/moving) tags too.

from node import bin, hex
from i18n import _
import encoding
import error

def findglobaltags(ui, repo, alltags, tagtypes):
    '''Find global tags in repo by reading .hgtags from every head that
    has a distinct version of it.  Updates the dicts alltags, tagtypes
    in place: alltags maps tag name to (node, hist) pair (see _readtags()
    below), and tagtypes maps tag name to tag type ('global' in this
    case).'''

    seen = set()
    fctx = None
    ctxs = []                       # list of filectx
    for node in repo.heads():
        try:
            fnode = repo[node].filenode('.hgtags')
        except error.LookupError:
            continue
        if fnode not in seen:
            seen.add(fnode)
            if not fctx:
                fctx = repo.filectx('.hgtags', fileid=fnode)
            else:
                fctx = fctx.filectx(fnode)
            ctxs.append(fctx)

    # read the tags file from each head, ending with the tip
    for fctx in reversed(ctxs):
        filetags = _readtags(
            ui, repo, fctx.data().splitlines(), fctx)
        _updatetags(filetags, "global", alltags, tagtypes)

def readlocaltags(ui, repo, alltags, tagtypes):
    '''Read local tags in repo.  Update alltags and tagtypes.'''
    try:
        data = encoding.fromlocal(repo.opener("localtags").read())
        # localtags are stored in the local character set
        # while the internal tag table is stored in UTF-8
        filetags = _readtags(
            ui, repo, data.splitlines(), "localtags")
        _updatetags(filetags, "local", alltags, tagtypes)
    except IOError:
        pass

def _readtags(ui, repo, lines, fn):
    '''Read tag definitions from a file (or any source of lines).
    Return a mapping from tag name to (node, hist): node is the node id
    from the last line read for that name, and hist is the list of node
    ids previously associated with it (in file order).  All node ids are
    binary, not hex.'''

    filetags = {}               # map tag name to (node, hist)
    count = 0

    def warn(msg):
        ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))

    for line in lines:
        count += 1
        if not line:
            continue
        try:
            (nodehex, name) = line.split(" ", 1)
        except ValueError:
            warn(_("cannot parse entry"))
            continue
        name = encoding.tolocal(name.strip()) # stored in UTF-8
        try:
            nodebin = bin(nodehex)
        except TypeError:
            warn(_("node '%s' is not well formed") % nodehex)
            continue
        if nodebin not in repo.changelog.nodemap:
            # silently ignore as pull -r might cause this
            continue

        # update filetags
        hist = []
        if name in filetags:
            n, hist = filetags[name]
            hist.append(n)
        filetags[name] = (nodebin, hist)
    return filetags

def _updatetags(filetags, tagtype, alltags, tagtypes):
    '''Incorporate the tag info read from one file into the two
    dictionaries, alltags and tagtypes, that contain all tag
    info (global across all heads plus local).'''

    for name, nodehist in filetags.iteritems():
        if name not in alltags:
            alltags[name] = nodehist
            tagtypes[name] = tagtype
            continue

        # we prefer alltags[name] if:
        #  it supercedes us OR
        #  mutual supercedes and it has a higher rank
        # otherwise we win because we're tip-most
        anode, ahist = nodehist
        bnode, bhist = alltags[name]
        if (bnode != anode and anode in bhist and
            (bnode not in ahist or len(bhist) > len(ahist))):
            anode = bnode
        ahist.extend([n for n in bhist if n not in ahist])
        alltags[name] = anode, ahist
        tagtypes[name] = tagtype