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