##// END OF EJS Templates
manifest: persist the manifestfulltext cache...
manifest: persist the manifestfulltext cache Reconstructing the manifest from the revlog takes time, so much so that there already is a LRU cache to avoid having to load a manifest multiple times. This patch persists that LRU cache in the .hg/cache directory, so we can re-use this cache across hg commands. Commit benchmark (run on Macos 10.13 on a 2017-model Macbook Pro with Core i7 2.9GHz and flash drive), testing without and with patch run 5 times, baseline is r2a227782e754: * committing to an existing file, against the mozilla-central repository. Baseline real time average 1.9692, with patch 1.3786. A new debugcommand "hg debugmanifestfulltextcache" lets you inspect the cache, clear it, or add specific manifest nodeids to it. When calling repo.updatecaches(), the manifest(s) for the working copy parents are added to the cache. The hg perfmanifest command has an additional --clear-disk switch to clear this cache when testing manifest loading performance. Using this command to test performance on the firefox repository for revision f947d902ed91, whose manifest has a delta chain length of 60540, we see: $ hg perfmanifest f947d902ed91 --clear-disk ! wall 0.972253 comb 0.970000 user 0.850000 sys 0.120000 (best of 10) $ hg debugmanifestfulltextcache -a `hg log --debug -r f947d902ed91 | grep manifest | cut -d: -f3` Cache contains 1 manifest entries, in order of most to least recent: id: 0294517df4aad07c70701db43bc7ff24c3ce7dbc, size 25.6 MB Total cache data size 25.6 MB, on-disk 0 bytes $ hg perfmanifest f947d902ed91 ! wall 0.036748 comb 0.040000 user 0.020000 sys 0.020000 (best of 100) Worst-case scenario: a manifest text loaded from a single delta; in the firefox repository manifest node 9a1246ff762e is the chain base for the manifest attached to revision f947d902ed91. Loading this from a full cache file is just as fast as without the cache; the extra node ids ensure a big full cache: $ for node in 9a1246ff762e 1a1922c14a3e 54a31d11a36a 0294517df4aa; do > hgd debugmanifestfulltextcache -a $node > /dev/null > done $ hgd perfmanifest -m 9a1246ff762e ! wall 0.077513 comb 0.080000 user 0.030000 sys 0.050000 (best of 100) $ hgd perfmanifest -m 9a1246ff762e --clear-disk ! wall 0.078547 comb 0.080000 user 0.070000 sys 0.010000 (best of 100)

File last commit:

r36488:d851951b default
r38803:0a57945a default
Show More
narrowspec.py
199 lines | 6.8 KiB | text/x-python | PythonLexer
# narrowspec.py - methods for working with a narrow view of a repository
#
# Copyright 2017 Google, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
import errno
from .i18n import _
from . import (
error,
match as matchmod,
util,
)
FILENAME = 'narrowspec'
def _parsestoredpatterns(text):
"""Parses the narrowspec format that's stored on disk."""
patlist = None
includepats = []
excludepats = []
for l in text.splitlines():
if l == '[includes]':
if patlist is None:
patlist = includepats
else:
raise error.Abort(_('narrowspec includes section must appear '
'at most once, before excludes'))
elif l == '[excludes]':
if patlist is not excludepats:
patlist = excludepats
else:
raise error.Abort(_('narrowspec excludes section must appear '
'at most once'))
else:
patlist.append(l)
return set(includepats), set(excludepats)
def parseserverpatterns(text):
"""Parses the narrowspec format that's returned by the server."""
includepats = set()
excludepats = set()
# We get one entry per line, in the format "<key> <value>".
# It's OK for value to contain other spaces.
for kp in (l.split(' ', 1) for l in text.splitlines()):
if len(kp) != 2:
raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
key = kp[0]
pat = kp[1]
if key == 'include':
includepats.add(pat)
elif key == 'exclude':
excludepats.add(pat)
else:
raise error.Abort(_('Invalid key "%s" in server response') % key)
return includepats, excludepats
def normalizesplitpattern(kind, pat):
"""Returns the normalized version of a pattern and kind.
Returns a tuple with the normalized kind and normalized pattern.
"""
pat = pat.rstrip('/')
_validatepattern(pat)
return kind, pat
def _numlines(s):
"""Returns the number of lines in s, including ending empty lines."""
# We use splitlines because it is Unicode-friendly and thus Python 3
# compatible. However, it does not count empty lines at the end, so trick
# it by adding a character at the end.
return len((s + 'x').splitlines())
def _validatepattern(pat):
"""Validates the pattern and aborts if it is invalid.
Patterns are stored in the narrowspec as newline-separated
POSIX-style bytestring paths. There's no escaping.
"""
# We use newlines as separators in the narrowspec file, so don't allow them
# in patterns.
if _numlines(pat) > 1:
raise error.Abort(_('newlines are not allowed in narrowspec paths'))
components = pat.split('/')
if '.' in components or '..' in components:
raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
def normalizepattern(pattern, defaultkind='path'):
"""Returns the normalized version of a text-format pattern.
If the pattern has no kind, the default will be added.
"""
kind, pat = matchmod._patsplit(pattern, defaultkind)
return '%s:%s' % normalizesplitpattern(kind, pat)
def parsepatterns(pats):
"""Parses a list of patterns into a typed pattern set."""
return set(normalizepattern(p) for p in pats)
def format(includes, excludes):
output = '[includes]\n'
for i in sorted(includes - excludes):
output += i + '\n'
output += '[excludes]\n'
for e in sorted(excludes):
output += e + '\n'
return output
def match(root, include=None, exclude=None):
if not include:
# Passing empty include and empty exclude to matchmod.match()
# gives a matcher that matches everything, so explicitly use
# the nevermatcher.
return matchmod.never(root, '')
return matchmod.match(root, '', [], include=include or [],
exclude=exclude or [])
def needsexpansion(includes):
return [i for i in includes if i.startswith('include:')]
def load(repo):
try:
spec = repo.vfs.read(FILENAME)
except IOError as e:
# Treat "narrowspec does not exist" the same as "narrowspec file exists
# and is empty".
if e.errno == errno.ENOENT:
# Without this the next call to load will use the cached
# non-existence of the file, which can cause some odd issues.
repo.invalidate(clearfilecache=True)
return set(), set()
raise
return _parsestoredpatterns(spec)
def save(repo, includepats, excludepats):
spec = format(includepats, excludepats)
repo.vfs.write(FILENAME, spec)
def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
r""" Restricts the patterns according to repo settings,
results in a logical AND operation
:param req_includes: requested includes
:param req_excludes: requested excludes
:param repo_includes: repo includes
:param repo_excludes: repo excludes
:return: include patterns, exclude patterns, and invalid include patterns.
>>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
(set(['f1']), {}, [])
>>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
(set(['f1']), {}, [])
>>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
(set(['f1/fc1']), {}, [])
>>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
([], set(['path:.']), [])
>>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
(set(['f2/fc2']), {}, [])
>>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
([], set(['path:.']), [])
>>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
(set(['f1/$non_exitent_var']), {}, [])
"""
res_excludes = set(req_excludes)
res_excludes.update(repo_excludes)
invalid_includes = []
if not req_includes:
res_includes = set(repo_includes)
elif 'path:.' not in repo_includes:
res_includes = []
for req_include in req_includes:
req_include = util.expandpath(util.normpath(req_include))
if req_include in repo_includes:
res_includes.append(req_include)
continue
valid = False
for repo_include in repo_includes:
if req_include.startswith(repo_include + '/'):
valid = True
res_includes.append(req_include)
break
if not valid:
invalid_includes.append(req_include)
if len(res_includes) == 0:
res_excludes = {'path:.'}
else:
res_includes = set(res_includes)
else:
res_includes = set(req_includes)
return res_includes, res_excludes, invalid_includes