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.
--- a/mercurial/localrepo.py Thu Jul 16 10:39:41 2009 -0400
+++ b/mercurial/localrepo.py Thu Jul 16 10:39:41 2009 -0400
@@ -13,6 +13,7 @@
import util, extensions, hook, error
import match as match_
import merge as merge_
+import tags as tags_
from lock import release
import weakref, stat, errno, os, time, inspect
propertycache = util.propertycache
@@ -258,102 +259,11 @@
# be one tagtype for all such "virtual" tags? Or is the status
# quo fine?
- def readtags(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):
- self.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 self.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
-
alltags = {} # map tag name to (node, hist)
tagtypes = {}
- def updatetags(filetags, tagtype):
- '''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
-
- seen = set()
- fctx = None
- ctxs = [] # list of filectx
- for node in self.heads():
- try:
- fnode = self[node].filenode('.hgtags')
- except error.LookupError:
- continue
- if fnode not in seen:
- seen.add(fnode)
- if not fctx:
- fctx = self.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(fctx.data().splitlines(), fctx)
- updatetags(filetags, "global")
-
- try:
- data = encoding.fromlocal(self.opener("localtags").read())
- # localtags are stored in the local character set
- # while the internal tag table is stored in UTF-8
- filetags = readtags(data.splitlines(), "localtags")
- updatetags(filetags, "local")
- except IOError:
- pass
+ tags_.findglobaltags(self.ui, self, alltags, tagtypes)
+ tags_.readlocaltags(self.ui, self, alltags, tagtypes)
tags = {}
for (name, (node, hist)) in alltags.iteritems():
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/tags.py Thu Jul 16 10:39:41 2009 -0400
@@ -0,0 +1,122 @@
+# 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
+