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