##// END OF EJS Templates
phases: move pure phase computation in a function
phases: move pure phase computation in a function

File last commit:

r24445:c71edbaf default
r24519:de3acfab default
Show More
tags.py
391 lines | 14.0 KiB | text/x-python | PythonLexer
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 # tags.py - read tag info from local repository
#
# Copyright 2009 Matt Mackall <mpm@selenic.com>
# Copyright 2009 Greg Ward <greg@gerg.ca>
#
# This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 # Currently this module only deals with reading and caching tags.
# Eventually, it could take care of updating (adding/removing/moving)
# tags too.
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 from node import nullid, bin, hex, short
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 from i18n import _
Angel Ezquerra
tags: read tag info into a sorted dict (rather than into a regular dict)...
r21814 import util
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 import encoding
import error
Idan Kamara
tags: loosen IOError filtering when reading localtags
r14038 import errno
Gregory Szorc
tags: log events related to tags cache...
r21030 import time
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Gregory Szorc
tags: improve documentation...
r24445 # The tags cache stores information about heads and the history of tags.
#
# The cache file consists of two parts. The first part maps head nodes
# to .hgtags filenodes. The second part is a history of tags. The two
# parts are separated by an empty line.
#
# The first part consists of lines of the form:
#
# <headrev> <headnode> [<hgtagsnode>]
#
# <headrev> is an integer revision and <headnode> is a 40 character hex
# node for that changeset. These redundantly identify a repository
# head from the time the cache was written.
#
# <tagnode> is the filenode of .hgtags on that head. Heads with no .hgtags
# file will have no <hgtagsnode> (just 2 values per line).
#
# The filenode cache is ordered from tip to oldest (which is part of why
# <headrev> is there: a quick check of the tip from when the cache was
# written against the current tip is all that is needed to check whether
# the cache is up to date).
#
# The purpose of the filenode cache is to avoid the most expensive part
# of finding global tags, which is looking up the .hgtags filenode in the
# manifest for each head. This can take over a minute on repositories
# that have large manifests and many heads.
#
# The second part of the tags cache consists of lines of the form:
#
# <node> <tag>
#
# (This format is identical to that of .hgtags files.)
#
# <tag> is the tag name and <node> is the 40 character hex changeset
# the tag is associated with.
#
# Tags are written sorted by tag name.
#
# Tags associated with multiple changesets have an entry for each changeset.
# The most recent changeset (in terms of revlog ordering for the head
# setting it) for each tag is last.
Greg Ward
tags: remove the old non-caching implementation of findglobaltags()....
r11351 def findglobaltags(ui, repo, alltags, tagtypes):
Gregory Szorc
tags: improve documentation...
r24445 '''Find global tags in a repo.
"alltags" maps tag name to (node, hist) 2-tuples.
"tagtypes" maps tag name to tag type. Global tags always have the
"global" tag type.
The "alltags" and "tagtypes" dicts are updated in place. Empty dicts
should be passed in.
The tags cache is read and updated as a side-effect of calling.
'''
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 # This is so we can be lazy and assume alltags contains only global
# tags when we pass it to _writetagcache().
assert len(alltags) == len(tagtypes) == 0, \
"findglobaltags() should be called first"
(heads, tagfnode, cachetags, shouldwrite) = _readtagcache(ui, repo)
if cachetags is not None:
assert not shouldwrite
# XXX is this really 100% correct? are there oddball special
# cases where a global tag should outrank a local tag but won't,
# because cachetags does not contain rank info?
_updatetags(cachetags, 'global', alltags, tagtypes)
return
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Gregory Szorc
tags: improve documentation...
r24445 seen = set() # set of fnode
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 fctx = None
Gregory Szorc
tags: improve documentation...
r24445 for head in reversed(heads): # oldest to newest
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 assert head in repo.changelog.nodemap, \
"tag cache returned bogus head %s" % short(head)
fnode = tagfnode.get(head)
if fnode and fnode not in seen:
seen.add(fnode)
if not fctx:
fctx = repo.filectx('.hgtags', fileid=fnode)
else:
fctx = fctx.filectx(fnode)
filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
_updatetags(filetags, 'global', alltags, tagtypes)
# and update the cache (if necessary)
if shouldwrite:
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 _writetagcache(ui, repo, heads, tagfnode, alltags)
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 def readlocaltags(ui, repo, alltags, tagtypes):
Gregory Szorc
tags: improve documentation...
r24445 '''Read local tags in repo. Update alltags and tagtypes.'''
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 try:
Angel Ezquerra
localrepo: remove all external users of localrepo.opener...
r23877 data = repo.vfs.read("localtags")
Idan Kamara
tags: loosen IOError filtering when reading localtags
r14038 except IOError, inst:
if inst.errno != errno.ENOENT:
raise
return
# localtags is in the local encoding; re-encode to UTF-8 on
# input for consistency with the rest of this module.
filetags = _readtags(
ui, repo, data.splitlines(), "localtags",
recode=encoding.fromlocal)
Angel Ezquerra
repoview: do not crash when localtags refers to non existing revisions...
r21823
# remove tags pointing to invalid nodes
cl = repo.changelog
for t in filetags.keys():
try:
cl.rev(filetags[t][0])
except (LookupError, ValueError):
del filetags[t]
Idan Kamara
tags: loosen IOError filtering when reading localtags
r14038 _updatetags(filetags, "local", alltags, tagtypes)
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 '''Read tag definitions from a file (or any source of lines).
Gregory Szorc
tags: improve documentation...
r24445
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 This function returns two sortdicts with similar information:
Gregory Szorc
tags: improve documentation...
r24445
Mads Kiilerich
spelling: fixes from proofreading of spell checker issues
r23139 - the first dict, bintaghist, contains the tag information as expected by
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 the _readtags function, i.e. a mapping from tag name to (node, hist):
- node is the node id from the last line read for that name,
- hist is the list of node ids previously associated with it (in file
Gregory Szorc
tags: improve documentation...
r24445 order). All node ids are binary, not hex.
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 - the second dict, hextaglines, is a mapping from tag name to a list of
[hexnode, line number] pairs, ordered from the oldest to the newest node.
Gregory Szorc
tags: improve documentation...
r24445
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 When calcnodelines is False the hextaglines dict is not calculated (an
empty dict is returned). This is done to improve this function's
performance in cases where the line numbers are not needed.
'''
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 bintaghist = util.sortdict()
hextaglines = util.sortdict()
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 count = 0
def warn(msg):
ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 for nline, line in enumerate(lines):
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 count += 1
if not line:
continue
try:
(nodehex, name) = line.split(" ", 1)
except ValueError:
warn(_("cannot parse entry"))
continue
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 name = name.strip()
if recode:
name = recode(name)
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 try:
nodebin = bin(nodehex)
except TypeError:
warn(_("node '%s' is not well formed") % nodehex)
continue
# update filetags
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 if calcnodelines:
# map tag name to a list of line numbers
if name not in hextaglines:
hextaglines[name] = []
hextaglines[name].append([nodehex, nline])
continue
# map tag name to (node, hist)
if name not in bintaghist:
bintaghist[name] = []
bintaghist[name].append(nodebin)
return bintaghist, hextaglines
def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
'''Read tag definitions from a file (or any source of lines).
Gregory Szorc
tags: improve documentation...
r24445
Returns a mapping from tag name to (node, hist).
"node" is the node id from the last line read for that name. "hist"
is the list of node ids previously associated with it (in file order).
All node ids are binary, not hex.
'''
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode,
calcnodelines=calcnodelines)
for tag, taghist in filetags.items():
filetags[tag] = (taghist[-1], taghist[:-1])
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 return filetags
def _updatetags(filetags, tagtype, alltags, tagtypes):
'''Incorporate the tag info read from one file into the two
dictionaries, alltags and tagtypes, that contain all tag
info (global across all heads plus local).'''
for name, nodehist in filetags.iteritems():
if name not in alltags:
alltags[name] = nodehist
tagtypes[name] = tagtype
continue
# we prefer alltags[name] if:
Mads Kiilerich
fix trivial spelling errors
r17424 # it supersedes us OR
# mutual supersedes and it has a higher rank
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 # otherwise we win because we're tip-most
anode, ahist = nodehist
bnode, bhist = alltags[name]
if (bnode != anode and anode in bhist and
(bnode not in ahist or len(bhist) > len(ahist))):
anode = bnode
FUJIWARA Katsunori
tags: update tag type only if tag node is updated (issue3911)...
r19108 else:
tagtypes[name] = tagtype
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 ahist.extend([n for n in bhist if n not in ahist])
alltags[name] = anode, ahist
Gregory Szorc
tags: improve documentation...
r24445 def _readtagcache(ui, repo):
'''Read the tag cache.
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Gregory Szorc
tags: improve documentation...
r24445 Returns a tuple (heads, fnodes, cachetags, shouldwrite).
If the cache is completely up-to-date, "cachetags" is a dict of the
form returned by _readtags() and "heads" and "fnodes" are None and
"shouldwrite" is False.
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Gregory Szorc
tags: improve documentation...
r24445 If the cache is not up to date, "cachetags" is None. "heads" is a list
of all heads currently in the repository, ordered from tip to oldest.
"fnodes" is a mapping from head to .hgtags filenode. "shouldwrite" is
True.
If the cache is not up to date, the caller is responsible for reading tag
info from each returned head. (See findglobaltags().)
'''
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
try:
Angel Ezquerra
localrepo: remove all external users of localrepo.opener...
r23877 cachefile = repo.vfs('cache/tags', 'r')
Nicolas Dumazet
static-http: mimic more closely localrepo (issue2164: allow clone -r )...
r11066 # force reading the file for static-http
cachelines = iter(cachefile)
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 except IOError:
cachefile = None
Gregory Szorc
tags: improve documentation...
r24445 cacherevs = [] # list of headrev
cacheheads = [] # list of headnode
cachefnode = {} # map headnode to filenode
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 if cachefile:
Nicolas Dumazet
tags: do not fail if tags.cache is corrupted (issue2444)...
r12758 try:
for line in cachelines:
if line == "\n":
break
Martin Geisler
tags: line.rstrip().split() can be replaced with line.split()...
r16589 line = line.split()
Nicolas Dumazet
tags: do not fail if tags.cache is corrupted (issue2444)...
r12758 cacherevs.append(int(line[0]))
headnode = bin(line[1])
cacheheads.append(headnode)
if len(line) == 3:
fnode = bin(line[2])
cachefnode[headnode] = fnode
Matt Mackall
tags: catch more corruption during cache parsing (issue2779)
r14020 except Exception:
jfh
move tags.cache and branchheads.cache to a collected cache folder .hg/cache/...
r13272 # corruption of the tags cache, just recompute it
ui.warn(_('.hg/cache/tags is corrupt, rebuilding it\n'))
Nicolas Dumazet
tags: do not fail if tags.cache is corrupted (issue2444)...
r12758 cacheheads = []
cacherevs = []
cachefnode = {}
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
tipnode = repo.changelog.tip()
tiprev = len(repo.changelog) - 1
# Case 1 (common): tip is the same, so nothing has changed.
# (Unchanged tip trivially means no changesets have been added.
# But, thanks to localrepository.destroyed(), it also means none
# have been destroyed by strip or rollback.)
if cacheheads and cacheheads[0] == tipnode and cacherevs[0] == tiprev:
Nicolas Dumazet
static-http: mimic more closely localrepo (issue2164: allow clone -r )...
r11066 tags = _readtags(ui, repo, cachelines, cachefile.name)
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 cachefile.close()
return (None, None, tags, False)
if cachefile:
cachefile.close() # ignore rest of file
Dirkjan Ochtman
kill trailing whitespace
r9312
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 repoheads = repo.heads()
# Case 2 (uncommon): empty repo; get out quickly and don't bother
# writing an empty cache.
if repoheads == [nullid]:
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 return ([], {}, {}, False)
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
# Case 3 (uncommon): cache file missing or empty.
# Case 4 (uncommon): tip rev decreased. This should only happen
# when we're called from localrepository.destroyed(). Refresh the
# cache so future invocations will not see disappeared heads in the
# cache.
# Case 5 (common): tip has changed, so we've added/replaced heads.
Greg Ward
tags: remove inactive debugging code....
r11352 # As it happens, the code to handle cases 3, 4, 5 is the same.
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
# N.B. in case 4 (nodes destroyed), "new head" really means "newly
# exposed".
Bryan O'Sullivan
tags: short-circuit if no tags have ever been committed
r16730 if not len(repo.file('.hgtags')):
# No tags have ever been committed, so we can avoid a
# potentially expensive search.
return (repoheads, cachefnode, None, True)
Gregory Szorc
tags: log events related to tags cache...
r21030 starttime = time.time()
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 newheads = [head
for head in repoheads
if head not in set(cacheheads)]
# Now we have to lookup the .hgtags filenode for every new head.
# This is the most expensive part of finding tags, so performance
# depends primarily on the size of newheads. Worst case: no cache
# file, so newheads == repoheads.
Matt Mackall
tags: visit new heads in forward order when rebuilding cache...
r17256 for head in reversed(newheads):
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 cctx = repo[head]
try:
fnode = cctx.filenode('.hgtags')
cachefnode[head] = fnode
except error.LookupError:
# no .hgtags file on this head
pass
Gregory Szorc
tags: log events related to tags cache...
r21030 duration = time.time() - starttime
ui.log('tagscache',
'resolved %d tags cache entries from %d manifests in %0.4f '
'seconds\n',
len(cachefnode), len(newheads), duration)
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 # Caller has to iterate over all heads, but can use the filenodes in
# cachefnode to get to each .hgtags revision quickly.
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 return (repoheads, cachefnode, None, True)
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 def _writetagcache(ui, repo, heads, tagfnode, cachetags):
Greg Ward
tags: don't crash if unable to write tag cache...
r9366 try:
Angel Ezquerra
localrepo: remove all external users of localrepo.opener...
r23877 cachefile = repo.vfs('cache/tags', 'w', atomictemp=True)
Greg Ward
tags: don't crash if unable to write tag cache...
r9366 except (OSError, IOError):
return
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Gregory Szorc
tags: log events related to tags cache...
r21030 ui.log('tagscache', 'writing tags cache file with %d heads and %d tags\n',
len(heads), len(cachetags))
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 realheads = repo.heads() # for sanity checks below
for head in heads:
# temporary sanity checks; these can probably be removed
# once this code has been in crew for a few weeks
assert head in repo.changelog.nodemap, \
'trying to write non-existent node %s to tag cache' % short(head)
assert head in realheads, \
'trying to write non-head %s to tag cache' % short(head)
assert head != nullid, \
'trying to write nullid to tag cache'
# This can't fail because of the first assert above. When/if we
# remove that assert, we might want to catch LookupError here
# and downgrade it to a warning.
rev = repo.changelog.rev(head)
fnode = tagfnode.get(head)
if fnode:
cachefile.write('%d %s %s\n' % (rev, hex(head), hex(fnode)))
else:
cachefile.write('%d %s\n' % (rev, hex(head)))
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 # Tag names in the cache are in UTF-8 -- which is the whole reason
# we keep them in UTF-8 throughout this module. If we converted
# them local encoding on input, we would lose info writing them to
# the cache.
cachefile.write('\n')
Gregory Szorc
tags: write tags cache deterministically...
r24143 for (name, (node, hist)) in sorted(cachetags.iteritems()):
FUJIWARA Katsunori
tags: write tag overwriting history also into tag cache file (issue3911)...
r19646 for n in hist:
cachefile.write("%s %s\n" % (hex(n), name))
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 cachefile.write("%s %s\n" % (hex(node), name))
Steve Borho
tags: don't allow environment errors to be raised from _writetagscache...
r14662 try:
Greg Ward
atomictempfile: make close() consistent with other file-like objects....
r15057 cachefile.close()
Steve Borho
tags: don't allow environment errors to be raised from _writetagscache...
r14662 except (OSError, IOError):
pass