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