##// END OF EJS Templates
histedit: render a rolled up description using the proper roll colours...
histedit: render a rolled up description using the proper roll colours Users have rightfully complained that the old behaviour of completely removing the description of a rolled commit makes it difficult to remember what was in that commit. Instead, we now render the removed description in red. I couldn't think of a simpler way to do this. You can't just combine existing curses colours into new effects; only secondary effects like bold or underline can be logically OR'ed to generate a combined text effect. It seems easier to just redundantly keep track of what the roll colour should be.

File last commit:

r44051:ba5c39b9 default
r44063:bde66eb4 default
Show More
tags.py
872 lines | 27.9 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
Augie Fackler
cleanup: use named constants for second arg to .seek()...
r42767 import io
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Gregory Szorc
tags: use absolute_import
r25982 from .node import (
bin,
hex,
nullid,
hgtagsfnodescache: inherit fnode from parent when possible...
r42423 nullrev,
Gregory Szorc
tags: use absolute_import
r25982 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,
Gregory Szorc
py3: finish porting iteritems() to pycompat and remove source transformer...
r43376 pycompat,
Yuya Nishihara
scmutil: proxy revrange() through repo to break import cycles...
r31025 scmutil,
Gregory Szorc
tags: use absolute_import
r25982 util,
)
Augie Fackler
formatting: blacken the codebase...
r43346 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.
Augie Fackler
formatting: blacken the codebase...
r43346
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]
Martin von Zweigbergk
tags: avoid double-reversing a list...
r42425 fnodes = _getfnodes(ui, repo, nodes)
Pierre-Yves David
tags: introduce a function to return a valid fnodes list from revs...
r31993 fnodes = _filterfnodes(fnodes, nodes)
return fnodes
Augie Fackler
formatting: blacken the codebase...
r43346
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
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
track-tags: compute the actual differences between tags pre/post transaction...
r31995 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
Augie Fackler
formatting: blacken the codebase...
r43346
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.
"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 add = b'+A %s %s\n'
remove = b'-R %s %s\n'
updateold = b'-M %s %s\n'
updatenew = b'+M %s %s\n'
Pierre-Yves David
track-tags: write all tag changes to a file...
r31996 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))
Augie Fackler
formatting: blacken the codebase...
r43346
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
index: use `index.has_node` in `tags.findglobaltags`...
r43943 assert repo.changelog.index.has_node(
head
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ), b"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
Augie Fackler
formatting: blacken the codebase...
r43346
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
Augie Fackler
formatting: blacken the codebase...
r43346
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fctx = repo.filectx(b'.hgtags', fileid=fnode)
Pierre-Yves David
tags: extract tags computation from fnodes into its own function...
r31710 else:
fctx = fctx.filectx(fnode)
filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
_updatetags(filetags, alltags)
return alltags
Augie Fackler
formatting: blacken the codebase...
r43346
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 data = repo.vfs.read(b"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(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui, repo, data.splitlines(), b"localtags", recode=encoding.fromlocal
Augie Fackler
formatting: blacken the codebase...
r43346 )
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]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _updatetags(filetags, alltags, b'local', tagtypes)
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149
Augie Fackler
formatting: blacken the codebase...
r43346
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):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b"%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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 (nodehex, name) = line.split(b" ", 1)
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 except ValueError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 dbg(b"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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 dbg(b"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
Augie Fackler
formatting: blacken the codebase...
r43346
Angel Ezquerra
tags: introduce _readtaghist function...
r21892 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.
'''
Augie Fackler
formatting: blacken the codebase...
r43346 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
Augie Fackler
formatting: blacken the codebase...
r43346
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
Gregory Szorc
py3: finish porting iteritems() to pycompat and remove source transformer...
r43376 for name, nodehist in pycompat.iteritems(filetags):
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 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]
Augie Fackler
formatting: blacken the codebase...
r43346 if (
bnode != anode
and anode in bhist
and (bnode not in ahist or len(bhist) > len(ahist))
):
Greg Ward
Factor tags module out of localrepo (issue548)....
r9149 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
Augie Fackler
formatting: blacken the codebase...
r43346
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"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 filename = b'tags2'
Pierre-Yves David
tags: have a different cache file per filter level...
r24737 if repo.filtername:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 filename = b'%s-%s' % (filename, repo.filtername)
Pierre-Yves David
tags: have a different cache file per filter level...
r24737 return filename
Augie Fackler
formatting: blacken the codebase...
r43346
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cachefile = repo.cachevfs(_filename(repo), b'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.)
Augie Fackler
formatting: blacken the codebase...
r43346 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:
Augie Fackler
formatting: blacken the codebase...
r43346 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".
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not len(repo.file(b'.hgtags')):
Bryan O'Sullivan
tags: short-circuit if no tags have ever been committed
r16730 # 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
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.
Martin von Zweigbergk
tags: avoid double-reversing a list...
r42425 # Reversed order helps the cache ('repoheads' is in descending order)
cachefnode = _getfnodes(ui, repo, reversed(repoheads))
Pierre-Yves David
tags: extract fnode retrieval into its own function...
r31705
# 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)
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
tags: extract fnode retrieval into its own function...
r31705 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: avoid double-reversing a list...
r42425 for node in nodes:
Martin von Zweigbergk
tags: rename "head" to "node" where we don't care...
r31788 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
Augie Fackler
formatting: blacken the codebase...
r43346 ui.log(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'tagscache',
b'%d/%d cache hits/lookups in %0.4f seconds\n',
Augie Fackler
formatting: blacken the codebase...
r43346 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
Augie Fackler
formatting: blacken the codebase...
r43346
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cachefile = repo.cachevfs(filename, b'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
Augie Fackler
formatting: blacken the codebase...
r43346 ui.log(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'tagscache',
b'writing .hg/cache/%s with %d tags\n',
Augie Fackler
formatting: blacken the codebase...
r43346 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]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cachefile.write(
b'%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2]))
)
Gregory Szorc
tags: change format of tags cache files...
r24760 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cachefile.write(b'%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
py3: finish porting iteritems() to pycompat and remove source transformer...
r43376 for (name, (node, hist)) in sorted(pycompat.iteritems(cachetags)):
FUJIWARA Katsunori
tags: write tag overwriting history also into tag cache file (issue3911)...
r19646 for n in hist:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cachefile.write(b"%s %s\n" % (hex(n), name))
cachefile.write(b"%s %s\n" % (hex(node), name))
Greg Ward
tags: support 'instant' tag retrieval (issue548)...
r9152
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
Augie Fackler
formatting: blacken the codebase...
r43346
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 m = matchmod.exact([b'.hgtags'])
Augie Fackler
tags: use field names instead of field numbers on scmutil.status...
r44051 st = repo.status(match=m, unknown=True, ignored=True)
if any(
(
st.modified,
st.added,
st.removed,
st.deleted,
st.unknown,
st.ignored,
st.clean,
)
):
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'working copy of .hgtags is changed'),
hint=_(b'please commit .hgtags manually'),
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
tags: move 'repo.tag' in the 'tags' module...
r31669
tag: make sure the repository is locked when tagging...
r33253 with repo.wlock():
Augie Fackler
formatting: blacken the codebase...
r43346 repo.tags() # instantiate the cache
_tag(repo, names, node, message, local, user, date, editor=editor)
Pierre-Yves David
tags: move 'repo.tag' in the 'tags' module...
r31669
Augie Fackler
formatting: blacken the codebase...
r43346 def _tag(
repo, names, node, message, local, user, date, extra=None, editor=False
):
Pulkit Goyal
py3: use bytes instead of str in isinstance()...
r42007 if isinstance(names, bytes):
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 names = (names,)
branches = repo.branchmap()
for name in names:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 repo.hook(b'pretag', throw=True, node=hex(node), tag=name, local=local)
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 if name in branches:
Augie Fackler
formatting: blacken the codebase...
r43346 repo.ui.warn(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b"warning: tag %s conflicts with existing branch name\n")
Augie Fackler
formatting: blacken the codebase...
r43346 % name
)
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668
def writetags(fp, names, munge, prevtags):
Augie Fackler
cleanup: use named constants for second arg to .seek()...
r42767 fp.seek(0, io.SEEK_END)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if prevtags and not prevtags.endswith(b'\n'):
fp.write(b'\n')
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 for name in names:
if munge:
m = munge(name)
else:
m = name
Augie Fackler
formatting: blacken the codebase...
r43346 if repo._tagscache.tagtypes and name in repo._tagscache.tagtypes:
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 old = repo.tags().get(name, nullid)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fp.write(b'%s %s\n' % (hex(old), m))
fp.write(b'%s %s\n' % (hex(node), m))
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 fp.close()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 prevtags = b''
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 if local:
try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fp = repo.vfs(b'localtags', b'r+')
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 except IOError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fp = repo.vfs(b'localtags', b'a')
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 else:
prevtags = fp.read()
# local tags are stored in the current charset
writetags(fp, names, None, prevtags)
for name in names:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 repo.hook(b'tag', node=hex(node), tag=name, local=local)
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 return
try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fp = repo.wvfs(b'.hgtags', b'rb+')
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 except IOError as e:
if e.errno != errno.ENOENT:
raise
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fp = repo.wvfs(b'.hgtags', b'ab')
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668 else:
prevtags = fp.read()
# committed tags are stored in UTF-8
writetags(fp, names, encoding.fromlocal, prevtags)
fp.close()
repo.invalidatecaches()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'.hgtags' not in repo.dirstate:
repo[None].add([b'.hgtags'])
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 m = matchmod.exact([b'.hgtags'])
Augie Fackler
formatting: blacken the codebase...
r43346 tagnode = repo.commit(
message, user, date, extra=extra, match=m, editor=editor
)
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668
for name in names:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 repo.hook(b'tag', node=hex(node), tag=name, local=local)
Pierre-Yves David
tags: move '_tags' from 'repo' to 'tags' module...
r31668
return tagnode
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _fnodescachefile = b'hgtagsfnodes1'
Augie Fackler
formatting: blacken the codebase...
r43346 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _fnodesmissingrec = b'\xff' * 24
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 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.
"""
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 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):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 data = b""
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
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._raw.extend(b'\xff' * (wantedlen - rawlen))
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 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.
"""
hgtagsfnodescache: handle nullid lookup...
r42422 if node == nullid:
return nullid
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 ctx = self._repo[node]
rev = ctx.rev()
self.lookupcount += 1
offset = rev * _fnodesrecsize
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 record = b'%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
hgtagsfnodescache: inherit fnode from parent when possible...
r42423 fnode = None
cl = self._repo.changelog
p1rev, p2rev = cl._uncheckedparentrevs(rev)
p1node = cl.node(p1rev)
p1fnode = self.getfnode(p1node, computemissing=False)
if p2rev != nullrev:
# There is some no-merge changeset where p1 is null and p2 is set
# Processing them as merge is just slower, but still gives a good
# result.
p2node = cl.node(p1rev)
p2fnode = self.getfnode(p2node, computemissing=False)
if p1fnode != p2fnode:
# we cannot rely on readfast because we don't know against what
# parent the readfast delta is computed
p1fnode = None
if p1fnode is not None:
mctx = ctx.manifestctx()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fnode = mctx.readfast().get(b'.hgtags')
hgtagsfnodescache: inherit fnode from parent when possible...
r42423 if fnode is None:
fnode = p1fnode
if fnode is None:
# Populate missing entry.
try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fnode = ctx.filenode(b'.hgtags')
hgtagsfnodescache: inherit fnode from parent when possible...
r42423 except error.LookupError:
# No .hgtags file on this revision.
fnode = nullid
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735
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)
Augie Fackler
formatting: blacken the codebase...
r43346 self._raw[offset : offset + _fnodesrecsize] = entry
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 # 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
Augie Fackler
formatting: blacken the codebase...
r43346 data = self._raw[self._dirtyoffset :]
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 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:
Augie Fackler
formatting: blacken the codebase...
r43346 repo.ui.log(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'tagscache',
b'not writing .hg/cache/%s because '
b'lock cannot be acquired\n' % _fnodescachefile,
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 return
try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 f = repo.cachevfs.open(_fnodescachefile, b'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
Augie Fackler
formatting: blacken the codebase...
r43346 data = self._raw[self._dirtyoffset :]
Matt Mackall
tags: use try/except/finally
r25087 f.seek(self._dirtyoffset)
f.truncate()
Augie Fackler
formatting: blacken the codebase...
r43346 repo.ui.log(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'tagscache',
b'writing %d bytes to cache/%s\n'
Augie Fackler
formatting: blacken the codebase...
r43346 % (len(data), _fnodescachefile),
)
Matt Mackall
tags: use try/except/finally
r25087 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:
Augie Fackler
formatting: blacken the codebase...
r43346 repo.ui.log(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'tagscache',
b"couldn't write cache/%s: %s\n"
Augie Fackler
formatting: blacken the codebase...
r43346 % (_fnodescachefile, stringutil.forcebytestr(inst)),
)
Gregory Szorc
tags: extract .hgtags filenodes cache to a standalone file...
r24735 finally:
lock.release()