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