##// END OF EJS Templates
store: initialize "vfs" fields by "vfs" constructors...
FUJIWARA Katsunori -
r17653:dacb5069 default
parent child Browse files
Show More
@@ -1,494 +1,497 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 ef = _encodefname(encodedir(path)).split('/')
258 ef = _encodefname(encodedir(path)).split('/')
259 res = '/'.join(_auxencode(ef, True))
259 res = '/'.join(_auxencode(ef, True))
260 if len(res) > _maxstorepathlen:
260 if len(res) > _maxstorepathlen:
261 return None
261 return None
262 return res
262 return res
263
263
264 _pathencode = getattr(parsers, 'pathencode', _pathencode)
264 _pathencode = getattr(parsers, 'pathencode', _pathencode)
265
265
266 def _dothybridencode(f):
266 def _dothybridencode(f):
267 ef = _pathencode(f)
267 ef = _pathencode(f)
268 if ef is None:
268 if ef is None:
269 return _hashencode(encodedir(f), True)
269 return _hashencode(encodedir(f), True)
270 return ef
270 return ef
271
271
272 def _plainhybridencode(f):
272 def _plainhybridencode(f):
273 return _hybridencode(f, False)
273 return _hybridencode(f, False)
274
274
275 def _calcmode(path):
275 def _calcmode(path):
276 try:
276 try:
277 # files in .hg/ will be created using this mode
277 # files in .hg/ will be created using this mode
278 mode = os.stat(path).st_mode
278 mode = os.stat(path).st_mode
279 # avoid some useless chmods
279 # avoid some useless chmods
280 if (0777 & ~util.umask) == (0777 & mode):
280 if (0777 & ~util.umask) == (0777 & mode):
281 mode = None
281 mode = None
282 except OSError:
282 except OSError:
283 mode = None
283 mode = None
284 return mode
284 return mode
285
285
286 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
286 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
287 ' phaseroots obsstore')
287 ' phaseroots obsstore')
288
288
289 class basicstore(object):
289 class basicstore(object):
290 '''base class for local repository stores'''
290 '''base class for local repository stores'''
291 def __init__(self, path, vfstype):
291 def __init__(self, path, vfstype):
292 self.path = path
292 self.path = path
293 self.createmode = _calcmode(path)
293 self.createmode = _calcmode(path)
294 vfs = vfstype(self.path)
294 vfs = vfstype(self.path)
295 vfs.createmode = self.createmode
295 vfs.createmode = self.createmode
296 self.opener = scmutil.filteropener(vfs, encodedir)
296 self.vfs = scmutil.filtervfs(vfs, encodedir)
297 self.opener = self.vfs
297
298
298 def join(self, f):
299 def join(self, f):
299 return self.path + '/' + encodedir(f)
300 return self.path + '/' + encodedir(f)
300
301
301 def _walk(self, relpath, recurse):
302 def _walk(self, relpath, recurse):
302 '''yields (unencoded, encoded, size)'''
303 '''yields (unencoded, encoded, size)'''
303 path = self.path
304 path = self.path
304 if relpath:
305 if relpath:
305 path += '/' + relpath
306 path += '/' + relpath
306 striplen = len(self.path) + 1
307 striplen = len(self.path) + 1
307 l = []
308 l = []
308 if os.path.isdir(path):
309 if os.path.isdir(path):
309 visit = [path]
310 visit = [path]
310 while visit:
311 while visit:
311 p = visit.pop()
312 p = visit.pop()
312 for f, kind, st in osutil.listdir(p, stat=True):
313 for f, kind, st in osutil.listdir(p, stat=True):
313 fp = p + '/' + f
314 fp = p + '/' + f
314 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
315 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
315 n = util.pconvert(fp[striplen:])
316 n = util.pconvert(fp[striplen:])
316 l.append((decodedir(n), n, st.st_size))
317 l.append((decodedir(n), n, st.st_size))
317 elif kind == stat.S_IFDIR and recurse:
318 elif kind == stat.S_IFDIR and recurse:
318 visit.append(fp)
319 visit.append(fp)
319 l.sort()
320 l.sort()
320 return l
321 return l
321
322
322 def datafiles(self):
323 def datafiles(self):
323 return self._walk('data', True)
324 return self._walk('data', True)
324
325
325 def walk(self):
326 def walk(self):
326 '''yields (unencoded, encoded, size)'''
327 '''yields (unencoded, encoded, size)'''
327 # yield data files first
328 # yield data files first
328 for x in self.datafiles():
329 for x in self.datafiles():
329 yield x
330 yield x
330 # yield manifest before changelog
331 # yield manifest before changelog
331 for x in reversed(self._walk('', False)):
332 for x in reversed(self._walk('', False)):
332 yield x
333 yield x
333
334
334 def copylist(self):
335 def copylist(self):
335 return ['requires'] + _data.split()
336 return ['requires'] + _data.split()
336
337
337 def write(self):
338 def write(self):
338 pass
339 pass
339
340
340 class encodedstore(basicstore):
341 class encodedstore(basicstore):
341 def __init__(self, path, vfstype):
342 def __init__(self, path, vfstype):
342 self.path = path + '/store'
343 self.path = path + '/store'
343 self.createmode = _calcmode(self.path)
344 self.createmode = _calcmode(self.path)
344 vfs = vfstype(self.path)
345 vfs = vfstype(self.path)
345 vfs.createmode = self.createmode
346 vfs.createmode = self.createmode
346 self.opener = scmutil.filteropener(vfs, encodefilename)
347 self.vfs = scmutil.filtervfs(vfs, encodefilename)
348 self.opener = self.vfs
347
349
348 def datafiles(self):
350 def datafiles(self):
349 for a, b, size in self._walk('data', True):
351 for a, b, size in self._walk('data', True):
350 try:
352 try:
351 a = decodefilename(a)
353 a = decodefilename(a)
352 except KeyError:
354 except KeyError:
353 a = None
355 a = None
354 yield a, b, size
356 yield a, b, size
355
357
356 def join(self, f):
358 def join(self, f):
357 return self.path + '/' + encodefilename(f)
359 return self.path + '/' + encodefilename(f)
358
360
359 def copylist(self):
361 def copylist(self):
360 return (['requires', '00changelog.i'] +
362 return (['requires', '00changelog.i'] +
361 ['store/' + f for f in _data.split()])
363 ['store/' + f for f in _data.split()])
362
364
363 class fncache(object):
365 class fncache(object):
364 # the filename used to be partially encoded
366 # the filename used to be partially encoded
365 # hence the encodedir/decodedir dance
367 # hence the encodedir/decodedir dance
366 def __init__(self, opener):
368 def __init__(self, opener):
367 self.opener = opener
369 self.opener = opener
368 self.entries = None
370 self.entries = None
369 self._dirty = False
371 self._dirty = False
370
372
371 def _load(self):
373 def _load(self):
372 '''fill the entries from the fncache file'''
374 '''fill the entries from the fncache file'''
373 self._dirty = False
375 self._dirty = False
374 try:
376 try:
375 fp = self.opener('fncache', mode='rb')
377 fp = self.opener('fncache', mode='rb')
376 except IOError:
378 except IOError:
377 # skip nonexistent file
379 # skip nonexistent file
378 self.entries = set()
380 self.entries = set()
379 return
381 return
380 self.entries = set(decodedir(fp.read()).splitlines())
382 self.entries = set(decodedir(fp.read()).splitlines())
381 if '' in self.entries:
383 if '' in self.entries:
382 fp.seek(0)
384 fp.seek(0)
383 for n, line in enumerate(fp):
385 for n, line in enumerate(fp):
384 if not line.rstrip('\n'):
386 if not line.rstrip('\n'):
385 t = _('invalid entry in fncache, line %s') % (n + 1)
387 t = _('invalid entry in fncache, line %s') % (n + 1)
386 raise util.Abort(t)
388 raise util.Abort(t)
387 fp.close()
389 fp.close()
388
390
389 def _write(self, files, atomictemp):
391 def _write(self, files, atomictemp):
390 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
392 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
391 if files:
393 if files:
392 fp.write(encodedir('\n'.join(files) + '\n'))
394 fp.write(encodedir('\n'.join(files) + '\n'))
393 fp.close()
395 fp.close()
394 self._dirty = False
396 self._dirty = False
395
397
396 def rewrite(self, files):
398 def rewrite(self, files):
397 self._write(files, False)
399 self._write(files, False)
398 self.entries = set(files)
400 self.entries = set(files)
399
401
400 def write(self):
402 def write(self):
401 if self._dirty:
403 if self._dirty:
402 self._write(self.entries, True)
404 self._write(self.entries, True)
403
405
404 def add(self, fn):
406 def add(self, fn):
405 if self.entries is None:
407 if self.entries is None:
406 self._load()
408 self._load()
407 if fn not in self.entries:
409 if fn not in self.entries:
408 self._dirty = True
410 self._dirty = True
409 self.entries.add(fn)
411 self.entries.add(fn)
410
412
411 def __contains__(self, fn):
413 def __contains__(self, fn):
412 if self.entries is None:
414 if self.entries is None:
413 self._load()
415 self._load()
414 return fn in self.entries
416 return fn in self.entries
415
417
416 def __iter__(self):
418 def __iter__(self):
417 if self.entries is None:
419 if self.entries is None:
418 self._load()
420 self._load()
419 return iter(self.entries)
421 return iter(self.entries)
420
422
421 class _fncachevfs(scmutil.abstractvfs):
423 class _fncachevfs(scmutil.abstractvfs):
422 def __init__(self, op, fnc, encode):
424 def __init__(self, op, fnc, encode):
423 self.opener = op
425 self.opener = op
424 self.fncache = fnc
426 self.fncache = fnc
425 self.encode = encode
427 self.encode = encode
426
428
427 def _getmustaudit(self):
429 def _getmustaudit(self):
428 return self.opener.mustaudit
430 return self.opener.mustaudit
429
431
430 def _setmustaudit(self, onoff):
432 def _setmustaudit(self, onoff):
431 self.opener.mustaudit = onoff
433 self.opener.mustaudit = onoff
432
434
433 mustaudit = property(_getmustaudit, _setmustaudit)
435 mustaudit = property(_getmustaudit, _setmustaudit)
434
436
435 def __call__(self, path, mode='r', *args, **kw):
437 def __call__(self, path, mode='r', *args, **kw):
436 if mode not in ('r', 'rb') and path.startswith('data/'):
438 if mode not in ('r', 'rb') and path.startswith('data/'):
437 self.fncache.add(path)
439 self.fncache.add(path)
438 return self.opener(self.encode(path), mode, *args, **kw)
440 return self.opener(self.encode(path), mode, *args, **kw)
439
441
440 class fncachestore(basicstore):
442 class fncachestore(basicstore):
441 def __init__(self, path, vfstype, dotencode):
443 def __init__(self, path, vfstype, dotencode):
442 if dotencode:
444 if dotencode:
443 encode = _dothybridencode
445 encode = _dothybridencode
444 else:
446 else:
445 encode = _plainhybridencode
447 encode = _plainhybridencode
446 self.encode = encode
448 self.encode = encode
447 self.path = path + '/store'
449 self.path = path + '/store'
448 self.pathsep = self.path + '/'
450 self.pathsep = self.path + '/'
449 self.createmode = _calcmode(self.path)
451 self.createmode = _calcmode(self.path)
450 vfs = vfstype(self.path)
452 vfs = vfstype(self.path)
451 vfs.createmode = self.createmode
453 vfs.createmode = self.createmode
452 fnc = fncache(vfs)
454 fnc = fncache(vfs)
453 self.fncache = fnc
455 self.fncache = fnc
454 self.opener = _fncachevfs(vfs, fnc, encode)
456 self.vfs = _fncachevfs(vfs, fnc, encode)
457 self.opener = self.vfs
455
458
456 def join(self, f):
459 def join(self, f):
457 return self.pathsep + self.encode(f)
460 return self.pathsep + self.encode(f)
458
461
459 def getsize(self, path):
462 def getsize(self, path):
460 return os.stat(self.pathsep + path).st_size
463 return os.stat(self.pathsep + path).st_size
461
464
462 def datafiles(self):
465 def datafiles(self):
463 rewrite = False
466 rewrite = False
464 existing = []
467 existing = []
465 for f in sorted(self.fncache):
468 for f in sorted(self.fncache):
466 ef = self.encode(f)
469 ef = self.encode(f)
467 try:
470 try:
468 yield f, ef, self.getsize(ef)
471 yield f, ef, self.getsize(ef)
469 existing.append(f)
472 existing.append(f)
470 except OSError, err:
473 except OSError, err:
471 if err.errno != errno.ENOENT:
474 if err.errno != errno.ENOENT:
472 raise
475 raise
473 # nonexistent entry
476 # nonexistent entry
474 rewrite = True
477 rewrite = True
475 if rewrite:
478 if rewrite:
476 # rewrite fncache to remove nonexistent entries
479 # rewrite fncache to remove nonexistent entries
477 # (may be caused by rollback / strip)
480 # (may be caused by rollback / strip)
478 self.fncache.rewrite(existing)
481 self.fncache.rewrite(existing)
479
482
480 def copylist(self):
483 def copylist(self):
481 d = ('data dh fncache phaseroots obsstore'
484 d = ('data dh fncache phaseroots obsstore'
482 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
485 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
483 return (['requires', '00changelog.i'] +
486 return (['requires', '00changelog.i'] +
484 ['store/' + f for f in d.split()])
487 ['store/' + f for f in d.split()])
485
488
486 def write(self):
489 def write(self):
487 self.fncache.write()
490 self.fncache.write()
488
491
489 def store(requirements, path, vfstype):
492 def store(requirements, path, vfstype):
490 if 'store' in requirements:
493 if 'store' in requirements:
491 if 'fncache' in requirements:
494 if 'fncache' in requirements:
492 return fncachestore(path, vfstype, 'dotencode' in requirements)
495 return fncachestore(path, vfstype, 'dotencode' in requirements)
493 return encodedstore(path, vfstype)
496 return encodedstore(path, vfstype)
494 return basicstore(path, vfstype)
497 return basicstore(path, vfstype)
General Comments 0
You need to be logged in to leave comments. Login now