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