##// END OF EJS Templates
store: move top file walk to a separate function...
Durham Goode -
r19177:1e104aaa default
parent child Browse files
Show More
@@ -1,533 +1,536 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):
326 # yield manifest before changelog
327 return reversed(self._walk('', False))
328
325 def walk(self):
329 def walk(self):
326 '''yields (unencoded, encoded, size)'''
330 '''yields (unencoded, encoded, size)'''
327 # yield data files first
331 # yield data files first
328 for x in self.datafiles():
332 for x in self.datafiles():
329 yield x
333 yield x
330 # yield manifest before changelog
334 for x in self.topfiles():
331 for x in reversed(self._walk('', False)):
332 yield x
335 yield x
333
336
334 def copylist(self):
337 def copylist(self):
335 return ['requires'] + _data.split()
338 return ['requires'] + _data.split()
336
339
337 def write(self):
340 def write(self):
338 pass
341 pass
339
342
340 def __contains__(self, path):
343 def __contains__(self, path):
341 '''Checks if the store contains path'''
344 '''Checks if the store contains path'''
342 path = "/".join(("data", path))
345 path = "/".join(("data", path))
343 # file?
346 # file?
344 if os.path.exists(self.join(path + ".i")):
347 if os.path.exists(self.join(path + ".i")):
345 return True
348 return True
346 # dir?
349 # dir?
347 if not path.endswith("/"):
350 if not path.endswith("/"):
348 path = path + "/"
351 path = path + "/"
349 return os.path.exists(self.join(path))
352 return os.path.exists(self.join(path))
350
353
351 class encodedstore(basicstore):
354 class encodedstore(basicstore):
352 def __init__(self, path, vfstype):
355 def __init__(self, path, vfstype):
353 vfs = vfstype(path + '/store')
356 vfs = vfstype(path + '/store')
354 self.path = vfs.base
357 self.path = vfs.base
355 self.createmode = _calcmode(vfs)
358 self.createmode = _calcmode(vfs)
356 vfs.createmode = self.createmode
359 vfs.createmode = self.createmode
357 self.rawvfs = vfs
360 self.rawvfs = vfs
358 self.vfs = scmutil.filtervfs(vfs, encodefilename)
361 self.vfs = scmutil.filtervfs(vfs, encodefilename)
359 self.opener = self.vfs
362 self.opener = self.vfs
360
363
361 def datafiles(self):
364 def datafiles(self):
362 for a, b, size in self._walk('data', True):
365 for a, b, size in self._walk('data', True):
363 try:
366 try:
364 a = decodefilename(a)
367 a = decodefilename(a)
365 except KeyError:
368 except KeyError:
366 a = None
369 a = None
367 yield a, b, size
370 yield a, b, size
368
371
369 def join(self, f):
372 def join(self, f):
370 return self.path + '/' + encodefilename(f)
373 return self.path + '/' + encodefilename(f)
371
374
372 def copylist(self):
375 def copylist(self):
373 return (['requires', '00changelog.i'] +
376 return (['requires', '00changelog.i'] +
374 ['store/' + f for f in _data.split()])
377 ['store/' + f for f in _data.split()])
375
378
376 class fncache(object):
379 class fncache(object):
377 # the filename used to be partially encoded
380 # the filename used to be partially encoded
378 # hence the encodedir/decodedir dance
381 # hence the encodedir/decodedir dance
379 def __init__(self, vfs):
382 def __init__(self, vfs):
380 self.vfs = vfs
383 self.vfs = vfs
381 self.entries = None
384 self.entries = None
382 self._dirty = False
385 self._dirty = False
383
386
384 def _load(self):
387 def _load(self):
385 '''fill the entries from the fncache file'''
388 '''fill the entries from the fncache file'''
386 self._dirty = False
389 self._dirty = False
387 try:
390 try:
388 fp = self.vfs('fncache', mode='rb')
391 fp = self.vfs('fncache', mode='rb')
389 except IOError:
392 except IOError:
390 # skip nonexistent file
393 # skip nonexistent file
391 self.entries = set()
394 self.entries = set()
392 return
395 return
393 self.entries = set(decodedir(fp.read()).splitlines())
396 self.entries = set(decodedir(fp.read()).splitlines())
394 if '' in self.entries:
397 if '' in self.entries:
395 fp.seek(0)
398 fp.seek(0)
396 for n, line in enumerate(fp):
399 for n, line in enumerate(fp):
397 if not line.rstrip('\n'):
400 if not line.rstrip('\n'):
398 t = _('invalid entry in fncache, line %s') % (n + 1)
401 t = _('invalid entry in fncache, line %s') % (n + 1)
399 raise util.Abort(t)
402 raise util.Abort(t)
400 fp.close()
403 fp.close()
401
404
402 def _write(self, files, atomictemp):
405 def _write(self, files, atomictemp):
403 fp = self.vfs('fncache', mode='wb', atomictemp=atomictemp)
406 fp = self.vfs('fncache', mode='wb', atomictemp=atomictemp)
404 if files:
407 if files:
405 fp.write(encodedir('\n'.join(files) + '\n'))
408 fp.write(encodedir('\n'.join(files) + '\n'))
406 fp.close()
409 fp.close()
407 self._dirty = False
410 self._dirty = False
408
411
409 def rewrite(self, files):
412 def rewrite(self, files):
410 self._write(files, False)
413 self._write(files, False)
411 self.entries = set(files)
414 self.entries = set(files)
412
415
413 def write(self):
416 def write(self):
414 if self._dirty:
417 if self._dirty:
415 self._write(self.entries, True)
418 self._write(self.entries, True)
416
419
417 def add(self, fn):
420 def add(self, fn):
418 if self.entries is None:
421 if self.entries is None:
419 self._load()
422 self._load()
420 if fn not in self.entries:
423 if fn not in self.entries:
421 self._dirty = True
424 self._dirty = True
422 self.entries.add(fn)
425 self.entries.add(fn)
423
426
424 def __contains__(self, fn):
427 def __contains__(self, fn):
425 if self.entries is None:
428 if self.entries is None:
426 self._load()
429 self._load()
427 return fn in self.entries
430 return fn in self.entries
428
431
429 def __iter__(self):
432 def __iter__(self):
430 if self.entries is None:
433 if self.entries is None:
431 self._load()
434 self._load()
432 return iter(self.entries)
435 return iter(self.entries)
433
436
434 class _fncachevfs(scmutil.abstractvfs, scmutil.auditvfs):
437 class _fncachevfs(scmutil.abstractvfs, scmutil.auditvfs):
435 def __init__(self, vfs, fnc, encode):
438 def __init__(self, vfs, fnc, encode):
436 scmutil.auditvfs.__init__(self, vfs)
439 scmutil.auditvfs.__init__(self, vfs)
437 self.fncache = fnc
440 self.fncache = fnc
438 self.encode = encode
441 self.encode = encode
439
442
440 def __call__(self, path, mode='r', *args, **kw):
443 def __call__(self, path, mode='r', *args, **kw):
441 if mode not in ('r', 'rb') and path.startswith('data/'):
444 if mode not in ('r', 'rb') and path.startswith('data/'):
442 self.fncache.add(path)
445 self.fncache.add(path)
443 return self.vfs(self.encode(path), mode, *args, **kw)
446 return self.vfs(self.encode(path), mode, *args, **kw)
444
447
445 def join(self, path):
448 def join(self, path):
446 if path:
449 if path:
447 return self.vfs.join(self.encode(path))
450 return self.vfs.join(self.encode(path))
448 else:
451 else:
449 return self.vfs.join(path)
452 return self.vfs.join(path)
450
453
451 class fncachestore(basicstore):
454 class fncachestore(basicstore):
452 def __init__(self, path, vfstype, dotencode):
455 def __init__(self, path, vfstype, dotencode):
453 if dotencode:
456 if dotencode:
454 encode = _pathencode
457 encode = _pathencode
455 else:
458 else:
456 encode = _plainhybridencode
459 encode = _plainhybridencode
457 self.encode = encode
460 self.encode = encode
458 vfs = vfstype(path + '/store')
461 vfs = vfstype(path + '/store')
459 self.path = vfs.base
462 self.path = vfs.base
460 self.pathsep = self.path + '/'
463 self.pathsep = self.path + '/'
461 self.createmode = _calcmode(vfs)
464 self.createmode = _calcmode(vfs)
462 vfs.createmode = self.createmode
465 vfs.createmode = self.createmode
463 self.rawvfs = vfs
466 self.rawvfs = vfs
464 fnc = fncache(vfs)
467 fnc = fncache(vfs)
465 self.fncache = fnc
468 self.fncache = fnc
466 self.vfs = _fncachevfs(vfs, fnc, encode)
469 self.vfs = _fncachevfs(vfs, fnc, encode)
467 self.opener = self.vfs
470 self.opener = self.vfs
468
471
469 def join(self, f):
472 def join(self, f):
470 return self.pathsep + self.encode(f)
473 return self.pathsep + self.encode(f)
471
474
472 def getsize(self, path):
475 def getsize(self, path):
473 return self.rawvfs.stat(path).st_size
476 return self.rawvfs.stat(path).st_size
474
477
475 def datafiles(self):
478 def datafiles(self):
476 rewrite = False
479 rewrite = False
477 existing = []
480 existing = []
478 for f in sorted(self.fncache):
481 for f in sorted(self.fncache):
479 ef = self.encode(f)
482 ef = self.encode(f)
480 try:
483 try:
481 yield f, ef, self.getsize(ef)
484 yield f, ef, self.getsize(ef)
482 existing.append(f)
485 existing.append(f)
483 except OSError, err:
486 except OSError, err:
484 if err.errno != errno.ENOENT:
487 if err.errno != errno.ENOENT:
485 raise
488 raise
486 # nonexistent entry
489 # nonexistent entry
487 rewrite = True
490 rewrite = True
488 if rewrite:
491 if rewrite:
489 # rewrite fncache to remove nonexistent entries
492 # rewrite fncache to remove nonexistent entries
490 # (may be caused by rollback / strip)
493 # (may be caused by rollback / strip)
491 self.fncache.rewrite(existing)
494 self.fncache.rewrite(existing)
492
495
493 def copylist(self):
496 def copylist(self):
494 d = ('data dh fncache phaseroots obsstore'
497 d = ('data dh fncache phaseroots obsstore'
495 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
498 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
496 return (['requires', '00changelog.i'] +
499 return (['requires', '00changelog.i'] +
497 ['store/' + f for f in d.split()])
500 ['store/' + f for f in d.split()])
498
501
499 def write(self):
502 def write(self):
500 self.fncache.write()
503 self.fncache.write()
501
504
502 def _exists(self, f):
505 def _exists(self, f):
503 ef = self.encode(f)
506 ef = self.encode(f)
504 try:
507 try:
505 self.getsize(ef)
508 self.getsize(ef)
506 return True
509 return True
507 except OSError, err:
510 except OSError, err:
508 if err.errno != errno.ENOENT:
511 if err.errno != errno.ENOENT:
509 raise
512 raise
510 # nonexistent entry
513 # nonexistent entry
511 return False
514 return False
512
515
513 def __contains__(self, path):
516 def __contains__(self, path):
514 '''Checks if the store contains path'''
517 '''Checks if the store contains path'''
515 path = "/".join(("data", path))
518 path = "/".join(("data", path))
516 # check for files (exact match)
519 # check for files (exact match)
517 e = path + '.i'
520 e = path + '.i'
518 if e in self.fncache and self._exists(e):
521 if e in self.fncache and self._exists(e):
519 return True
522 return True
520 # now check for directories (prefix match)
523 # now check for directories (prefix match)
521 if not path.endswith('/'):
524 if not path.endswith('/'):
522 path += '/'
525 path += '/'
523 for e in self.fncache:
526 for e in self.fncache:
524 if e.startswith(path) and self._exists(e):
527 if e.startswith(path) and self._exists(e):
525 return True
528 return True
526 return False
529 return False
527
530
528 def store(requirements, path, vfstype):
531 def store(requirements, path, vfstype):
529 if 'store' in requirements:
532 if 'store' in requirements:
530 if 'fncache' in requirements:
533 if 'fncache' in requirements:
531 return fncachestore(path, vfstype, 'dotencode' in requirements)
534 return fncachestore(path, vfstype, 'dotencode' in requirements)
532 return encodedstore(path, vfstype)
535 return encodedstore(path, vfstype)
533 return basicstore(path, vfstype)
536 return basicstore(path, vfstype)
General Comments 0
You need to be logged in to leave comments. Login now