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