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