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