##// END OF EJS Templates
store: abstract out how we retrieve a file's size
Bryan O'Sullivan -
r17154:d592759a default
parent child Browse files
Show More
@@ -1,428 +1,429
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 i18n import _
8 from i18n import _
9 import osutil, scmutil, util
9 import osutil, scmutil, util
10 import os, stat
10 import os, stat
11
11
12 _sha = util.sha1
12 _sha = util.sha1
13
13
14 # This avoids a collision between a file named foo and a dir named
14 # This avoids a collision between a file named foo and a dir named
15 # foo.i or foo.d
15 # foo.i or foo.d
16 def encodedir(path):
16 def encodedir(path):
17 '''
17 '''
18 >>> encodedir('data/foo.i')
18 >>> encodedir('data/foo.i')
19 'data/foo.i'
19 'data/foo.i'
20 >>> encodedir('data/foo.i/bla.i')
20 >>> encodedir('data/foo.i/bla.i')
21 'data/foo.i.hg/bla.i'
21 'data/foo.i.hg/bla.i'
22 >>> encodedir('data/foo.i.hg/bla.i')
22 >>> encodedir('data/foo.i.hg/bla.i')
23 'data/foo.i.hg.hg/bla.i'
23 'data/foo.i.hg.hg/bla.i'
24 '''
24 '''
25 if not path.startswith('data/'):
25 if not path.startswith('data/'):
26 return path
26 return path
27 return (path
27 return (path
28 .replace(".hg/", ".hg.hg/")
28 .replace(".hg/", ".hg.hg/")
29 .replace(".i/", ".i.hg/")
29 .replace(".i/", ".i.hg/")
30 .replace(".d/", ".d.hg/"))
30 .replace(".d/", ".d.hg/"))
31
31
32 def decodedir(path):
32 def decodedir(path):
33 '''
33 '''
34 >>> decodedir('data/foo.i')
34 >>> decodedir('data/foo.i')
35 'data/foo.i'
35 'data/foo.i'
36 >>> decodedir('data/foo.i.hg/bla.i')
36 >>> decodedir('data/foo.i.hg/bla.i')
37 'data/foo.i/bla.i'
37 'data/foo.i/bla.i'
38 >>> decodedir('data/foo.i.hg.hg/bla.i')
38 >>> decodedir('data/foo.i.hg.hg/bla.i')
39 'data/foo.i.hg/bla.i'
39 'data/foo.i.hg/bla.i'
40 '''
40 '''
41 if not path.startswith('data/') or ".hg/" not in path:
41 if not path.startswith('data/') or ".hg/" not in path:
42 return path
42 return path
43 return (path
43 return (path
44 .replace(".d.hg/", ".d/")
44 .replace(".d.hg/", ".d/")
45 .replace(".i.hg/", ".i/")
45 .replace(".i.hg/", ".i/")
46 .replace(".hg.hg/", ".hg/"))
46 .replace(".hg.hg/", ".hg/"))
47
47
48 def _buildencodefun():
48 def _buildencodefun():
49 '''
49 '''
50 >>> enc, dec = _buildencodefun()
50 >>> enc, dec = _buildencodefun()
51
51
52 >>> enc('nothing/special.txt')
52 >>> enc('nothing/special.txt')
53 'nothing/special.txt'
53 'nothing/special.txt'
54 >>> dec('nothing/special.txt')
54 >>> dec('nothing/special.txt')
55 'nothing/special.txt'
55 'nothing/special.txt'
56
56
57 >>> enc('HELLO')
57 >>> enc('HELLO')
58 '_h_e_l_l_o'
58 '_h_e_l_l_o'
59 >>> dec('_h_e_l_l_o')
59 >>> dec('_h_e_l_l_o')
60 'HELLO'
60 'HELLO'
61
61
62 >>> enc('hello:world?')
62 >>> enc('hello:world?')
63 'hello~3aworld~3f'
63 'hello~3aworld~3f'
64 >>> dec('hello~3aworld~3f')
64 >>> dec('hello~3aworld~3f')
65 'hello:world?'
65 'hello:world?'
66
66
67 >>> enc('the\x07quick\xADshot')
67 >>> enc('the\x07quick\xADshot')
68 'the~07quick~adshot'
68 'the~07quick~adshot'
69 >>> dec('the~07quick~adshot')
69 >>> dec('the~07quick~adshot')
70 'the\\x07quick\\xadshot'
70 'the\\x07quick\\xadshot'
71 '''
71 '''
72 e = '_'
72 e = '_'
73 winreserved = [ord(x) for x in '\\:*?"<>|']
73 winreserved = [ord(x) for x in '\\:*?"<>|']
74 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
74 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
75 for x in (range(32) + range(126, 256) + winreserved):
75 for x in (range(32) + range(126, 256) + winreserved):
76 cmap[chr(x)] = "~%02x" % x
76 cmap[chr(x)] = "~%02x" % x
77 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
77 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
78 cmap[chr(x)] = e + chr(x).lower()
78 cmap[chr(x)] = e + chr(x).lower()
79 dmap = {}
79 dmap = {}
80 for k, v in cmap.iteritems():
80 for k, v in cmap.iteritems():
81 dmap[v] = k
81 dmap[v] = k
82 def decode(s):
82 def decode(s):
83 i = 0
83 i = 0
84 while i < len(s):
84 while i < len(s):
85 for l in xrange(1, 4):
85 for l in xrange(1, 4):
86 try:
86 try:
87 yield dmap[s[i:i + l]]
87 yield dmap[s[i:i + l]]
88 i += l
88 i += l
89 break
89 break
90 except KeyError:
90 except KeyError:
91 pass
91 pass
92 else:
92 else:
93 raise KeyError
93 raise KeyError
94 return (lambda s: "".join([cmap[c] for c in encodedir(s)]),
94 return (lambda s: "".join([cmap[c] for c in encodedir(s)]),
95 lambda s: decodedir("".join(list(decode(s)))))
95 lambda s: decodedir("".join(list(decode(s)))))
96
96
97 encodefilename, decodefilename = _buildencodefun()
97 encodefilename, decodefilename = _buildencodefun()
98
98
99 def _buildlowerencodefun():
99 def _buildlowerencodefun():
100 '''
100 '''
101 >>> f = _buildlowerencodefun()
101 >>> f = _buildlowerencodefun()
102 >>> f('nothing/special.txt')
102 >>> f('nothing/special.txt')
103 'nothing/special.txt'
103 'nothing/special.txt'
104 >>> f('HELLO')
104 >>> f('HELLO')
105 'hello'
105 'hello'
106 >>> f('hello:world?')
106 >>> f('hello:world?')
107 'hello~3aworld~3f'
107 'hello~3aworld~3f'
108 >>> f('the\x07quick\xADshot')
108 >>> f('the\x07quick\xADshot')
109 'the~07quick~adshot'
109 'the~07quick~adshot'
110 '''
110 '''
111 winreserved = [ord(x) for x in '\\:*?"<>|']
111 winreserved = [ord(x) for x in '\\:*?"<>|']
112 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
112 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
113 for x in (range(32) + range(126, 256) + winreserved):
113 for x in (range(32) + range(126, 256) + winreserved):
114 cmap[chr(x)] = "~%02x" % x
114 cmap[chr(x)] = "~%02x" % x
115 for x in range(ord("A"), ord("Z")+1):
115 for x in range(ord("A"), ord("Z")+1):
116 cmap[chr(x)] = chr(x).lower()
116 cmap[chr(x)] = chr(x).lower()
117 return lambda s: "".join([cmap[c] for c in s])
117 return lambda s: "".join([cmap[c] for c in s])
118
118
119 lowerencode = _buildlowerencodefun()
119 lowerencode = _buildlowerencodefun()
120
120
121 _winreservednames = '''con prn aux nul
121 _winreservednames = '''con prn aux nul
122 com1 com2 com3 com4 com5 com6 com7 com8 com9
122 com1 com2 com3 com4 com5 com6 com7 com8 com9
123 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
123 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
124 def _auxencode(path, dotencode):
124 def _auxencode(path, dotencode):
125 '''
125 '''
126 Encodes filenames containing names reserved by Windows or which end in
126 Encodes filenames containing names reserved by Windows or which end in
127 period or space. Does not touch other single reserved characters c.
127 period or space. Does not touch other single reserved characters c.
128 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
128 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
129 Additionally encodes space or period at the beginning, if dotencode is
129 Additionally encodes space or period at the beginning, if dotencode is
130 True.
130 True.
131 path is assumed to be all lowercase.
131 path is assumed to be all lowercase.
132
132
133 >>> _auxencode('.foo/aux.txt/txt.aux/con/prn/nul/foo.', True)
133 >>> _auxencode('.foo/aux.txt/txt.aux/con/prn/nul/foo.', True)
134 '~2efoo/au~78.txt/txt.aux/co~6e/pr~6e/nu~6c/foo~2e'
134 '~2efoo/au~78.txt/txt.aux/co~6e/pr~6e/nu~6c/foo~2e'
135 >>> _auxencode('.com1com2/lpt9.lpt4.lpt1/conprn/foo.', False)
135 >>> _auxencode('.com1com2/lpt9.lpt4.lpt1/conprn/foo.', False)
136 '.com1com2/lp~749.lpt4.lpt1/conprn/foo~2e'
136 '.com1com2/lp~749.lpt4.lpt1/conprn/foo~2e'
137 >>> _auxencode('foo. ', True)
137 >>> _auxencode('foo. ', True)
138 'foo.~20'
138 'foo.~20'
139 >>> _auxencode(' .foo', True)
139 >>> _auxencode(' .foo', True)
140 '~20.foo'
140 '~20.foo'
141 '''
141 '''
142 res = []
142 res = []
143 for n in path.split('/'):
143 for n in path.split('/'):
144 if n:
144 if n:
145 base = n.split('.')[0]
145 base = n.split('.')[0]
146 if base and (base in _winreservednames):
146 if base and (base in _winreservednames):
147 # encode third letter ('aux' -> 'au~78')
147 # encode third letter ('aux' -> 'au~78')
148 ec = "~%02x" % ord(n[2])
148 ec = "~%02x" % ord(n[2])
149 n = n[0:2] + ec + n[3:]
149 n = n[0:2] + ec + n[3:]
150 if n[-1] in '. ':
150 if n[-1] in '. ':
151 # encode last period or space ('foo...' -> 'foo..~2e')
151 # encode last period or space ('foo...' -> 'foo..~2e')
152 n = n[:-1] + "~%02x" % ord(n[-1])
152 n = n[:-1] + "~%02x" % ord(n[-1])
153 if dotencode and n[0] in '. ':
153 if dotencode and n[0] in '. ':
154 n = "~%02x" % ord(n[0]) + n[1:]
154 n = "~%02x" % ord(n[0]) + n[1:]
155 res.append(n)
155 res.append(n)
156 return '/'.join(res)
156 return '/'.join(res)
157
157
158 _maxstorepathlen = 120
158 _maxstorepathlen = 120
159 _dirprefixlen = 8
159 _dirprefixlen = 8
160 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
160 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
161 def _hybridencode(path, auxencode):
161 def _hybridencode(path, auxencode):
162 '''encodes path with a length limit
162 '''encodes path with a length limit
163
163
164 Encodes all paths that begin with 'data/', according to the following.
164 Encodes all paths that begin with 'data/', according to the following.
165
165
166 Default encoding (reversible):
166 Default encoding (reversible):
167
167
168 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
168 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
169 characters are encoded as '~xx', where xx is the two digit hex code
169 characters are encoded as '~xx', where xx is the two digit hex code
170 of the character (see encodefilename).
170 of the character (see encodefilename).
171 Relevant path components consisting of Windows reserved filenames are
171 Relevant path components consisting of Windows reserved filenames are
172 masked by encoding the third character ('aux' -> 'au~78', see auxencode).
172 masked by encoding the third character ('aux' -> 'au~78', see auxencode).
173
173
174 Hashed encoding (not reversible):
174 Hashed encoding (not reversible):
175
175
176 If the default-encoded path is longer than _maxstorepathlen, a
176 If the default-encoded path is longer than _maxstorepathlen, a
177 non-reversible hybrid hashing of the path is done instead.
177 non-reversible hybrid hashing of the path is done instead.
178 This encoding uses up to _dirprefixlen characters of all directory
178 This encoding uses up to _dirprefixlen characters of all directory
179 levels of the lowerencoded path, but not more levels than can fit into
179 levels of the lowerencoded path, but not more levels than can fit into
180 _maxshortdirslen.
180 _maxshortdirslen.
181 Then follows the filler followed by the sha digest of the full path.
181 Then follows the filler followed by the sha digest of the full path.
182 The filler is the beginning of the basename of the lowerencoded path
182 The filler is the beginning of the basename of the lowerencoded path
183 (the basename is everything after the last path separator). The filler
183 (the basename is everything after the last path separator). The filler
184 is as long as possible, filling in characters from the basename until
184 is as long as possible, filling in characters from the basename until
185 the encoded path has _maxstorepathlen characters (or all chars of the
185 the encoded path has _maxstorepathlen characters (or all chars of the
186 basename have been taken).
186 basename have been taken).
187 The extension (e.g. '.i' or '.d') is preserved.
187 The extension (e.g. '.i' or '.d') is preserved.
188
188
189 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
189 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
190 encoding was used.
190 encoding was used.
191 '''
191 '''
192 if not path.startswith('data/'):
192 if not path.startswith('data/'):
193 return path
193 return path
194 # escape directories ending with .i and .d
194 # escape directories ending with .i and .d
195 path = encodedir(path)
195 path = encodedir(path)
196 ndpath = path[len('data/'):]
196 ndpath = path[len('data/'):]
197 res = 'data/' + auxencode(encodefilename(ndpath))
197 res = 'data/' + auxencode(encodefilename(ndpath))
198 if len(res) > _maxstorepathlen:
198 if len(res) > _maxstorepathlen:
199 digest = _sha(path).hexdigest()
199 digest = _sha(path).hexdigest()
200 aep = auxencode(lowerencode(ndpath))
200 aep = auxencode(lowerencode(ndpath))
201 _root, ext = os.path.splitext(aep)
201 _root, ext = os.path.splitext(aep)
202 parts = aep.split('/')
202 parts = aep.split('/')
203 basename = parts[-1]
203 basename = parts[-1]
204 sdirs = []
204 sdirs = []
205 for p in parts[:-1]:
205 for p in parts[:-1]:
206 d = p[:_dirprefixlen]
206 d = p[:_dirprefixlen]
207 if d[-1] in '. ':
207 if d[-1] in '. ':
208 # Windows can't access dirs ending in period or space
208 # Windows can't access dirs ending in period or space
209 d = d[:-1] + '_'
209 d = d[:-1] + '_'
210 t = '/'.join(sdirs) + '/' + d
210 t = '/'.join(sdirs) + '/' + d
211 if len(t) > _maxshortdirslen:
211 if len(t) > _maxshortdirslen:
212 break
212 break
213 sdirs.append(d)
213 sdirs.append(d)
214 dirs = '/'.join(sdirs)
214 dirs = '/'.join(sdirs)
215 if len(dirs) > 0:
215 if len(dirs) > 0:
216 dirs += '/'
216 dirs += '/'
217 res = 'dh/' + dirs + digest + ext
217 res = 'dh/' + dirs + digest + ext
218 spaceleft = _maxstorepathlen - len(res)
218 spaceleft = _maxstorepathlen - len(res)
219 if spaceleft > 0:
219 if spaceleft > 0:
220 filler = basename[:spaceleft]
220 filler = basename[:spaceleft]
221 res = 'dh/' + dirs + filler + digest + ext
221 res = 'dh/' + dirs + filler + digest + ext
222 return res
222 return res
223
223
224 def _calcmode(path):
224 def _calcmode(path):
225 try:
225 try:
226 # files in .hg/ will be created using this mode
226 # files in .hg/ will be created using this mode
227 mode = os.stat(path).st_mode
227 mode = os.stat(path).st_mode
228 # avoid some useless chmods
228 # avoid some useless chmods
229 if (0777 & ~util.umask) == (0777 & mode):
229 if (0777 & ~util.umask) == (0777 & mode):
230 mode = None
230 mode = None
231 except OSError:
231 except OSError:
232 mode = None
232 mode = None
233 return mode
233 return mode
234
234
235 _data = 'data 00manifest.d 00manifest.i 00changelog.d 00changelog.i phaseroots'
235 _data = 'data 00manifest.d 00manifest.i 00changelog.d 00changelog.i phaseroots'
236
236
237 class basicstore(object):
237 class basicstore(object):
238 '''base class for local repository stores'''
238 '''base class for local repository stores'''
239 def __init__(self, path, openertype):
239 def __init__(self, path, openertype):
240 self.path = path
240 self.path = path
241 self.createmode = _calcmode(path)
241 self.createmode = _calcmode(path)
242 op = openertype(self.path)
242 op = openertype(self.path)
243 op.createmode = self.createmode
243 op.createmode = self.createmode
244 self.opener = scmutil.filteropener(op, encodedir)
244 self.opener = scmutil.filteropener(op, encodedir)
245
245
246 def join(self, f):
246 def join(self, f):
247 return self.path + '/' + encodedir(f)
247 return self.path + '/' + encodedir(f)
248
248
249 def _walk(self, relpath, recurse):
249 def _walk(self, relpath, recurse):
250 '''yields (unencoded, encoded, size)'''
250 '''yields (unencoded, encoded, size)'''
251 path = self.path
251 path = self.path
252 if relpath:
252 if relpath:
253 path += '/' + relpath
253 path += '/' + relpath
254 striplen = len(self.path) + 1
254 striplen = len(self.path) + 1
255 l = []
255 l = []
256 if os.path.isdir(path):
256 if os.path.isdir(path):
257 visit = [path]
257 visit = [path]
258 while visit:
258 while visit:
259 p = visit.pop()
259 p = visit.pop()
260 for f, kind, st in osutil.listdir(p, stat=True):
260 for f, kind, st in osutil.listdir(p, stat=True):
261 fp = p + '/' + f
261 fp = p + '/' + f
262 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
262 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
263 n = util.pconvert(fp[striplen:])
263 n = util.pconvert(fp[striplen:])
264 l.append((decodedir(n), n, st.st_size))
264 l.append((decodedir(n), n, st.st_size))
265 elif kind == stat.S_IFDIR and recurse:
265 elif kind == stat.S_IFDIR and recurse:
266 visit.append(fp)
266 visit.append(fp)
267 l.sort()
267 l.sort()
268 return l
268 return l
269
269
270 def datafiles(self):
270 def datafiles(self):
271 return self._walk('data', True)
271 return self._walk('data', True)
272
272
273 def walk(self):
273 def walk(self):
274 '''yields (unencoded, encoded, size)'''
274 '''yields (unencoded, encoded, size)'''
275 # yield data files first
275 # yield data files first
276 for x in self.datafiles():
276 for x in self.datafiles():
277 yield x
277 yield x
278 # yield manifest before changelog
278 # yield manifest before changelog
279 for x in reversed(self._walk('', False)):
279 for x in reversed(self._walk('', False)):
280 yield x
280 yield x
281
281
282 def copylist(self):
282 def copylist(self):
283 return ['requires'] + _data.split()
283 return ['requires'] + _data.split()
284
284
285 def write(self):
285 def write(self):
286 pass
286 pass
287
287
288 class encodedstore(basicstore):
288 class encodedstore(basicstore):
289 def __init__(self, path, openertype):
289 def __init__(self, path, openertype):
290 self.path = path + '/store'
290 self.path = path + '/store'
291 self.createmode = _calcmode(self.path)
291 self.createmode = _calcmode(self.path)
292 op = openertype(self.path)
292 op = openertype(self.path)
293 op.createmode = self.createmode
293 op.createmode = self.createmode
294 self.opener = scmutil.filteropener(op, encodefilename)
294 self.opener = scmutil.filteropener(op, encodefilename)
295
295
296 def datafiles(self):
296 def datafiles(self):
297 for a, b, size in self._walk('data', True):
297 for a, b, size in self._walk('data', True):
298 try:
298 try:
299 a = decodefilename(a)
299 a = decodefilename(a)
300 except KeyError:
300 except KeyError:
301 a = None
301 a = None
302 yield a, b, size
302 yield a, b, size
303
303
304 def join(self, f):
304 def join(self, f):
305 return self.path + '/' + encodefilename(f)
305 return self.path + '/' + encodefilename(f)
306
306
307 def copylist(self):
307 def copylist(self):
308 return (['requires', '00changelog.i'] +
308 return (['requires', '00changelog.i'] +
309 ['store/' + f for f in _data.split()])
309 ['store/' + f for f in _data.split()])
310
310
311 class fncache(object):
311 class fncache(object):
312 # the filename used to be partially encoded
312 # the filename used to be partially encoded
313 # hence the encodedir/decodedir dance
313 # hence the encodedir/decodedir dance
314 def __init__(self, opener):
314 def __init__(self, opener):
315 self.opener = opener
315 self.opener = opener
316 self.entries = None
316 self.entries = None
317 self._dirty = False
317 self._dirty = False
318
318
319 def _load(self):
319 def _load(self):
320 '''fill the entries from the fncache file'''
320 '''fill the entries from the fncache file'''
321 self._dirty = False
321 self._dirty = False
322 try:
322 try:
323 fp = self.opener('fncache', mode='rb')
323 fp = self.opener('fncache', mode='rb')
324 except IOError:
324 except IOError:
325 # skip nonexistent file
325 # skip nonexistent file
326 self.entries = set()
326 self.entries = set()
327 return
327 return
328 self.entries = set(map(decodedir, fp.read().splitlines()))
328 self.entries = set(map(decodedir, fp.read().splitlines()))
329 if '' in self.entries:
329 if '' in self.entries:
330 fp.seek(0)
330 fp.seek(0)
331 for n, line in enumerate(fp):
331 for n, line in enumerate(fp):
332 if not line.rstrip('\n'):
332 if not line.rstrip('\n'):
333 t = _('invalid entry in fncache, line %s') % (n + 1)
333 t = _('invalid entry in fncache, line %s') % (n + 1)
334 raise util.Abort(t)
334 raise util.Abort(t)
335 fp.close()
335 fp.close()
336
336
337 def _write(self, files, atomictemp):
337 def _write(self, files, atomictemp):
338 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
338 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
339 if files:
339 if files:
340 fp.write('\n'.join(map(encodedir, files)) + '\n')
340 fp.write('\n'.join(map(encodedir, files)) + '\n')
341 fp.close()
341 fp.close()
342 self._dirty = False
342 self._dirty = False
343
343
344 def rewrite(self, files):
344 def rewrite(self, files):
345 self._write(files, False)
345 self._write(files, False)
346 self.entries = set(files)
346 self.entries = set(files)
347
347
348 def write(self):
348 def write(self):
349 if self._dirty:
349 if self._dirty:
350 self._write(self.entries, True)
350 self._write(self.entries, True)
351
351
352 def add(self, fn):
352 def add(self, fn):
353 if self.entries is None:
353 if self.entries is None:
354 self._load()
354 self._load()
355 if fn not in self.entries:
355 if fn not in self.entries:
356 self._dirty = True
356 self._dirty = True
357 self.entries.add(fn)
357 self.entries.add(fn)
358
358
359 def __contains__(self, fn):
359 def __contains__(self, fn):
360 if self.entries is None:
360 if self.entries is None:
361 self._load()
361 self._load()
362 return fn in self.entries
362 return fn in self.entries
363
363
364 def __iter__(self):
364 def __iter__(self):
365 if self.entries is None:
365 if self.entries is None:
366 self._load()
366 self._load()
367 return iter(self.entries)
367 return iter(self.entries)
368
368
369 class _fncacheopener(scmutil.abstractopener):
369 class _fncacheopener(scmutil.abstractopener):
370 def __init__(self, op, fnc, encode):
370 def __init__(self, op, fnc, encode):
371 self.opener = op
371 self.opener = op
372 self.fncache = fnc
372 self.fncache = fnc
373 self.encode = encode
373 self.encode = encode
374
374
375 def __call__(self, path, mode='r', *args, **kw):
375 def __call__(self, path, mode='r', *args, **kw):
376 if mode not in ('r', 'rb') and path.startswith('data/'):
376 if mode not in ('r', 'rb') and path.startswith('data/'):
377 self.fncache.add(path)
377 self.fncache.add(path)
378 return self.opener(self.encode(path), mode, *args, **kw)
378 return self.opener(self.encode(path), mode, *args, **kw)
379
379
380 class fncachestore(basicstore):
380 class fncachestore(basicstore):
381 def __init__(self, path, openertype, encode):
381 def __init__(self, path, openertype, encode):
382 self.encode = encode
382 self.encode = encode
383 self.path = path + '/store'
383 self.path = path + '/store'
384 self.createmode = _calcmode(self.path)
384 self.createmode = _calcmode(self.path)
385 op = openertype(self.path)
385 op = openertype(self.path)
386 op.createmode = self.createmode
386 op.createmode = self.createmode
387 fnc = fncache(op)
387 fnc = fncache(op)
388 self.fncache = fnc
388 self.fncache = fnc
389 self.opener = _fncacheopener(op, fnc, encode)
389 self.opener = _fncacheopener(op, fnc, encode)
390
390
391 def join(self, f):
391 def join(self, f):
392 return self.path + '/' + self.encode(f)
392 return self.path + '/' + self.encode(f)
393
393
394 def getsize(self, path):
395 return os.stat(self.path + '/' + path).st_size
396
394 def datafiles(self):
397 def datafiles(self):
395 rewrite = False
398 rewrite = False
396 existing = []
399 existing = []
397 spath = self.path
398 for f in self.fncache:
400 for f in self.fncache:
399 ef = self.encode(f)
401 ef = self.encode(f)
400 try:
402 try:
401 st = os.stat(spath + '/' + ef)
403 yield f, ef, self.getsize(ef)
402 yield f, ef, st.st_size
403 existing.append(f)
404 existing.append(f)
404 except OSError:
405 except OSError:
405 # nonexistent entry
406 # nonexistent entry
406 rewrite = True
407 rewrite = True
407 if rewrite:
408 if rewrite:
408 # rewrite fncache to remove nonexistent entries
409 # rewrite fncache to remove nonexistent entries
409 # (may be caused by rollback / strip)
410 # (may be caused by rollback / strip)
410 self.fncache.rewrite(existing)
411 self.fncache.rewrite(existing)
411
412
412 def copylist(self):
413 def copylist(self):
413 d = ('data dh fncache phaseroots'
414 d = ('data dh fncache phaseroots'
414 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
415 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
415 return (['requires', '00changelog.i'] +
416 return (['requires', '00changelog.i'] +
416 ['store/' + f for f in d.split()])
417 ['store/' + f for f in d.split()])
417
418
418 def write(self):
419 def write(self):
419 self.fncache.write()
420 self.fncache.write()
420
421
421 def store(requirements, path, openertype):
422 def store(requirements, path, openertype):
422 if 'store' in requirements:
423 if 'store' in requirements:
423 if 'fncache' in requirements:
424 if 'fncache' in requirements:
424 auxencode = lambda f: _auxencode(f, 'dotencode' in requirements)
425 auxencode = lambda f: _auxencode(f, 'dotencode' in requirements)
425 encode = lambda f: _hybridencode(f, auxencode)
426 encode = lambda f: _hybridencode(f, auxencode)
426 return fncachestore(path, openertype, encode)
427 return fncachestore(path, openertype, encode)
427 return encodedstore(path, openertype)
428 return encodedstore(path, openertype)
428 return basicstore(path, openertype)
429 return basicstore(path, openertype)
General Comments 0
You need to be logged in to leave comments. Login now