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