##// END OF EJS Templates
match: delete unused root and cwd arguments to constructors (API)...
match: delete unused root and cwd arguments to constructors (API) Most matchers no longer need the root and cwd arguments. patternmatcher and includematcher still need the root argument for subincludes. Differential Revision: https://phab.mercurial-scm.org/D5929

File last commit:

r41230:4c5864da default
r41824:ddbebce9 default
Show More
tags.py
790 lines | 26.4 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
Gregory Szorc
tags: use absolute_import
r25982 from __future__ import absolute_import
Idan Kamara
tags: loosen IOError filtering when reading localtags
r14038 import errno
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Gregory Szorc
tags: use absolute_import
r25982 from .node import (
bin,
hex,
nullid,
short,
)
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 from .i18n import _
Gregory Szorc
tags: use absolute_import
r25982 from . import (
encoding,
error,
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 match as matchmod,
Yuya Nishihara
scmutil: proxy revrange() through repo to break import cycles...
r31025 scmutil,
Gregory Szorc
tags: use absolute_import
r25982 util,
)
Yuya Nishihara
stringutil: bulk-replace call sites to point to new module...
r37102 from .utils import (
stringutil,
)
Gregory Szorc
tags: use absolute_import
r25982
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 # Tags computation can be expensive and caches exist to make it fast in
# the common case.
#
# The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
# each revision in the repository. The file is effectively an array of
# fixed length records. Read the docs for "hgtagsfnodescache" for technical
# details.
#
# The .hgtags filenode cache grows in proportion to the length of the
# changelog. The file is truncated when the # changelog is stripped.
#
# 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 dozens or over 100ms for
# repositories with very large manifests. Multiplied by dozens or even
# hundreds of heads and there is a significant performance concern.
#
Gregory Szorc
tags: write a separate tags cache file for unfiltered repos...
r24762 # There also exist a separate cache file for each repository filter.
# These "tags-*" files store information about the history of tags.
Gregory Szorc
tags: improve documentation...
r24445 #
Gregory Szorc
tags: write a separate tags cache file for unfiltered repos...
r24762 # The tags cache files consists of a cache validation line followed by
# a history of tags.
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 #
Gregory Szorc
tags: change format of tags cache files...
r24760 # The cache validation line has the format:
Gregory Szorc
tags: improve documentation...
r24445 #
Gregory Szorc
tags: change format of tags cache files...
r24760 # <tiprev> <tipnode> [<filteredhash>]
Gregory Szorc
tags: improve documentation...
r24445 #
Gregory Szorc
tags: change format of tags cache files...
r24760 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
# node for that changeset. These redundantly identify the repository
# tip from the time the cache was written. In addition, <filteredhash>,
# if present, is a 40 character hex hash of the contents of the filtered
# revisions for this filter. If the set of filtered revs changes, the
# hash will change and invalidate the cache.
Gregory Szorc
tags: improve documentation...
r24445 #
Gregory Szorc
tags: change format of tags cache files...
r24760 # The history part of the tags cache consists of lines of the form:
Gregory Szorc
tags: improve documentation...
r24445 #
# <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.
Pierre-Yves David
tags: introduce a function to return a valid fnodes list from revs...
r31993 def fnoderevs(ui, repo, revs):
"""return the list of '.hgtags' fnodes used in a set revisions
This is returned as list of unique fnodes. We use a list instead of a set
because order matters when it comes to tags."""
unfi = repo.unfiltered()
tonode = unfi.changelog.node
nodes = [tonode(r) for r in revs]
fnodes = _getfnodes(ui, repo, nodes[::-1]) # reversed help the cache
fnodes = _filterfnodes(fnodes, nodes)
return fnodes
Pierre-Yves David
track-tags: compute the actual differences between tags pre/post transaction...
r31995 def _nulltonone(value):
"""convert nullid to None
For tag value, nullid means "deleted". This small utility function helps
translating that to None."""
if value == nullid:
return None
return value
def difftags(ui, repo, oldfnodes, newfnodes):
"""list differences between tags expressed in two set of file-nodes
The list contains entries in the form: (tagname, oldvalue, new value).
None is used to expressed missing value:
('foo', None, 'abcd') is a new tag,
('bar', 'ef01', None) is a deletion,
('baz', 'abcd', 'ef01') is a tag movement.
"""
if oldfnodes == newfnodes:
return []
oldtags = _tagsfromfnodes(ui, repo, oldfnodes)
newtags = _tagsfromfnodes(ui, repo, newfnodes)
# list of (tag, old, new): None means missing
entries = []
for tag, (new, __) in newtags.items():
new = _nulltonone(new)
old, __ = oldtags.pop(tag, (None, None))
old = _nulltonone(old)
if old != new:
entries.append((tag, old, new))
# handle deleted tags
for tag, (old, __) in oldtags.items():
old = _nulltonone(old)
if old is not None:
entries.append((tag, old, None))
entries.sort()
return entries
Pierre-Yves David
track-tags: write all tag changes to a file...
r31996 def writediff(fp, difflist):
"""write tags diff information to a file.
Data are stored with a line based format:
<action> <hex-node> <tag-name>\n
Action are defined as follow:
-R tag is removed,
+A tag is added,
-M tag is moved (old value),
+M tag is moved (new value),
Example:
+A 875517b4806a848f942811a315a5bce30804ae85 t5
See documentation of difftags output for details about the input.
"""
add = '+A %s %s\n'
remove = '-R %s %s\n'
updateold = '-M %s %s\n'
updatenew = '+M %s %s\n'
for tag, old, new in difflist:
# translate to hex
if old is not None:
old = hex(old)
if new is not None:
new = hex(new)
# write to file
if old is None:
fp.write(add % (new, tag))
elif new is None:
fp.write(remove % (old, tag))
else:
fp.write(updateold % (old, tag))
fp.write(updatenew % (new, tag))
Pierre-Yves David
tags: do not feed dictionaries to 'findglobaltags'...
r31706 def findglobaltags(ui, repo):
Pierre-Yves David
tags: only return 'alltags' in 'findglobaltags'...
r31709 '''Find global tags in a repo: return a tagsmap
Gregory Szorc
tags: improve documentation...
r24445
Pierre-Yves David
tags: only return 'alltags' in 'findglobaltags'...
r31709 tagsmap: tag name to (node, hist) 2-tuples.
Gregory Szorc
tags: improve documentation...
r24445
The tags cache is read and updated as a side-effect of calling.
'''
Gregory Szorc
tags: change format of tags cache files...
r24760 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 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?
Pierre-Yves David
tags: extract tags computation from fnodes into its own function...
r31710 alltags = {}
Pierre-Yves David
tags: only return 'alltags' in 'findglobaltags'...
r31709 _updatetags(cachetags, alltags)
return alltags
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
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)
Pierre-Yves David
tags: extract filenode filtering into its own function...
r31711 fnodes = _filterfnodes(tagfnode, reversed(heads))
Pierre-Yves David
tags: extract tags computation from fnodes into its own function...
r31710 alltags = _tagsfromfnodes(ui, repo, fnodes)
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
# and update the cache (if necessary)
if shouldwrite:
Gregory Szorc
tags: change format of tags cache files...
r24760 _writetagcache(ui, repo, valid, alltags)
Pierre-Yves David
tags: only return 'alltags' in 'findglobaltags'...
r31709 return alltags
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Pierre-Yves David
tags: extract filenode filtering into its own function...
r31711 def _filterfnodes(tagfnode, nodes):
"""return a list of unique fnodes
The order of this list matches the order of "nodes". Preserving this order
is important as reading tags in different order provides different
results."""
seen = set() # set of fnode
fnodes = []
for no in nodes: # oldest to newest
fnode = tagfnode.get(no)
if fnode and fnode not in seen:
seen.add(fnode)
fnodes.append(fnode)
return fnodes
Pierre-Yves David
tags: extract tags computation from fnodes into its own function...
r31710 def _tagsfromfnodes(ui, repo, fnodes):
"""return a tagsmap from a list of file-node
tagsmap: tag name to (node, hist) 2-tuples.
The order of the list matters."""
alltags = {}
fctx = None
for fnode in fnodes:
if fctx is None:
fctx = repo.filectx('.hgtags', fileid=fnode)
else:
fctx = fctx.filectx(fnode)
filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
_updatetags(filetags, alltags)
return alltags
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")
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except IOError as inst:
Idan Kamara
tags: loosen IOError filtering when reading localtags
r14038 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
Augie Fackler
tags: explicitly grab list of dict keys...
r35846 for t in list(filetags):
Angel Ezquerra
repoview: do not crash when localtags refers to non existing revisions...
r21823 try:
cl.rev(filetags[t][0])
except (LookupError, ValueError):
del filetags[t]
Pierre-Yves David
tags: reorder argument of '_updatetags'...
r31707 _updatetags(filetags, alltags, 'local', 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
Matt Mackall
tags: silence cache parsing errors...
r29038 def dbg(msg):
Pulkit Goyal
py3: use '%d' for integers instead of '%s'...
r36417 ui.debug("%s, line %d: %s\n" % (fn, count, msg))
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
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:
Matt Mackall
tags: silence cache parsing errors...
r29038 dbg("cannot parse entry")
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 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:
Matt Mackall
tags: silence cache parsing errors...
r29038 dbg("node '%s' is not well formed" % nodehex)
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 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)
Gregory Szorc
tags: create new sortdict for performance reasons...
r26945 # util.sortdict().__setitem__ is much slower at replacing then inserting
# new entries. The difference can matter if there are thousands of tags.
# Create a new sortdict to avoid the performance penalty.
newtags = util.sortdict()
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 for tag, taghist in filetags.items():
Gregory Szorc
tags: create new sortdict for performance reasons...
r26945 newtags[tag] = (taghist[-1], taghist[:-1])
return newtags
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Pierre-Yves David
tags: make argument 'tagtype' optional in '_updatetags'...
r31708 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None):
"""Incorporate the tag info read from one file into dictionnaries
The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details).
The second one, 'tagtypes', is optional and will be updated to track the
"tagtype" of entries in the tagmaps. When set, the 'tagtype' argument also
needs to be set."""
if tagtype is None:
assert tagtypes is None
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
for name, nodehist in filetags.iteritems():
if name not in alltags:
alltags[name] = nodehist
Pierre-Yves David
tags: make argument 'tagtype' optional in '_updatetags'...
r31708 if tagtype is not None:
tagtypes[name] = tagtype
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 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
Pierre-Yves David
tags: make argument 'tagtype' optional in '_updatetags'...
r31708 elif tagtype is not None:
FUJIWARA Katsunori
tags: update tag type only if tag node is updated (issue3911)...
r19108 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
Pierre-Yves David
tags: have a different cache file per filter level...
r24737 def _filename(repo):
"""name of a tagcache file for a given repo or repoview"""
Boris Feld
cachevfs: migrate tagscache to 'cachevfs'...
r33536 filename = 'tags2'
Pierre-Yves David
tags: have a different cache file per filter level...
r24737 if repo.filtername:
filename = '%s-%s' % (filename, repo.filtername)
return filename
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: change format of tags cache files...
r24760 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
Gregory Szorc
tags: improve documentation...
r24445
If the cache is completely up-to-date, "cachetags" is a dict of the
Gregory Szorc
tags: change format of tags cache files...
r24760 form returned by _readtags() and "heads", "fnodes", and "validinfo" 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.
Gregory Szorc
tags: change format of tags cache files...
r24760 "validinfo" is a tuple describing cache validation info. This is used
when writing the tags cache. "fnodes" is a mapping from head to .hgtags
filenode. "shouldwrite" is True.
Gregory Szorc
tags: improve documentation...
r24445
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:
Boris Feld
cachevfs: migrate tagscache to 'cachevfs'...
r33536 cachefile = repo.cachevfs(_filename(repo), '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: change format of tags cache files...
r24760 cacherev = None
cachenode = None
cachehash = None
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:
timeless
py3: convert to next() function...
r29216 validline = next(cachelines)
Gregory Szorc
tags: change format of tags cache files...
r24760 validline = validline.split()
cacherev = int(validline[0])
cachenode = bin(validline[1])
if len(validline) > 2:
cachehash = bin(validline[2])
Matt Mackall
tags: catch more corruption during cache parsing (issue2779)
r14020 except Exception:
Gregory Szorc
tags: don't read .hgtags fnodes from tags cache files...
r24759 # corruption of the cache, just recompute it.
pass
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.)
Gregory Szorc
tags: change format of tags cache files...
r24760 if (cacherev == tiprev
and cachenode == tipnode
and cachehash == scmutil.filteredhash(repo, 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()
Gregory Szorc
tags: change format of tags cache files...
r24760 return (None, None, None, tags, False)
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152 if cachefile:
cachefile.close() # ignore rest of file
Dirkjan Ochtman
kill trailing whitespace
r9312
Gregory Szorc
tags: change format of tags cache files...
r24760 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
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]:
Gregory Szorc
tags: change format of tags cache files...
r24760 return ([], {}, valid, {}, 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.
Gregory Szorc
tags: return empty list of heads for no .hgtags case...
r24761 return ([], {}, valid, None, True)
Bryan O'Sullivan
tags: short-circuit if no tags have ever been committed
r16730
Gregory Szorc
tags: log events related to tags cache...
r21030
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151 # 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.
Pierre-Yves David
tags: extract fnode retrieval into its own function...
r31705 cachefnode = _getfnodes(ui, repo, repoheads)
# Caller has to iterate over all heads, but can use the filenodes in
# cachefnode to get to each .hgtags revision quickly.
return (repoheads, cachefnode, valid, None, True)
def _getfnodes(ui, repo, nodes):
"""return .hgtags fnodes for a list of changeset nodes
Return value is a {node: fnode} mapping. There will be no entry for nodes
without a '.hgtags' file.
"""
starttime = util.timer()
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 fnodescache = hgtagsfnodescache(repo.unfiltered())
Gregory Szorc
tags: don't read .hgtags fnodes from tags cache files...
r24759 cachefnode = {}
Martin von Zweigbergk
tags: rename "head" to "node" where we don't care...
r31788 for node in reversed(nodes):
fnode = fnodescache.getfnode(node)
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 if fnode != nullid:
Martin von Zweigbergk
tags: rename "head" to "node" where we don't care...
r31788 cachefnode[node] = fnode
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735
fnodescache.write()
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Simon Farnsworth
mercurial: switch to util.timer for all interval timings...
r30975 duration = util.timer() - starttime
Gregory Szorc
tags: log events related to tags cache...
r21030 ui.log('tagscache',
Martin von Zweigbergk
tags: join string that's unnecessarily split across lines...
r41230 '%d/%d cache hits/lookups in %0.4f seconds\n',
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 fnodescache.hitcount, fnodescache.lookupcount, duration)
Pierre-Yves David
tags: extract fnode retrieval into its own function...
r31705 return cachefnode
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
Gregory Szorc
tags: change format of tags cache files...
r24760 def _writetagcache(ui, repo, valid, cachetags):
Gregory Szorc
tags: explicitly log which tags cache file is being written...
r24763 filename = _filename(repo)
Greg Ward
tags: don't crash if unable to write tag cache...
r9366 try:
Boris Feld
cachevfs: migrate tagscache to 'cachevfs'...
r33536 cachefile = repo.cachevfs(filename, '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
Boris Feld
cachevfs: migrate tagscache to 'cachevfs'...
r33536 ui.log('tagscache', 'writing .hg/cache/%s with %d tags\n',
Gregory Szorc
tags: explicitly log which tags cache file is being written...
r24763 filename, len(cachetags))
Gregory Szorc
tags: log events related to tags cache...
r21030
Gregory Szorc
tags: change format of tags cache files...
r24760 if valid[2]:
cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2])))
else:
cachefile.write('%d %s\n' % (valid[0], hex(valid[1])))
Greg Ward
tags: implement persistent tag caching (issue548)....
r9151
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.
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
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735
Pierre-Yves David
tags: move 'repo.tag' in the 'tags' module...
r31669 def tag(repo, names, node, message, local, user, date, editor=False):
'''tag a revision with one or more symbolic names.
names is a list of strings or, when adding a single tag, names may be a
string.
if local is True, the tags are stored in a per-repository file.
otherwise, they are stored in the .hgtags file, and a new
changeset is committed with the change.
keyword arguments:
local: whether to store tags in non-version-controlled file
(default False)
message: commit message to use if committing
user: name of user to use if committing
date: date tuple to use if committing'''
if not local:
m = matchmod.exact(repo.root, '', ['.hgtags'])
if any(repo.status(match=m, unknown=True, ignored=True)):
raise error.Abort(_('working copy of .hgtags is changed'),
hint=_('please commit .hgtags manually'))
tag: make sure the repository is locked when tagging...
r33253 with repo.wlock():
repo.tags() # instantiate the cache
Denis Laxalde
tag: use filtered repo when creating new tags (issue5539)...
r34017 _tag(repo, names, node, message, local, user, date,
tag: make sure the repository is locked when tagging...
r33253 editor=editor)
Pierre-Yves David
tags: move 'repo.tag' in the 'tags' module...
r31669
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 def _tag(repo, names, node, message, local, user, date, extra=None,
editor=False):
if isinstance(names, str):
names = (names,)
branches = repo.branchmap()
for name in names:
repo.hook('pretag', throw=True, node=hex(node), tag=name,
local=local)
if name in branches:
repo.ui.warn(_("warning: tag %s conflicts with existing"
" branch name\n") % name)
def writetags(fp, names, munge, prevtags):
fp.seek(0, 2)
Yuya Nishihara
py3: use startswith() to check existence of trailing '\n' in .hgtags file
r36561 if prevtags and not prevtags.endswith('\n'):
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 fp.write('\n')
for name in names:
if munge:
m = munge(name)
else:
m = name
if (repo._tagscache.tagtypes and
name in repo._tagscache.tagtypes):
old = repo.tags().get(name, nullid)
fp.write('%s %s\n' % (hex(old), m))
fp.write('%s %s\n' % (hex(node), m))
fp.close()
prevtags = ''
if local:
try:
fp = repo.vfs('localtags', 'r+')
except IOError:
fp = repo.vfs('localtags', 'a')
else:
prevtags = fp.read()
# local tags are stored in the current charset
writetags(fp, names, None, prevtags)
for name in names:
repo.hook('tag', node=hex(node), tag=name, local=local)
return
try:
fp = repo.wvfs('.hgtags', 'rb+')
except IOError as e:
if e.errno != errno.ENOENT:
raise
fp = repo.wvfs('.hgtags', 'ab')
else:
prevtags = fp.read()
# committed tags are stored in UTF-8
writetags(fp, names, encoding.fromlocal, prevtags)
fp.close()
repo.invalidatecaches()
if '.hgtags' not in repo.dirstate:
repo[None].add(['.hgtags'])
m = matchmod.exact(repo.root, '', ['.hgtags'])
tagnode = repo.commit(message, user, date, extra=extra, match=m,
editor=editor)
for name in names:
repo.hook('tag', node=hex(node), tag=name, local=local)
return tagnode
Boris Feld
cachevfs: migration the tags fnode cache to 'cachevfs'...
r33537 _fnodescachefile = 'hgtagsfnodes1'
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
_fnodesmissingrec = '\xff' * 24
class hgtagsfnodescache(object):
"""Persistent cache mapping revisions to .hgtags filenodes.
The cache is an array of records. Each item in the array corresponds to
a changelog revision. Values in the array contain the first 4 bytes of
the node hash and the 20 bytes .hgtags filenode for that revision.
The first 4 bytes are present as a form of verification. Repository
stripping and rewriting may change the node at a numeric revision in the
changelog. The changeset fragment serves as a verifier to detect
rewriting. This logic is shared with the rev branch cache (see
branchmap.py).
The instance holds in memory the full cache content but entries are
only parsed on read.
Instances behave like lists. ``c[i]`` works where i is a rev or
changeset node. Missing indexes are populated automatically on access.
"""
def __init__(self, repo):
assert repo.filtername is None
self._repo = repo
# Only for reporting purposes.
self.lookupcount = 0
self.hitcount = 0
Matt Mackall
tags: silence hgtagsfnodes reading failures...
r29039 try:
Boris Feld
cachevfs: migration the tags fnode cache to 'cachevfs'...
r33537 data = repo.cachevfs.read(_fnodescachefile)
Matt Mackall
tags: silence hgtagsfnodes reading failures...
r29039 except (OSError, IOError):
data = ""
Augie Fackler
py3: use bytearray() instead of array('c', ...) constructions...
r31346 self._raw = bytearray(data)
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735
# The end state of self._raw is an array that is of the exact length
# required to hold a record for every revision in the repository.
# We truncate or extend the array as necessary. self._dirtyoffset is
# defined to be the start offset at which we need to write the output
# file. This offset is also adjusted when new entries are calculated
# for array members.
cllen = len(repo.changelog)
wantedlen = cllen * _fnodesrecsize
rawlen = len(self._raw)
self._dirtyoffset = None
if rawlen < wantedlen:
self._dirtyoffset = rawlen
self._raw.extend('\xff' * (wantedlen - rawlen))
elif rawlen > wantedlen:
# There's no easy way to truncate array instances. This seems
# slightly less evil than copying a potentially large array slice.
for i in range(rawlen - wantedlen):
self._raw.pop()
self._dirtyoffset = len(self._raw)
Gregory Szorc
tags: support reading tags cache without populating...
r25380 def getfnode(self, node, computemissing=True):
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 """Obtain the filenode of the .hgtags file at a specified revision.
If the value is in the cache, the entry will be validated and returned.
Gregory Szorc
tags: support reading tags cache without populating...
r25380 Otherwise, the filenode will be computed and returned unless
"computemissing" is False, in which case None will be returned without
any potentially expensive computation being performed.
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735
If an .hgtags does not exist at the specified revision, nullid is
returned.
"""
ctx = self._repo[node]
rev = ctx.rev()
self.lookupcount += 1
offset = rev * _fnodesrecsize
Augie Fackler
py3: use bytearray() instead of array('c', ...) constructions...
r31346 record = '%s' % self._raw[offset:offset + _fnodesrecsize]
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 properprefix = node[0:4]
# Validate and return existing entry.
if record != _fnodesmissingrec:
fileprefix = record[0:4]
if fileprefix == properprefix:
self.hitcount += 1
return record[4:]
# Fall through.
Gregory Szorc
tags: support reading tags cache without populating...
r25380 # If we get here, the entry is either missing or invalid.
if not computemissing:
return None
# Populate missing entry.
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 try:
fnode = ctx.filenode('.hgtags')
except error.LookupError:
# No .hgtags file on this revision.
fnode = nullid
Gregory Szorc
tags: support setting hgtags fnodes cache entries...
r25381 self._writeentry(offset, properprefix, fnode)
return fnode
def setfnode(self, node, fnode):
"""Set the .hgtags filenode for a given changeset."""
assert len(fnode) == 20
ctx = self._repo[node]
# Do a lookup first to avoid writing if nothing has changed.
if self.getfnode(ctx.node(), computemissing=False) == fnode:
return
self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
def _writeentry(self, offset, prefix, fnode):
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 # Slices on array instances only accept other array.
Augie Fackler
py3: use bytearray() instead of array('c', ...) constructions...
r31346 entry = bytearray(prefix + fnode)
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 self._raw[offset:offset + _fnodesrecsize] = entry
# self._dirtyoffset could be None.
Augie Fackler
tags: don't feed both int and None to min()...
r36291 self._dirtyoffset = min(self._dirtyoffset or 0, offset or 0)
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735
def write(self):
"""Perform all necessary writes to cache file.
This may no-op if no writes are needed or if a write lock could
not be obtained.
"""
if self._dirtyoffset is None:
return
data = self._raw[self._dirtyoffset:]
if not data:
return
repo = self._repo
try:
lock = repo.wlock(wait=False)
Yuya Nishihara
tags: do not abort if failed to write lock file to save cache...
r24806 except error.LockError:
Boris Feld
cachevfs: migration the tags fnode cache to 'cachevfs'...
r33537 repo.ui.log('tagscache', 'not writing .hg/cache/%s because '
'lock cannot be acquired\n' % (_fnodescachefile))
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 return
try:
Boris Feld
cachevfs: migration the tags fnode cache to 'cachevfs'...
r33537 f = repo.cachevfs.open(_fnodescachefile, 'ab')
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 try:
Matt Mackall
tags: use try/except/finally
r25087 # if the file has been truncated
actualoffset = f.tell()
if actualoffset < self._dirtyoffset:
self._dirtyoffset = actualoffset
data = self._raw[self._dirtyoffset:]
f.seek(self._dirtyoffset)
f.truncate()
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 repo.ui.log('tagscache',
Boris Feld
cachevfs: migration the tags fnode cache to 'cachevfs'...
r33537 'writing %d bytes to cache/%s\n' % (
Matt Mackall
tags: use try/except/finally
r25087 len(data), _fnodescachefile))
f.write(data)
self._dirtyoffset = None
finally:
f.close()
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except (IOError, OSError) as inst:
Matt Mackall
tags: use try/except/finally
r25087 repo.ui.log('tagscache',
Boris Feld
cachevfs: migration the tags fnode cache to 'cachevfs'...
r33537 "couldn't write cache/%s: %s\n" % (
Yuya Nishihara
stringutil: bulk-replace call sites to point to new module...
r37102 _fnodescachefile, stringutil.forcebytestr(inst)))
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 finally:
lock.release()