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