|
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 |