##// END OF EJS Templates
store: pass matcher to store.datafiles()...
Pulkit Goyal -
r40376:2d45b549 default
parent child Browse files
Show More
@@ -1,587 +1,591 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import os
12 import os
13 import stat
13 import stat
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 node,
18 node,
19 policy,
19 policy,
20 pycompat,
20 pycompat,
21 util,
21 util,
22 vfs as vfsmod,
22 vfs as vfsmod,
23 )
23 )
24
24
25 parsers = policy.importmod(r'parsers')
25 parsers = policy.importmod(r'parsers')
26
26
27 # This avoids a collision between a file named foo and a dir named
27 # This avoids a collision between a file named foo and a dir named
28 # foo.i or foo.d
28 # foo.i or foo.d
29 def _encodedir(path):
29 def _encodedir(path):
30 '''
30 '''
31 >>> _encodedir(b'data/foo.i')
31 >>> _encodedir(b'data/foo.i')
32 'data/foo.i'
32 'data/foo.i'
33 >>> _encodedir(b'data/foo.i/bla.i')
33 >>> _encodedir(b'data/foo.i/bla.i')
34 'data/foo.i.hg/bla.i'
34 'data/foo.i.hg/bla.i'
35 >>> _encodedir(b'data/foo.i.hg/bla.i')
35 >>> _encodedir(b'data/foo.i.hg/bla.i')
36 'data/foo.i.hg.hg/bla.i'
36 'data/foo.i.hg.hg/bla.i'
37 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
37 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
38 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
38 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
39 '''
39 '''
40 return (path
40 return (path
41 .replace(".hg/", ".hg.hg/")
41 .replace(".hg/", ".hg.hg/")
42 .replace(".i/", ".i.hg/")
42 .replace(".i/", ".i.hg/")
43 .replace(".d/", ".d.hg/"))
43 .replace(".d/", ".d.hg/"))
44
44
45 encodedir = getattr(parsers, 'encodedir', _encodedir)
45 encodedir = getattr(parsers, 'encodedir', _encodedir)
46
46
47 def decodedir(path):
47 def decodedir(path):
48 '''
48 '''
49 >>> decodedir(b'data/foo.i')
49 >>> decodedir(b'data/foo.i')
50 'data/foo.i'
50 'data/foo.i'
51 >>> decodedir(b'data/foo.i.hg/bla.i')
51 >>> decodedir(b'data/foo.i.hg/bla.i')
52 'data/foo.i/bla.i'
52 'data/foo.i/bla.i'
53 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
53 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
54 'data/foo.i.hg/bla.i'
54 'data/foo.i.hg/bla.i'
55 '''
55 '''
56 if ".hg/" not in path:
56 if ".hg/" not in path:
57 return path
57 return path
58 return (path
58 return (path
59 .replace(".d.hg/", ".d/")
59 .replace(".d.hg/", ".d/")
60 .replace(".i.hg/", ".i/")
60 .replace(".i.hg/", ".i/")
61 .replace(".hg.hg/", ".hg/"))
61 .replace(".hg.hg/", ".hg/"))
62
62
63 def _reserved():
63 def _reserved():
64 ''' characters that are problematic for filesystems
64 ''' characters that are problematic for filesystems
65
65
66 * ascii escapes (0..31)
66 * ascii escapes (0..31)
67 * ascii hi (126..255)
67 * ascii hi (126..255)
68 * windows specials
68 * windows specials
69
69
70 these characters will be escaped by encodefunctions
70 these characters will be escaped by encodefunctions
71 '''
71 '''
72 winreserved = [ord(x) for x in u'\\:*?"<>|']
72 winreserved = [ord(x) for x in u'\\:*?"<>|']
73 for x in range(32):
73 for x in range(32):
74 yield x
74 yield x
75 for x in range(126, 256):
75 for x in range(126, 256):
76 yield x
76 yield x
77 for x in winreserved:
77 for x in winreserved:
78 yield x
78 yield x
79
79
80 def _buildencodefun():
80 def _buildencodefun():
81 '''
81 '''
82 >>> enc, dec = _buildencodefun()
82 >>> enc, dec = _buildencodefun()
83
83
84 >>> enc(b'nothing/special.txt')
84 >>> enc(b'nothing/special.txt')
85 'nothing/special.txt'
85 'nothing/special.txt'
86 >>> dec(b'nothing/special.txt')
86 >>> dec(b'nothing/special.txt')
87 'nothing/special.txt'
87 'nothing/special.txt'
88
88
89 >>> enc(b'HELLO')
89 >>> enc(b'HELLO')
90 '_h_e_l_l_o'
90 '_h_e_l_l_o'
91 >>> dec(b'_h_e_l_l_o')
91 >>> dec(b'_h_e_l_l_o')
92 'HELLO'
92 'HELLO'
93
93
94 >>> enc(b'hello:world?')
94 >>> enc(b'hello:world?')
95 'hello~3aworld~3f'
95 'hello~3aworld~3f'
96 >>> dec(b'hello~3aworld~3f')
96 >>> dec(b'hello~3aworld~3f')
97 'hello:world?'
97 'hello:world?'
98
98
99 >>> enc(b'the\\x07quick\\xADshot')
99 >>> enc(b'the\\x07quick\\xADshot')
100 'the~07quick~adshot'
100 'the~07quick~adshot'
101 >>> dec(b'the~07quick~adshot')
101 >>> dec(b'the~07quick~adshot')
102 'the\\x07quick\\xadshot'
102 'the\\x07quick\\xadshot'
103 '''
103 '''
104 e = '_'
104 e = '_'
105 xchr = pycompat.bytechr
105 xchr = pycompat.bytechr
106 asciistr = list(map(xchr, range(127)))
106 asciistr = list(map(xchr, range(127)))
107 capitals = list(range(ord("A"), ord("Z") + 1))
107 capitals = list(range(ord("A"), ord("Z") + 1))
108
108
109 cmap = dict((x, x) for x in asciistr)
109 cmap = dict((x, x) for x in asciistr)
110 for x in _reserved():
110 for x in _reserved():
111 cmap[xchr(x)] = "~%02x" % x
111 cmap[xchr(x)] = "~%02x" % x
112 for x in capitals + [ord(e)]:
112 for x in capitals + [ord(e)]:
113 cmap[xchr(x)] = e + xchr(x).lower()
113 cmap[xchr(x)] = e + xchr(x).lower()
114
114
115 dmap = {}
115 dmap = {}
116 for k, v in cmap.iteritems():
116 for k, v in cmap.iteritems():
117 dmap[v] = k
117 dmap[v] = k
118 def decode(s):
118 def decode(s):
119 i = 0
119 i = 0
120 while i < len(s):
120 while i < len(s):
121 for l in pycompat.xrange(1, 4):
121 for l in pycompat.xrange(1, 4):
122 try:
122 try:
123 yield dmap[s[i:i + l]]
123 yield dmap[s[i:i + l]]
124 i += l
124 i += l
125 break
125 break
126 except KeyError:
126 except KeyError:
127 pass
127 pass
128 else:
128 else:
129 raise KeyError
129 raise KeyError
130 return (lambda s: ''.join([cmap[s[c:c + 1]]
130 return (lambda s: ''.join([cmap[s[c:c + 1]]
131 for c in pycompat.xrange(len(s))]),
131 for c in pycompat.xrange(len(s))]),
132 lambda s: ''.join(list(decode(s))))
132 lambda s: ''.join(list(decode(s))))
133
133
134 _encodefname, _decodefname = _buildencodefun()
134 _encodefname, _decodefname = _buildencodefun()
135
135
136 def encodefilename(s):
136 def encodefilename(s):
137 '''
137 '''
138 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
138 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
139 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
139 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
140 '''
140 '''
141 return _encodefname(encodedir(s))
141 return _encodefname(encodedir(s))
142
142
143 def decodefilename(s):
143 def decodefilename(s):
144 '''
144 '''
145 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
145 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
146 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
146 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
147 '''
147 '''
148 return decodedir(_decodefname(s))
148 return decodedir(_decodefname(s))
149
149
150 def _buildlowerencodefun():
150 def _buildlowerencodefun():
151 '''
151 '''
152 >>> f = _buildlowerencodefun()
152 >>> f = _buildlowerencodefun()
153 >>> f(b'nothing/special.txt')
153 >>> f(b'nothing/special.txt')
154 'nothing/special.txt'
154 'nothing/special.txt'
155 >>> f(b'HELLO')
155 >>> f(b'HELLO')
156 'hello'
156 'hello'
157 >>> f(b'hello:world?')
157 >>> f(b'hello:world?')
158 'hello~3aworld~3f'
158 'hello~3aworld~3f'
159 >>> f(b'the\\x07quick\\xADshot')
159 >>> f(b'the\\x07quick\\xADshot')
160 'the~07quick~adshot'
160 'the~07quick~adshot'
161 '''
161 '''
162 xchr = pycompat.bytechr
162 xchr = pycompat.bytechr
163 cmap = dict([(xchr(x), xchr(x)) for x in pycompat.xrange(127)])
163 cmap = dict([(xchr(x), xchr(x)) for x in pycompat.xrange(127)])
164 for x in _reserved():
164 for x in _reserved():
165 cmap[xchr(x)] = "~%02x" % x
165 cmap[xchr(x)] = "~%02x" % x
166 for x in range(ord("A"), ord("Z") + 1):
166 for x in range(ord("A"), ord("Z") + 1):
167 cmap[xchr(x)] = xchr(x).lower()
167 cmap[xchr(x)] = xchr(x).lower()
168 def lowerencode(s):
168 def lowerencode(s):
169 return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
169 return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
170 return lowerencode
170 return lowerencode
171
171
172 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
172 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
173
173
174 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
174 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
175 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
175 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
176 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
176 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
177 def _auxencode(path, dotencode):
177 def _auxencode(path, dotencode):
178 '''
178 '''
179 Encodes filenames containing names reserved by Windows or which end in
179 Encodes filenames containing names reserved by Windows or which end in
180 period or space. Does not touch other single reserved characters c.
180 period or space. Does not touch other single reserved characters c.
181 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
181 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
182 Additionally encodes space or period at the beginning, if dotencode is
182 Additionally encodes space or period at the beginning, if dotencode is
183 True. Parameter path is assumed to be all lowercase.
183 True. Parameter path is assumed to be all lowercase.
184 A segment only needs encoding if a reserved name appears as a
184 A segment only needs encoding if a reserved name appears as a
185 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
185 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
186 doesn't need encoding.
186 doesn't need encoding.
187
187
188 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
188 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
189 >>> _auxencode(s.split(b'/'), True)
189 >>> _auxencode(s.split(b'/'), True)
190 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
190 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
191 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
191 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
192 >>> _auxencode(s.split(b'/'), False)
192 >>> _auxencode(s.split(b'/'), False)
193 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
193 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
194 >>> _auxencode([b'foo. '], True)
194 >>> _auxencode([b'foo. '], True)
195 ['foo.~20']
195 ['foo.~20']
196 >>> _auxencode([b' .foo'], True)
196 >>> _auxencode([b' .foo'], True)
197 ['~20.foo']
197 ['~20.foo']
198 '''
198 '''
199 for i, n in enumerate(path):
199 for i, n in enumerate(path):
200 if not n:
200 if not n:
201 continue
201 continue
202 if dotencode and n[0] in '. ':
202 if dotencode and n[0] in '. ':
203 n = "~%02x" % ord(n[0:1]) + n[1:]
203 n = "~%02x" % ord(n[0:1]) + n[1:]
204 path[i] = n
204 path[i] = n
205 else:
205 else:
206 l = n.find('.')
206 l = n.find('.')
207 if l == -1:
207 if l == -1:
208 l = len(n)
208 l = len(n)
209 if ((l == 3 and n[:3] in _winres3) or
209 if ((l == 3 and n[:3] in _winres3) or
210 (l == 4 and n[3:4] <= '9' and n[3:4] >= '1'
210 (l == 4 and n[3:4] <= '9' and n[3:4] >= '1'
211 and n[:3] in _winres4)):
211 and n[:3] in _winres4)):
212 # encode third letter ('aux' -> 'au~78')
212 # encode third letter ('aux' -> 'au~78')
213 ec = "~%02x" % ord(n[2:3])
213 ec = "~%02x" % ord(n[2:3])
214 n = n[0:2] + ec + n[3:]
214 n = n[0:2] + ec + n[3:]
215 path[i] = n
215 path[i] = n
216 if n[-1] in '. ':
216 if n[-1] in '. ':
217 # encode last period or space ('foo...' -> 'foo..~2e')
217 # encode last period or space ('foo...' -> 'foo..~2e')
218 path[i] = n[:-1] + "~%02x" % ord(n[-1:])
218 path[i] = n[:-1] + "~%02x" % ord(n[-1:])
219 return path
219 return path
220
220
221 _maxstorepathlen = 120
221 _maxstorepathlen = 120
222 _dirprefixlen = 8
222 _dirprefixlen = 8
223 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
223 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
224
224
225 def _hashencode(path, dotencode):
225 def _hashencode(path, dotencode):
226 digest = node.hex(hashlib.sha1(path).digest())
226 digest = node.hex(hashlib.sha1(path).digest())
227 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
227 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
228 parts = _auxencode(le, dotencode)
228 parts = _auxencode(le, dotencode)
229 basename = parts[-1]
229 basename = parts[-1]
230 _root, ext = os.path.splitext(basename)
230 _root, ext = os.path.splitext(basename)
231 sdirs = []
231 sdirs = []
232 sdirslen = 0
232 sdirslen = 0
233 for p in parts[:-1]:
233 for p in parts[:-1]:
234 d = p[:_dirprefixlen]
234 d = p[:_dirprefixlen]
235 if d[-1] in '. ':
235 if d[-1] in '. ':
236 # Windows can't access dirs ending in period or space
236 # Windows can't access dirs ending in period or space
237 d = d[:-1] + '_'
237 d = d[:-1] + '_'
238 if sdirslen == 0:
238 if sdirslen == 0:
239 t = len(d)
239 t = len(d)
240 else:
240 else:
241 t = sdirslen + 1 + len(d)
241 t = sdirslen + 1 + len(d)
242 if t > _maxshortdirslen:
242 if t > _maxshortdirslen:
243 break
243 break
244 sdirs.append(d)
244 sdirs.append(d)
245 sdirslen = t
245 sdirslen = t
246 dirs = '/'.join(sdirs)
246 dirs = '/'.join(sdirs)
247 if len(dirs) > 0:
247 if len(dirs) > 0:
248 dirs += '/'
248 dirs += '/'
249 res = 'dh/' + dirs + digest + ext
249 res = 'dh/' + dirs + digest + ext
250 spaceleft = _maxstorepathlen - len(res)
250 spaceleft = _maxstorepathlen - len(res)
251 if spaceleft > 0:
251 if spaceleft > 0:
252 filler = basename[:spaceleft]
252 filler = basename[:spaceleft]
253 res = 'dh/' + dirs + filler + digest + ext
253 res = 'dh/' + dirs + filler + digest + ext
254 return res
254 return res
255
255
256 def _hybridencode(path, dotencode):
256 def _hybridencode(path, dotencode):
257 '''encodes path with a length limit
257 '''encodes path with a length limit
258
258
259 Encodes all paths that begin with 'data/', according to the following.
259 Encodes all paths that begin with 'data/', according to the following.
260
260
261 Default encoding (reversible):
261 Default encoding (reversible):
262
262
263 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
263 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
264 characters are encoded as '~xx', where xx is the two digit hex code
264 characters are encoded as '~xx', where xx is the two digit hex code
265 of the character (see encodefilename).
265 of the character (see encodefilename).
266 Relevant path components consisting of Windows reserved filenames are
266 Relevant path components consisting of Windows reserved filenames are
267 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
267 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
268
268
269 Hashed encoding (not reversible):
269 Hashed encoding (not reversible):
270
270
271 If the default-encoded path is longer than _maxstorepathlen, a
271 If the default-encoded path is longer than _maxstorepathlen, a
272 non-reversible hybrid hashing of the path is done instead.
272 non-reversible hybrid hashing of the path is done instead.
273 This encoding uses up to _dirprefixlen characters of all directory
273 This encoding uses up to _dirprefixlen characters of all directory
274 levels of the lowerencoded path, but not more levels than can fit into
274 levels of the lowerencoded path, but not more levels than can fit into
275 _maxshortdirslen.
275 _maxshortdirslen.
276 Then follows the filler followed by the sha digest of the full path.
276 Then follows the filler followed by the sha digest of the full path.
277 The filler is the beginning of the basename of the lowerencoded path
277 The filler is the beginning of the basename of the lowerencoded path
278 (the basename is everything after the last path separator). The filler
278 (the basename is everything after the last path separator). The filler
279 is as long as possible, filling in characters from the basename until
279 is as long as possible, filling in characters from the basename until
280 the encoded path has _maxstorepathlen characters (or all chars of the
280 the encoded path has _maxstorepathlen characters (or all chars of the
281 basename have been taken).
281 basename have been taken).
282 The extension (e.g. '.i' or '.d') is preserved.
282 The extension (e.g. '.i' or '.d') is preserved.
283
283
284 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
284 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
285 encoding was used.
285 encoding was used.
286 '''
286 '''
287 path = encodedir(path)
287 path = encodedir(path)
288 ef = _encodefname(path).split('/')
288 ef = _encodefname(path).split('/')
289 res = '/'.join(_auxencode(ef, dotencode))
289 res = '/'.join(_auxencode(ef, dotencode))
290 if len(res) > _maxstorepathlen:
290 if len(res) > _maxstorepathlen:
291 res = _hashencode(path, dotencode)
291 res = _hashencode(path, dotencode)
292 return res
292 return res
293
293
294 def _pathencode(path):
294 def _pathencode(path):
295 de = encodedir(path)
295 de = encodedir(path)
296 if len(path) > _maxstorepathlen:
296 if len(path) > _maxstorepathlen:
297 return _hashencode(de, True)
297 return _hashencode(de, True)
298 ef = _encodefname(de).split('/')
298 ef = _encodefname(de).split('/')
299 res = '/'.join(_auxencode(ef, True))
299 res = '/'.join(_auxencode(ef, True))
300 if len(res) > _maxstorepathlen:
300 if len(res) > _maxstorepathlen:
301 return _hashencode(de, True)
301 return _hashencode(de, True)
302 return res
302 return res
303
303
304 _pathencode = getattr(parsers, 'pathencode', _pathencode)
304 _pathencode = getattr(parsers, 'pathencode', _pathencode)
305
305
306 def _plainhybridencode(f):
306 def _plainhybridencode(f):
307 return _hybridencode(f, False)
307 return _hybridencode(f, False)
308
308
309 def _calcmode(vfs):
309 def _calcmode(vfs):
310 try:
310 try:
311 # files in .hg/ will be created using this mode
311 # files in .hg/ will be created using this mode
312 mode = vfs.stat().st_mode
312 mode = vfs.stat().st_mode
313 # avoid some useless chmods
313 # avoid some useless chmods
314 if (0o777 & ~util.umask) == (0o777 & mode):
314 if (0o777 & ~util.umask) == (0o777 & mode):
315 mode = None
315 mode = None
316 except OSError:
316 except OSError:
317 mode = None
317 mode = None
318 return mode
318 return mode
319
319
320 _data = ('narrowspec data meta 00manifest.d 00manifest.i'
320 _data = ('narrowspec data meta 00manifest.d 00manifest.i'
321 ' 00changelog.d 00changelog.i phaseroots obsstore')
321 ' 00changelog.d 00changelog.i phaseroots obsstore')
322
322
323 def isrevlog(f, kind, st):
323 def isrevlog(f, kind, st):
324 return kind == stat.S_IFREG and f[-2:] in ('.i', '.d')
324 return kind == stat.S_IFREG and f[-2:] in ('.i', '.d')
325
325
326 class basicstore(object):
326 class basicstore(object):
327 '''base class for local repository stores'''
327 '''base class for local repository stores'''
328 def __init__(self, path, vfstype):
328 def __init__(self, path, vfstype):
329 vfs = vfstype(path)
329 vfs = vfstype(path)
330 self.path = vfs.base
330 self.path = vfs.base
331 self.createmode = _calcmode(vfs)
331 self.createmode = _calcmode(vfs)
332 vfs.createmode = self.createmode
332 vfs.createmode = self.createmode
333 self.rawvfs = vfs
333 self.rawvfs = vfs
334 self.vfs = vfsmod.filtervfs(vfs, encodedir)
334 self.vfs = vfsmod.filtervfs(vfs, encodedir)
335 self.opener = self.vfs
335 self.opener = self.vfs
336
336
337 def join(self, f):
337 def join(self, f):
338 return self.path + '/' + encodedir(f)
338 return self.path + '/' + encodedir(f)
339
339
340 def _walk(self, relpath, recurse, filefilter=isrevlog):
340 def _walk(self, relpath, recurse, filefilter=isrevlog):
341 '''yields (unencoded, encoded, size)'''
341 '''yields (unencoded, encoded, size)'''
342 path = self.path
342 path = self.path
343 if relpath:
343 if relpath:
344 path += '/' + relpath
344 path += '/' + relpath
345 striplen = len(self.path) + 1
345 striplen = len(self.path) + 1
346 l = []
346 l = []
347 if self.rawvfs.isdir(path):
347 if self.rawvfs.isdir(path):
348 visit = [path]
348 visit = [path]
349 readdir = self.rawvfs.readdir
349 readdir = self.rawvfs.readdir
350 while visit:
350 while visit:
351 p = visit.pop()
351 p = visit.pop()
352 for f, kind, st in readdir(p, stat=True):
352 for f, kind, st in readdir(p, stat=True):
353 fp = p + '/' + f
353 fp = p + '/' + f
354 if filefilter(f, kind, st):
354 if filefilter(f, kind, st):
355 n = util.pconvert(fp[striplen:])
355 n = util.pconvert(fp[striplen:])
356 l.append((decodedir(n), n, st.st_size))
356 l.append((decodedir(n), n, st.st_size))
357 elif kind == stat.S_IFDIR and recurse:
357 elif kind == stat.S_IFDIR and recurse:
358 visit.append(fp)
358 visit.append(fp)
359 l.sort()
359 l.sort()
360 return l
360 return l
361
361
362 def datafiles(self):
362 def datafiles(self, matcher=None):
363 return self._walk('data', True) + self._walk('meta', True)
363 return self._walk('data', True) + self._walk('meta', True)
364
364
365 def topfiles(self):
365 def topfiles(self):
366 # yield manifest before changelog
366 # yield manifest before changelog
367 return reversed(self._walk('', False))
367 return reversed(self._walk('', False))
368
368
369 def walk(self):
369 def walk(self, matcher=None):
370 '''yields (unencoded, encoded, size)'''
370 '''yields (unencoded, encoded, size)
371
372 if a matcher is passed, storage files of only those tracked paths
373 are passed with matches the matcher
374 '''
371 # yield data files first
375 # yield data files first
372 for x in self.datafiles():
376 for x in self.datafiles(matcher):
373 yield x
377 yield x
374 for x in self.topfiles():
378 for x in self.topfiles():
375 yield x
379 yield x
376
380
377 def copylist(self):
381 def copylist(self):
378 return ['requires'] + _data.split()
382 return ['requires'] + _data.split()
379
383
380 def write(self, tr):
384 def write(self, tr):
381 pass
385 pass
382
386
383 def invalidatecaches(self):
387 def invalidatecaches(self):
384 pass
388 pass
385
389
386 def markremoved(self, fn):
390 def markremoved(self, fn):
387 pass
391 pass
388
392
389 def __contains__(self, path):
393 def __contains__(self, path):
390 '''Checks if the store contains path'''
394 '''Checks if the store contains path'''
391 path = "/".join(("data", path))
395 path = "/".join(("data", path))
392 # file?
396 # file?
393 if self.vfs.exists(path + ".i"):
397 if self.vfs.exists(path + ".i"):
394 return True
398 return True
395 # dir?
399 # dir?
396 if not path.endswith("/"):
400 if not path.endswith("/"):
397 path = path + "/"
401 path = path + "/"
398 return self.vfs.exists(path)
402 return self.vfs.exists(path)
399
403
400 class encodedstore(basicstore):
404 class encodedstore(basicstore):
401 def __init__(self, path, vfstype):
405 def __init__(self, path, vfstype):
402 vfs = vfstype(path + '/store')
406 vfs = vfstype(path + '/store')
403 self.path = vfs.base
407 self.path = vfs.base
404 self.createmode = _calcmode(vfs)
408 self.createmode = _calcmode(vfs)
405 vfs.createmode = self.createmode
409 vfs.createmode = self.createmode
406 self.rawvfs = vfs
410 self.rawvfs = vfs
407 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
411 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
408 self.opener = self.vfs
412 self.opener = self.vfs
409
413
410 def datafiles(self):
414 def datafiles(self, matcher=None):
411 for a, b, size in super(encodedstore, self).datafiles():
415 for a, b, size in super(encodedstore, self).datafiles():
412 try:
416 try:
413 a = decodefilename(a)
417 a = decodefilename(a)
414 except KeyError:
418 except KeyError:
415 a = None
419 a = None
416 yield a, b, size
420 yield a, b, size
417
421
418 def join(self, f):
422 def join(self, f):
419 return self.path + '/' + encodefilename(f)
423 return self.path + '/' + encodefilename(f)
420
424
421 def copylist(self):
425 def copylist(self):
422 return (['requires', '00changelog.i'] +
426 return (['requires', '00changelog.i'] +
423 ['store/' + f for f in _data.split()])
427 ['store/' + f for f in _data.split()])
424
428
425 class fncache(object):
429 class fncache(object):
426 # the filename used to be partially encoded
430 # the filename used to be partially encoded
427 # hence the encodedir/decodedir dance
431 # hence the encodedir/decodedir dance
428 def __init__(self, vfs):
432 def __init__(self, vfs):
429 self.vfs = vfs
433 self.vfs = vfs
430 self.entries = None
434 self.entries = None
431 self._dirty = False
435 self._dirty = False
432
436
433 def _load(self):
437 def _load(self):
434 '''fill the entries from the fncache file'''
438 '''fill the entries from the fncache file'''
435 self._dirty = False
439 self._dirty = False
436 try:
440 try:
437 fp = self.vfs('fncache', mode='rb')
441 fp = self.vfs('fncache', mode='rb')
438 except IOError:
442 except IOError:
439 # skip nonexistent file
443 # skip nonexistent file
440 self.entries = set()
444 self.entries = set()
441 return
445 return
442 self.entries = set(decodedir(fp.read()).splitlines())
446 self.entries = set(decodedir(fp.read()).splitlines())
443 if '' in self.entries:
447 if '' in self.entries:
444 fp.seek(0)
448 fp.seek(0)
445 for n, line in enumerate(util.iterfile(fp)):
449 for n, line in enumerate(util.iterfile(fp)):
446 if not line.rstrip('\n'):
450 if not line.rstrip('\n'):
447 t = _('invalid entry in fncache, line %d') % (n + 1)
451 t = _('invalid entry in fncache, line %d') % (n + 1)
448 raise error.Abort(t)
452 raise error.Abort(t)
449 fp.close()
453 fp.close()
450
454
451 def write(self, tr):
455 def write(self, tr):
452 if self._dirty:
456 if self._dirty:
453 assert self.entries is not None
457 assert self.entries is not None
454 tr.addbackup('fncache')
458 tr.addbackup('fncache')
455 fp = self.vfs('fncache', mode='wb', atomictemp=True)
459 fp = self.vfs('fncache', mode='wb', atomictemp=True)
456 if self.entries:
460 if self.entries:
457 fp.write(encodedir('\n'.join(self.entries) + '\n'))
461 fp.write(encodedir('\n'.join(self.entries) + '\n'))
458 fp.close()
462 fp.close()
459 self._dirty = False
463 self._dirty = False
460
464
461 def add(self, fn):
465 def add(self, fn):
462 if self.entries is None:
466 if self.entries is None:
463 self._load()
467 self._load()
464 if fn not in self.entries:
468 if fn not in self.entries:
465 self._dirty = True
469 self._dirty = True
466 self.entries.add(fn)
470 self.entries.add(fn)
467
471
468 def remove(self, fn):
472 def remove(self, fn):
469 if self.entries is None:
473 if self.entries is None:
470 self._load()
474 self._load()
471 try:
475 try:
472 self.entries.remove(fn)
476 self.entries.remove(fn)
473 self._dirty = True
477 self._dirty = True
474 except KeyError:
478 except KeyError:
475 pass
479 pass
476
480
477 def __contains__(self, fn):
481 def __contains__(self, fn):
478 if self.entries is None:
482 if self.entries is None:
479 self._load()
483 self._load()
480 return fn in self.entries
484 return fn in self.entries
481
485
482 def __iter__(self):
486 def __iter__(self):
483 if self.entries is None:
487 if self.entries is None:
484 self._load()
488 self._load()
485 return iter(self.entries)
489 return iter(self.entries)
486
490
487 class _fncachevfs(vfsmod.abstractvfs, vfsmod.proxyvfs):
491 class _fncachevfs(vfsmod.abstractvfs, vfsmod.proxyvfs):
488 def __init__(self, vfs, fnc, encode):
492 def __init__(self, vfs, fnc, encode):
489 vfsmod.proxyvfs.__init__(self, vfs)
493 vfsmod.proxyvfs.__init__(self, vfs)
490 self.fncache = fnc
494 self.fncache = fnc
491 self.encode = encode
495 self.encode = encode
492
496
493 def __call__(self, path, mode='r', *args, **kw):
497 def __call__(self, path, mode='r', *args, **kw):
494 encoded = self.encode(path)
498 encoded = self.encode(path)
495 if mode not in ('r', 'rb') and (path.startswith('data/') or
499 if mode not in ('r', 'rb') and (path.startswith('data/') or
496 path.startswith('meta/')):
500 path.startswith('meta/')):
497 # do not trigger a fncache load when adding a file that already is
501 # do not trigger a fncache load when adding a file that already is
498 # known to exist.
502 # known to exist.
499 notload = self.fncache.entries is None and self.vfs.exists(encoded)
503 notload = self.fncache.entries is None and self.vfs.exists(encoded)
500 if notload and 'a' in mode and not self.vfs.stat(encoded).st_size:
504 if notload and 'a' in mode and not self.vfs.stat(encoded).st_size:
501 # when appending to an existing file, if the file has size zero,
505 # when appending to an existing file, if the file has size zero,
502 # it should be considered as missing. Such zero-size files are
506 # it should be considered as missing. Such zero-size files are
503 # the result of truncation when a transaction is aborted.
507 # the result of truncation when a transaction is aborted.
504 notload = False
508 notload = False
505 if not notload:
509 if not notload:
506 self.fncache.add(path)
510 self.fncache.add(path)
507 return self.vfs(encoded, mode, *args, **kw)
511 return self.vfs(encoded, mode, *args, **kw)
508
512
509 def join(self, path):
513 def join(self, path):
510 if path:
514 if path:
511 return self.vfs.join(self.encode(path))
515 return self.vfs.join(self.encode(path))
512 else:
516 else:
513 return self.vfs.join(path)
517 return self.vfs.join(path)
514
518
515 class fncachestore(basicstore):
519 class fncachestore(basicstore):
516 def __init__(self, path, vfstype, dotencode):
520 def __init__(self, path, vfstype, dotencode):
517 if dotencode:
521 if dotencode:
518 encode = _pathencode
522 encode = _pathencode
519 else:
523 else:
520 encode = _plainhybridencode
524 encode = _plainhybridencode
521 self.encode = encode
525 self.encode = encode
522 vfs = vfstype(path + '/store')
526 vfs = vfstype(path + '/store')
523 self.path = vfs.base
527 self.path = vfs.base
524 self.pathsep = self.path + '/'
528 self.pathsep = self.path + '/'
525 self.createmode = _calcmode(vfs)
529 self.createmode = _calcmode(vfs)
526 vfs.createmode = self.createmode
530 vfs.createmode = self.createmode
527 self.rawvfs = vfs
531 self.rawvfs = vfs
528 fnc = fncache(vfs)
532 fnc = fncache(vfs)
529 self.fncache = fnc
533 self.fncache = fnc
530 self.vfs = _fncachevfs(vfs, fnc, encode)
534 self.vfs = _fncachevfs(vfs, fnc, encode)
531 self.opener = self.vfs
535 self.opener = self.vfs
532
536
533 def join(self, f):
537 def join(self, f):
534 return self.pathsep + self.encode(f)
538 return self.pathsep + self.encode(f)
535
539
536 def getsize(self, path):
540 def getsize(self, path):
537 return self.rawvfs.stat(path).st_size
541 return self.rawvfs.stat(path).st_size
538
542
539 def datafiles(self):
543 def datafiles(self, matcher=None):
540 for f in sorted(self.fncache):
544 for f in sorted(self.fncache):
541 ef = self.encode(f)
545 ef = self.encode(f)
542 try:
546 try:
543 yield f, ef, self.getsize(ef)
547 yield f, ef, self.getsize(ef)
544 except OSError as err:
548 except OSError as err:
545 if err.errno != errno.ENOENT:
549 if err.errno != errno.ENOENT:
546 raise
550 raise
547
551
548 def copylist(self):
552 def copylist(self):
549 d = ('narrowspec data meta dh fncache phaseroots obsstore'
553 d = ('narrowspec data meta dh fncache phaseroots obsstore'
550 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
554 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
551 return (['requires', '00changelog.i'] +
555 return (['requires', '00changelog.i'] +
552 ['store/' + f for f in d.split()])
556 ['store/' + f for f in d.split()])
553
557
554 def write(self, tr):
558 def write(self, tr):
555 self.fncache.write(tr)
559 self.fncache.write(tr)
556
560
557 def invalidatecaches(self):
561 def invalidatecaches(self):
558 self.fncache.entries = None
562 self.fncache.entries = None
559
563
560 def markremoved(self, fn):
564 def markremoved(self, fn):
561 self.fncache.remove(fn)
565 self.fncache.remove(fn)
562
566
563 def _exists(self, f):
567 def _exists(self, f):
564 ef = self.encode(f)
568 ef = self.encode(f)
565 try:
569 try:
566 self.getsize(ef)
570 self.getsize(ef)
567 return True
571 return True
568 except OSError as err:
572 except OSError as err:
569 if err.errno != errno.ENOENT:
573 if err.errno != errno.ENOENT:
570 raise
574 raise
571 # nonexistent entry
575 # nonexistent entry
572 return False
576 return False
573
577
574 def __contains__(self, path):
578 def __contains__(self, path):
575 '''Checks if the store contains path'''
579 '''Checks if the store contains path'''
576 path = "/".join(("data", path))
580 path = "/".join(("data", path))
577 # check for files (exact match)
581 # check for files (exact match)
578 e = path + '.i'
582 e = path + '.i'
579 if e in self.fncache and self._exists(e):
583 if e in self.fncache and self._exists(e):
580 return True
584 return True
581 # now check for directories (prefix match)
585 # now check for directories (prefix match)
582 if not path.endswith('/'):
586 if not path.endswith('/'):
583 path += '/'
587 path += '/'
584 for e in self.fncache:
588 for e in self.fncache:
585 if e.startswith(path) and self._exists(e):
589 if e.startswith(path) and self._exists(e):
586 return True
590 return True
587 return False
591 return False
General Comments 0
You need to be logged in to leave comments. Login now