##// END OF EJS Templates
introduce fncache repository layout...
Adrian Buehlmann -
r7229:7946503e default
parent child Browse files
Show More
@@ -16,7 +16,7 import merge as merge_
16
16
17 class localrepository(repo.repository):
17 class localrepository(repo.repository):
18 capabilities = util.set(('lookup', 'changegroupsubset'))
18 capabilities = util.set(('lookup', 'changegroupsubset'))
19 supported = ('revlogv1', 'store')
19 supported = ('revlogv1', 'store', 'fncache')
20
20
21 def __init__(self, parentui, path=None, create=0):
21 def __init__(self, parentui, path=None, create=0):
22 repo.repository.__init__(self)
22 repo.repository.__init__(self)
@@ -35,6 +35,7 class localrepository(repo.repository):
35 if parentui.configbool('format', 'usestore', True):
35 if parentui.configbool('format', 'usestore', True):
36 os.mkdir(os.path.join(self.path, "store"))
36 os.mkdir(os.path.join(self.path, "store"))
37 requirements.append("store")
37 requirements.append("store")
38 requirements.append("fncache")
38 # create an invalid changelog
39 # create an invalid changelog
39 self.opener("00changelog.i", "a").write(
40 self.opener("00changelog.i", "a").write(
40 '\0\0\0\2' # represents revlogv2
41 '\0\0\0\2' # represents revlogv2
@@ -5,8 +5,11
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 import os, stat, osutil, util
9 import os, stat, osutil, util
9
10
11 _sha = util.sha1
12
10 def _buildencodefun():
13 def _buildencodefun():
11 e = '_'
14 e = '_'
12 win_reserved = [ord(x) for x in '\\:*?"<>|']
15 win_reserved = [ord(x) for x in '\\:*?"<>|']
@@ -35,6 +38,93 def _buildencodefun():
35
38
36 encodefilename, decodefilename = _buildencodefun()
39 encodefilename, decodefilename = _buildencodefun()
37
40
41 def _build_lower_encodefun():
42 win_reserved = [ord(x) for x in '\\:*?"<>|']
43 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
44 for x in (range(32) + range(126, 256) + win_reserved):
45 cmap[chr(x)] = "~%02x" % x
46 for x in range(ord("A"), ord("Z")+1):
47 cmap[chr(x)] = chr(x).lower()
48 return lambda s: "".join([cmap[c] for c in s])
49
50 lowerencode = _build_lower_encodefun()
51
52 _windows_reserved_filenames = '''con prn aux nul
53 com1 com2 com3 com4 com5 com6 com7 com8 com9
54 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
55 def auxencode(path):
56 res = []
57 for n in path.split('/'):
58 if n:
59 base = n.split('.')[0]
60 if base and (base in _windows_reserved_filenames):
61 # encode third letter ('aux' -> 'au~78')
62 ec = "~%02x" % ord(n[2])
63 n = n[0:2] + ec + n[3:]
64 res.append(n)
65 return '/'.join(res)
66
67 MAX_PATH_LEN_IN_HGSTORE = 120
68 DIR_PREFIX_LEN = 8
69 _MAX_SHORTENED_DIRS_LEN = 8 * (DIR_PREFIX_LEN + 1) - 4
70 def hybridencode(path):
71 '''encodes path with a length limit
72
73 Encodes all paths that begin with 'data/', according to the following.
74
75 Default encoding (reversible):
76
77 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
78 characters are encoded as '~xx', where xx is the two digit hex code
79 of the character (see encodefilename).
80 Relevant path components consisting of Windows reserved filenames are
81 masked by encoding the third character ('aux' -> 'au~78', see auxencode).
82
83 Hashed encoding (not reversible):
84
85 If the default-encoded path is longer than MAX_PATH_LEN_IN_HGSTORE, a
86 non-reversible hybrid hashing of the path is done instead.
87 This encoding uses up to DIR_PREFIX_LEN characters of all directory
88 levels of the lowerencoded path, but not more levels than can fit into
89 _MAX_SHORTENED_DIRS_LEN.
90 Then follows the filler followed by the sha digest of the full path.
91 The filler is the beginning of the basename of the lowerencoded path
92 (the basename is everything after the last path separator). The filler
93 is as long as possible, filling in characters from the basename until
94 the encoded path has MAX_PATH_LEN_IN_HGSTORE characters (or all chars
95 of the basename have been taken).
96 The extension (e.g. '.i' or '.d') is preserved.
97
98 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
99 encoding was used.
100 '''
101 if not path.startswith('data/'):
102 return path
103 ndpath = path[len('data/'):]
104 res = 'data/' + auxencode(encodefilename(ndpath))
105 if len(res) > MAX_PATH_LEN_IN_HGSTORE:
106 digest = _sha(path).hexdigest()
107 aep = auxencode(lowerencode(ndpath))
108 _root, ext = os.path.splitext(aep)
109 parts = aep.split('/')
110 basename = parts[-1]
111 sdirs = []
112 for p in parts[:-1]:
113 d = p[:DIR_PREFIX_LEN]
114 t = '/'.join(sdirs) + '/' + d
115 if len(t) > _MAX_SHORTENED_DIRS_LEN:
116 break
117 sdirs.append(d)
118 dirs = '/'.join(sdirs)
119 if len(dirs) > 0:
120 dirs += '/'
121 res = 'dh/' + dirs + digest + ext
122 space_left = MAX_PATH_LEN_IN_HGSTORE - len(res)
123 if space_left > 0:
124 filler = basename[:space_left]
125 res = 'dh/' + dirs + filler + digest + ext
126 return res
127
38 def _calcmode(path):
128 def _calcmode(path):
39 try:
129 try:
40 # files in .hg/ will be created using this mode
130 # files in .hg/ will be created using this mode
@@ -120,8 +210,83 class encodedstore(basicstore):
120 return (['requires', '00changelog.i'] +
210 return (['requires', '00changelog.i'] +
121 [self.pathjoiner('store', f) for f in _data.split()])
211 [self.pathjoiner('store', f) for f in _data.split()])
122
212
213 def fncache(opener):
214 '''yields the entries in the fncache file'''
215 try:
216 fp = opener('fncache', mode='rb')
217 except IOError:
218 # skip nonexistent file
219 return
220 for n, line in enumerate(fp):
221 if (len(line) < 2) or (line[-1] != '\n'):
222 t = _('invalid entry in fncache, line %s') % (n + 1)
223 raise util.Abort(t)
224 yield line[:-1]
225 fp.close()
226
227 class fncacheopener(object):
228 def __init__(self, opener):
229 self.opener = opener
230 self.entries = None
231
232 def loadfncache(self):
233 self.entries = {}
234 for f in fncache(self.opener):
235 self.entries[f] = True
236
237 def __call__(self, path, mode='r', *args, **kw):
238 if mode not in ('r', 'rb') and path.startswith('data/'):
239 if self.entries is None:
240 self.loadfncache()
241 if path not in self.entries:
242 self.opener('fncache', 'ab').write(path + '\n')
243 # fncache may contain non-existent files after rollback / strip
244 self.entries[path] = True
245 return self.opener(hybridencode(path), mode, *args, **kw)
246
247 class fncachestore(basicstore):
248 def __init__(self, path, opener, pathjoiner):
249 self.pathjoiner = pathjoiner
250 self.path = self.pathjoiner(path, 'store')
251 self.createmode = _calcmode(self.path)
252 self._op = opener(self.path)
253 self._op.createmode = self.createmode
254 self.opener = fncacheopener(self._op)
255
256 def join(self, f):
257 return self.pathjoiner(self.path, hybridencode(f))
258
259 def datafiles(self):
260 rewrite = False
261 existing = []
262 pjoin = self.pathjoiner
263 spath = self.path
264 for f in fncache(self._op):
265 ef = hybridencode(f)
266 try:
267 st = os.stat(pjoin(spath, ef))
268 yield f, ef, st.st_size
269 existing.append(f)
270 except OSError:
271 # nonexistent entry
272 rewrite = True
273 if rewrite:
274 # rewrite fncache to remove nonexistent entries
275 # (may be caused by rollback / strip)
276 fp = self._op('fncache', mode='wb')
277 for p in existing:
278 fp.write(p + '\n')
279 fp.close()
280
281 def copylist(self):
282 d = _data + ' dh fncache'
283 return (['requires', '00changelog.i'] +
284 [self.pathjoiner('store', f) for f in d.split()])
285
123 def store(requirements, path, opener, pathjoiner=None):
286 def store(requirements, path, opener, pathjoiner=None):
124 pathjoiner = pathjoiner or os.path.join
287 pathjoiner = pathjoiner or os.path.join
125 if 'store' in requirements:
288 if 'store' in requirements:
289 if 'fncache' in requirements:
290 return fncachestore(path, opener, pathjoiner)
126 return encodedstore(path, opener, pathjoiner)
291 return encodedstore(path, opener, pathjoiner)
127 return basicstore(path, opener, pathjoiner)
292 return basicstore(path, opener, pathjoiner)
@@ -2,6 +2,7
2
2
3 CONTRIBDIR=$TESTDIR/../contrib
3 CONTRIBDIR=$TESTDIR/../contrib
4
4
5 echo % prepare repo-a
5 mkdir repo-a
6 mkdir repo-a
6 cd repo-a
7 cd repo-a
7 hg init
8 hg init
@@ -18,11 +19,13 hg commit -m third -d '0 0'
18
19
19 hg verify
20 hg verify
20
21
21 echo dumping revlog of file a to stdout:
22 echo
23 echo % dumping revlog of file a to stdout
22 python $CONTRIBDIR/dumprevlog .hg/store/data/a.i
24 python $CONTRIBDIR/dumprevlog .hg/store/data/a.i
23 echo dumprevlog done
25 echo % dumprevlog done
24
26
25 # dump all revlogs to file repo.dump
27 echo
28 echo % dump all revlogs to file repo.dump
26 find .hg/store -name "*.i" | sort | xargs python $CONTRIBDIR/dumprevlog > ../repo.dump
29 find .hg/store -name "*.i" | sort | xargs python $CONTRIBDIR/dumprevlog > ../repo.dump
27
30
28 cd ..
31 cd ..
@@ -31,17 +34,28 mkdir repo-b
31 cd repo-b
34 cd repo-b
32 hg init
35 hg init
33
36
34 echo undumping:
37 echo
38 echo % undumping into repo-b
35 python $CONTRIBDIR/undumprevlog < ../repo.dump
39 python $CONTRIBDIR/undumprevlog < ../repo.dump
36 echo undumping done
40 echo % undumping done
41
42 cd ..
37
43
44 echo
45 echo % clone --pull repo-b repo-c to rebuild fncache
46 hg clone --pull -U repo-b repo-c
47
48 cd repo-c
49
50 echo
51 echo % verify repo-c
38 hg verify
52 hg verify
39
53
40 cd ..
54 cd ..
41
55
42 echo comparing repos:
56 echo
43 hg -R repo-b incoming repo-a
57 echo % comparing repos
44 hg -R repo-a incoming repo-b
58 hg -R repo-c incoming repo-a
45 echo comparing done
59 hg -R repo-a incoming repo-c
46
60
47 exit 0
61 exit 0
@@ -1,9 +1,11
1 % prepare repo-a
1 checking changesets
2 checking changesets
2 checking manifests
3 checking manifests
3 crosschecking files in changesets and manifests
4 crosschecking files in changesets and manifests
4 checking files
5 checking files
5 1 files, 3 changesets, 3 total revisions
6 1 files, 3 changesets, 3 total revisions
6 dumping revlog of file a to stdout:
7
8 % dumping revlog of file a to stdout
7 file: .hg/store/data/a.i
9 file: .hg/store/data/a.i
8 node: 183d2312b35066fb6b3b449b84efc370d50993d0
10 node: 183d2312b35066fb6b3b449b84efc370d50993d0
9 linkrev: 0
11 linkrev: 0
@@ -32,22 +34,34 adding to file a
32 adding more to file a
34 adding more to file a
33
35
34 -end-
36 -end-
35 dumprevlog done
37 % dumprevlog done
36 undumping:
38
39 % dump all revlogs to file repo.dump
40
41 % undumping into repo-b
37 .hg/store/00changelog.i
42 .hg/store/00changelog.i
38 .hg/store/00manifest.i
43 .hg/store/00manifest.i
39 .hg/store/data/a.i
44 .hg/store/data/a.i
40 undumping done
45 % undumping done
46
47 % clone --pull repo-b repo-c to rebuild fncache
48 requesting all changes
49 adding changesets
50 adding manifests
51 adding file changes
52 added 3 changesets with 3 changes to 1 files
53
54 % verify repo-c
41 checking changesets
55 checking changesets
42 checking manifests
56 checking manifests
43 crosschecking files in changesets and manifests
57 crosschecking files in changesets and manifests
44 checking files
58 checking files
45 1 files, 3 changesets, 3 total revisions
59 1 files, 3 changesets, 3 total revisions
46 comparing repos:
60
61 % comparing repos
47 comparing with repo-a
62 comparing with repo-a
48 searching for changes
63 searching for changes
49 no changes found
64 no changes found
50 comparing with repo-b
65 comparing with repo-c
51 searching for changes
66 searching for changes
52 no changes found
67 no changes found
53 comparing done
@@ -22,6 +22,7 00770 ./.hg/store/data/
22 00770 ./.hg/store/data/dir/
22 00770 ./.hg/store/data/dir/
23 00660 ./.hg/store/data/dir/bar.i
23 00660 ./.hg/store/data/dir/bar.i
24 00660 ./.hg/store/data/foo.i
24 00660 ./.hg/store/data/foo.i
25 00660 ./.hg/store/fncache
25 00660 ./.hg/store/undo
26 00660 ./.hg/store/undo
26 00660 ./.hg/undo.branch
27 00660 ./.hg/undo.branch
27 00660 ./.hg/undo.dirstate
28 00660 ./.hg/undo.dirstate
@@ -49,6 +50,7 00770 ../push/.hg/store/data/
49 00770 ../push/.hg/store/data/dir/
50 00770 ../push/.hg/store/data/dir/
50 00660 ../push/.hg/store/data/dir/bar.i
51 00660 ../push/.hg/store/data/dir/bar.i
51 00660 ../push/.hg/store/data/foo.i
52 00660 ../push/.hg/store/data/foo.i
53 00660 ../push/.hg/store/fncache
52 00660 ../push/.hg/store/undo
54 00660 ../push/.hg/store/undo
53 00660 ../push/.hg/undo.branch
55 00660 ../push/.hg/undo.branch
54 00660 ../push/.hg/undo.dirstate
56 00660 ../push/.hg/undo.dirstate
@@ -3,6 +3,7 store created
3 00changelog.i created
3 00changelog.i created
4 revlogv1
4 revlogv1
5 store
5 store
6 fncache
6 adding foo
7 adding foo
7 # creating repo with old format
8 # creating repo with old format
8 revlogv1
9 revlogv1
@@ -17,7 +17,6 checking changesets
17 checking manifests
17 checking manifests
18 crosschecking files in changesets and manifests
18 crosschecking files in changesets and manifests
19 checking files
19 checking files
20 ?: cannot decode filename 'data/X_f_o_o.txt.i'
21 data/FOO.txt.i@0: missing revlog!
20 data/FOO.txt.i@0: missing revlog!
22 0: empty or missing FOO.txt
21 0: empty or missing FOO.txt
23 FOO.txt@0: f62022d3d590 in manifests not found
22 FOO.txt@0: f62022d3d590 in manifests not found
@@ -27,8 +26,6 checking files
27 data/bar.txt.i@0: missing revlog!
26 data/bar.txt.i@0: missing revlog!
28 0: empty or missing bar.txt
27 0: empty or missing bar.txt
29 bar.txt@0: 256559129457 in manifests not found
28 bar.txt@0: 256559129457 in manifests not found
30 warning: orphan revlog 'data/xbar.txt.i'
31 3 files, 1 changesets, 0 total revisions
29 3 files, 1 changesets, 0 total revisions
32 1 warnings encountered!
30 9 integrity errors encountered!
33 10 integrity errors encountered!
34 (first damaged changeset appears to be 0)
31 (first damaged changeset appears to be 0)
General Comments 0
You need to be logged in to leave comments. Login now