11 import changelog, dirstate, filelog, manifest, context |
11 import changelog, dirstate, filelog, manifest, context |
12 import lock, transaction, store, encoding |
12 import lock, transaction, store, encoding |
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 |
19 |
20 |
20 class localrepository(repo.repository): |
21 class localrepository(repo.repository): |
256 # mq and bookmarks add tags, but do not set the tagtype at all. |
257 # mq and bookmarks add tags, but do not set the tagtype at all. |
257 # Should each extension invent its own tag type? Should there |
258 # Should each extension invent its own tag type? Should there |
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(): |
360 if node != nullid: |
270 if node != nullid: |
361 tags[name] = node |
271 tags[name] = node |