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