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