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