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