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