Mercurial > hg
comparison mercurial/tags.py @ 9152:4017291c4c48
tags: support 'instant' tag retrieval (issue548)
- modify _readtagcache() and _writetagcache() to read/write tag->node
mapping for global tags
- if (and only if) tip unchanged, use that cached mapping to avoid
reading any revisions of .hgtags
- change so tag names are UTF-8 in memory in tags.py, and converted to
local encoding as late as possible (in localrepository._findtags())
author | Greg Ward <greg-hg@gerg.ca> |
---|---|
date | Thu, 16 Jul 2009 10:41:19 -0400 |
parents | f528d1a93491 |
children | c5f0825c1dbb |
comparison
equal
deleted
inserted
replaced
9151:f528d1a93491 | 9152:4017291c4c48 |
---|---|
57 ui, repo, fctx.data().splitlines(), fctx) | 57 ui, repo, fctx.data().splitlines(), fctx) |
58 _updatetags(filetags, "global", alltags, tagtypes) | 58 _updatetags(filetags, "global", alltags, tagtypes) |
59 | 59 |
60 def findglobaltags2(ui, repo, alltags, tagtypes): | 60 def findglobaltags2(ui, repo, alltags, tagtypes): |
61 '''Same as findglobaltags1(), but with caching.''' | 61 '''Same as findglobaltags1(), but with caching.''' |
62 (heads, tagfnode, shouldwrite) = _readtagcache(ui, repo) | 62 # This is so we can be lazy and assume alltags contains only global |
63 # tags when we pass it to _writetagcache(). | |
64 assert len(alltags) == len(tagtypes) == 0, \ | |
65 "findglobaltags() should be called first" | |
66 | |
67 (heads, tagfnode, cachetags, shouldwrite) = _readtagcache(ui, repo) | |
68 if cachetags is not None: | |
69 assert not shouldwrite | |
70 # XXX is this really 100% correct? are there oddball special | |
71 # cases where a global tag should outrank a local tag but won't, | |
72 # because cachetags does not contain rank info? | |
73 _updatetags(cachetags, 'global', alltags, tagtypes) | |
74 return | |
63 | 75 |
64 _debug(ui, "reading tags from %d head(s): %s\n" | 76 _debug(ui, "reading tags from %d head(s): %s\n" |
65 % (len(heads), map(short, reversed(heads)))) | 77 % (len(heads), map(short, reversed(heads)))) |
66 seen = set() # set of fnode | 78 seen = set() # set of fnode |
67 fctx = None | 79 fctx = None |
80 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx) | 92 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx) |
81 _updatetags(filetags, 'global', alltags, tagtypes) | 93 _updatetags(filetags, 'global', alltags, tagtypes) |
82 | 94 |
83 # and update the cache (if necessary) | 95 # and update the cache (if necessary) |
84 if shouldwrite: | 96 if shouldwrite: |
85 _writetagcache(ui, repo, heads, tagfnode) | 97 _writetagcache(ui, repo, heads, tagfnode, alltags) |
86 | 98 |
87 # Set this to findglobaltags1 to disable tag caching. | 99 # Set this to findglobaltags1 to disable tag caching. |
88 findglobaltags = findglobaltags2 | 100 findglobaltags = findglobaltags2 |
89 | 101 |
90 def readlocaltags(ui, repo, alltags, tagtypes): | 102 def readlocaltags(ui, repo, alltags, tagtypes): |
91 '''Read local tags in repo. Update alltags and tagtypes.''' | 103 '''Read local tags in repo. Update alltags and tagtypes.''' |
92 try: | 104 try: |
93 data = encoding.fromlocal(repo.opener("localtags").read()) | 105 # localtags is in the local encoding; re-encode to UTF-8 on |
94 # localtags are stored in the local character set | 106 # input for consistency with the rest of this module. |
95 # while the internal tag table is stored in UTF-8 | 107 data = repo.opener("localtags").read() |
96 filetags = _readtags( | 108 filetags = _readtags( |
97 ui, repo, data.splitlines(), "localtags") | 109 ui, repo, data.splitlines(), "localtags", |
110 recode=encoding.fromlocal) | |
98 _updatetags(filetags, "local", alltags, tagtypes) | 111 _updatetags(filetags, "local", alltags, tagtypes) |
99 except IOError: | 112 except IOError: |
100 pass | 113 pass |
101 | 114 |
102 def _readtags(ui, repo, lines, fn): | 115 def _readtags(ui, repo, lines, fn, recode=None): |
103 '''Read tag definitions from a file (or any source of lines). | 116 '''Read tag definitions from a file (or any source of lines). |
104 Return a mapping from tag name to (node, hist): node is the node id | 117 Return a mapping from tag name to (node, hist): node is the node id |
105 from the last line read for that name, and hist is the list of node | 118 from the last line read for that name, and hist is the list of node |
106 ids previously associated with it (in file order). All node ids are | 119 ids previously associated with it (in file order). All node ids are |
107 binary, not hex.''' | 120 binary, not hex.''' |
119 try: | 132 try: |
120 (nodehex, name) = line.split(" ", 1) | 133 (nodehex, name) = line.split(" ", 1) |
121 except ValueError: | 134 except ValueError: |
122 warn(_("cannot parse entry")) | 135 warn(_("cannot parse entry")) |
123 continue | 136 continue |
124 name = encoding.tolocal(name.strip()) # stored in UTF-8 | 137 name = name.strip() |
138 if recode: | |
139 name = recode(name) | |
125 try: | 140 try: |
126 nodebin = bin(nodehex) | 141 nodebin = bin(nodehex) |
127 except TypeError: | 142 except TypeError: |
128 warn(_("node '%s' is not well formed") % nodehex) | 143 warn(_("node '%s' is not well formed") % nodehex) |
129 continue | 144 continue |
171 # avoids looking up .hgtags in the manifest for every head, and it | 186 # avoids looking up .hgtags in the manifest for every head, and it |
172 # can avoid calling heads() at all if there have been no changes to | 187 # can avoid calling heads() at all if there have been no changes to |
173 # the repo. | 188 # the repo. |
174 | 189 |
175 def _readtagcache(ui, repo): | 190 def _readtagcache(ui, repo): |
176 '''Read the tag cache and return a tuple (heads, fnodes, | 191 '''Read the tag cache and return a tuple (heads, fnodes, cachetags, |
177 shouldwrite). heads is the list of all heads currently in the | 192 shouldwrite). If the cache is completely up-to-date, cachetags is a |
178 repository (ordered from tip to oldest) and fnodes is a mapping from | 193 dict of the form returned by _readtags(); otherwise, it is None and |
179 head to .hgtags filenode. Caller is responsible for reading tag | 194 heads and fnodes are set. In that case, heads is the list of all |
180 info from each head.''' | 195 heads currently in the repository (ordered from tip to oldest) and |
196 fnodes is a mapping from head to .hgtags filenode. If those two are | |
197 set, caller is responsible for reading tag info from each head.''' | |
181 | 198 |
182 try: | 199 try: |
183 cachefile = repo.opener('tags.cache', 'r') | 200 cachefile = repo.opener('tags.cache', 'r') |
184 _debug(ui, 'reading tag cache from %s\n' % cachefile.name) | 201 _debug(ui, 'reading tag cache from %s\n' % cachefile.name) |
185 except IOError: | 202 except IOError: |
200 cacherevs = [] # list of headrev | 217 cacherevs = [] # list of headrev |
201 cacheheads = [] # list of headnode | 218 cacheheads = [] # list of headnode |
202 cachefnode = {} # map headnode to filenode | 219 cachefnode = {} # map headnode to filenode |
203 if cachefile: | 220 if cachefile: |
204 for line in cachefile: | 221 for line in cachefile: |
222 if line == "\n": | |
223 break | |
205 line = line.rstrip().split() | 224 line = line.rstrip().split() |
206 cacherevs.append(int(line[0])) | 225 cacherevs.append(int(line[0])) |
207 headnode = bin(line[1]) | 226 headnode = bin(line[1]) |
208 cacheheads.append(headnode) | 227 cacheheads.append(headnode) |
209 if len(line) == 3: | 228 if len(line) == 3: |
210 fnode = bin(line[2]) | 229 fnode = bin(line[2]) |
211 cachefnode[headnode] = fnode | 230 cachefnode[headnode] = fnode |
212 | 231 |
213 cachefile.close() | |
214 | |
215 tipnode = repo.changelog.tip() | 232 tipnode = repo.changelog.tip() |
216 tiprev = len(repo.changelog) - 1 | 233 tiprev = len(repo.changelog) - 1 |
217 | 234 |
218 # Case 1 (common): tip is the same, so nothing has changed. | 235 # Case 1 (common): tip is the same, so nothing has changed. |
219 # (Unchanged tip trivially means no changesets have been added. | 236 # (Unchanged tip trivially means no changesets have been added. |
220 # But, thanks to localrepository.destroyed(), it also means none | 237 # But, thanks to localrepository.destroyed(), it also means none |
221 # have been destroyed by strip or rollback.) | 238 # have been destroyed by strip or rollback.) |
222 if cacheheads and cacheheads[0] == tipnode and cacherevs[0] == tiprev: | 239 if cacheheads and cacheheads[0] == tipnode and cacherevs[0] == tiprev: |
223 _debug(ui, "tag cache: tip unchanged\n") | 240 _debug(ui, "tag cache: tip unchanged\n") |
224 return (cacheheads, cachefnode, False) | 241 tags = _readtags(ui, repo, cachefile, cachefile.name) |
242 cachefile.close() | |
243 return (None, None, tags, False) | |
244 if cachefile: | |
245 cachefile.close() # ignore rest of file | |
225 | 246 |
226 repoheads = repo.heads() | 247 repoheads = repo.heads() |
227 | 248 |
228 # Case 2 (uncommon): empty repo; get out quickly and don't bother | 249 # Case 2 (uncommon): empty repo; get out quickly and don't bother |
229 # writing an empty cache. | 250 # writing an empty cache. |
230 if repoheads == [nullid]: | 251 if repoheads == [nullid]: |
231 return ([], {}, False) | 252 return ([], {}, {}, False) |
232 | 253 |
233 # Case 3 (uncommon): cache file missing or empty. | 254 # Case 3 (uncommon): cache file missing or empty. |
234 if not cacheheads: | 255 if not cacheheads: |
235 _debug(ui, 'tag cache: cache file missing or empty\n') | 256 _debug(ui, 'tag cache: cache file missing or empty\n') |
236 | 257 |
275 # no .hgtags file on this head | 296 # no .hgtags file on this head |
276 pass | 297 pass |
277 | 298 |
278 # Caller has to iterate over all heads, but can use the filenodes in | 299 # Caller has to iterate over all heads, but can use the filenodes in |
279 # cachefnode to get to each .hgtags revision quickly. | 300 # cachefnode to get to each .hgtags revision quickly. |
280 return (repoheads, cachefnode, True) | 301 return (repoheads, cachefnode, None, True) |
281 | 302 |
282 def _writetagcache(ui, repo, heads, tagfnode): | 303 def _writetagcache(ui, repo, heads, tagfnode, cachetags): |
283 | 304 |
284 cachefile = repo.opener('tags.cache', 'w', atomictemp=True) | 305 cachefile = repo.opener('tags.cache', 'w', atomictemp=True) |
285 _debug(ui, 'writing cache file %s\n' % cachefile.name) | 306 _debug(ui, 'writing cache file %s\n' % cachefile.name) |
286 | 307 |
287 realheads = repo.heads() # for sanity checks below | 308 realheads = repo.heads() # for sanity checks below |
304 if fnode: | 325 if fnode: |
305 cachefile.write('%d %s %s\n' % (rev, hex(head), hex(fnode))) | 326 cachefile.write('%d %s %s\n' % (rev, hex(head), hex(fnode))) |
306 else: | 327 else: |
307 cachefile.write('%d %s\n' % (rev, hex(head))) | 328 cachefile.write('%d %s\n' % (rev, hex(head))) |
308 | 329 |
330 # Tag names in the cache are in UTF-8 -- which is the whole reason | |
331 # we keep them in UTF-8 throughout this module. If we converted | |
332 # them local encoding on input, we would lose info writing them to | |
333 # the cache. | |
334 cachefile.write('\n') | |
335 for (name, (node, hist)) in cachetags.iteritems(): | |
336 cachefile.write("%s %s\n" % (hex(node), name)) | |
337 | |
309 cachefile.rename() | 338 cachefile.rename() |
310 cachefile.close() | 339 cachefile.close() |