store.py
654 lines
| 20.3 KiB
| text/x-python
|
PythonLexer
/ mercurial / store.py
Adrian Buehlmann
|
r6839 | # store.py - repository store handling for Mercurial | ||
# | ||||
# Copyright 2008 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Adrian Buehlmann
|
r6839 | |||
Gregory Szorc
|
r27480 | from __future__ import absolute_import | ||
import errno | ||||
Pulkit Goyal
|
r42144 | import functools | ||
Augie Fackler
|
r29338 | import hashlib | ||
Gregory Szorc
|
r27480 | import os | ||
import stat | ||||
from .i18n import _ | ||||
from . import ( | ||||
error, | ||||
Pulkit Goyal
|
r35600 | node, | ||
Yuya Nishihara
|
r32372 | policy, | ||
Mateusz Kwapich
|
r30077 | pycompat, | ||
Gregory Szorc
|
r27480 | util, | ||
Pierre-Yves David
|
r31234 | vfs as vfsmod, | ||
Gregory Szorc
|
r27480 | ) | ||
Adrian Buehlmann
|
r6840 | |||
Yuya Nishihara
|
r32372 | parsers = policy.importmod(r'parsers') | ||
Pulkit Goyal
|
r42144 | # how much bytes should be read from fncache in one read | ||
# It is done to prevent loading large fncache files into memory | ||||
fncache_chunksize = 10 ** 6 | ||||
Yuya Nishihara
|
r32372 | |||
Pulkit Goyal
|
r40529 | def _matchtrackedpath(path, matcher): | ||
"""parses a fncache entry and returns whether the entry is tracking a path | ||||
matched by matcher or not. | ||||
If matcher is None, returns True""" | ||||
if matcher is None: | ||||
return True | ||||
path = decodedir(path) | ||||
if path.startswith('data/'): | ||||
return matcher(path[len('data/'):-len('.i')]) | ||||
elif path.startswith('meta/'): | ||||
return matcher.visitdir(path[len('meta/'):-len('/00manifest.i')] or '.') | ||||
Pulkit Goyal
|
r40658 | raise error.ProgrammingError("cannot decode path %s" % path) | ||
Benoit Boissinot
|
r8531 | # This avoids a collision between a file named foo and a dir named | ||
# foo.i or foo.d | ||||
Adrian Buehlmann
|
r17607 | def _encodedir(path): | ||
Adrian Buehlmann
|
r13949 | ''' | ||
Yuya Nishihara
|
r34133 | >>> _encodedir(b'data/foo.i') | ||
Adrian Buehlmann
|
r13949 | 'data/foo.i' | ||
Yuya Nishihara
|
r34133 | >>> _encodedir(b'data/foo.i/bla.i') | ||
Adrian Buehlmann
|
r13949 | 'data/foo.i.hg/bla.i' | ||
Yuya Nishihara
|
r34133 | >>> _encodedir(b'data/foo.i.hg/bla.i') | ||
Adrian Buehlmann
|
r13949 | 'data/foo.i.hg.hg/bla.i' | ||
Yuya Nishihara
|
r34133 | >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n') | ||
Adrian Buehlmann
|
r17605 | 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n' | ||
Adrian Buehlmann
|
r13949 | ''' | ||
Benoit Boissinot
|
r8531 | return (path | ||
.replace(".hg/", ".hg.hg/") | ||||
.replace(".i/", ".i.hg/") | ||||
.replace(".d/", ".d.hg/")) | ||||
Adrian Buehlmann
|
r17607 | encodedir = getattr(parsers, 'encodedir', _encodedir) | ||
Benoit Boissinot
|
r8531 | def decodedir(path): | ||
Adrian Buehlmann
|
r13949 | ''' | ||
Yuya Nishihara
|
r34133 | >>> decodedir(b'data/foo.i') | ||
Adrian Buehlmann
|
r13949 | 'data/foo.i' | ||
Yuya Nishihara
|
r34133 | >>> decodedir(b'data/foo.i.hg/bla.i') | ||
Adrian Buehlmann
|
r13949 | 'data/foo.i/bla.i' | ||
Yuya Nishihara
|
r34133 | >>> decodedir(b'data/foo.i.hg.hg/bla.i') | ||
Adrian Buehlmann
|
r13949 | 'data/foo.i.hg/bla.i' | ||
''' | ||||
Adrian Buehlmann
|
r17586 | if ".hg/" not in path: | ||
Benoit Boissinot
|
r8531 | return path | ||
return (path | ||||
.replace(".d.hg/", ".d/") | ||||
.replace(".i.hg/", ".i/") | ||||
.replace(".hg.hg/", ".hg/")) | ||||
timeless
|
r29071 | def _reserved(): | ||
''' characters that are problematic for filesystems | ||||
* ascii escapes (0..31) | ||||
* ascii hi (126..255) | ||||
* windows specials | ||||
these characters will be escaped by encodefunctions | ||||
''' | ||||
Mateusz Kwapich
|
r30076 | winreserved = [ord(x) for x in u'\\:*?"<>|'] | ||
timeless
|
r29071 | for x in range(32): | ||
yield x | ||||
for x in range(126, 256): | ||||
yield x | ||||
for x in winreserved: | ||||
yield x | ||||
Adrian Buehlmann
|
r6839 | def _buildencodefun(): | ||
Adrian Buehlmann
|
r13949 | ''' | ||
>>> enc, dec = _buildencodefun() | ||||
Yuya Nishihara
|
r34133 | >>> enc(b'nothing/special.txt') | ||
Adrian Buehlmann
|
r13949 | 'nothing/special.txt' | ||
Yuya Nishihara
|
r34133 | >>> dec(b'nothing/special.txt') | ||
Adrian Buehlmann
|
r13949 | 'nothing/special.txt' | ||
Yuya Nishihara
|
r34133 | >>> enc(b'HELLO') | ||
Adrian Buehlmann
|
r13949 | '_h_e_l_l_o' | ||
Yuya Nishihara
|
r34133 | >>> dec(b'_h_e_l_l_o') | ||
Adrian Buehlmann
|
r13949 | 'HELLO' | ||
Yuya Nishihara
|
r34133 | >>> enc(b'hello:world?') | ||
Adrian Buehlmann
|
r13949 | 'hello~3aworld~3f' | ||
Yuya Nishihara
|
r34133 | >>> dec(b'hello~3aworld~3f') | ||
Adrian Buehlmann
|
r13949 | 'hello:world?' | ||
Yuya Nishihara
|
r34138 | >>> enc(b'the\\x07quick\\xADshot') | ||
Adrian Buehlmann
|
r13949 | 'the~07quick~adshot' | ||
Yuya Nishihara
|
r34133 | >>> dec(b'the~07quick~adshot') | ||
Adrian Buehlmann
|
r13949 | 'the\\x07quick\\xadshot' | ||
''' | ||||
Adrian Buehlmann
|
r6839 | e = '_' | ||
Yuya Nishihara
|
r31253 | xchr = pycompat.bytechr | ||
asciistr = list(map(xchr, range(127))) | ||||
Mateusz Kwapich
|
r30077 | capitals = list(range(ord("A"), ord("Z") + 1)) | ||
Martijn Pieters
|
r30108 | cmap = dict((x, x) for x in asciistr) | ||
timeless
|
r29071 | for x in _reserved(): | ||
Mateusz Kwapich
|
r30077 | cmap[xchr(x)] = "~%02x" % x | ||
for x in capitals + [ord(e)]: | ||||
cmap[xchr(x)] = e + xchr(x).lower() | ||||
Adrian Buehlmann
|
r6839 | dmap = {} | ||
for k, v in cmap.iteritems(): | ||||
dmap[v] = k | ||||
def decode(s): | ||||
i = 0 | ||||
while i < len(s): | ||||
Gregory Szorc
|
r38806 | for l in pycompat.xrange(1, 4): | ||
Adrian Buehlmann
|
r6839 | try: | ||
Matt Mackall
|
r10282 | yield dmap[s[i:i + l]] | ||
Adrian Buehlmann
|
r6839 | i += l | ||
break | ||||
except KeyError: | ||||
pass | ||||
else: | ||||
raise KeyError | ||||
Gregory Szorc
|
r38806 | return (lambda s: ''.join([cmap[s[c:c + 1]] | ||
for c in pycompat.xrange(len(s))]), | ||||
Adrian Buehlmann
|
r17608 | lambda s: ''.join(list(decode(s)))) | ||
_encodefname, _decodefname = _buildencodefun() | ||||
Adrian Buehlmann
|
r6839 | |||
Adrian Buehlmann
|
r17608 | def encodefilename(s): | ||
''' | ||||
Yuya Nishihara
|
r34133 | >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO') | ||
Adrian Buehlmann
|
r17608 | 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o' | ||
''' | ||||
return _encodefname(encodedir(s)) | ||||
def decodefilename(s): | ||||
''' | ||||
Yuya Nishihara
|
r34133 | >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o') | ||
Adrian Buehlmann
|
r17608 | 'foo.i/bar.d/bla.hg/hi:world?/HELLO' | ||
''' | ||||
return decodedir(_decodefname(s)) | ||||
Adrian Buehlmann
|
r6839 | |||
Adrian Buehlmann
|
r14288 | def _buildlowerencodefun(): | ||
Adrian Buehlmann
|
r13949 | ''' | ||
Adrian Buehlmann
|
r14288 | >>> f = _buildlowerencodefun() | ||
Yuya Nishihara
|
r34133 | >>> f(b'nothing/special.txt') | ||
Adrian Buehlmann
|
r13949 | 'nothing/special.txt' | ||
Yuya Nishihara
|
r34133 | >>> f(b'HELLO') | ||
Adrian Buehlmann
|
r13949 | 'hello' | ||
Yuya Nishihara
|
r34133 | >>> f(b'hello:world?') | ||
Adrian Buehlmann
|
r13949 | 'hello~3aworld~3f' | ||
Yuya Nishihara
|
r34138 | >>> f(b'the\\x07quick\\xADshot') | ||
Adrian Buehlmann
|
r13949 | 'the~07quick~adshot' | ||
''' | ||||
Yuya Nishihara
|
r34211 | xchr = pycompat.bytechr | ||
Gregory Szorc
|
r38806 | cmap = dict([(xchr(x), xchr(x)) for x in pycompat.xrange(127)]) | ||
timeless
|
r29071 | for x in _reserved(): | ||
Yuya Nishihara
|
r34211 | cmap[xchr(x)] = "~%02x" % x | ||
Mads Kiilerich
|
r18054 | for x in range(ord("A"), ord("Z") + 1): | ||
Yuya Nishihara
|
r34211 | cmap[xchr(x)] = xchr(x).lower() | ||
Yuya Nishihara
|
r34210 | def lowerencode(s): | ||
Yuya Nishihara
|
r34212 | return "".join([cmap[c] for c in pycompat.iterbytestr(s)]) | ||
Yuya Nishihara
|
r34210 | return lowerencode | ||
Adrian Buehlmann
|
r7229 | |||
Bryan O'Sullivan
|
r18430 | lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun() | ||
Adrian Buehlmann
|
r7229 | |||
Adrian Buehlmann
|
r17570 | # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9 | ||
_winres3 = ('aux', 'con', 'prn', 'nul') # length 3 | ||||
_winres4 = ('com', 'lpt') # length 4 (with trailing 1..9) | ||||
Adrian Buehlmann
|
r12687 | def _auxencode(path, dotencode): | ||
Adrian Buehlmann
|
r13949 | ''' | ||
Encodes filenames containing names reserved by Windows or which end in | ||||
period or space. Does not touch other single reserved characters c. | ||||
Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here. | ||||
Additionally encodes space or period at the beginning, if dotencode is | ||||
Adrian Buehlmann
|
r17569 | True. Parameter path is assumed to be all lowercase. | ||
A segment only needs encoding if a reserved name appears as a | ||||
basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux" | ||||
doesn't need encoding. | ||||
Adrian Buehlmann
|
r13949 | |||
Yuya Nishihara
|
r34133 | >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.' | ||
>>> _auxencode(s.split(b'/'), True) | ||||
Adrian Buehlmann
|
r17574 | ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e'] | ||
Yuya Nishihara
|
r34133 | >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.' | ||
>>> _auxencode(s.split(b'/'), False) | ||||
Adrian Buehlmann
|
r17574 | ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e'] | ||
Yuya Nishihara
|
r34133 | >>> _auxencode([b'foo. '], True) | ||
Adrian Buehlmann
|
r17574 | ['foo.~20'] | ||
Yuya Nishihara
|
r34133 | >>> _auxencode([b' .foo'], True) | ||
Adrian Buehlmann
|
r17574 | ['~20.foo'] | ||
Adrian Buehlmann
|
r13949 | ''' | ||
Adrian Buehlmann
|
r17589 | for i, n in enumerate(path): | ||
Adrian Buehlmann
|
r17572 | if not n: | ||
continue | ||||
if dotencode and n[0] in '. ': | ||||
Augie Fackler
|
r31362 | n = "~%02x" % ord(n[0:1]) + n[1:] | ||
Adrian Buehlmann
|
r17589 | path[i] = n | ||
Adrian Buehlmann
|
r17572 | else: | ||
l = n.find('.') | ||||
if l == -1: | ||||
l = len(n) | ||||
if ((l == 3 and n[:3] in _winres3) or | ||||
Augie Fackler
|
r31362 | (l == 4 and n[3:4] <= '9' and n[3:4] >= '1' | ||
Adrian Buehlmann
|
r17572 | and n[:3] in _winres4)): | ||
# encode third letter ('aux' -> 'au~78') | ||||
Augie Fackler
|
r31362 | ec = "~%02x" % ord(n[2:3]) | ||
Adrian Buehlmann
|
r17572 | n = n[0:2] + ec + n[3:] | ||
Adrian Buehlmann
|
r17589 | path[i] = n | ||
Adrian Buehlmann
|
r17572 | if n[-1] in '. ': | ||
# encode last period or space ('foo...' -> 'foo..~2e') | ||||
Augie Fackler
|
r31362 | path[i] = n[:-1] + "~%02x" % ord(n[-1:]) | ||
Adrian Buehlmann
|
r17589 | return path | ||
Adrian Buehlmann
|
r7229 | |||
Adrian Buehlmann
|
r14288 | _maxstorepathlen = 120 | ||
_dirprefixlen = 8 | ||||
_maxshortdirslen = 8 * (_dirprefixlen + 1) - 4 | ||||
Bryan O'Sullivan
|
r17610 | |||
def _hashencode(path, dotencode): | ||||
Pulkit Goyal
|
r35600 | digest = node.hex(hashlib.sha1(path).digest()) | ||
Martin von Zweigbergk
|
r25091 | le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/' | ||
Bryan O'Sullivan
|
r17610 | parts = _auxencode(le, dotencode) | ||
basename = parts[-1] | ||||
_root, ext = os.path.splitext(basename) | ||||
sdirs = [] | ||||
sdirslen = 0 | ||||
for p in parts[:-1]: | ||||
d = p[:_dirprefixlen] | ||||
if d[-1] in '. ': | ||||
# Windows can't access dirs ending in period or space | ||||
d = d[:-1] + '_' | ||||
if sdirslen == 0: | ||||
t = len(d) | ||||
else: | ||||
t = sdirslen + 1 + len(d) | ||||
if t > _maxshortdirslen: | ||||
break | ||||
sdirs.append(d) | ||||
sdirslen = t | ||||
dirs = '/'.join(sdirs) | ||||
if len(dirs) > 0: | ||||
dirs += '/' | ||||
res = 'dh/' + dirs + digest + ext | ||||
spaceleft = _maxstorepathlen - len(res) | ||||
if spaceleft > 0: | ||||
filler = basename[:spaceleft] | ||||
res = 'dh/' + dirs + filler + digest + ext | ||||
return res | ||||
Adrian Buehlmann
|
r17590 | def _hybridencode(path, dotencode): | ||
Adrian Buehlmann
|
r7229 | '''encodes path with a length limit | ||
Encodes all paths that begin with 'data/', according to the following. | ||||
Default encoding (reversible): | ||||
Encodes all uppercase letters 'X' as '_x'. All reserved or illegal | ||||
characters are encoded as '~xx', where xx is the two digit hex code | ||||
of the character (see encodefilename). | ||||
Relevant path components consisting of Windows reserved filenames are | ||||
Mads Kiilerich
|
r17738 | masked by encoding the third character ('aux' -> 'au~78', see _auxencode). | ||
Adrian Buehlmann
|
r7229 | |||
Hashed encoding (not reversible): | ||||
Adrian Buehlmann
|
r14288 | If the default-encoded path is longer than _maxstorepathlen, a | ||
Adrian Buehlmann
|
r7229 | non-reversible hybrid hashing of the path is done instead. | ||
Adrian Buehlmann
|
r14288 | This encoding uses up to _dirprefixlen characters of all directory | ||
Adrian Buehlmann
|
r7229 | levels of the lowerencoded path, but not more levels than can fit into | ||
Adrian Buehlmann
|
r14288 | _maxshortdirslen. | ||
Adrian Buehlmann
|
r7229 | Then follows the filler followed by the sha digest of the full path. | ||
The filler is the beginning of the basename of the lowerencoded path | ||||
(the basename is everything after the last path separator). The filler | ||||
is as long as possible, filling in characters from the basename until | ||||
Adrian Buehlmann
|
r14288 | the encoded path has _maxstorepathlen characters (or all chars of the | ||
basename have been taken). | ||||
Adrian Buehlmann
|
r7229 | The extension (e.g. '.i' or '.d') is preserved. | ||
The string 'data/' at the beginning is replaced with 'dh/', if the hashed | ||||
encoding was used. | ||||
''' | ||||
Adrian Buehlmann
|
r17609 | path = encodedir(path) | ||
ef = _encodefname(path).split('/') | ||||
Adrian Buehlmann
|
r17590 | res = '/'.join(_auxencode(ef, dotencode)) | ||
Adrian Buehlmann
|
r14288 | if len(res) > _maxstorepathlen: | ||
Bryan O'Sullivan
|
r17610 | res = _hashencode(path, dotencode) | ||
Adrian Buehlmann
|
r7229 | return res | ||
Adrian Buehlmann
|
r17624 | def _pathencode(path): | ||
Bryan O'Sullivan
|
r18435 | de = encodedir(path) | ||
Adrian Buehlmann
|
r17693 | if len(path) > _maxstorepathlen: | ||
Bryan O'Sullivan
|
r18435 | return _hashencode(de, True) | ||
ef = _encodefname(de).split('/') | ||||
Adrian Buehlmann
|
r17624 | res = '/'.join(_auxencode(ef, True)) | ||
if len(res) > _maxstorepathlen: | ||||
Bryan O'Sullivan
|
r18435 | return _hashencode(de, True) | ||
Adrian Buehlmann
|
r17624 | return res | ||
_pathencode = getattr(parsers, 'pathencode', _pathencode) | ||||
Adrian Buehlmann
|
r17623 | def _plainhybridencode(f): | ||
return _hybridencode(f, False) | ||||
FUJIWARA Katsunori
|
r17726 | def _calcmode(vfs): | ||
Matt Mackall
|
r6898 | try: | ||
# files in .hg/ will be created using this mode | ||||
FUJIWARA Katsunori
|
r17726 | mode = vfs.stat().st_mode | ||
Matt Mackall
|
r6898 | # avoid some useless chmods | ||
Gregory Szorc
|
r25658 | if (0o777 & ~util.umask) == (0o777 & mode): | ||
Matt Mackall
|
r6898 | mode = None | ||
except OSError: | ||||
mode = None | ||||
return mode | ||||
Martin von Zweigbergk
|
r38908 | _data = ('narrowspec data meta 00manifest.d 00manifest.i' | ||
' 00changelog.d 00changelog.i phaseroots obsstore') | ||||
Matt Mackall
|
r6903 | |||
Gregory Szorc
|
r37427 | def isrevlog(f, kind, st): | ||
return kind == stat.S_IFREG and f[-2:] in ('.i', '.d') | ||||
Benoit Boissinot
|
r8778 | class basicstore(object): | ||
Adrian Buehlmann
|
r6840 | '''base class for local repository stores''' | ||
FUJIWARA Katsunori
|
r17651 | def __init__(self, path, vfstype): | ||
FUJIWARA Katsunori
|
r17724 | vfs = vfstype(path) | ||
self.path = vfs.base | ||||
FUJIWARA Katsunori
|
r17726 | self.createmode = _calcmode(vfs) | ||
FUJIWARA Katsunori
|
r17652 | vfs.createmode = self.createmode | ||
FUJIWARA Katsunori
|
r17728 | self.rawvfs = vfs | ||
Pierre-Yves David
|
r31234 | self.vfs = vfsmod.filtervfs(vfs, encodedir) | ||
FUJIWARA Katsunori
|
r17653 | self.opener = self.vfs | ||
Adrian Buehlmann
|
r6840 | |||
def join(self, f): | ||||
Adrian Buehlmann
|
r13426 | return self.path + '/' + encodedir(f) | ||
Adrian Buehlmann
|
r6840 | |||
Gregory Szorc
|
r37427 | def _walk(self, relpath, recurse, filefilter=isrevlog): | ||
Matt Mackall
|
r6900 | '''yields (unencoded, encoded, size)''' | ||
Adrian Buehlmann
|
r13426 | path = self.path | ||
if relpath: | ||||
path += '/' + relpath | ||||
striplen = len(self.path) + 1 | ||||
Matt Mackall
|
r6899 | l = [] | ||
FUJIWARA Katsunori
|
r17728 | if self.rawvfs.isdir(path): | ||
Matt Mackall
|
r6899 | visit = [path] | ||
FUJIWARA Katsunori
|
r17747 | readdir = self.rawvfs.readdir | ||
Matt Mackall
|
r6899 | while visit: | ||
p = visit.pop() | ||||
FUJIWARA Katsunori
|
r17747 | for f, kind, st in readdir(p, stat=True): | ||
Adrian Buehlmann
|
r13426 | fp = p + '/' + f | ||
Gregory Szorc
|
r37427 | if filefilter(f, kind, st): | ||
Matt Mackall
|
r6900 | n = util.pconvert(fp[striplen:]) | ||
Benoit Boissinot
|
r8531 | l.append((decodedir(n), n, st.st_size)) | ||
Matt Mackall
|
r6899 | elif kind == stat.S_IFDIR and recurse: | ||
visit.append(fp) | ||||
Bryan O'Sullivan
|
r17054 | l.sort() | ||
return l | ||||
Adrian Buehlmann
|
r6840 | |||
Pulkit Goyal
|
r40376 | def datafiles(self, matcher=None): | ||
Martin von Zweigbergk
|
r28007 | return self._walk('data', True) + self._walk('meta', True) | ||
Adrian Buehlmann
|
r6840 | |||
Durham Goode
|
r19177 | def topfiles(self): | ||
# yield manifest before changelog | ||||
return reversed(self._walk('', False)) | ||||
Pulkit Goyal
|
r40376 | def walk(self, matcher=None): | ||
'''yields (unencoded, encoded, size) | ||||
if a matcher is passed, storage files of only those tracked paths | ||||
are passed with matches the matcher | ||||
''' | ||||
Adrian Buehlmann
|
r6840 | # yield data files first | ||
Pulkit Goyal
|
r40376 | for x in self.datafiles(matcher): | ||
Adrian Buehlmann
|
r6840 | yield x | ||
Durham Goode
|
r19177 | for x in self.topfiles(): | ||
Adrian Buehlmann
|
r6840 | yield x | ||
Matt Mackall
|
r6903 | def copylist(self): | ||
return ['requires'] + _data.split() | ||||
Durham Goode
|
r20883 | def write(self, tr): | ||
Adrian Buehlmann
|
r13391 | pass | ||
Durham Goode
|
r20884 | def invalidatecaches(self): | ||
pass | ||||
Durham Goode
|
r20885 | def markremoved(self, fn): | ||
pass | ||||
smuralid
|
r17744 | def __contains__(self, path): | ||
'''Checks if the store contains path''' | ||||
path = "/".join(("data", path)) | ||||
# file? | ||||
FUJIWARA Katsunori
|
r19903 | if self.vfs.exists(path + ".i"): | ||
smuralid
|
r17744 | return True | ||
# dir? | ||||
if not path.endswith("/"): | ||||
path = path + "/" | ||||
FUJIWARA Katsunori
|
r19903 | return self.vfs.exists(path) | ||
smuralid
|
r17744 | |||
Matt Mackall
|
r6898 | class encodedstore(basicstore): | ||
FUJIWARA Katsunori
|
r17651 | def __init__(self, path, vfstype): | ||
FUJIWARA Katsunori
|
r17724 | vfs = vfstype(path + '/store') | ||
self.path = vfs.base | ||||
FUJIWARA Katsunori
|
r17726 | self.createmode = _calcmode(vfs) | ||
FUJIWARA Katsunori
|
r17652 | vfs.createmode = self.createmode | ||
FUJIWARA Katsunori
|
r17728 | self.rawvfs = vfs | ||
Pierre-Yves David
|
r31234 | self.vfs = vfsmod.filtervfs(vfs, encodefilename) | ||
FUJIWARA Katsunori
|
r17653 | self.opener = self.vfs | ||
Adrian Buehlmann
|
r6840 | |||
Pulkit Goyal
|
r40376 | def datafiles(self, matcher=None): | ||
Martin von Zweigbergk
|
r28007 | for a, b, size in super(encodedstore, self).datafiles(): | ||
Adrian Buehlmann
|
r6892 | try: | ||
Matt Mackall
|
r6900 | a = decodefilename(a) | ||
Adrian Buehlmann
|
r6892 | except KeyError: | ||
Matt Mackall
|
r6900 | a = None | ||
Yuya Nishihara
|
r40620 | if a is not None and not _matchtrackedpath(a, matcher): | ||
continue | ||||
Matt Mackall
|
r6900 | yield a, b, size | ||
Adrian Buehlmann
|
r6840 | |||
def join(self, f): | ||||
Adrian Buehlmann
|
r13426 | return self.path + '/' + encodefilename(f) | ||
Adrian Buehlmann
|
r6840 | |||
Matt Mackall
|
r6903 | def copylist(self): | ||
return (['requires', '00changelog.i'] + | ||||
Adrian Buehlmann
|
r13426 | ['store/' + f for f in _data.split()]) | ||
Matt Mackall
|
r6903 | |||
Benoit Boissinot
|
r8530 | class fncache(object): | ||
Benoit Boissinot
|
r8531 | # the filename used to be partially encoded | ||
# hence the encodedir/decodedir dance | ||||
FUJIWARA Katsunori
|
r17722 | def __init__(self, vfs): | ||
self.vfs = vfs | ||||
Adrian Buehlmann
|
r7229 | self.entries = None | ||
Adrian Buehlmann
|
r13391 | self._dirty = False | ||
Pulkit Goyal
|
r40767 | # set of new additions to fncache | ||
self.addls = set() | ||||
Adrian Buehlmann
|
r7229 | |||
Benoit Boissinot
|
r8530 | def _load(self): | ||
'''fill the entries from the fncache file''' | ||||
Adrian Buehlmann
|
r13391 | self._dirty = False | ||
Benoit Boissinot
|
r8530 | try: | ||
FUJIWARA Katsunori
|
r17722 | fp = self.vfs('fncache', mode='rb') | ||
Benoit Boissinot
|
r8530 | except IOError: | ||
# skip nonexistent file | ||||
Bryan O'Sullivan
|
r16404 | self.entries = set() | ||
Benoit Boissinot
|
r8530 | return | ||
Pulkit Goyal
|
r42144 | |||
self.entries = set() | ||||
chunk = b'' | ||||
for c in iter(functools.partial(fp.read, fncache_chunksize), b''): | ||||
chunk += c | ||||
try: | ||||
p = chunk.rindex(b'\n') | ||||
self.entries.update(decodedir(chunk[:p + 1]).splitlines()) | ||||
chunk = chunk[p + 1:] | ||||
except ValueError: | ||||
# substring '\n' not found, maybe the entry is bigger than the | ||||
# chunksize, so let's keep iterating | ||||
pass | ||||
Pulkit Goyal
|
r42147 | if chunk: | ||
Pulkit Goyal
|
r42156 | raise error.Abort(_("fncache does not ends with a newline"), | ||
hint=_("use 'hg debugrebuildfncache' to rebuild" | ||||
" the fncache")) | ||||
Pulkit Goyal
|
r42139 | self._checkentries(fp) | ||
fp.close() | ||||
def _checkentries(self, fp): | ||||
""" make sure there is no empty string in entries """ | ||||
Bryan O'Sullivan
|
r16404 | if '' in self.entries: | ||
fp.seek(0) | ||||
Jun Wu
|
r30398 | for n, line in enumerate(util.iterfile(fp)): | ||
Bryan O'Sullivan
|
r16404 | if not line.rstrip('\n'): | ||
timeless@mozdev.org
|
r26778 | t = _('invalid entry in fncache, line %d') % (n + 1) | ||
Pierre-Yves David
|
r26587 | raise error.Abort(t) | ||
Adrian Buehlmann
|
r7229 | |||
Durham Goode
|
r20883 | def write(self, tr): | ||
Bryan O'Sullivan
|
r16404 | if self._dirty: | ||
Boris Feld
|
r38718 | assert self.entries is not None | ||
Pulkit Goyal
|
r40779 | self.entries = self.entries | self.addls | ||
self.addls = set() | ||||
Durham Goode
|
r20883 | tr.addbackup('fncache') | ||
Durham Goode
|
r20879 | fp = self.vfs('fncache', mode='wb', atomictemp=True) | ||
if self.entries: | ||||
fp.write(encodedir('\n'.join(self.entries) + '\n')) | ||||
fp.close() | ||||
self._dirty = False | ||||
Pulkit Goyal
|
r40767 | if self.addls: | ||
# if we have just new entries, let's append them to the fncache | ||||
tr.addbackup('fncache') | ||||
fp = self.vfs('fncache', mode='ab', atomictemp=True) | ||||
if self.addls: | ||||
fp.write(encodedir('\n'.join(self.addls) + '\n')) | ||||
fp.close() | ||||
self.entries = None | ||||
self.addls = set() | ||||
Benoit Boissinot
|
r8530 | |||
def add(self, fn): | ||||
if self.entries is None: | ||||
self._load() | ||||
Adrian Buehlmann
|
r10577 | if fn not in self.entries: | ||
Pulkit Goyal
|
r40767 | self.addls.add(fn) | ||
Benoit Boissinot
|
r8530 | |||
Durham Goode
|
r20885 | def remove(self, fn): | ||
if self.entries is None: | ||||
self._load() | ||||
Pulkit Goyal
|
r40767 | if fn in self.addls: | ||
self.addls.remove(fn) | ||||
return | ||||
Durham Goode
|
r20885 | try: | ||
self.entries.remove(fn) | ||||
self._dirty = True | ||||
except KeyError: | ||||
pass | ||||
Adrian Buehlmann
|
r17782 | def __contains__(self, fn): | ||
Pulkit Goyal
|
r40767 | if fn in self.addls: | ||
return True | ||||
Benoit Boissinot
|
r8530 | if self.entries is None: | ||
self._load() | ||||
Adrian Buehlmann
|
r17782 | return fn in self.entries | ||
Benoit Boissinot
|
r8530 | |||
def __iter__(self): | ||||
if self.entries is None: | ||||
self._load() | ||||
Pulkit Goyal
|
r40767 | return iter(self.entries | self.addls) | ||
Adrian Buehlmann
|
r7229 | |||
Boris Feld
|
r41125 | class _fncachevfs(vfsmod.proxyvfs): | ||
FUJIWARA Katsunori
|
r17721 | def __init__(self, vfs, fnc, encode): | ||
Yuya Nishihara
|
r33412 | vfsmod.proxyvfs.__init__(self, vfs) | ||
Adrian Buehlmann
|
r14194 | self.fncache = fnc | ||
self.encode = encode | ||||
def __call__(self, path, mode='r', *args, **kw): | ||||
Martijn Pieters
|
r38683 | encoded = self.encode(path) | ||
Martin von Zweigbergk
|
r28007 | if mode not in ('r', 'rb') and (path.startswith('data/') or | ||
path.startswith('meta/')): | ||||
Martijn Pieters
|
r38683 | # do not trigger a fncache load when adding a file that already is | ||
# known to exist. | ||||
notload = self.fncache.entries is None and self.vfs.exists(encoded) | ||||
if notload and 'a' in mode and not self.vfs.stat(encoded).st_size: | ||||
# when appending to an existing file, if the file has size zero, | ||||
# it should be considered as missing. Such zero-size files are | ||||
# the result of truncation when a transaction is aborted. | ||||
notload = False | ||||
if not notload: | ||||
self.fncache.add(path) | ||||
return self.vfs(encoded, mode, *args, **kw) | ||||
Adrian Buehlmann
|
r14194 | |||
FUJIWARA Katsunori
|
r17725 | def join(self, path): | ||
if path: | ||||
return self.vfs.join(self.encode(path)) | ||||
else: | ||||
return self.vfs.join(path) | ||||
Adrian Buehlmann
|
r7229 | class fncachestore(basicstore): | ||
FUJIWARA Katsunori
|
r17651 | def __init__(self, path, vfstype, dotencode): | ||
Adrian Buehlmann
|
r17591 | if dotencode: | ||
Bryan O'Sullivan
|
r18435 | encode = _pathencode | ||
Adrian Buehlmann
|
r17591 | else: | ||
encode = _plainhybridencode | ||||
Adrian Buehlmann
|
r12687 | self.encode = encode | ||
FUJIWARA Katsunori
|
r17724 | vfs = vfstype(path + '/store') | ||
self.path = vfs.base | ||||
Bryan O'Sullivan
|
r17562 | self.pathsep = self.path + '/' | ||
FUJIWARA Katsunori
|
r17726 | self.createmode = _calcmode(vfs) | ||
FUJIWARA Katsunori
|
r17652 | vfs.createmode = self.createmode | ||
FUJIWARA Katsunori
|
r17727 | self.rawvfs = vfs | ||
FUJIWARA Katsunori
|
r17652 | fnc = fncache(vfs) | ||
Simon Heimberg
|
r9133 | self.fncache = fnc | ||
FUJIWARA Katsunori
|
r17653 | self.vfs = _fncachevfs(vfs, fnc, encode) | ||
self.opener = self.vfs | ||||
Adrian Buehlmann
|
r7229 | |||
def join(self, f): | ||||
Bryan O'Sullivan
|
r17562 | return self.pathsep + self.encode(f) | ||
Adrian Buehlmann
|
r7229 | |||
Matt Mackall
|
r17731 | def getsize(self, path): | ||
return self.rawvfs.stat(path).st_size | ||||
Pulkit Goyal
|
r40376 | def datafiles(self, matcher=None): | ||
Bryan O'Sullivan
|
r17373 | for f in sorted(self.fncache): | ||
Pulkit Goyal
|
r40529 | if not _matchtrackedpath(f, matcher): | ||
continue | ||||
Adrian Buehlmann
|
r12687 | ef = self.encode(f) | ||
Adrian Buehlmann
|
r7229 | try: | ||
Matt Mackall
|
r17731 | yield f, ef, self.getsize(ef) | ||
Gregory Szorc
|
r25660 | except OSError as err: | ||
Bryan O'Sullivan
|
r17374 | if err.errno != errno.ENOENT: | ||
raise | ||||
Adrian Buehlmann
|
r7229 | |||
def copylist(self): | ||||
Martin von Zweigbergk
|
r38908 | d = ('narrowspec data meta dh fncache phaseroots obsstore' | ||
Pierre-Yves David
|
r15742 | ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i') | ||
Adrian Buehlmann
|
r7229 | return (['requires', '00changelog.i'] + | ||
Adrian Buehlmann
|
r13426 | ['store/' + f for f in d.split()]) | ||
Adrian Buehlmann
|
r7229 | |||
Durham Goode
|
r20883 | def write(self, tr): | ||
self.fncache.write(tr) | ||||
Adrian Buehlmann
|
r13391 | |||
Durham Goode
|
r20884 | def invalidatecaches(self): | ||
self.fncache.entries = None | ||||
Pulkit Goyal
|
r40767 | self.fncache.addls = set() | ||
Durham Goode
|
r20884 | |||
Durham Goode
|
r20885 | def markremoved(self, fn): | ||
self.fncache.remove(fn) | ||||
Adrian Buehlmann
|
r17783 | def _exists(self, f): | ||
ef = self.encode(f) | ||||
try: | ||||
self.getsize(ef) | ||||
return True | ||||
Gregory Szorc
|
r25660 | except OSError as err: | ||
Adrian Buehlmann
|
r17783 | if err.errno != errno.ENOENT: | ||
raise | ||||
# nonexistent entry | ||||
return False | ||||
smuralid
|
r17745 | def __contains__(self, path): | ||
'''Checks if the store contains path''' | ||||
path = "/".join(("data", path)) | ||||
Adrian Buehlmann
|
r17782 | # check for files (exact match) | ||
Adrian Buehlmann
|
r17784 | e = path + '.i' | ||
if e in self.fncache and self._exists(e): | ||||
Adrian Buehlmann
|
r17782 | return True | ||
# now check for directories (prefix match) | ||||
if not path.endswith('/'): | ||||
path += '/' | ||||
for e in self.fncache: | ||||
Adrian Buehlmann
|
r17784 | if e.startswith(path) and self._exists(e): | ||
Adrian Buehlmann
|
r17782 | return True | ||
return False | ||||