##// END OF EJS Templates
Factor tags module out of localrepo (issue548)....
Greg Ward -
r9149:abb7d4d4 default
parent child Browse files
Show More
@@ -0,0 +1,122 b''
1 # tags.py - read tag info from local repository
2 #
3 # Copyright 2009 Matt Mackall <mpm@selenic.com>
4 # Copyright 2009 Greg Ward <greg@gerg.ca>
5 #
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
8
9 # Currently this module only deals with reading tags. Soon it will grow
10 # support for caching tag info. Eventually, it could take care of
11 # updating (adding/removing/moving) tags too.
12
13 from node import bin, hex
14 from i18n import _
15 import encoding
16 import error
17
18 def findglobaltags(ui, repo, alltags, tagtypes):
19 '''Find global tags in repo by reading .hgtags from every head that
20 has a distinct version of it. Updates the dicts alltags, tagtypes
21 in place: alltags maps tag name to (node, hist) pair (see _readtags()
22 below), and tagtypes maps tag name to tag type ('global' in this
23 case).'''
24
25 seen = set()
26 fctx = None
27 ctxs = [] # list of filectx
28 for node in repo.heads():
29 try:
30 fnode = repo[node].filenode('.hgtags')
31 except error.LookupError:
32 continue
33 if fnode not in seen:
34 seen.add(fnode)
35 if not fctx:
36 fctx = repo.filectx('.hgtags', fileid=fnode)
37 else:
38 fctx = fctx.filectx(fnode)
39 ctxs.append(fctx)
40
41 # read the tags file from each head, ending with the tip
42 for fctx in reversed(ctxs):
43 filetags = _readtags(
44 ui, repo, fctx.data().splitlines(), fctx)
45 _updatetags(filetags, "global", alltags, tagtypes)
46
47 def readlocaltags(ui, repo, alltags, tagtypes):
48 '''Read local tags in repo. Update alltags and tagtypes.'''
49 try:
50 data = encoding.fromlocal(repo.opener("localtags").read())
51 # localtags are stored in the local character set
52 # while the internal tag table is stored in UTF-8
53 filetags = _readtags(
54 ui, repo, data.splitlines(), "localtags")
55 _updatetags(filetags, "local", alltags, tagtypes)
56 except IOError:
57 pass
58
59 def _readtags(ui, repo, lines, fn):
60 '''Read tag definitions from a file (or any source of lines).
61 Return a mapping from tag name to (node, hist): node is the node id
62 from the last line read for that name, and hist is the list of node
63 ids previously associated with it (in file order). All node ids are
64 binary, not hex.'''
65
66 filetags = {} # map tag name to (node, hist)
67 count = 0
68
69 def warn(msg):
70 ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
71
72 for line in lines:
73 count += 1
74 if not line:
75 continue
76 try:
77 (nodehex, name) = line.split(" ", 1)
78 except ValueError:
79 warn(_("cannot parse entry"))
80 continue
81 name = encoding.tolocal(name.strip()) # stored in UTF-8
82 try:
83 nodebin = bin(nodehex)
84 except TypeError:
85 warn(_("node '%s' is not well formed") % nodehex)
86 continue
87 if nodebin not in repo.changelog.nodemap:
88 # silently ignore as pull -r might cause this
89 continue
90
91 # update filetags
92 hist = []
93 if name in filetags:
94 n, hist = filetags[name]
95 hist.append(n)
96 filetags[name] = (nodebin, hist)
97 return filetags
98
99 def _updatetags(filetags, tagtype, alltags, tagtypes):
100 '''Incorporate the tag info read from one file into the two
101 dictionaries, alltags and tagtypes, that contain all tag
102 info (global across all heads plus local).'''
103
104 for name, nodehist in filetags.iteritems():
105 if name not in alltags:
106 alltags[name] = nodehist
107 tagtypes[name] = tagtype
108 continue
109
110 # we prefer alltags[name] if:
111 # it supercedes us OR
112 # mutual supercedes and it has a higher rank
113 # otherwise we win because we're tip-most
114 anode, ahist = nodehist
115 bnode, bhist = alltags[name]
116 if (bnode != anode and anode in bhist and
117 (bnode not in ahist or len(bhist) > len(ahist))):
118 anode = bnode
119 ahist.extend([n for n in bhist if n not in ahist])
120 alltags[name] = anode, ahist
121 tagtypes[name] = tagtype
122
@@ -13,6 +13,7 b' import lock, transaction, store, encodin'
13 import util, extensions, hook, error
13 import util, extensions, hook, error
14 import match as match_
14 import match as match_
15 import merge as merge_
15 import merge as merge_
16 import tags as tags_
16 from lock import release
17 from lock import release
17 import weakref, stat, errno, os, time, inspect
18 import weakref, stat, errno, os, time, inspect
18 propertycache = util.propertycache
19 propertycache = util.propertycache
@@ -258,102 +259,11 b' class localrepository(repo.repository):'
258 # be one tagtype for all such "virtual" tags? Or is the status
259 # be one tagtype for all such "virtual" tags? Or is the status
259 # quo fine?
260 # quo fine?
260
261
261 def readtags(lines, fn):
262 '''Read tag definitions from a file (or any source of
263 lines). Return a mapping from tag name to (node, hist):
264 node is the node id from the last line read for that name,
265 and hist is the list of node ids previously associated with
266 it (in file order). All node ids are binary, not hex.'''
267
268 filetags = {} # map tag name to (node, hist)
269 count = 0
270
271 def warn(msg):
272 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
273
274 for line in lines:
275 count += 1
276 if not line:
277 continue
278 try:
279 (nodehex, name) = line.split(" ", 1)
280 except ValueError:
281 warn(_("cannot parse entry"))
282 continue
283 name = encoding.tolocal(name.strip()) # stored in UTF-8
284 try:
285 nodebin = bin(nodehex)
286 except TypeError:
287 warn(_("node '%s' is not well formed") % nodehex)
288 continue
289 if nodebin not in self.changelog.nodemap:
290 # silently ignore as pull -r might cause this
291 continue
292
293 # update filetags
294 hist = []
295 if name in filetags:
296 n, hist = filetags[name]
297 hist.append(n)
298 filetags[name] = (nodebin, hist)
299 return filetags
300
301 alltags = {} # map tag name to (node, hist)
262 alltags = {} # map tag name to (node, hist)
302 tagtypes = {}
263 tagtypes = {}
303
264
304 def updatetags(filetags, tagtype):
265 tags_.findglobaltags(self.ui, self, alltags, tagtypes)
305 '''Incorporate the tag info read from one file into the two
266 tags_.readlocaltags(self.ui, self, alltags, tagtypes)
306 dictionaries, alltags and tagtypes, that contain all tag
307 info (global across all heads plus local).'''
308
309 for name, nodehist in filetags.iteritems():
310 if name not in alltags:
311 alltags[name] = nodehist
312 tagtypes[name] = tagtype
313 continue
314
315 # we prefer alltags[name] if:
316 # it supercedes us OR
317 # mutual supercedes and it has a higher rank
318 # otherwise we win because we're tip-most
319 anode, ahist = nodehist
320 bnode, bhist = alltags[name]
321 if (bnode != anode and anode in bhist and
322 (bnode not in ahist or len(bhist) > len(ahist))):
323 anode = bnode
324 ahist.extend([n for n in bhist if n not in ahist])
325 alltags[name] = anode, ahist
326 tagtypes[name] = tagtype
327
328 seen = set()
329 fctx = None
330 ctxs = [] # list of filectx
331 for node in self.heads():
332 try:
333 fnode = self[node].filenode('.hgtags')
334 except error.LookupError:
335 continue
336 if fnode not in seen:
337 seen.add(fnode)
338 if not fctx:
339 fctx = self.filectx('.hgtags', fileid=fnode)
340 else:
341 fctx = fctx.filectx(fnode)
342 ctxs.append(fctx)
343
344 # read the tags file from each head, ending with the tip
345 for fctx in reversed(ctxs):
346 filetags = readtags(fctx.data().splitlines(), fctx)
347 updatetags(filetags, "global")
348
349 try:
350 data = encoding.fromlocal(self.opener("localtags").read())
351 # localtags are stored in the local character set
352 # while the internal tag table is stored in UTF-8
353 filetags = readtags(data.splitlines(), "localtags")
354 updatetags(filetags, "local")
355 except IOError:
356 pass
357
267
358 tags = {}
268 tags = {}
359 for (name, (node, hist)) in alltags.iteritems():
269 for (name, (node, hist)) in alltags.iteritems():
General Comments 0
You need to be logged in to leave comments. Login now