##// END OF EJS Templates
store: optimze _auxencode() a bit by grouping the reserved names by length...
Adrian Buehlmann -
r17570:f53a7b25 default
parent child Browse files
Show More
@@ -1,443 +1,448 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 i18n import _
8 from i18n import _
9 import osutil, scmutil, util
9 import osutil, scmutil, util
10 import os, stat, errno
10 import os, stat, errno
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 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
122 com1 com2 com3 com4 com5 com6 com7 com8 com9
122 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
123 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
123 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
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. Parameter path is assumed to be all lowercase.
130 True. Parameter path is assumed to be all lowercase.
131 A segment only needs encoding if a reserved name appears as a
131 A segment only needs encoding if a reserved name appears as a
132 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
132 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
133 doesn't need encoding.
133 doesn't need encoding.
134
134
135 >>> _auxencode('.foo/aux.txt/txt.aux/con/prn/nul/foo.', True)
135 >>> _auxencode('.foo/aux.txt/txt.aux/con/prn/nul/foo.', True)
136 '~2efoo/au~78.txt/txt.aux/co~6e/pr~6e/nu~6c/foo~2e'
136 '~2efoo/au~78.txt/txt.aux/co~6e/pr~6e/nu~6c/foo~2e'
137 >>> _auxencode('.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.', False)
137 >>> _auxencode('.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.', False)
138 '.com1com2/lp~749.lpt4.lpt1/conprn/com0/lpt0/foo~2e'
138 '.com1com2/lp~749.lpt4.lpt1/conprn/com0/lpt0/foo~2e'
139 >>> _auxencode('foo. ', True)
139 >>> _auxencode('foo. ', True)
140 'foo.~20'
140 'foo.~20'
141 >>> _auxencode(' .foo', True)
141 >>> _auxencode(' .foo', True)
142 '~20.foo'
142 '~20.foo'
143 '''
143 '''
144 res = []
144 res = []
145 for n in path.split('/'):
145 for n in path.split('/'):
146 if n:
146 if n:
147 base = n.split('.')[0]
147 if dotencode and n[0] in '. ':
148 if base and (base in _winreservednames):
148 n = "~%02x" % ord(n[0]) + n[1:]
149 else:
150 l = n.find('.')
151 if l == -1:
152 l = len(n)
153 if ((l == 3 and n[:3] in _winres3) or
154 (l == 4 and n[3] <= '9' and n[3] >= '1'
155 and n[:3] in _winres4)):
149 # encode third letter ('aux' -> 'au~78')
156 # encode third letter ('aux' -> 'au~78')
150 ec = "~%02x" % ord(n[2])
157 ec = "~%02x" % ord(n[2])
151 n = n[0:2] + ec + n[3:]
158 n = n[0:2] + ec + n[3:]
152 if n[-1] in '. ':
159 if n[-1] in '. ':
153 # encode last period or space ('foo...' -> 'foo..~2e')
160 # encode last period or space ('foo...' -> 'foo..~2e')
154 n = n[:-1] + "~%02x" % ord(n[-1])
161 n = n[:-1] + "~%02x" % ord(n[-1])
155 if dotencode and n[0] in '. ':
156 n = "~%02x" % ord(n[0]) + n[1:]
157 res.append(n)
162 res.append(n)
158 return '/'.join(res)
163 return '/'.join(res)
159
164
160 _maxstorepathlen = 120
165 _maxstorepathlen = 120
161 _dirprefixlen = 8
166 _dirprefixlen = 8
162 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
167 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
163 def _hybridencode(path, auxencode):
168 def _hybridencode(path, auxencode):
164 '''encodes path with a length limit
169 '''encodes path with a length limit
165
170
166 Encodes all paths that begin with 'data/', according to the following.
171 Encodes all paths that begin with 'data/', according to the following.
167
172
168 Default encoding (reversible):
173 Default encoding (reversible):
169
174
170 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
175 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
171 characters are encoded as '~xx', where xx is the two digit hex code
176 characters are encoded as '~xx', where xx is the two digit hex code
172 of the character (see encodefilename).
177 of the character (see encodefilename).
173 Relevant path components consisting of Windows reserved filenames are
178 Relevant path components consisting of Windows reserved filenames are
174 masked by encoding the third character ('aux' -> 'au~78', see auxencode).
179 masked by encoding the third character ('aux' -> 'au~78', see auxencode).
175
180
176 Hashed encoding (not reversible):
181 Hashed encoding (not reversible):
177
182
178 If the default-encoded path is longer than _maxstorepathlen, a
183 If the default-encoded path is longer than _maxstorepathlen, a
179 non-reversible hybrid hashing of the path is done instead.
184 non-reversible hybrid hashing of the path is done instead.
180 This encoding uses up to _dirprefixlen characters of all directory
185 This encoding uses up to _dirprefixlen characters of all directory
181 levels of the lowerencoded path, but not more levels than can fit into
186 levels of the lowerencoded path, but not more levels than can fit into
182 _maxshortdirslen.
187 _maxshortdirslen.
183 Then follows the filler followed by the sha digest of the full path.
188 Then follows the filler followed by the sha digest of the full path.
184 The filler is the beginning of the basename of the lowerencoded path
189 The filler is the beginning of the basename of the lowerencoded path
185 (the basename is everything after the last path separator). The filler
190 (the basename is everything after the last path separator). The filler
186 is as long as possible, filling in characters from the basename until
191 is as long as possible, filling in characters from the basename until
187 the encoded path has _maxstorepathlen characters (or all chars of the
192 the encoded path has _maxstorepathlen characters (or all chars of the
188 basename have been taken).
193 basename have been taken).
189 The extension (e.g. '.i' or '.d') is preserved.
194 The extension (e.g. '.i' or '.d') is preserved.
190
195
191 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
196 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
192 encoding was used.
197 encoding was used.
193 '''
198 '''
194 if not path.startswith('data/'):
199 if not path.startswith('data/'):
195 return path
200 return path
196 # escape directories ending with .i and .d
201 # escape directories ending with .i and .d
197 path = encodedir(path)
202 path = encodedir(path)
198 ndpath = path[len('data/'):]
203 ndpath = path[len('data/'):]
199 res = 'data/' + auxencode(encodefilename(ndpath))
204 res = 'data/' + auxencode(encodefilename(ndpath))
200 if len(res) > _maxstorepathlen:
205 if len(res) > _maxstorepathlen:
201 digest = _sha(path).hexdigest()
206 digest = _sha(path).hexdigest()
202 aep = auxencode(lowerencode(ndpath))
207 aep = auxencode(lowerencode(ndpath))
203 _root, ext = os.path.splitext(aep)
208 _root, ext = os.path.splitext(aep)
204 parts = aep.split('/')
209 parts = aep.split('/')
205 basename = parts[-1]
210 basename = parts[-1]
206 sdirs = []
211 sdirs = []
207 for p in parts[:-1]:
212 for p in parts[:-1]:
208 d = p[:_dirprefixlen]
213 d = p[:_dirprefixlen]
209 if d[-1] in '. ':
214 if d[-1] in '. ':
210 # Windows can't access dirs ending in period or space
215 # Windows can't access dirs ending in period or space
211 d = d[:-1] + '_'
216 d = d[:-1] + '_'
212 t = '/'.join(sdirs) + '/' + d
217 t = '/'.join(sdirs) + '/' + d
213 if len(t) > _maxshortdirslen:
218 if len(t) > _maxshortdirslen:
214 break
219 break
215 sdirs.append(d)
220 sdirs.append(d)
216 dirs = '/'.join(sdirs)
221 dirs = '/'.join(sdirs)
217 if len(dirs) > 0:
222 if len(dirs) > 0:
218 dirs += '/'
223 dirs += '/'
219 res = 'dh/' + dirs + digest + ext
224 res = 'dh/' + dirs + digest + ext
220 spaceleft = _maxstorepathlen - len(res)
225 spaceleft = _maxstorepathlen - len(res)
221 if spaceleft > 0:
226 if spaceleft > 0:
222 filler = basename[:spaceleft]
227 filler = basename[:spaceleft]
223 res = 'dh/' + dirs + filler + digest + ext
228 res = 'dh/' + dirs + filler + digest + ext
224 return res
229 return res
225
230
226 def _calcmode(path):
231 def _calcmode(path):
227 try:
232 try:
228 # files in .hg/ will be created using this mode
233 # files in .hg/ will be created using this mode
229 mode = os.stat(path).st_mode
234 mode = os.stat(path).st_mode
230 # avoid some useless chmods
235 # avoid some useless chmods
231 if (0777 & ~util.umask) == (0777 & mode):
236 if (0777 & ~util.umask) == (0777 & mode):
232 mode = None
237 mode = None
233 except OSError:
238 except OSError:
234 mode = None
239 mode = None
235 return mode
240 return mode
236
241
237 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
242 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
238 ' phaseroots obsstore')
243 ' phaseroots obsstore')
239
244
240 class basicstore(object):
245 class basicstore(object):
241 '''base class for local repository stores'''
246 '''base class for local repository stores'''
242 def __init__(self, path, openertype):
247 def __init__(self, path, openertype):
243 self.path = path
248 self.path = path
244 self.createmode = _calcmode(path)
249 self.createmode = _calcmode(path)
245 op = openertype(self.path)
250 op = openertype(self.path)
246 op.createmode = self.createmode
251 op.createmode = self.createmode
247 self.opener = scmutil.filteropener(op, encodedir)
252 self.opener = scmutil.filteropener(op, encodedir)
248
253
249 def join(self, f):
254 def join(self, f):
250 return self.path + '/' + encodedir(f)
255 return self.path + '/' + encodedir(f)
251
256
252 def _walk(self, relpath, recurse):
257 def _walk(self, relpath, recurse):
253 '''yields (unencoded, encoded, size)'''
258 '''yields (unencoded, encoded, size)'''
254 path = self.path
259 path = self.path
255 if relpath:
260 if relpath:
256 path += '/' + relpath
261 path += '/' + relpath
257 striplen = len(self.path) + 1
262 striplen = len(self.path) + 1
258 l = []
263 l = []
259 if os.path.isdir(path):
264 if os.path.isdir(path):
260 visit = [path]
265 visit = [path]
261 while visit:
266 while visit:
262 p = visit.pop()
267 p = visit.pop()
263 for f, kind, st in osutil.listdir(p, stat=True):
268 for f, kind, st in osutil.listdir(p, stat=True):
264 fp = p + '/' + f
269 fp = p + '/' + f
265 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
270 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
266 n = util.pconvert(fp[striplen:])
271 n = util.pconvert(fp[striplen:])
267 l.append((decodedir(n), n, st.st_size))
272 l.append((decodedir(n), n, st.st_size))
268 elif kind == stat.S_IFDIR and recurse:
273 elif kind == stat.S_IFDIR and recurse:
269 visit.append(fp)
274 visit.append(fp)
270 l.sort()
275 l.sort()
271 return l
276 return l
272
277
273 def datafiles(self):
278 def datafiles(self):
274 return self._walk('data', True)
279 return self._walk('data', True)
275
280
276 def walk(self):
281 def walk(self):
277 '''yields (unencoded, encoded, size)'''
282 '''yields (unencoded, encoded, size)'''
278 # yield data files first
283 # yield data files first
279 for x in self.datafiles():
284 for x in self.datafiles():
280 yield x
285 yield x
281 # yield manifest before changelog
286 # yield manifest before changelog
282 for x in reversed(self._walk('', False)):
287 for x in reversed(self._walk('', False)):
283 yield x
288 yield x
284
289
285 def copylist(self):
290 def copylist(self):
286 return ['requires'] + _data.split()
291 return ['requires'] + _data.split()
287
292
288 def write(self):
293 def write(self):
289 pass
294 pass
290
295
291 class encodedstore(basicstore):
296 class encodedstore(basicstore):
292 def __init__(self, path, openertype):
297 def __init__(self, path, openertype):
293 self.path = path + '/store'
298 self.path = path + '/store'
294 self.createmode = _calcmode(self.path)
299 self.createmode = _calcmode(self.path)
295 op = openertype(self.path)
300 op = openertype(self.path)
296 op.createmode = self.createmode
301 op.createmode = self.createmode
297 self.opener = scmutil.filteropener(op, encodefilename)
302 self.opener = scmutil.filteropener(op, encodefilename)
298
303
299 def datafiles(self):
304 def datafiles(self):
300 for a, b, size in self._walk('data', True):
305 for a, b, size in self._walk('data', True):
301 try:
306 try:
302 a = decodefilename(a)
307 a = decodefilename(a)
303 except KeyError:
308 except KeyError:
304 a = None
309 a = None
305 yield a, b, size
310 yield a, b, size
306
311
307 def join(self, f):
312 def join(self, f):
308 return self.path + '/' + encodefilename(f)
313 return self.path + '/' + encodefilename(f)
309
314
310 def copylist(self):
315 def copylist(self):
311 return (['requires', '00changelog.i'] +
316 return (['requires', '00changelog.i'] +
312 ['store/' + f for f in _data.split()])
317 ['store/' + f for f in _data.split()])
313
318
314 class fncache(object):
319 class fncache(object):
315 # the filename used to be partially encoded
320 # the filename used to be partially encoded
316 # hence the encodedir/decodedir dance
321 # hence the encodedir/decodedir dance
317 def __init__(self, opener):
322 def __init__(self, opener):
318 self.opener = opener
323 self.opener = opener
319 self.entries = None
324 self.entries = None
320 self._dirty = False
325 self._dirty = False
321
326
322 def _load(self):
327 def _load(self):
323 '''fill the entries from the fncache file'''
328 '''fill the entries from the fncache file'''
324 self._dirty = False
329 self._dirty = False
325 try:
330 try:
326 fp = self.opener('fncache', mode='rb')
331 fp = self.opener('fncache', mode='rb')
327 except IOError:
332 except IOError:
328 # skip nonexistent file
333 # skip nonexistent file
329 self.entries = set()
334 self.entries = set()
330 return
335 return
331 self.entries = set(map(decodedir, fp.read().splitlines()))
336 self.entries = set(map(decodedir, fp.read().splitlines()))
332 if '' in self.entries:
337 if '' in self.entries:
333 fp.seek(0)
338 fp.seek(0)
334 for n, line in enumerate(fp):
339 for n, line in enumerate(fp):
335 if not line.rstrip('\n'):
340 if not line.rstrip('\n'):
336 t = _('invalid entry in fncache, line %s') % (n + 1)
341 t = _('invalid entry in fncache, line %s') % (n + 1)
337 raise util.Abort(t)
342 raise util.Abort(t)
338 fp.close()
343 fp.close()
339
344
340 def _write(self, files, atomictemp):
345 def _write(self, files, atomictemp):
341 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
346 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
342 if files:
347 if files:
343 fp.write('\n'.join(map(encodedir, files)) + '\n')
348 fp.write('\n'.join(map(encodedir, files)) + '\n')
344 fp.close()
349 fp.close()
345 self._dirty = False
350 self._dirty = False
346
351
347 def rewrite(self, files):
352 def rewrite(self, files):
348 self._write(files, False)
353 self._write(files, False)
349 self.entries = set(files)
354 self.entries = set(files)
350
355
351 def write(self):
356 def write(self):
352 if self._dirty:
357 if self._dirty:
353 self._write(self.entries, True)
358 self._write(self.entries, True)
354
359
355 def add(self, fn):
360 def add(self, fn):
356 if self.entries is None:
361 if self.entries is None:
357 self._load()
362 self._load()
358 if fn not in self.entries:
363 if fn not in self.entries:
359 self._dirty = True
364 self._dirty = True
360 self.entries.add(fn)
365 self.entries.add(fn)
361
366
362 def __contains__(self, fn):
367 def __contains__(self, fn):
363 if self.entries is None:
368 if self.entries is None:
364 self._load()
369 self._load()
365 return fn in self.entries
370 return fn in self.entries
366
371
367 def __iter__(self):
372 def __iter__(self):
368 if self.entries is None:
373 if self.entries is None:
369 self._load()
374 self._load()
370 return iter(self.entries)
375 return iter(self.entries)
371
376
372 class _fncacheopener(scmutil.abstractopener):
377 class _fncacheopener(scmutil.abstractopener):
373 def __init__(self, op, fnc, encode):
378 def __init__(self, op, fnc, encode):
374 self.opener = op
379 self.opener = op
375 self.fncache = fnc
380 self.fncache = fnc
376 self.encode = encode
381 self.encode = encode
377
382
378 def _getmustaudit(self):
383 def _getmustaudit(self):
379 return self.opener.mustaudit
384 return self.opener.mustaudit
380
385
381 def _setmustaudit(self, onoff):
386 def _setmustaudit(self, onoff):
382 self.opener.mustaudit = onoff
387 self.opener.mustaudit = onoff
383
388
384 mustaudit = property(_getmustaudit, _setmustaudit)
389 mustaudit = property(_getmustaudit, _setmustaudit)
385
390
386 def __call__(self, path, mode='r', *args, **kw):
391 def __call__(self, path, mode='r', *args, **kw):
387 if mode not in ('r', 'rb') and path.startswith('data/'):
392 if mode not in ('r', 'rb') and path.startswith('data/'):
388 self.fncache.add(path)
393 self.fncache.add(path)
389 return self.opener(self.encode(path), mode, *args, **kw)
394 return self.opener(self.encode(path), mode, *args, **kw)
390
395
391 class fncachestore(basicstore):
396 class fncachestore(basicstore):
392 def __init__(self, path, openertype, encode):
397 def __init__(self, path, openertype, encode):
393 self.encode = encode
398 self.encode = encode
394 self.path = path + '/store'
399 self.path = path + '/store'
395 self.pathsep = self.path + '/'
400 self.pathsep = self.path + '/'
396 self.createmode = _calcmode(self.path)
401 self.createmode = _calcmode(self.path)
397 op = openertype(self.path)
402 op = openertype(self.path)
398 op.createmode = self.createmode
403 op.createmode = self.createmode
399 fnc = fncache(op)
404 fnc = fncache(op)
400 self.fncache = fnc
405 self.fncache = fnc
401 self.opener = _fncacheopener(op, fnc, encode)
406 self.opener = _fncacheopener(op, fnc, encode)
402
407
403 def join(self, f):
408 def join(self, f):
404 return self.pathsep + self.encode(f)
409 return self.pathsep + self.encode(f)
405
410
406 def getsize(self, path):
411 def getsize(self, path):
407 return os.stat(self.pathsep + path).st_size
412 return os.stat(self.pathsep + path).st_size
408
413
409 def datafiles(self):
414 def datafiles(self):
410 rewrite = False
415 rewrite = False
411 existing = []
416 existing = []
412 for f in sorted(self.fncache):
417 for f in sorted(self.fncache):
413 ef = self.encode(f)
418 ef = self.encode(f)
414 try:
419 try:
415 yield f, ef, self.getsize(ef)
420 yield f, ef, self.getsize(ef)
416 existing.append(f)
421 existing.append(f)
417 except OSError, err:
422 except OSError, err:
418 if err.errno != errno.ENOENT:
423 if err.errno != errno.ENOENT:
419 raise
424 raise
420 # nonexistent entry
425 # nonexistent entry
421 rewrite = True
426 rewrite = True
422 if rewrite:
427 if rewrite:
423 # rewrite fncache to remove nonexistent entries
428 # rewrite fncache to remove nonexistent entries
424 # (may be caused by rollback / strip)
429 # (may be caused by rollback / strip)
425 self.fncache.rewrite(existing)
430 self.fncache.rewrite(existing)
426
431
427 def copylist(self):
432 def copylist(self):
428 d = ('data dh fncache phaseroots obsstore'
433 d = ('data dh fncache phaseroots obsstore'
429 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
434 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
430 return (['requires', '00changelog.i'] +
435 return (['requires', '00changelog.i'] +
431 ['store/' + f for f in d.split()])
436 ['store/' + f for f in d.split()])
432
437
433 def write(self):
438 def write(self):
434 self.fncache.write()
439 self.fncache.write()
435
440
436 def store(requirements, path, openertype):
441 def store(requirements, path, openertype):
437 if 'store' in requirements:
442 if 'store' in requirements:
438 if 'fncache' in requirements:
443 if 'fncache' in requirements:
439 auxencode = lambda f: _auxencode(f, 'dotencode' in requirements)
444 auxencode = lambda f: _auxencode(f, 'dotencode' in requirements)
440 encode = lambda f: _hybridencode(f, auxencode)
445 encode = lambda f: _hybridencode(f, auxencode)
441 return fncachestore(path, openertype, encode)
446 return fncachestore(path, openertype, encode)
442 return encodedstore(path, openertype)
447 return encodedstore(path, openertype)
443 return basicstore(path, openertype)
448 return basicstore(path, openertype)
General Comments 0
You need to be logged in to leave comments. Login now