##// END OF EJS Templates
store: remove uneeded startswith('data/') check in _hybridencode()
Adrian Buehlmann -
r17585:8ed2783f default
parent child Browse files
Show More
@@ -1,447 +1,445 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 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/'):
202 return path
203 res = '/'.join(auxencode(encodefilename(path)))
201 res = '/'.join(auxencode(encodefilename(path)))
204 if len(res) > _maxstorepathlen:
202 if len(res) > _maxstorepathlen:
205 path = encodedir(path)
203 path = encodedir(path)
206 digest = _sha(path).hexdigest()
204 digest = _sha(path).hexdigest()
207 parts = auxencode(lowerencode(path))[1:]
205 parts = auxencode(lowerencode(path))[1:]
208 _root, ext = os.path.splitext(parts[-1])
206 _root, ext = os.path.splitext(parts[-1])
209 basename = parts[-1]
207 basename = parts[-1]
210 sdirs = []
208 sdirs = []
211 for p in parts[:-1]:
209 for p in parts[:-1]:
212 d = p[:_dirprefixlen]
210 d = p[:_dirprefixlen]
213 if d[-1] in '. ':
211 if d[-1] in '. ':
214 # Windows can't access dirs ending in period or space
212 # Windows can't access dirs ending in period or space
215 d = d[:-1] + '_'
213 d = d[:-1] + '_'
216 t = '/'.join(sdirs) + '/' + d
214 t = '/'.join(sdirs) + '/' + d
217 if len(t) > _maxshortdirslen:
215 if len(t) > _maxshortdirslen:
218 break
216 break
219 sdirs.append(d)
217 sdirs.append(d)
220 dirs = '/'.join(sdirs)
218 dirs = '/'.join(sdirs)
221 if len(dirs) > 0:
219 if len(dirs) > 0:
222 dirs += '/'
220 dirs += '/'
223 res = 'dh/' + dirs + digest + ext
221 res = 'dh/' + dirs + digest + ext
224 spaceleft = _maxstorepathlen - len(res)
222 spaceleft = _maxstorepathlen - len(res)
225 if spaceleft > 0:
223 if spaceleft > 0:
226 filler = basename[:spaceleft]
224 filler = basename[:spaceleft]
227 res = 'dh/' + dirs + filler + digest + ext
225 res = 'dh/' + dirs + filler + digest + ext
228 return res
226 return res
229
227
230 def _calcmode(path):
228 def _calcmode(path):
231 try:
229 try:
232 # files in .hg/ will be created using this mode
230 # files in .hg/ will be created using this mode
233 mode = os.stat(path).st_mode
231 mode = os.stat(path).st_mode
234 # avoid some useless chmods
232 # avoid some useless chmods
235 if (0777 & ~util.umask) == (0777 & mode):
233 if (0777 & ~util.umask) == (0777 & mode):
236 mode = None
234 mode = None
237 except OSError:
235 except OSError:
238 mode = None
236 mode = None
239 return mode
237 return mode
240
238
241 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
239 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
242 ' phaseroots obsstore')
240 ' phaseroots obsstore')
243
241
244 class basicstore(object):
242 class basicstore(object):
245 '''base class for local repository stores'''
243 '''base class for local repository stores'''
246 def __init__(self, path, openertype):
244 def __init__(self, path, openertype):
247 self.path = path
245 self.path = path
248 self.createmode = _calcmode(path)
246 self.createmode = _calcmode(path)
249 op = openertype(self.path)
247 op = openertype(self.path)
250 op.createmode = self.createmode
248 op.createmode = self.createmode
251 self.opener = scmutil.filteropener(op, encodedir)
249 self.opener = scmutil.filteropener(op, encodedir)
252
250
253 def join(self, f):
251 def join(self, f):
254 return self.path + '/' + encodedir(f)
252 return self.path + '/' + encodedir(f)
255
253
256 def _walk(self, relpath, recurse):
254 def _walk(self, relpath, recurse):
257 '''yields (unencoded, encoded, size)'''
255 '''yields (unencoded, encoded, size)'''
258 path = self.path
256 path = self.path
259 if relpath:
257 if relpath:
260 path += '/' + relpath
258 path += '/' + relpath
261 striplen = len(self.path) + 1
259 striplen = len(self.path) + 1
262 l = []
260 l = []
263 if os.path.isdir(path):
261 if os.path.isdir(path):
264 visit = [path]
262 visit = [path]
265 while visit:
263 while visit:
266 p = visit.pop()
264 p = visit.pop()
267 for f, kind, st in osutil.listdir(p, stat=True):
265 for f, kind, st in osutil.listdir(p, stat=True):
268 fp = p + '/' + f
266 fp = p + '/' + f
269 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
267 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
270 n = util.pconvert(fp[striplen:])
268 n = util.pconvert(fp[striplen:])
271 l.append((decodedir(n), n, st.st_size))
269 l.append((decodedir(n), n, st.st_size))
272 elif kind == stat.S_IFDIR and recurse:
270 elif kind == stat.S_IFDIR and recurse:
273 visit.append(fp)
271 visit.append(fp)
274 l.sort()
272 l.sort()
275 return l
273 return l
276
274
277 def datafiles(self):
275 def datafiles(self):
278 return self._walk('data', True)
276 return self._walk('data', True)
279
277
280 def walk(self):
278 def walk(self):
281 '''yields (unencoded, encoded, size)'''
279 '''yields (unencoded, encoded, size)'''
282 # yield data files first
280 # yield data files first
283 for x in self.datafiles():
281 for x in self.datafiles():
284 yield x
282 yield x
285 # yield manifest before changelog
283 # yield manifest before changelog
286 for x in reversed(self._walk('', False)):
284 for x in reversed(self._walk('', False)):
287 yield x
285 yield x
288
286
289 def copylist(self):
287 def copylist(self):
290 return ['requires'] + _data.split()
288 return ['requires'] + _data.split()
291
289
292 def write(self):
290 def write(self):
293 pass
291 pass
294
292
295 class encodedstore(basicstore):
293 class encodedstore(basicstore):
296 def __init__(self, path, openertype):
294 def __init__(self, path, openertype):
297 self.path = path + '/store'
295 self.path = path + '/store'
298 self.createmode = _calcmode(self.path)
296 self.createmode = _calcmode(self.path)
299 op = openertype(self.path)
297 op = openertype(self.path)
300 op.createmode = self.createmode
298 op.createmode = self.createmode
301 self.opener = scmutil.filteropener(op, encodefilename)
299 self.opener = scmutil.filteropener(op, encodefilename)
302
300
303 def datafiles(self):
301 def datafiles(self):
304 for a, b, size in self._walk('data', True):
302 for a, b, size in self._walk('data', True):
305 try:
303 try:
306 a = decodefilename(a)
304 a = decodefilename(a)
307 except KeyError:
305 except KeyError:
308 a = None
306 a = None
309 yield a, b, size
307 yield a, b, size
310
308
311 def join(self, f):
309 def join(self, f):
312 return self.path + '/' + encodefilename(f)
310 return self.path + '/' + encodefilename(f)
313
311
314 def copylist(self):
312 def copylist(self):
315 return (['requires', '00changelog.i'] +
313 return (['requires', '00changelog.i'] +
316 ['store/' + f for f in _data.split()])
314 ['store/' + f for f in _data.split()])
317
315
318 class fncache(object):
316 class fncache(object):
319 # the filename used to be partially encoded
317 # the filename used to be partially encoded
320 # hence the encodedir/decodedir dance
318 # hence the encodedir/decodedir dance
321 def __init__(self, opener):
319 def __init__(self, opener):
322 self.opener = opener
320 self.opener = opener
323 self.entries = None
321 self.entries = None
324 self._dirty = False
322 self._dirty = False
325
323
326 def _load(self):
324 def _load(self):
327 '''fill the entries from the fncache file'''
325 '''fill the entries from the fncache file'''
328 self._dirty = False
326 self._dirty = False
329 try:
327 try:
330 fp = self.opener('fncache', mode='rb')
328 fp = self.opener('fncache', mode='rb')
331 except IOError:
329 except IOError:
332 # skip nonexistent file
330 # skip nonexistent file
333 self.entries = set()
331 self.entries = set()
334 return
332 return
335 self.entries = set(map(decodedir, fp.read().splitlines()))
333 self.entries = set(map(decodedir, fp.read().splitlines()))
336 if '' in self.entries:
334 if '' in self.entries:
337 fp.seek(0)
335 fp.seek(0)
338 for n, line in enumerate(fp):
336 for n, line in enumerate(fp):
339 if not line.rstrip('\n'):
337 if not line.rstrip('\n'):
340 t = _('invalid entry in fncache, line %s') % (n + 1)
338 t = _('invalid entry in fncache, line %s') % (n + 1)
341 raise util.Abort(t)
339 raise util.Abort(t)
342 fp.close()
340 fp.close()
343
341
344 def _write(self, files, atomictemp):
342 def _write(self, files, atomictemp):
345 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
343 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
346 if files:
344 if files:
347 fp.write('\n'.join(map(encodedir, files)) + '\n')
345 fp.write('\n'.join(map(encodedir, files)) + '\n')
348 fp.close()
346 fp.close()
349 self._dirty = False
347 self._dirty = False
350
348
351 def rewrite(self, files):
349 def rewrite(self, files):
352 self._write(files, False)
350 self._write(files, False)
353 self.entries = set(files)
351 self.entries = set(files)
354
352
355 def write(self):
353 def write(self):
356 if self._dirty:
354 if self._dirty:
357 self._write(self.entries, True)
355 self._write(self.entries, True)
358
356
359 def add(self, fn):
357 def add(self, fn):
360 if self.entries is None:
358 if self.entries is None:
361 self._load()
359 self._load()
362 if fn not in self.entries:
360 if fn not in self.entries:
363 self._dirty = True
361 self._dirty = True
364 self.entries.add(fn)
362 self.entries.add(fn)
365
363
366 def __contains__(self, fn):
364 def __contains__(self, fn):
367 if self.entries is None:
365 if self.entries is None:
368 self._load()
366 self._load()
369 return fn in self.entries
367 return fn in self.entries
370
368
371 def __iter__(self):
369 def __iter__(self):
372 if self.entries is None:
370 if self.entries is None:
373 self._load()
371 self._load()
374 return iter(self.entries)
372 return iter(self.entries)
375
373
376 class _fncacheopener(scmutil.abstractopener):
374 class _fncacheopener(scmutil.abstractopener):
377 def __init__(self, op, fnc, encode):
375 def __init__(self, op, fnc, encode):
378 self.opener = op
376 self.opener = op
379 self.fncache = fnc
377 self.fncache = fnc
380 self.encode = encode
378 self.encode = encode
381
379
382 def _getmustaudit(self):
380 def _getmustaudit(self):
383 return self.opener.mustaudit
381 return self.opener.mustaudit
384
382
385 def _setmustaudit(self, onoff):
383 def _setmustaudit(self, onoff):
386 self.opener.mustaudit = onoff
384 self.opener.mustaudit = onoff
387
385
388 mustaudit = property(_getmustaudit, _setmustaudit)
386 mustaudit = property(_getmustaudit, _setmustaudit)
389
387
390 def __call__(self, path, mode='r', *args, **kw):
388 def __call__(self, path, mode='r', *args, **kw):
391 if mode not in ('r', 'rb') and path.startswith('data/'):
389 if mode not in ('r', 'rb') and path.startswith('data/'):
392 self.fncache.add(path)
390 self.fncache.add(path)
393 return self.opener(self.encode(path), mode, *args, **kw)
391 return self.opener(self.encode(path), mode, *args, **kw)
394
392
395 class fncachestore(basicstore):
393 class fncachestore(basicstore):
396 def __init__(self, path, openertype, encode):
394 def __init__(self, path, openertype, encode):
397 self.encode = encode
395 self.encode = encode
398 self.path = path + '/store'
396 self.path = path + '/store'
399 self.pathsep = self.path + '/'
397 self.pathsep = self.path + '/'
400 self.createmode = _calcmode(self.path)
398 self.createmode = _calcmode(self.path)
401 op = openertype(self.path)
399 op = openertype(self.path)
402 op.createmode = self.createmode
400 op.createmode = self.createmode
403 fnc = fncache(op)
401 fnc = fncache(op)
404 self.fncache = fnc
402 self.fncache = fnc
405 self.opener = _fncacheopener(op, fnc, encode)
403 self.opener = _fncacheopener(op, fnc, encode)
406
404
407 def join(self, f):
405 def join(self, f):
408 return self.pathsep + self.encode(f)
406 return self.pathsep + self.encode(f)
409
407
410 def getsize(self, path):
408 def getsize(self, path):
411 return os.stat(self.pathsep + path).st_size
409 return os.stat(self.pathsep + path).st_size
412
410
413 def datafiles(self):
411 def datafiles(self):
414 rewrite = False
412 rewrite = False
415 existing = []
413 existing = []
416 for f in sorted(self.fncache):
414 for f in sorted(self.fncache):
417 ef = self.encode(f)
415 ef = self.encode(f)
418 try:
416 try:
419 yield f, ef, self.getsize(ef)
417 yield f, ef, self.getsize(ef)
420 existing.append(f)
418 existing.append(f)
421 except OSError, err:
419 except OSError, err:
422 if err.errno != errno.ENOENT:
420 if err.errno != errno.ENOENT:
423 raise
421 raise
424 # nonexistent entry
422 # nonexistent entry
425 rewrite = True
423 rewrite = True
426 if rewrite:
424 if rewrite:
427 # rewrite fncache to remove nonexistent entries
425 # rewrite fncache to remove nonexistent entries
428 # (may be caused by rollback / strip)
426 # (may be caused by rollback / strip)
429 self.fncache.rewrite(existing)
427 self.fncache.rewrite(existing)
430
428
431 def copylist(self):
429 def copylist(self):
432 d = ('data dh fncache phaseroots obsstore'
430 d = ('data dh fncache phaseroots obsstore'
433 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
431 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
434 return (['requires', '00changelog.i'] +
432 return (['requires', '00changelog.i'] +
435 ['store/' + f for f in d.split()])
433 ['store/' + f for f in d.split()])
436
434
437 def write(self):
435 def write(self):
438 self.fncache.write()
436 self.fncache.write()
439
437
440 def store(requirements, path, openertype):
438 def store(requirements, path, openertype):
441 if 'store' in requirements:
439 if 'store' in requirements:
442 if 'fncache' in requirements:
440 if 'fncache' in requirements:
443 auxencode = lambda f: _auxencode(f, 'dotencode' in requirements)
441 auxencode = lambda f: _auxencode(f, 'dotencode' in requirements)
444 encode = lambda f: _hybridencode(f, auxencode)
442 encode = lambda f: _hybridencode(f, auxencode)
445 return fncachestore(path, openertype, encode)
443 return fncachestore(path, openertype, encode)
446 return encodedstore(path, openertype)
444 return encodedstore(path, openertype)
447 return basicstore(path, openertype)
445 return basicstore(path, openertype)
General Comments 0
You need to be logged in to leave comments. Login now