##// END OF EJS Templates
store: make file filtering during walk configurable...
Gregory Szorc -
r37427:4c15bee4 default
parent child Browse files
Show More
@@ -1,579 +1,582 b''
1 # store.py - repository store handling for Mercurial
1 # store.py - repository store handling for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import os
12 import os
13 import stat
13 import stat
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 node,
18 node,
19 policy,
19 policy,
20 pycompat,
20 pycompat,
21 util,
21 util,
22 vfs as vfsmod,
22 vfs as vfsmod,
23 )
23 )
24
24
25 parsers = policy.importmod(r'parsers')
25 parsers = policy.importmod(r'parsers')
26
26
27 # This avoids a collision between a file named foo and a dir named
27 # This avoids a collision between a file named foo and a dir named
28 # foo.i or foo.d
28 # foo.i or foo.d
29 def _encodedir(path):
29 def _encodedir(path):
30 '''
30 '''
31 >>> _encodedir(b'data/foo.i')
31 >>> _encodedir(b'data/foo.i')
32 'data/foo.i'
32 'data/foo.i'
33 >>> _encodedir(b'data/foo.i/bla.i')
33 >>> _encodedir(b'data/foo.i/bla.i')
34 'data/foo.i.hg/bla.i'
34 'data/foo.i.hg/bla.i'
35 >>> _encodedir(b'data/foo.i.hg/bla.i')
35 >>> _encodedir(b'data/foo.i.hg/bla.i')
36 'data/foo.i.hg.hg/bla.i'
36 'data/foo.i.hg.hg/bla.i'
37 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
37 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
38 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
38 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
39 '''
39 '''
40 return (path
40 return (path
41 .replace(".hg/", ".hg.hg/")
41 .replace(".hg/", ".hg.hg/")
42 .replace(".i/", ".i.hg/")
42 .replace(".i/", ".i.hg/")
43 .replace(".d/", ".d.hg/"))
43 .replace(".d/", ".d.hg/"))
44
44
45 encodedir = getattr(parsers, 'encodedir', _encodedir)
45 encodedir = getattr(parsers, 'encodedir', _encodedir)
46
46
47 def decodedir(path):
47 def decodedir(path):
48 '''
48 '''
49 >>> decodedir(b'data/foo.i')
49 >>> decodedir(b'data/foo.i')
50 'data/foo.i'
50 'data/foo.i'
51 >>> decodedir(b'data/foo.i.hg/bla.i')
51 >>> decodedir(b'data/foo.i.hg/bla.i')
52 'data/foo.i/bla.i'
52 'data/foo.i/bla.i'
53 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
53 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
54 'data/foo.i.hg/bla.i'
54 'data/foo.i.hg/bla.i'
55 '''
55 '''
56 if ".hg/" not in path:
56 if ".hg/" not in path:
57 return path
57 return path
58 return (path
58 return (path
59 .replace(".d.hg/", ".d/")
59 .replace(".d.hg/", ".d/")
60 .replace(".i.hg/", ".i/")
60 .replace(".i.hg/", ".i/")
61 .replace(".hg.hg/", ".hg/"))
61 .replace(".hg.hg/", ".hg/"))
62
62
63 def _reserved():
63 def _reserved():
64 ''' characters that are problematic for filesystems
64 ''' characters that are problematic for filesystems
65
65
66 * ascii escapes (0..31)
66 * ascii escapes (0..31)
67 * ascii hi (126..255)
67 * ascii hi (126..255)
68 * windows specials
68 * windows specials
69
69
70 these characters will be escaped by encodefunctions
70 these characters will be escaped by encodefunctions
71 '''
71 '''
72 winreserved = [ord(x) for x in u'\\:*?"<>|']
72 winreserved = [ord(x) for x in u'\\:*?"<>|']
73 for x in range(32):
73 for x in range(32):
74 yield x
74 yield x
75 for x in range(126, 256):
75 for x in range(126, 256):
76 yield x
76 yield x
77 for x in winreserved:
77 for x in winreserved:
78 yield x
78 yield x
79
79
80 def _buildencodefun():
80 def _buildencodefun():
81 '''
81 '''
82 >>> enc, dec = _buildencodefun()
82 >>> enc, dec = _buildencodefun()
83
83
84 >>> enc(b'nothing/special.txt')
84 >>> enc(b'nothing/special.txt')
85 'nothing/special.txt'
85 'nothing/special.txt'
86 >>> dec(b'nothing/special.txt')
86 >>> dec(b'nothing/special.txt')
87 'nothing/special.txt'
87 'nothing/special.txt'
88
88
89 >>> enc(b'HELLO')
89 >>> enc(b'HELLO')
90 '_h_e_l_l_o'
90 '_h_e_l_l_o'
91 >>> dec(b'_h_e_l_l_o')
91 >>> dec(b'_h_e_l_l_o')
92 'HELLO'
92 'HELLO'
93
93
94 >>> enc(b'hello:world?')
94 >>> enc(b'hello:world?')
95 'hello~3aworld~3f'
95 'hello~3aworld~3f'
96 >>> dec(b'hello~3aworld~3f')
96 >>> dec(b'hello~3aworld~3f')
97 'hello:world?'
97 'hello:world?'
98
98
99 >>> enc(b'the\\x07quick\\xADshot')
99 >>> enc(b'the\\x07quick\\xADshot')
100 'the~07quick~adshot'
100 'the~07quick~adshot'
101 >>> dec(b'the~07quick~adshot')
101 >>> dec(b'the~07quick~adshot')
102 'the\\x07quick\\xadshot'
102 'the\\x07quick\\xadshot'
103 '''
103 '''
104 e = '_'
104 e = '_'
105 xchr = pycompat.bytechr
105 xchr = pycompat.bytechr
106 asciistr = list(map(xchr, range(127)))
106 asciistr = list(map(xchr, range(127)))
107 capitals = list(range(ord("A"), ord("Z") + 1))
107 capitals = list(range(ord("A"), ord("Z") + 1))
108
108
109 cmap = dict((x, x) for x in asciistr)
109 cmap = dict((x, x) for x in asciistr)
110 for x in _reserved():
110 for x in _reserved():
111 cmap[xchr(x)] = "~%02x" % x
111 cmap[xchr(x)] = "~%02x" % x
112 for x in capitals + [ord(e)]:
112 for x in capitals + [ord(e)]:
113 cmap[xchr(x)] = e + xchr(x).lower()
113 cmap[xchr(x)] = e + xchr(x).lower()
114
114
115 dmap = {}
115 dmap = {}
116 for k, v in cmap.iteritems():
116 for k, v in cmap.iteritems():
117 dmap[v] = k
117 dmap[v] = k
118 def decode(s):
118 def decode(s):
119 i = 0
119 i = 0
120 while i < len(s):
120 while i < len(s):
121 for l in xrange(1, 4):
121 for l in xrange(1, 4):
122 try:
122 try:
123 yield dmap[s[i:i + l]]
123 yield dmap[s[i:i + l]]
124 i += l
124 i += l
125 break
125 break
126 except KeyError:
126 except KeyError:
127 pass
127 pass
128 else:
128 else:
129 raise KeyError
129 raise KeyError
130 return (lambda s: ''.join([cmap[s[c:c + 1]] for c in xrange(len(s))]),
130 return (lambda s: ''.join([cmap[s[c:c + 1]] for c in xrange(len(s))]),
131 lambda s: ''.join(list(decode(s))))
131 lambda s: ''.join(list(decode(s))))
132
132
133 _encodefname, _decodefname = _buildencodefun()
133 _encodefname, _decodefname = _buildencodefun()
134
134
135 def encodefilename(s):
135 def encodefilename(s):
136 '''
136 '''
137 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
137 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
138 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
138 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
139 '''
139 '''
140 return _encodefname(encodedir(s))
140 return _encodefname(encodedir(s))
141
141
142 def decodefilename(s):
142 def decodefilename(s):
143 '''
143 '''
144 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
144 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
145 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
145 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
146 '''
146 '''
147 return decodedir(_decodefname(s))
147 return decodedir(_decodefname(s))
148
148
149 def _buildlowerencodefun():
149 def _buildlowerencodefun():
150 '''
150 '''
151 >>> f = _buildlowerencodefun()
151 >>> f = _buildlowerencodefun()
152 >>> f(b'nothing/special.txt')
152 >>> f(b'nothing/special.txt')
153 'nothing/special.txt'
153 'nothing/special.txt'
154 >>> f(b'HELLO')
154 >>> f(b'HELLO')
155 'hello'
155 'hello'
156 >>> f(b'hello:world?')
156 >>> f(b'hello:world?')
157 'hello~3aworld~3f'
157 'hello~3aworld~3f'
158 >>> f(b'the\\x07quick\\xADshot')
158 >>> f(b'the\\x07quick\\xADshot')
159 'the~07quick~adshot'
159 'the~07quick~adshot'
160 '''
160 '''
161 xchr = pycompat.bytechr
161 xchr = pycompat.bytechr
162 cmap = dict([(xchr(x), xchr(x)) for x in xrange(127)])
162 cmap = dict([(xchr(x), xchr(x)) for x in xrange(127)])
163 for x in _reserved():
163 for x in _reserved():
164 cmap[xchr(x)] = "~%02x" % x
164 cmap[xchr(x)] = "~%02x" % x
165 for x in range(ord("A"), ord("Z") + 1):
165 for x in range(ord("A"), ord("Z") + 1):
166 cmap[xchr(x)] = xchr(x).lower()
166 cmap[xchr(x)] = xchr(x).lower()
167 def lowerencode(s):
167 def lowerencode(s):
168 return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
168 return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
169 return lowerencode
169 return lowerencode
170
170
171 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
171 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
172
172
173 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
173 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
174 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
174 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
175 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
175 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
176 def _auxencode(path, dotencode):
176 def _auxencode(path, dotencode):
177 '''
177 '''
178 Encodes filenames containing names reserved by Windows or which end in
178 Encodes filenames containing names reserved by Windows or which end in
179 period or space. Does not touch other single reserved characters c.
179 period or space. Does not touch other single reserved characters c.
180 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
180 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
181 Additionally encodes space or period at the beginning, if dotencode is
181 Additionally encodes space or period at the beginning, if dotencode is
182 True. Parameter path is assumed to be all lowercase.
182 True. Parameter path is assumed to be all lowercase.
183 A segment only needs encoding if a reserved name appears as a
183 A segment only needs encoding if a reserved name appears as a
184 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
184 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
185 doesn't need encoding.
185 doesn't need encoding.
186
186
187 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
187 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
188 >>> _auxencode(s.split(b'/'), True)
188 >>> _auxencode(s.split(b'/'), True)
189 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
189 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
190 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
190 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
191 >>> _auxencode(s.split(b'/'), False)
191 >>> _auxencode(s.split(b'/'), False)
192 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
192 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
193 >>> _auxencode([b'foo. '], True)
193 >>> _auxencode([b'foo. '], True)
194 ['foo.~20']
194 ['foo.~20']
195 >>> _auxencode([b' .foo'], True)
195 >>> _auxencode([b' .foo'], True)
196 ['~20.foo']
196 ['~20.foo']
197 '''
197 '''
198 for i, n in enumerate(path):
198 for i, n in enumerate(path):
199 if not n:
199 if not n:
200 continue
200 continue
201 if dotencode and n[0] in '. ':
201 if dotencode and n[0] in '. ':
202 n = "~%02x" % ord(n[0:1]) + n[1:]
202 n = "~%02x" % ord(n[0:1]) + n[1:]
203 path[i] = n
203 path[i] = n
204 else:
204 else:
205 l = n.find('.')
205 l = n.find('.')
206 if l == -1:
206 if l == -1:
207 l = len(n)
207 l = len(n)
208 if ((l == 3 and n[:3] in _winres3) or
208 if ((l == 3 and n[:3] in _winres3) or
209 (l == 4 and n[3:4] <= '9' and n[3:4] >= '1'
209 (l == 4 and n[3:4] <= '9' and n[3:4] >= '1'
210 and n[:3] in _winres4)):
210 and n[:3] in _winres4)):
211 # encode third letter ('aux' -> 'au~78')
211 # encode third letter ('aux' -> 'au~78')
212 ec = "~%02x" % ord(n[2:3])
212 ec = "~%02x" % ord(n[2:3])
213 n = n[0:2] + ec + n[3:]
213 n = n[0:2] + ec + n[3:]
214 path[i] = n
214 path[i] = n
215 if n[-1] in '. ':
215 if n[-1] in '. ':
216 # encode last period or space ('foo...' -> 'foo..~2e')
216 # encode last period or space ('foo...' -> 'foo..~2e')
217 path[i] = n[:-1] + "~%02x" % ord(n[-1:])
217 path[i] = n[:-1] + "~%02x" % ord(n[-1:])
218 return path
218 return path
219
219
220 _maxstorepathlen = 120
220 _maxstorepathlen = 120
221 _dirprefixlen = 8
221 _dirprefixlen = 8
222 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
222 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
223
223
224 def _hashencode(path, dotencode):
224 def _hashencode(path, dotencode):
225 digest = node.hex(hashlib.sha1(path).digest())
225 digest = node.hex(hashlib.sha1(path).digest())
226 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
226 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
227 parts = _auxencode(le, dotencode)
227 parts = _auxencode(le, dotencode)
228 basename = parts[-1]
228 basename = parts[-1]
229 _root, ext = os.path.splitext(basename)
229 _root, ext = os.path.splitext(basename)
230 sdirs = []
230 sdirs = []
231 sdirslen = 0
231 sdirslen = 0
232 for p in parts[:-1]:
232 for p in parts[:-1]:
233 d = p[:_dirprefixlen]
233 d = p[:_dirprefixlen]
234 if d[-1] in '. ':
234 if d[-1] in '. ':
235 # Windows can't access dirs ending in period or space
235 # Windows can't access dirs ending in period or space
236 d = d[:-1] + '_'
236 d = d[:-1] + '_'
237 if sdirslen == 0:
237 if sdirslen == 0:
238 t = len(d)
238 t = len(d)
239 else:
239 else:
240 t = sdirslen + 1 + len(d)
240 t = sdirslen + 1 + len(d)
241 if t > _maxshortdirslen:
241 if t > _maxshortdirslen:
242 break
242 break
243 sdirs.append(d)
243 sdirs.append(d)
244 sdirslen = t
244 sdirslen = t
245 dirs = '/'.join(sdirs)
245 dirs = '/'.join(sdirs)
246 if len(dirs) > 0:
246 if len(dirs) > 0:
247 dirs += '/'
247 dirs += '/'
248 res = 'dh/' + dirs + digest + ext
248 res = 'dh/' + dirs + digest + ext
249 spaceleft = _maxstorepathlen - len(res)
249 spaceleft = _maxstorepathlen - len(res)
250 if spaceleft > 0:
250 if spaceleft > 0:
251 filler = basename[:spaceleft]
251 filler = basename[:spaceleft]
252 res = 'dh/' + dirs + filler + digest + ext
252 res = 'dh/' + dirs + filler + digest + ext
253 return res
253 return res
254
254
255 def _hybridencode(path, dotencode):
255 def _hybridencode(path, dotencode):
256 '''encodes path with a length limit
256 '''encodes path with a length limit
257
257
258 Encodes all paths that begin with 'data/', according to the following.
258 Encodes all paths that begin with 'data/', according to the following.
259
259
260 Default encoding (reversible):
260 Default encoding (reversible):
261
261
262 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
262 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
263 characters are encoded as '~xx', where xx is the two digit hex code
263 characters are encoded as '~xx', where xx is the two digit hex code
264 of the character (see encodefilename).
264 of the character (see encodefilename).
265 Relevant path components consisting of Windows reserved filenames are
265 Relevant path components consisting of Windows reserved filenames are
266 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
266 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
267
267
268 Hashed encoding (not reversible):
268 Hashed encoding (not reversible):
269
269
270 If the default-encoded path is longer than _maxstorepathlen, a
270 If the default-encoded path is longer than _maxstorepathlen, a
271 non-reversible hybrid hashing of the path is done instead.
271 non-reversible hybrid hashing of the path is done instead.
272 This encoding uses up to _dirprefixlen characters of all directory
272 This encoding uses up to _dirprefixlen characters of all directory
273 levels of the lowerencoded path, but not more levels than can fit into
273 levels of the lowerencoded path, but not more levels than can fit into
274 _maxshortdirslen.
274 _maxshortdirslen.
275 Then follows the filler followed by the sha digest of the full path.
275 Then follows the filler followed by the sha digest of the full path.
276 The filler is the beginning of the basename of the lowerencoded path
276 The filler is the beginning of the basename of the lowerencoded path
277 (the basename is everything after the last path separator). The filler
277 (the basename is everything after the last path separator). The filler
278 is as long as possible, filling in characters from the basename until
278 is as long as possible, filling in characters from the basename until
279 the encoded path has _maxstorepathlen characters (or all chars of the
279 the encoded path has _maxstorepathlen characters (or all chars of the
280 basename have been taken).
280 basename have been taken).
281 The extension (e.g. '.i' or '.d') is preserved.
281 The extension (e.g. '.i' or '.d') is preserved.
282
282
283 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
283 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
284 encoding was used.
284 encoding was used.
285 '''
285 '''
286 path = encodedir(path)
286 path = encodedir(path)
287 ef = _encodefname(path).split('/')
287 ef = _encodefname(path).split('/')
288 res = '/'.join(_auxencode(ef, dotencode))
288 res = '/'.join(_auxencode(ef, dotencode))
289 if len(res) > _maxstorepathlen:
289 if len(res) > _maxstorepathlen:
290 res = _hashencode(path, dotencode)
290 res = _hashencode(path, dotencode)
291 return res
291 return res
292
292
293 def _pathencode(path):
293 def _pathencode(path):
294 de = encodedir(path)
294 de = encodedir(path)
295 if len(path) > _maxstorepathlen:
295 if len(path) > _maxstorepathlen:
296 return _hashencode(de, True)
296 return _hashencode(de, True)
297 ef = _encodefname(de).split('/')
297 ef = _encodefname(de).split('/')
298 res = '/'.join(_auxencode(ef, True))
298 res = '/'.join(_auxencode(ef, True))
299 if len(res) > _maxstorepathlen:
299 if len(res) > _maxstorepathlen:
300 return _hashencode(de, True)
300 return _hashencode(de, True)
301 return res
301 return res
302
302
303 _pathencode = getattr(parsers, 'pathencode', _pathencode)
303 _pathencode = getattr(parsers, 'pathencode', _pathencode)
304
304
305 def _plainhybridencode(f):
305 def _plainhybridencode(f):
306 return _hybridencode(f, False)
306 return _hybridencode(f, False)
307
307
308 def _calcmode(vfs):
308 def _calcmode(vfs):
309 try:
309 try:
310 # files in .hg/ will be created using this mode
310 # files in .hg/ will be created using this mode
311 mode = vfs.stat().st_mode
311 mode = vfs.stat().st_mode
312 # avoid some useless chmods
312 # avoid some useless chmods
313 if (0o777 & ~util.umask) == (0o777 & mode):
313 if (0o777 & ~util.umask) == (0o777 & mode):
314 mode = None
314 mode = None
315 except OSError:
315 except OSError:
316 mode = None
316 mode = None
317 return mode
317 return mode
318
318
319 _data = ('data meta 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
319 _data = ('data meta 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
320 ' phaseroots obsstore')
320 ' phaseroots obsstore')
321
321
322 def isrevlog(f, kind, st):
323 return kind == stat.S_IFREG and f[-2:] in ('.i', '.d')
324
322 class basicstore(object):
325 class basicstore(object):
323 '''base class for local repository stores'''
326 '''base class for local repository stores'''
324 def __init__(self, path, vfstype):
327 def __init__(self, path, vfstype):
325 vfs = vfstype(path)
328 vfs = vfstype(path)
326 self.path = vfs.base
329 self.path = vfs.base
327 self.createmode = _calcmode(vfs)
330 self.createmode = _calcmode(vfs)
328 vfs.createmode = self.createmode
331 vfs.createmode = self.createmode
329 self.rawvfs = vfs
332 self.rawvfs = vfs
330 self.vfs = vfsmod.filtervfs(vfs, encodedir)
333 self.vfs = vfsmod.filtervfs(vfs, encodedir)
331 self.opener = self.vfs
334 self.opener = self.vfs
332
335
333 def join(self, f):
336 def join(self, f):
334 return self.path + '/' + encodedir(f)
337 return self.path + '/' + encodedir(f)
335
338
336 def _walk(self, relpath, recurse):
339 def _walk(self, relpath, recurse, filefilter=isrevlog):
337 '''yields (unencoded, encoded, size)'''
340 '''yields (unencoded, encoded, size)'''
338 path = self.path
341 path = self.path
339 if relpath:
342 if relpath:
340 path += '/' + relpath
343 path += '/' + relpath
341 striplen = len(self.path) + 1
344 striplen = len(self.path) + 1
342 l = []
345 l = []
343 if self.rawvfs.isdir(path):
346 if self.rawvfs.isdir(path):
344 visit = [path]
347 visit = [path]
345 readdir = self.rawvfs.readdir
348 readdir = self.rawvfs.readdir
346 while visit:
349 while visit:
347 p = visit.pop()
350 p = visit.pop()
348 for f, kind, st in readdir(p, stat=True):
351 for f, kind, st in readdir(p, stat=True):
349 fp = p + '/' + f
352 fp = p + '/' + f
350 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
353 if filefilter(f, kind, st):
351 n = util.pconvert(fp[striplen:])
354 n = util.pconvert(fp[striplen:])
352 l.append((decodedir(n), n, st.st_size))
355 l.append((decodedir(n), n, st.st_size))
353 elif kind == stat.S_IFDIR and recurse:
356 elif kind == stat.S_IFDIR and recurse:
354 visit.append(fp)
357 visit.append(fp)
355 l.sort()
358 l.sort()
356 return l
359 return l
357
360
358 def datafiles(self):
361 def datafiles(self):
359 return self._walk('data', True) + self._walk('meta', True)
362 return self._walk('data', True) + self._walk('meta', True)
360
363
361 def topfiles(self):
364 def topfiles(self):
362 # yield manifest before changelog
365 # yield manifest before changelog
363 return reversed(self._walk('', False))
366 return reversed(self._walk('', False))
364
367
365 def walk(self):
368 def walk(self):
366 '''yields (unencoded, encoded, size)'''
369 '''yields (unencoded, encoded, size)'''
367 # yield data files first
370 # yield data files first
368 for x in self.datafiles():
371 for x in self.datafiles():
369 yield x
372 yield x
370 for x in self.topfiles():
373 for x in self.topfiles():
371 yield x
374 yield x
372
375
373 def copylist(self):
376 def copylist(self):
374 return ['requires'] + _data.split()
377 return ['requires'] + _data.split()
375
378
376 def write(self, tr):
379 def write(self, tr):
377 pass
380 pass
378
381
379 def invalidatecaches(self):
382 def invalidatecaches(self):
380 pass
383 pass
381
384
382 def markremoved(self, fn):
385 def markremoved(self, fn):
383 pass
386 pass
384
387
385 def __contains__(self, path):
388 def __contains__(self, path):
386 '''Checks if the store contains path'''
389 '''Checks if the store contains path'''
387 path = "/".join(("data", path))
390 path = "/".join(("data", path))
388 # file?
391 # file?
389 if self.vfs.exists(path + ".i"):
392 if self.vfs.exists(path + ".i"):
390 return True
393 return True
391 # dir?
394 # dir?
392 if not path.endswith("/"):
395 if not path.endswith("/"):
393 path = path + "/"
396 path = path + "/"
394 return self.vfs.exists(path)
397 return self.vfs.exists(path)
395
398
396 class encodedstore(basicstore):
399 class encodedstore(basicstore):
397 def __init__(self, path, vfstype):
400 def __init__(self, path, vfstype):
398 vfs = vfstype(path + '/store')
401 vfs = vfstype(path + '/store')
399 self.path = vfs.base
402 self.path = vfs.base
400 self.createmode = _calcmode(vfs)
403 self.createmode = _calcmode(vfs)
401 vfs.createmode = self.createmode
404 vfs.createmode = self.createmode
402 self.rawvfs = vfs
405 self.rawvfs = vfs
403 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
406 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
404 self.opener = self.vfs
407 self.opener = self.vfs
405
408
406 def datafiles(self):
409 def datafiles(self):
407 for a, b, size in super(encodedstore, self).datafiles():
410 for a, b, size in super(encodedstore, self).datafiles():
408 try:
411 try:
409 a = decodefilename(a)
412 a = decodefilename(a)
410 except KeyError:
413 except KeyError:
411 a = None
414 a = None
412 yield a, b, size
415 yield a, b, size
413
416
414 def join(self, f):
417 def join(self, f):
415 return self.path + '/' + encodefilename(f)
418 return self.path + '/' + encodefilename(f)
416
419
417 def copylist(self):
420 def copylist(self):
418 return (['requires', '00changelog.i'] +
421 return (['requires', '00changelog.i'] +
419 ['store/' + f for f in _data.split()])
422 ['store/' + f for f in _data.split()])
420
423
421 class fncache(object):
424 class fncache(object):
422 # the filename used to be partially encoded
425 # the filename used to be partially encoded
423 # hence the encodedir/decodedir dance
426 # hence the encodedir/decodedir dance
424 def __init__(self, vfs):
427 def __init__(self, vfs):
425 self.vfs = vfs
428 self.vfs = vfs
426 self.entries = None
429 self.entries = None
427 self._dirty = False
430 self._dirty = False
428
431
429 def _load(self):
432 def _load(self):
430 '''fill the entries from the fncache file'''
433 '''fill the entries from the fncache file'''
431 self._dirty = False
434 self._dirty = False
432 try:
435 try:
433 fp = self.vfs('fncache', mode='rb')
436 fp = self.vfs('fncache', mode='rb')
434 except IOError:
437 except IOError:
435 # skip nonexistent file
438 # skip nonexistent file
436 self.entries = set()
439 self.entries = set()
437 return
440 return
438 self.entries = set(decodedir(fp.read()).splitlines())
441 self.entries = set(decodedir(fp.read()).splitlines())
439 if '' in self.entries:
442 if '' in self.entries:
440 fp.seek(0)
443 fp.seek(0)
441 for n, line in enumerate(util.iterfile(fp)):
444 for n, line in enumerate(util.iterfile(fp)):
442 if not line.rstrip('\n'):
445 if not line.rstrip('\n'):
443 t = _('invalid entry in fncache, line %d') % (n + 1)
446 t = _('invalid entry in fncache, line %d') % (n + 1)
444 raise error.Abort(t)
447 raise error.Abort(t)
445 fp.close()
448 fp.close()
446
449
447 def write(self, tr):
450 def write(self, tr):
448 if self._dirty:
451 if self._dirty:
449 tr.addbackup('fncache')
452 tr.addbackup('fncache')
450 fp = self.vfs('fncache', mode='wb', atomictemp=True)
453 fp = self.vfs('fncache', mode='wb', atomictemp=True)
451 if self.entries:
454 if self.entries:
452 fp.write(encodedir('\n'.join(self.entries) + '\n'))
455 fp.write(encodedir('\n'.join(self.entries) + '\n'))
453 fp.close()
456 fp.close()
454 self._dirty = False
457 self._dirty = False
455
458
456 def add(self, fn):
459 def add(self, fn):
457 if self.entries is None:
460 if self.entries is None:
458 self._load()
461 self._load()
459 if fn not in self.entries:
462 if fn not in self.entries:
460 self._dirty = True
463 self._dirty = True
461 self.entries.add(fn)
464 self.entries.add(fn)
462
465
463 def remove(self, fn):
466 def remove(self, fn):
464 if self.entries is None:
467 if self.entries is None:
465 self._load()
468 self._load()
466 try:
469 try:
467 self.entries.remove(fn)
470 self.entries.remove(fn)
468 self._dirty = True
471 self._dirty = True
469 except KeyError:
472 except KeyError:
470 pass
473 pass
471
474
472 def __contains__(self, fn):
475 def __contains__(self, fn):
473 if self.entries is None:
476 if self.entries is None:
474 self._load()
477 self._load()
475 return fn in self.entries
478 return fn in self.entries
476
479
477 def __iter__(self):
480 def __iter__(self):
478 if self.entries is None:
481 if self.entries is None:
479 self._load()
482 self._load()
480 return iter(self.entries)
483 return iter(self.entries)
481
484
482 class _fncachevfs(vfsmod.abstractvfs, vfsmod.proxyvfs):
485 class _fncachevfs(vfsmod.abstractvfs, vfsmod.proxyvfs):
483 def __init__(self, vfs, fnc, encode):
486 def __init__(self, vfs, fnc, encode):
484 vfsmod.proxyvfs.__init__(self, vfs)
487 vfsmod.proxyvfs.__init__(self, vfs)
485 self.fncache = fnc
488 self.fncache = fnc
486 self.encode = encode
489 self.encode = encode
487
490
488 def __call__(self, path, mode='r', *args, **kw):
491 def __call__(self, path, mode='r', *args, **kw):
489 if mode not in ('r', 'rb') and (path.startswith('data/') or
492 if mode not in ('r', 'rb') and (path.startswith('data/') or
490 path.startswith('meta/')):
493 path.startswith('meta/')):
491 self.fncache.add(path)
494 self.fncache.add(path)
492 return self.vfs(self.encode(path), mode, *args, **kw)
495 return self.vfs(self.encode(path), mode, *args, **kw)
493
496
494 def join(self, path):
497 def join(self, path):
495 if path:
498 if path:
496 return self.vfs.join(self.encode(path))
499 return self.vfs.join(self.encode(path))
497 else:
500 else:
498 return self.vfs.join(path)
501 return self.vfs.join(path)
499
502
500 class fncachestore(basicstore):
503 class fncachestore(basicstore):
501 def __init__(self, path, vfstype, dotencode):
504 def __init__(self, path, vfstype, dotencode):
502 if dotencode:
505 if dotencode:
503 encode = _pathencode
506 encode = _pathencode
504 else:
507 else:
505 encode = _plainhybridencode
508 encode = _plainhybridencode
506 self.encode = encode
509 self.encode = encode
507 vfs = vfstype(path + '/store')
510 vfs = vfstype(path + '/store')
508 self.path = vfs.base
511 self.path = vfs.base
509 self.pathsep = self.path + '/'
512 self.pathsep = self.path + '/'
510 self.createmode = _calcmode(vfs)
513 self.createmode = _calcmode(vfs)
511 vfs.createmode = self.createmode
514 vfs.createmode = self.createmode
512 self.rawvfs = vfs
515 self.rawvfs = vfs
513 fnc = fncache(vfs)
516 fnc = fncache(vfs)
514 self.fncache = fnc
517 self.fncache = fnc
515 self.vfs = _fncachevfs(vfs, fnc, encode)
518 self.vfs = _fncachevfs(vfs, fnc, encode)
516 self.opener = self.vfs
519 self.opener = self.vfs
517
520
518 def join(self, f):
521 def join(self, f):
519 return self.pathsep + self.encode(f)
522 return self.pathsep + self.encode(f)
520
523
521 def getsize(self, path):
524 def getsize(self, path):
522 return self.rawvfs.stat(path).st_size
525 return self.rawvfs.stat(path).st_size
523
526
524 def datafiles(self):
527 def datafiles(self):
525 for f in sorted(self.fncache):
528 for f in sorted(self.fncache):
526 ef = self.encode(f)
529 ef = self.encode(f)
527 try:
530 try:
528 yield f, ef, self.getsize(ef)
531 yield f, ef, self.getsize(ef)
529 except OSError as err:
532 except OSError as err:
530 if err.errno != errno.ENOENT:
533 if err.errno != errno.ENOENT:
531 raise
534 raise
532
535
533 def copylist(self):
536 def copylist(self):
534 d = ('data meta dh fncache phaseroots obsstore'
537 d = ('data meta dh fncache phaseroots obsstore'
535 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
538 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
536 return (['requires', '00changelog.i'] +
539 return (['requires', '00changelog.i'] +
537 ['store/' + f for f in d.split()])
540 ['store/' + f for f in d.split()])
538
541
539 def write(self, tr):
542 def write(self, tr):
540 self.fncache.write(tr)
543 self.fncache.write(tr)
541
544
542 def invalidatecaches(self):
545 def invalidatecaches(self):
543 self.fncache.entries = None
546 self.fncache.entries = None
544
547
545 def markremoved(self, fn):
548 def markremoved(self, fn):
546 self.fncache.remove(fn)
549 self.fncache.remove(fn)
547
550
548 def _exists(self, f):
551 def _exists(self, f):
549 ef = self.encode(f)
552 ef = self.encode(f)
550 try:
553 try:
551 self.getsize(ef)
554 self.getsize(ef)
552 return True
555 return True
553 except OSError as err:
556 except OSError as err:
554 if err.errno != errno.ENOENT:
557 if err.errno != errno.ENOENT:
555 raise
558 raise
556 # nonexistent entry
559 # nonexistent entry
557 return False
560 return False
558
561
559 def __contains__(self, path):
562 def __contains__(self, path):
560 '''Checks if the store contains path'''
563 '''Checks if the store contains path'''
561 path = "/".join(("data", path))
564 path = "/".join(("data", path))
562 # check for files (exact match)
565 # check for files (exact match)
563 e = path + '.i'
566 e = path + '.i'
564 if e in self.fncache and self._exists(e):
567 if e in self.fncache and self._exists(e):
565 return True
568 return True
566 # now check for directories (prefix match)
569 # now check for directories (prefix match)
567 if not path.endswith('/'):
570 if not path.endswith('/'):
568 path += '/'
571 path += '/'
569 for e in self.fncache:
572 for e in self.fncache:
570 if e.startswith(path) and self._exists(e):
573 if e.startswith(path) and self._exists(e):
571 return True
574 return True
572 return False
575 return False
573
576
574 def store(requirements, path, vfstype):
577 def store(requirements, path, vfstype):
575 if 'store' in requirements:
578 if 'store' in requirements:
576 if 'fncache' in requirements:
579 if 'fncache' in requirements:
577 return fncachestore(path, vfstype, 'dotencode' in requirements)
580 return fncachestore(path, vfstype, 'dotencode' in requirements)
578 return encodedstore(path, vfstype)
581 return encodedstore(path, vfstype)
579 return basicstore(path, vfstype)
582 return basicstore(path, vfstype)
General Comments 0
You need to be logged in to leave comments. Login now