##// END OF EJS Templates
persistent-nodemap: also exchange the nodemap data over the wire...
marmoute -
r47040:7c1367c0 default
parent child Browse files
Show More
@@ -1,746 +1,750 b''
1 1 # store.py - repository store handling for Mercurial
2 2 #
3 3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import functools
12 12 import os
13 13 import stat
14 14
15 15 from .i18n import _
16 16 from .pycompat import getattr
17 17 from .node import hex
18 18 from . import (
19 19 changelog,
20 20 error,
21 21 manifest,
22 22 policy,
23 23 pycompat,
24 24 util,
25 25 vfs as vfsmod,
26 26 )
27 27 from .utils import hashutil
28 28
29 29 parsers = policy.importmod('parsers')
30 30 # how much bytes should be read from fncache in one read
31 31 # It is done to prevent loading large fncache files into memory
32 32 fncache_chunksize = 10 ** 6
33 33
34 34
35 35 def _matchtrackedpath(path, matcher):
36 36 """parses a fncache entry and returns whether the entry is tracking a path
37 37 matched by matcher or not.
38 38
39 39 If matcher is None, returns True"""
40 40
41 41 if matcher is None:
42 42 return True
43 43 path = decodedir(path)
44 44 if path.startswith(b'data/'):
45 45 return matcher(path[len(b'data/') : -len(b'.i')])
46 46 elif path.startswith(b'meta/'):
47 47 return matcher.visitdir(path[len(b'meta/') : -len(b'/00manifest.i')])
48 48
49 49 raise error.ProgrammingError(b"cannot decode path %s" % path)
50 50
51 51
52 52 # This avoids a collision between a file named foo and a dir named
53 53 # foo.i or foo.d
54 54 def _encodedir(path):
55 55 """
56 56 >>> _encodedir(b'data/foo.i')
57 57 'data/foo.i'
58 58 >>> _encodedir(b'data/foo.i/bla.i')
59 59 'data/foo.i.hg/bla.i'
60 60 >>> _encodedir(b'data/foo.i.hg/bla.i')
61 61 'data/foo.i.hg.hg/bla.i'
62 62 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
63 63 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
64 64 """
65 65 return (
66 66 path.replace(b".hg/", b".hg.hg/")
67 67 .replace(b".i/", b".i.hg/")
68 68 .replace(b".d/", b".d.hg/")
69 69 )
70 70
71 71
72 72 encodedir = getattr(parsers, 'encodedir', _encodedir)
73 73
74 74
75 75 def decodedir(path):
76 76 """
77 77 >>> decodedir(b'data/foo.i')
78 78 'data/foo.i'
79 79 >>> decodedir(b'data/foo.i.hg/bla.i')
80 80 'data/foo.i/bla.i'
81 81 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
82 82 'data/foo.i.hg/bla.i'
83 83 """
84 84 if b".hg/" not in path:
85 85 return path
86 86 return (
87 87 path.replace(b".d.hg/", b".d/")
88 88 .replace(b".i.hg/", b".i/")
89 89 .replace(b".hg.hg/", b".hg/")
90 90 )
91 91
92 92
93 93 def _reserved():
94 94 """characters that are problematic for filesystems
95 95
96 96 * ascii escapes (0..31)
97 97 * ascii hi (126..255)
98 98 * windows specials
99 99
100 100 these characters will be escaped by encodefunctions
101 101 """
102 102 winreserved = [ord(x) for x in u'\\:*?"<>|']
103 103 for x in range(32):
104 104 yield x
105 105 for x in range(126, 256):
106 106 yield x
107 107 for x in winreserved:
108 108 yield x
109 109
110 110
111 111 def _buildencodefun():
112 112 """
113 113 >>> enc, dec = _buildencodefun()
114 114
115 115 >>> enc(b'nothing/special.txt')
116 116 'nothing/special.txt'
117 117 >>> dec(b'nothing/special.txt')
118 118 'nothing/special.txt'
119 119
120 120 >>> enc(b'HELLO')
121 121 '_h_e_l_l_o'
122 122 >>> dec(b'_h_e_l_l_o')
123 123 'HELLO'
124 124
125 125 >>> enc(b'hello:world?')
126 126 'hello~3aworld~3f'
127 127 >>> dec(b'hello~3aworld~3f')
128 128 'hello:world?'
129 129
130 130 >>> enc(b'the\\x07quick\\xADshot')
131 131 'the~07quick~adshot'
132 132 >>> dec(b'the~07quick~adshot')
133 133 'the\\x07quick\\xadshot'
134 134 """
135 135 e = b'_'
136 136 xchr = pycompat.bytechr
137 137 asciistr = list(map(xchr, range(127)))
138 138 capitals = list(range(ord(b"A"), ord(b"Z") + 1))
139 139
140 140 cmap = {x: x for x in asciistr}
141 141 for x in _reserved():
142 142 cmap[xchr(x)] = b"~%02x" % x
143 143 for x in capitals + [ord(e)]:
144 144 cmap[xchr(x)] = e + xchr(x).lower()
145 145
146 146 dmap = {}
147 147 for k, v in pycompat.iteritems(cmap):
148 148 dmap[v] = k
149 149
150 150 def decode(s):
151 151 i = 0
152 152 while i < len(s):
153 153 for l in pycompat.xrange(1, 4):
154 154 try:
155 155 yield dmap[s[i : i + l]]
156 156 i += l
157 157 break
158 158 except KeyError:
159 159 pass
160 160 else:
161 161 raise KeyError
162 162
163 163 return (
164 164 lambda s: b''.join(
165 165 [cmap[s[c : c + 1]] for c in pycompat.xrange(len(s))]
166 166 ),
167 167 lambda s: b''.join(list(decode(s))),
168 168 )
169 169
170 170
171 171 _encodefname, _decodefname = _buildencodefun()
172 172
173 173
174 174 def encodefilename(s):
175 175 """
176 176 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
177 177 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
178 178 """
179 179 return _encodefname(encodedir(s))
180 180
181 181
182 182 def decodefilename(s):
183 183 """
184 184 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
185 185 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
186 186 """
187 187 return decodedir(_decodefname(s))
188 188
189 189
190 190 def _buildlowerencodefun():
191 191 """
192 192 >>> f = _buildlowerencodefun()
193 193 >>> f(b'nothing/special.txt')
194 194 'nothing/special.txt'
195 195 >>> f(b'HELLO')
196 196 'hello'
197 197 >>> f(b'hello:world?')
198 198 'hello~3aworld~3f'
199 199 >>> f(b'the\\x07quick\\xADshot')
200 200 'the~07quick~adshot'
201 201 """
202 202 xchr = pycompat.bytechr
203 203 cmap = {xchr(x): xchr(x) for x in pycompat.xrange(127)}
204 204 for x in _reserved():
205 205 cmap[xchr(x)] = b"~%02x" % x
206 206 for x in range(ord(b"A"), ord(b"Z") + 1):
207 207 cmap[xchr(x)] = xchr(x).lower()
208 208
209 209 def lowerencode(s):
210 210 return b"".join([cmap[c] for c in pycompat.iterbytestr(s)])
211 211
212 212 return lowerencode
213 213
214 214
215 215 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
216 216
217 217 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
218 218 _winres3 = (b'aux', b'con', b'prn', b'nul') # length 3
219 219 _winres4 = (b'com', b'lpt') # length 4 (with trailing 1..9)
220 220
221 221
222 222 def _auxencode(path, dotencode):
223 223 """
224 224 Encodes filenames containing names reserved by Windows or which end in
225 225 period or space. Does not touch other single reserved characters c.
226 226 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
227 227 Additionally encodes space or period at the beginning, if dotencode is
228 228 True. Parameter path is assumed to be all lowercase.
229 229 A segment only needs encoding if a reserved name appears as a
230 230 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
231 231 doesn't need encoding.
232 232
233 233 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
234 234 >>> _auxencode(s.split(b'/'), True)
235 235 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
236 236 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
237 237 >>> _auxencode(s.split(b'/'), False)
238 238 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
239 239 >>> _auxencode([b'foo. '], True)
240 240 ['foo.~20']
241 241 >>> _auxencode([b' .foo'], True)
242 242 ['~20.foo']
243 243 """
244 244 for i, n in enumerate(path):
245 245 if not n:
246 246 continue
247 247 if dotencode and n[0] in b'. ':
248 248 n = b"~%02x" % ord(n[0:1]) + n[1:]
249 249 path[i] = n
250 250 else:
251 251 l = n.find(b'.')
252 252 if l == -1:
253 253 l = len(n)
254 254 if (l == 3 and n[:3] in _winres3) or (
255 255 l == 4
256 256 and n[3:4] <= b'9'
257 257 and n[3:4] >= b'1'
258 258 and n[:3] in _winres4
259 259 ):
260 260 # encode third letter ('aux' -> 'au~78')
261 261 ec = b"~%02x" % ord(n[2:3])
262 262 n = n[0:2] + ec + n[3:]
263 263 path[i] = n
264 264 if n[-1] in b'. ':
265 265 # encode last period or space ('foo...' -> 'foo..~2e')
266 266 path[i] = n[:-1] + b"~%02x" % ord(n[-1:])
267 267 return path
268 268
269 269
270 270 _maxstorepathlen = 120
271 271 _dirprefixlen = 8
272 272 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
273 273
274 274
275 275 def _hashencode(path, dotencode):
276 276 digest = hex(hashutil.sha1(path).digest())
277 277 le = lowerencode(path[5:]).split(b'/') # skips prefix 'data/' or 'meta/'
278 278 parts = _auxencode(le, dotencode)
279 279 basename = parts[-1]
280 280 _root, ext = os.path.splitext(basename)
281 281 sdirs = []
282 282 sdirslen = 0
283 283 for p in parts[:-1]:
284 284 d = p[:_dirprefixlen]
285 285 if d[-1] in b'. ':
286 286 # Windows can't access dirs ending in period or space
287 287 d = d[:-1] + b'_'
288 288 if sdirslen == 0:
289 289 t = len(d)
290 290 else:
291 291 t = sdirslen + 1 + len(d)
292 292 if t > _maxshortdirslen:
293 293 break
294 294 sdirs.append(d)
295 295 sdirslen = t
296 296 dirs = b'/'.join(sdirs)
297 297 if len(dirs) > 0:
298 298 dirs += b'/'
299 299 res = b'dh/' + dirs + digest + ext
300 300 spaceleft = _maxstorepathlen - len(res)
301 301 if spaceleft > 0:
302 302 filler = basename[:spaceleft]
303 303 res = b'dh/' + dirs + filler + digest + ext
304 304 return res
305 305
306 306
307 307 def _hybridencode(path, dotencode):
308 308 """encodes path with a length limit
309 309
310 310 Encodes all paths that begin with 'data/', according to the following.
311 311
312 312 Default encoding (reversible):
313 313
314 314 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
315 315 characters are encoded as '~xx', where xx is the two digit hex code
316 316 of the character (see encodefilename).
317 317 Relevant path components consisting of Windows reserved filenames are
318 318 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
319 319
320 320 Hashed encoding (not reversible):
321 321
322 322 If the default-encoded path is longer than _maxstorepathlen, a
323 323 non-reversible hybrid hashing of the path is done instead.
324 324 This encoding uses up to _dirprefixlen characters of all directory
325 325 levels of the lowerencoded path, but not more levels than can fit into
326 326 _maxshortdirslen.
327 327 Then follows the filler followed by the sha digest of the full path.
328 328 The filler is the beginning of the basename of the lowerencoded path
329 329 (the basename is everything after the last path separator). The filler
330 330 is as long as possible, filling in characters from the basename until
331 331 the encoded path has _maxstorepathlen characters (or all chars of the
332 332 basename have been taken).
333 333 The extension (e.g. '.i' or '.d') is preserved.
334 334
335 335 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
336 336 encoding was used.
337 337 """
338 338 path = encodedir(path)
339 339 ef = _encodefname(path).split(b'/')
340 340 res = b'/'.join(_auxencode(ef, dotencode))
341 341 if len(res) > _maxstorepathlen:
342 342 res = _hashencode(path, dotencode)
343 343 return res
344 344
345 345
346 346 def _pathencode(path):
347 347 de = encodedir(path)
348 348 if len(path) > _maxstorepathlen:
349 349 return _hashencode(de, True)
350 350 ef = _encodefname(de).split(b'/')
351 351 res = b'/'.join(_auxencode(ef, True))
352 352 if len(res) > _maxstorepathlen:
353 353 return _hashencode(de, True)
354 354 return res
355 355
356 356
357 357 _pathencode = getattr(parsers, 'pathencode', _pathencode)
358 358
359 359
360 360 def _plainhybridencode(f):
361 361 return _hybridencode(f, False)
362 362
363 363
364 364 def _calcmode(vfs):
365 365 try:
366 366 # files in .hg/ will be created using this mode
367 367 mode = vfs.stat().st_mode
368 368 # avoid some useless chmods
369 369 if (0o777 & ~util.umask) == (0o777 & mode):
370 370 mode = None
371 371 except OSError:
372 372 mode = None
373 373 return mode
374 374
375 375
376 376 _data = [
377 377 b'bookmarks',
378 378 b'narrowspec',
379 379 b'data',
380 380 b'meta',
381 381 b'00manifest.d',
382 382 b'00manifest.i',
383 383 b'00changelog.d',
384 384 b'00changelog.i',
385 385 b'phaseroots',
386 386 b'obsstore',
387 387 b'requires',
388 388 ]
389 389
390 390
391 391 def isrevlog(f, kind, st):
392 return kind == stat.S_IFREG and f[-2:] in (b'.i', b'.d', b'.n', b'.nd')
392 if kind != stat.S_IFREG:
393 return False
394 if f[-2:] in (b'.i', b'.d', b'.n'):
395 return True
396 return f[-3:] == b'.nd'
393 397
394 398
395 399 class basicstore(object):
396 400 '''base class for local repository stores'''
397 401
398 402 def __init__(self, path, vfstype):
399 403 vfs = vfstype(path)
400 404 self.path = vfs.base
401 405 self.createmode = _calcmode(vfs)
402 406 vfs.createmode = self.createmode
403 407 self.rawvfs = vfs
404 408 self.vfs = vfsmod.filtervfs(vfs, encodedir)
405 409 self.opener = self.vfs
406 410
407 411 def join(self, f):
408 412 return self.path + b'/' + encodedir(f)
409 413
410 414 def _walk(self, relpath, recurse, filefilter=isrevlog):
411 415 '''yields (unencoded, encoded, size)'''
412 416 path = self.path
413 417 if relpath:
414 418 path += b'/' + relpath
415 419 striplen = len(self.path) + 1
416 420 l = []
417 421 if self.rawvfs.isdir(path):
418 422 visit = [path]
419 423 readdir = self.rawvfs.readdir
420 424 while visit:
421 425 p = visit.pop()
422 426 for f, kind, st in readdir(p, stat=True):
423 427 fp = p + b'/' + f
424 428 if filefilter(f, kind, st):
425 429 n = util.pconvert(fp[striplen:])
426 430 l.append((decodedir(n), n, st.st_size))
427 431 elif kind == stat.S_IFDIR and recurse:
428 432 visit.append(fp)
429 433 l.sort()
430 434 return l
431 435
432 436 def changelog(self, trypending):
433 437 return changelog.changelog(self.vfs, trypending=trypending)
434 438
435 439 def manifestlog(self, repo, storenarrowmatch):
436 440 rootstore = manifest.manifestrevlog(self.vfs)
437 441 return manifest.manifestlog(self.vfs, repo, rootstore, storenarrowmatch)
438 442
439 443 def datafiles(self, matcher=None):
440 444 return self._walk(b'data', True) + self._walk(b'meta', True)
441 445
442 446 def topfiles(self):
443 447 # yield manifest before changelog
444 448 return reversed(self._walk(b'', False))
445 449
446 450 def walk(self, matcher=None):
447 451 """yields (unencoded, encoded, size)
448 452
449 453 if a matcher is passed, storage files of only those tracked paths
450 454 are passed with matches the matcher
451 455 """
452 456 # yield data files first
453 457 for x in self.datafiles(matcher):
454 458 yield x
455 459 for x in self.topfiles():
456 460 yield x
457 461
458 462 def copylist(self):
459 463 return _data
460 464
461 465 def write(self, tr):
462 466 pass
463 467
464 468 def invalidatecaches(self):
465 469 pass
466 470
467 471 def markremoved(self, fn):
468 472 pass
469 473
470 474 def __contains__(self, path):
471 475 '''Checks if the store contains path'''
472 476 path = b"/".join((b"data", path))
473 477 # file?
474 478 if self.vfs.exists(path + b".i"):
475 479 return True
476 480 # dir?
477 481 if not path.endswith(b"/"):
478 482 path = path + b"/"
479 483 return self.vfs.exists(path)
480 484
481 485
482 486 class encodedstore(basicstore):
483 487 def __init__(self, path, vfstype):
484 488 vfs = vfstype(path + b'/store')
485 489 self.path = vfs.base
486 490 self.createmode = _calcmode(vfs)
487 491 vfs.createmode = self.createmode
488 492 self.rawvfs = vfs
489 493 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
490 494 self.opener = self.vfs
491 495
492 496 def datafiles(self, matcher=None):
493 497 for a, b, size in super(encodedstore, self).datafiles():
494 498 try:
495 499 a = decodefilename(a)
496 500 except KeyError:
497 501 a = None
498 502 if a is not None and not _matchtrackedpath(a, matcher):
499 503 continue
500 504 yield a, b, size
501 505
502 506 def join(self, f):
503 507 return self.path + b'/' + encodefilename(f)
504 508
505 509 def copylist(self):
506 510 return [b'requires', b'00changelog.i'] + [b'store/' + f for f in _data]
507 511
508 512
509 513 class fncache(object):
510 514 # the filename used to be partially encoded
511 515 # hence the encodedir/decodedir dance
512 516 def __init__(self, vfs):
513 517 self.vfs = vfs
514 518 self.entries = None
515 519 self._dirty = False
516 520 # set of new additions to fncache
517 521 self.addls = set()
518 522
519 523 def ensureloaded(self, warn=None):
520 524 """read the fncache file if not already read.
521 525
522 526 If the file on disk is corrupted, raise. If warn is provided,
523 527 warn and keep going instead."""
524 528 if self.entries is None:
525 529 self._load(warn)
526 530
527 531 def _load(self, warn=None):
528 532 '''fill the entries from the fncache file'''
529 533 self._dirty = False
530 534 try:
531 535 fp = self.vfs(b'fncache', mode=b'rb')
532 536 except IOError:
533 537 # skip nonexistent file
534 538 self.entries = set()
535 539 return
536 540
537 541 self.entries = set()
538 542 chunk = b''
539 543 for c in iter(functools.partial(fp.read, fncache_chunksize), b''):
540 544 chunk += c
541 545 try:
542 546 p = chunk.rindex(b'\n')
543 547 self.entries.update(decodedir(chunk[: p + 1]).splitlines())
544 548 chunk = chunk[p + 1 :]
545 549 except ValueError:
546 550 # substring '\n' not found, maybe the entry is bigger than the
547 551 # chunksize, so let's keep iterating
548 552 pass
549 553
550 554 if chunk:
551 555 msg = _(b"fncache does not ends with a newline")
552 556 if warn:
553 557 warn(msg + b'\n')
554 558 else:
555 559 raise error.Abort(
556 560 msg,
557 561 hint=_(
558 562 b"use 'hg debugrebuildfncache' to "
559 563 b"rebuild the fncache"
560 564 ),
561 565 )
562 566 self._checkentries(fp, warn)
563 567 fp.close()
564 568
565 569 def _checkentries(self, fp, warn):
566 570 """ make sure there is no empty string in entries """
567 571 if b'' in self.entries:
568 572 fp.seek(0)
569 573 for n, line in enumerate(util.iterfile(fp)):
570 574 if not line.rstrip(b'\n'):
571 575 t = _(b'invalid entry in fncache, line %d') % (n + 1)
572 576 if warn:
573 577 warn(t + b'\n')
574 578 else:
575 579 raise error.Abort(t)
576 580
577 581 def write(self, tr):
578 582 if self._dirty:
579 583 assert self.entries is not None
580 584 self.entries = self.entries | self.addls
581 585 self.addls = set()
582 586 tr.addbackup(b'fncache')
583 587 fp = self.vfs(b'fncache', mode=b'wb', atomictemp=True)
584 588 if self.entries:
585 589 fp.write(encodedir(b'\n'.join(self.entries) + b'\n'))
586 590 fp.close()
587 591 self._dirty = False
588 592 if self.addls:
589 593 # if we have just new entries, let's append them to the fncache
590 594 tr.addbackup(b'fncache')
591 595 fp = self.vfs(b'fncache', mode=b'ab', atomictemp=True)
592 596 if self.addls:
593 597 fp.write(encodedir(b'\n'.join(self.addls) + b'\n'))
594 598 fp.close()
595 599 self.entries = None
596 600 self.addls = set()
597 601
598 602 def add(self, fn):
599 603 if self.entries is None:
600 604 self._load()
601 605 if fn not in self.entries:
602 606 self.addls.add(fn)
603 607
604 608 def remove(self, fn):
605 609 if self.entries is None:
606 610 self._load()
607 611 if fn in self.addls:
608 612 self.addls.remove(fn)
609 613 return
610 614 try:
611 615 self.entries.remove(fn)
612 616 self._dirty = True
613 617 except KeyError:
614 618 pass
615 619
616 620 def __contains__(self, fn):
617 621 if fn in self.addls:
618 622 return True
619 623 if self.entries is None:
620 624 self._load()
621 625 return fn in self.entries
622 626
623 627 def __iter__(self):
624 628 if self.entries is None:
625 629 self._load()
626 630 return iter(self.entries | self.addls)
627 631
628 632
629 633 class _fncachevfs(vfsmod.proxyvfs):
630 634 def __init__(self, vfs, fnc, encode):
631 635 vfsmod.proxyvfs.__init__(self, vfs)
632 636 self.fncache = fnc
633 637 self.encode = encode
634 638
635 639 def __call__(self, path, mode=b'r', *args, **kw):
636 640 encoded = self.encode(path)
637 641 if mode not in (b'r', b'rb') and (
638 642 path.startswith(b'data/') or path.startswith(b'meta/')
639 643 ):
640 644 # do not trigger a fncache load when adding a file that already is
641 645 # known to exist.
642 646 notload = self.fncache.entries is None and self.vfs.exists(encoded)
643 647 if notload and b'a' in mode and not self.vfs.stat(encoded).st_size:
644 648 # when appending to an existing file, if the file has size zero,
645 649 # it should be considered as missing. Such zero-size files are
646 650 # the result of truncation when a transaction is aborted.
647 651 notload = False
648 652 if not notload:
649 653 self.fncache.add(path)
650 654 return self.vfs(encoded, mode, *args, **kw)
651 655
652 656 def join(self, path):
653 657 if path:
654 658 return self.vfs.join(self.encode(path))
655 659 else:
656 660 return self.vfs.join(path)
657 661
658 662
659 663 class fncachestore(basicstore):
660 664 def __init__(self, path, vfstype, dotencode):
661 665 if dotencode:
662 666 encode = _pathencode
663 667 else:
664 668 encode = _plainhybridencode
665 669 self.encode = encode
666 670 vfs = vfstype(path + b'/store')
667 671 self.path = vfs.base
668 672 self.pathsep = self.path + b'/'
669 673 self.createmode = _calcmode(vfs)
670 674 vfs.createmode = self.createmode
671 675 self.rawvfs = vfs
672 676 fnc = fncache(vfs)
673 677 self.fncache = fnc
674 678 self.vfs = _fncachevfs(vfs, fnc, encode)
675 679 self.opener = self.vfs
676 680
677 681 def join(self, f):
678 682 return self.pathsep + self.encode(f)
679 683
680 684 def getsize(self, path):
681 685 return self.rawvfs.stat(path).st_size
682 686
683 687 def datafiles(self, matcher=None):
684 688 for f in sorted(self.fncache):
685 689 if not _matchtrackedpath(f, matcher):
686 690 continue
687 691 ef = self.encode(f)
688 692 try:
689 693 yield f, ef, self.getsize(ef)
690 694 except OSError as err:
691 695 if err.errno != errno.ENOENT:
692 696 raise
693 697
694 698 def copylist(self):
695 699 d = (
696 700 b'bookmarks',
697 701 b'narrowspec',
698 702 b'data',
699 703 b'meta',
700 704 b'dh',
701 705 b'fncache',
702 706 b'phaseroots',
703 707 b'obsstore',
704 708 b'00manifest.d',
705 709 b'00manifest.i',
706 710 b'00changelog.d',
707 711 b'00changelog.i',
708 712 b'requires',
709 713 )
710 714 return [b'requires', b'00changelog.i'] + [b'store/' + f for f in d]
711 715
712 716 def write(self, tr):
713 717 self.fncache.write(tr)
714 718
715 719 def invalidatecaches(self):
716 720 self.fncache.entries = None
717 721 self.fncache.addls = set()
718 722
719 723 def markremoved(self, fn):
720 724 self.fncache.remove(fn)
721 725
722 726 def _exists(self, f):
723 727 ef = self.encode(f)
724 728 try:
725 729 self.getsize(ef)
726 730 return True
727 731 except OSError as err:
728 732 if err.errno != errno.ENOENT:
729 733 raise
730 734 # nonexistent entry
731 735 return False
732 736
733 737 def __contains__(self, path):
734 738 '''Checks if the store contains path'''
735 739 path = b"/".join((b"data", path))
736 740 # check for files (exact match)
737 741 e = path + b'.i'
738 742 if e in self.fncache and self._exists(e):
739 743 return True
740 744 # now check for directories (prefix match)
741 745 if not path.endswith(b'/'):
742 746 path += b'/'
743 747 for e in self.fncache:
744 748 if e.startswith(path) and self._exists(e):
745 749 return True
746 750 return False
@@ -1,725 +1,727 b''
1 1 ===================================
2 2 Test the persistent on-disk nodemap
3 3 ===================================
4 4
5 5 $ cat << EOF >> $HGRCPATH
6 6 > [format]
7 7 > use-persistent-nodemap=yes
8 8 > [devel]
9 9 > persistent-nodemap=yes
10 10 > EOF
11 11
12 12 $ hg init test-repo --config storage.revlog.persistent-nodemap.slow-path=allow
13 13 $ cd test-repo
14 14
15 15 Check handling of the default slow-path value
16 16
17 17 #if no-pure no-rust
18 18
19 19 $ hg id
20 20 abort: accessing `persistent-nodemap` repository without associated fast implementation.
21 21 (check `hg help config.format.use-persistent-nodemap` for details)
22 22 [255]
23 23
24 24 Unlock further check (we are here to test the feature)
25 25
26 26 $ cat << EOF >> $HGRCPATH
27 27 > [storage]
28 28 > # to avoid spamming the test
29 29 > revlog.persistent-nodemap.slow-path=allow
30 30 > EOF
31 31
32 32 #endif
33 33
34 34
35 35 $ hg debugformat
36 36 format-variant repo
37 37 fncache: yes
38 38 dotencode: yes
39 39 generaldelta: yes
40 40 exp-sharesafe: no
41 41 sparserevlog: yes
42 42 sidedata: no
43 43 persistent-nodemap: yes
44 44 copies-sdc: no
45 45 plain-cl-delta: yes
46 46 compression: zlib
47 47 compression-level: default
48 48 $ hg debugbuilddag .+5000 --new-file
49 49
50 50 $ hg debugnodemap --metadata
51 51 uid: ???????????????? (glob)
52 52 tip-rev: 5000
53 53 tip-node: 6b02b8c7b96654c25e86ba69eda198d7e6ad8b3c
54 54 data-length: 121088
55 55 data-unused: 0
56 56 data-unused: 0.000%
57 57 $ f --size .hg/store/00changelog.n
58 58 .hg/store/00changelog.n: size=70
59 59
60 60 Simple lookup works
61 61
62 62 $ ANYNODE=`hg log --template '{node|short}\n' --rev tip`
63 63 $ hg log -r "$ANYNODE" --template '{rev}\n'
64 64 5000
65 65
66 66
67 67 #if rust
68 68
69 69 $ f --sha256 .hg/store/00changelog-*.nd
70 70 .hg/store/00changelog-????????????????.nd: sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd (glob)
71 71
72 72 $ f --sha256 .hg/store/00manifest-*.nd
73 73 .hg/store/00manifest-????????????????.nd: sha256=97117b1c064ea2f86664a124589e47db0e254e8d34739b5c5cc5bf31c9da2b51 (glob)
74 74 $ hg debugnodemap --dump-new | f --sha256 --size
75 75 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
76 76 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
77 77 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
78 78 0000: 00 00 00 91 00 00 00 20 00 00 00 bb 00 00 00 e7 |....... ........|
79 79 0010: 00 00 00 66 00 00 00 a1 00 00 01 13 00 00 01 22 |...f..........."|
80 80 0020: 00 00 00 23 00 00 00 fc 00 00 00 ba 00 00 00 5e |...#...........^|
81 81 0030: 00 00 00 df 00 00 01 4e 00 00 01 65 00 00 00 ab |.......N...e....|
82 82 0040: 00 00 00 a9 00 00 00 95 00 00 00 73 00 00 00 38 |...........s...8|
83 83 0050: 00 00 00 cc 00 00 00 92 00 00 00 90 00 00 00 69 |...............i|
84 84 0060: 00 00 00 ec 00 00 00 8d 00 00 01 4f 00 00 00 12 |...........O....|
85 85 0070: 00 00 02 0c 00 00 00 77 00 00 00 9c 00 00 00 8f |.......w........|
86 86 0080: 00 00 00 d5 00 00 00 6b 00 00 00 48 00 00 00 b3 |.......k...H....|
87 87 0090: 00 00 00 e5 00 00 00 b5 00 00 00 8e 00 00 00 ad |................|
88 88 00a0: 00 00 00 7b 00 00 00 7c 00 00 00 0b 00 00 00 2b |...{...|.......+|
89 89 00b0: 00 00 00 c6 00 00 00 1e 00 00 01 08 00 00 00 11 |................|
90 90 00c0: 00 00 01 30 00 00 00 26 00 00 01 9c 00 00 00 35 |...0...&.......5|
91 91 00d0: 00 00 00 b8 00 00 01 31 00 00 00 2c 00 00 00 55 |.......1...,...U|
92 92 00e0: 00 00 00 8a 00 00 00 9a 00 00 00 0c 00 00 01 1e |................|
93 93 00f0: 00 00 00 a4 00 00 00 83 00 00 00 c9 00 00 00 8c |................|
94 94
95 95
96 96 #else
97 97
98 98 $ f --sha256 .hg/store/00changelog-*.nd
99 99 .hg/store/00changelog-????????????????.nd: sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 (glob)
100 100 $ hg debugnodemap --dump-new | f --sha256 --size
101 101 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
102 102 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
103 103 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
104 104 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
105 105 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
106 106 0020: ff ff ff ff ff ff f5 06 ff ff ff ff ff ff f3 e7 |................|
107 107 0030: ff ff ef ca ff ff ff ff ff ff ff ff ff ff ff ff |................|
108 108 0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
109 109 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ed 08 |................|
110 110 0060: ff ff ed 66 ff ff ff ff ff ff ff ff ff ff ff ff |...f............|
111 111 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
112 112 0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
113 113 0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f6 ed |................|
114 114 00a0: ff ff ff ff ff ff fe 61 ff ff ff ff ff ff ff ff |.......a........|
115 115 00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
116 116 00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
117 117 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
118 118 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f1 02 |................|
119 119 00f0: ff ff ff ff ff ff ed 1b ff ff ff ff ff ff ff ff |................|
120 120
121 121 #endif
122 122
123 123 $ hg debugnodemap --check
124 124 revision in index: 5001
125 125 revision in nodemap: 5001
126 126
127 127 add a new commit
128 128
129 129 $ hg up
130 130 5001 files updated, 0 files merged, 0 files removed, 0 files unresolved
131 131 $ echo foo > foo
132 132 $ hg add foo
133 133
134 134
135 135 Check slow-path config value handling
136 136 -------------------------------------
137 137
138 138 #if no-pure no-rust
139 139
140 140 $ hg id --config "storage.revlog.persistent-nodemap.slow-path=invalid-value"
141 141 unknown value for config "storage.revlog.persistent-nodemap.slow-path": "invalid-value"
142 142 falling back to default value: abort
143 143 abort: accessing `persistent-nodemap` repository without associated fast implementation.
144 144 (check `hg help config.format.use-persistent-nodemap` for details)
145 145 [255]
146 146
147 147 $ hg log -r . --config "storage.revlog.persistent-nodemap.slow-path=warn"
148 148 warning: accessing `persistent-nodemap` repository without associated fast implementation.
149 149 (check `hg help config.format.use-persistent-nodemap` for details)
150 150 changeset: 5000:6b02b8c7b966
151 151 tag: tip
152 152 user: debugbuilddag
153 153 date: Thu Jan 01 01:23:20 1970 +0000
154 154 summary: r5000
155 155
156 156 $ hg ci -m 'foo' --config "storage.revlog.persistent-nodemap.slow-path=abort"
157 157 abort: accessing `persistent-nodemap` repository without associated fast implementation.
158 158 (check `hg help config.format.use-persistent-nodemap` for details)
159 159 [255]
160 160
161 161 #else
162 162
163 163 $ hg id --config "storage.revlog.persistent-nodemap.slow-path=invalid-value"
164 164 unknown value for config "storage.revlog.persistent-nodemap.slow-path": "invalid-value"
165 165 falling back to default value: abort
166 166 6b02b8c7b966+ tip
167 167
168 168 #endif
169 169
170 170 $ hg ci -m 'foo'
171 171
172 172 #if no-pure no-rust
173 173 $ hg debugnodemap --metadata
174 174 uid: ???????????????? (glob)
175 175 tip-rev: 5001
176 176 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
177 177 data-length: 121088
178 178 data-unused: 0
179 179 data-unused: 0.000%
180 180 #else
181 181 $ hg debugnodemap --metadata
182 182 uid: ???????????????? (glob)
183 183 tip-rev: 5001
184 184 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
185 185 data-length: 121344
186 186 data-unused: 256
187 187 data-unused: 0.211%
188 188 #endif
189 189
190 190 $ f --size .hg/store/00changelog.n
191 191 .hg/store/00changelog.n: size=70
192 192
193 193 (The pure code use the debug code that perform incremental update, the C code reencode from scratch)
194 194
195 195 #if pure
196 196 $ f --sha256 .hg/store/00changelog-*.nd --size
197 197 .hg/store/00changelog-????????????????.nd: size=121344, sha256=cce54c5da5bde3ad72a4938673ed4064c86231b9c64376b082b163fdb20f8f66 (glob)
198 198 #endif
199 199
200 200 #if rust
201 201 $ f --sha256 .hg/store/00changelog-*.nd --size
202 202 .hg/store/00changelog-????????????????.nd: size=121344, sha256=952b042fcf614ceb37b542b1b723e04f18f83efe99bee4e0f5ccd232ef470e58 (glob)
203 203 #endif
204 204
205 205 #if no-pure no-rust
206 206 $ f --sha256 .hg/store/00changelog-*.nd --size
207 207 .hg/store/00changelog-????????????????.nd: size=121088, sha256=df7c06a035b96cb28c7287d349d603baef43240be7736fe34eea419a49702e17 (glob)
208 208 #endif
209 209
210 210 $ hg debugnodemap --check
211 211 revision in index: 5002
212 212 revision in nodemap: 5002
213 213
214 214 Test code path without mmap
215 215 ---------------------------
216 216
217 217 $ echo bar > bar
218 218 $ hg add bar
219 219 $ hg ci -m 'bar' --config storage.revlog.persistent-nodemap.mmap=no
220 220
221 221 $ hg debugnodemap --check --config storage.revlog.persistent-nodemap.mmap=yes
222 222 revision in index: 5003
223 223 revision in nodemap: 5003
224 224 $ hg debugnodemap --check --config storage.revlog.persistent-nodemap.mmap=no
225 225 revision in index: 5003
226 226 revision in nodemap: 5003
227 227
228 228
229 229 #if pure
230 230 $ hg debugnodemap --metadata
231 231 uid: ???????????????? (glob)
232 232 tip-rev: 5002
233 233 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
234 234 data-length: 121600
235 235 data-unused: 512
236 236 data-unused: 0.421%
237 237 $ f --sha256 .hg/store/00changelog-*.nd --size
238 238 .hg/store/00changelog-????????????????.nd: size=121600, sha256=def52503d049ccb823974af313a98a935319ba61f40f3aa06a8be4d35c215054 (glob)
239 239 #endif
240 240 #if rust
241 241 $ hg debugnodemap --metadata
242 242 uid: ???????????????? (glob)
243 243 tip-rev: 5002
244 244 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
245 245 data-length: 121600
246 246 data-unused: 512
247 247 data-unused: 0.421%
248 248 $ f --sha256 .hg/store/00changelog-*.nd --size
249 249 .hg/store/00changelog-????????????????.nd: size=121600, sha256=dacf5b5f1d4585fee7527d0e67cad5b1ba0930e6a0928f650f779aefb04ce3fb (glob)
250 250 #endif
251 251 #if no-pure no-rust
252 252 $ hg debugnodemap --metadata
253 253 uid: ???????????????? (glob)
254 254 tip-rev: 5002
255 255 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
256 256 data-length: 121088
257 257 data-unused: 0
258 258 data-unused: 0.000%
259 259 $ f --sha256 .hg/store/00changelog-*.nd --size
260 260 .hg/store/00changelog-????????????????.nd: size=121088, sha256=59fcede3e3cc587755916ceed29e3c33748cd1aa7d2f91828ac83e7979d935e8 (glob)
261 261 #endif
262 262
263 263 Test force warming the cache
264 264
265 265 $ rm .hg/store/00changelog.n
266 266 $ hg debugnodemap --metadata
267 267 $ hg debugupdatecache
268 268 #if pure
269 269 $ hg debugnodemap --metadata
270 270 uid: ???????????????? (glob)
271 271 tip-rev: 5002
272 272 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
273 273 data-length: 121088
274 274 data-unused: 0
275 275 data-unused: 0.000%
276 276 #else
277 277 $ hg debugnodemap --metadata
278 278 uid: ???????????????? (glob)
279 279 tip-rev: 5002
280 280 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
281 281 data-length: 121088
282 282 data-unused: 0
283 283 data-unused: 0.000%
284 284 #endif
285 285
286 286 Check out of sync nodemap
287 287 =========================
288 288
289 289 First copy old data on the side.
290 290
291 291 $ mkdir ../tmp-copies
292 292 $ cp .hg/store/00changelog-????????????????.nd .hg/store/00changelog.n ../tmp-copies
293 293
294 294 Nodemap lagging behind
295 295 ----------------------
296 296
297 297 make a new commit
298 298
299 299 $ echo bar2 > bar
300 300 $ hg ci -m 'bar2'
301 301 $ NODE=`hg log -r tip -T '{node}\n'`
302 302 $ hg log -r "$NODE" -T '{rev}\n'
303 303 5003
304 304
305 305 If the nodemap is lagging behind, it can catch up fine
306 306
307 307 $ hg debugnodemap --metadata
308 308 uid: ???????????????? (glob)
309 309 tip-rev: 5003
310 310 tip-node: c9329770f979ade2d16912267c38ba5f82fd37b3
311 311 data-length: 121344 (pure !)
312 312 data-length: 121344 (rust !)
313 313 data-length: 121152 (no-rust no-pure !)
314 314 data-unused: 192 (pure !)
315 315 data-unused: 192 (rust !)
316 316 data-unused: 0 (no-rust no-pure !)
317 317 data-unused: 0.158% (pure !)
318 318 data-unused: 0.158% (rust !)
319 319 data-unused: 0.000% (no-rust no-pure !)
320 320 $ cp -f ../tmp-copies/* .hg/store/
321 321 $ hg debugnodemap --metadata
322 322 uid: ???????????????? (glob)
323 323 tip-rev: 5002
324 324 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
325 325 data-length: 121088
326 326 data-unused: 0
327 327 data-unused: 0.000%
328 328 $ hg log -r "$NODE" -T '{rev}\n'
329 329 5003
330 330
331 331 changelog altered
332 332 -----------------
333 333
334 334 If the nodemap is not gated behind a requirements, an unaware client can alter
335 335 the repository so the revlog used to generate the nodemap is not longer
336 336 compatible with the persistent nodemap. We need to detect that.
337 337
338 338 $ hg up "$NODE~5"
339 339 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
340 340 $ echo bar > babar
341 341 $ hg add babar
342 342 $ hg ci -m 'babar'
343 343 created new head
344 344 $ OTHERNODE=`hg log -r tip -T '{node}\n'`
345 345 $ hg log -r "$OTHERNODE" -T '{rev}\n'
346 346 5004
347 347
348 348 $ hg --config extensions.strip= strip --rev "$NODE~1" --no-backup
349 349
350 350 the nodemap should detect the changelog have been tampered with and recover.
351 351
352 352 $ hg debugnodemap --metadata
353 353 uid: ???????????????? (glob)
354 354 tip-rev: 5002
355 355 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
356 356 data-length: 121536 (pure !)
357 357 data-length: 121088 (rust !)
358 358 data-length: 121088 (no-pure no-rust !)
359 359 data-unused: 448 (pure !)
360 360 data-unused: 0 (rust !)
361 361 data-unused: 0 (no-pure no-rust !)
362 362 data-unused: 0.000% (rust !)
363 363 data-unused: 0.369% (pure !)
364 364 data-unused: 0.000% (no-pure no-rust !)
365 365
366 366 $ cp -f ../tmp-copies/* .hg/store/
367 367 $ hg debugnodemap --metadata
368 368 uid: ???????????????? (glob)
369 369 tip-rev: 5002
370 370 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
371 371 data-length: 121088
372 372 data-unused: 0
373 373 data-unused: 0.000%
374 374 $ hg log -r "$OTHERNODE" -T '{rev}\n'
375 375 5002
376 376
377 377 missing data file
378 378 -----------------
379 379
380 380 $ UUID=`hg debugnodemap --metadata| grep 'uid:' | \
381 381 > sed 's/uid: //'`
382 382 $ FILE=.hg/store/00changelog-"${UUID}".nd
383 383 $ mv $FILE ../tmp-data-file
384 384 $ cp .hg/store/00changelog.n ../tmp-docket
385 385
386 386 mercurial don't crash
387 387
388 388 $ hg log -r .
389 389 changeset: 5002:b355ef8adce0
390 390 tag: tip
391 391 parent: 4998:d918ad6d18d3
392 392 user: test
393 393 date: Thu Jan 01 00:00:00 1970 +0000
394 394 summary: babar
395 395
396 396 $ hg debugnodemap --metadata
397 397
398 398 $ hg debugupdatecache
399 399 $ hg debugnodemap --metadata
400 400 uid: * (glob)
401 401 tip-rev: 5002
402 402 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
403 403 data-length: 121088
404 404 data-unused: 0
405 405 data-unused: 0.000%
406 406 $ mv ../tmp-data-file $FILE
407 407 $ mv ../tmp-docket .hg/store/00changelog.n
408 408
409 409 Check transaction related property
410 410 ==================================
411 411
412 412 An up to date nodemap should be available to shell hooks,
413 413
414 414 $ echo dsljfl > a
415 415 $ hg add a
416 416 $ hg ci -m a
417 417 $ hg debugnodemap --metadata
418 418 uid: ???????????????? (glob)
419 419 tip-rev: 5003
420 420 tip-node: a52c5079765b5865d97b993b303a18740113bbb2
421 421 data-length: 121088
422 422 data-unused: 0
423 423 data-unused: 0.000%
424 424 $ echo babar2 > babar
425 425 $ hg ci -m 'babar2' --config "hooks.pretxnclose.nodemap-test=hg debugnodemap --metadata"
426 426 uid: ???????????????? (glob)
427 427 tip-rev: 5004
428 428 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
429 429 data-length: 121280 (pure !)
430 430 data-length: 121280 (rust !)
431 431 data-length: 121088 (no-pure no-rust !)
432 432 data-unused: 192 (pure !)
433 433 data-unused: 192 (rust !)
434 434 data-unused: 0 (no-pure no-rust !)
435 435 data-unused: 0.158% (pure !)
436 436 data-unused: 0.158% (rust !)
437 437 data-unused: 0.000% (no-pure no-rust !)
438 438 $ hg debugnodemap --metadata
439 439 uid: ???????????????? (glob)
440 440 tip-rev: 5004
441 441 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
442 442 data-length: 121280 (pure !)
443 443 data-length: 121280 (rust !)
444 444 data-length: 121088 (no-pure no-rust !)
445 445 data-unused: 192 (pure !)
446 446 data-unused: 192 (rust !)
447 447 data-unused: 0 (no-pure no-rust !)
448 448 data-unused: 0.158% (pure !)
449 449 data-unused: 0.158% (rust !)
450 450 data-unused: 0.000% (no-pure no-rust !)
451 451
452 452 Another process does not see the pending nodemap content during run.
453 453
454 454 $ PATH=$RUNTESTDIR/testlib/:$PATH
455 455 $ echo qpoasp > a
456 456 $ hg ci -m a2 \
457 457 > --config "hooks.pretxnclose=wait-on-file 20 sync-repo-read sync-txn-pending" \
458 458 > --config "hooks.txnclose=touch sync-txn-close" > output.txt 2>&1 &
459 459
460 460 (read the repository while the commit transaction is pending)
461 461
462 462 $ wait-on-file 20 sync-txn-pending && \
463 463 > hg debugnodemap --metadata && \
464 464 > wait-on-file 20 sync-txn-close sync-repo-read
465 465 uid: ???????????????? (glob)
466 466 tip-rev: 5004
467 467 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
468 468 data-length: 121280 (pure !)
469 469 data-length: 121280 (rust !)
470 470 data-length: 121088 (no-pure no-rust !)
471 471 data-unused: 192 (pure !)
472 472 data-unused: 192 (rust !)
473 473 data-unused: 0 (no-pure no-rust !)
474 474 data-unused: 0.158% (pure !)
475 475 data-unused: 0.158% (rust !)
476 476 data-unused: 0.000% (no-pure no-rust !)
477 477 $ hg debugnodemap --metadata
478 478 uid: ???????????????? (glob)
479 479 tip-rev: 5005
480 480 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
481 481 data-length: 121536 (pure !)
482 482 data-length: 121536 (rust !)
483 483 data-length: 121088 (no-pure no-rust !)
484 484 data-unused: 448 (pure !)
485 485 data-unused: 448 (rust !)
486 486 data-unused: 0 (no-pure no-rust !)
487 487 data-unused: 0.369% (pure !)
488 488 data-unused: 0.369% (rust !)
489 489 data-unused: 0.000% (no-pure no-rust !)
490 490
491 491 $ cat output.txt
492 492
493 493 Check that a failing transaction will properly revert the data
494 494
495 495 $ echo plakfe > a
496 496 $ f --size --sha256 .hg/store/00changelog-*.nd
497 497 .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
498 498 .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
499 499 .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
500 500 $ hg ci -m a3 --config "extensions.abort=$RUNTESTDIR/testlib/crash_transaction_late.py"
501 501 transaction abort!
502 502 rollback completed
503 503 abort: This is a late abort
504 504 [255]
505 505 $ hg debugnodemap --metadata
506 506 uid: ???????????????? (glob)
507 507 tip-rev: 5005
508 508 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
509 509 data-length: 121536 (pure !)
510 510 data-length: 121536 (rust !)
511 511 data-length: 121088 (no-pure no-rust !)
512 512 data-unused: 448 (pure !)
513 513 data-unused: 448 (rust !)
514 514 data-unused: 0 (no-pure no-rust !)
515 515 data-unused: 0.369% (pure !)
516 516 data-unused: 0.369% (rust !)
517 517 data-unused: 0.000% (no-pure no-rust !)
518 518 $ f --size --sha256 .hg/store/00changelog-*.nd
519 519 .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
520 520 .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
521 521 .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
522 522
523 523 Check that removing content does not confuse the nodemap
524 524 --------------------------------------------------------
525 525
526 526 removing data with rollback
527 527
528 528 $ echo aso > a
529 529 $ hg ci -m a4
530 530 $ hg rollback
531 531 repository tip rolled back to revision 5005 (undo commit)
532 532 working directory now based on revision 5005
533 533 $ hg id -r .
534 534 90d5d3ba2fc4 tip
535 535
536 536 roming data with strip
537 537
538 538 $ echo aso > a
539 539 $ hg ci -m a4
540 540 $ hg --config extensions.strip= strip -r . --no-backup
541 541 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
542 542 $ hg id -r . --traceback
543 543 90d5d3ba2fc4 tip
544 544
545 545 Test upgrade / downgrade
546 546 ========================
547 547
548 548 downgrading
549 549
550 550 $ cat << EOF >> .hg/hgrc
551 551 > [format]
552 552 > use-persistent-nodemap=no
553 553 > EOF
554 554 $ hg debugformat -v
555 555 format-variant repo config default
556 556 fncache: yes yes yes
557 557 dotencode: yes yes yes
558 558 generaldelta: yes yes yes
559 559 exp-sharesafe: no no no
560 560 sparserevlog: yes yes yes
561 561 sidedata: no no no
562 562 persistent-nodemap: yes no no
563 563 copies-sdc: no no no
564 564 plain-cl-delta: yes yes yes
565 565 compression: zlib zlib zlib
566 566 compression-level: default default default
567 567 $ hg debugupgraderepo --run --no-backup --quiet
568 568 upgrade will perform the following actions:
569 569
570 570 requirements
571 571 preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
572 572 removed: persistent-nodemap
573 573
574 574 processed revlogs:
575 575 - all-filelogs
576 576 - changelog
577 577 - manifest
578 578
579 579 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
580 580 [1]
581 581 $ hg debugnodemap --metadata
582 582
583 583
584 584 upgrading
585 585
586 586 $ cat << EOF >> .hg/hgrc
587 587 > [format]
588 588 > use-persistent-nodemap=yes
589 589 > EOF
590 590 $ hg debugformat -v
591 591 format-variant repo config default
592 592 fncache: yes yes yes
593 593 dotencode: yes yes yes
594 594 generaldelta: yes yes yes
595 595 exp-sharesafe: no no no
596 596 sparserevlog: yes yes yes
597 597 sidedata: no no no
598 598 persistent-nodemap: no yes no
599 599 copies-sdc: no no no
600 600 plain-cl-delta: yes yes yes
601 601 compression: zlib zlib zlib
602 602 compression-level: default default default
603 603 $ hg debugupgraderepo --run --no-backup --quiet
604 604 upgrade will perform the following actions:
605 605
606 606 requirements
607 607 preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
608 608 added: persistent-nodemap
609 609
610 610 processed revlogs:
611 611 - all-filelogs
612 612 - changelog
613 613 - manifest
614 614
615 615 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
616 616 00changelog-*.nd (glob)
617 617 00changelog.n
618 618 00manifest-*.nd (glob)
619 619 00manifest.n
620 620
621 621 $ hg debugnodemap --metadata
622 622 uid: * (glob)
623 623 tip-rev: 5005
624 624 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
625 625 data-length: 121088
626 626 data-unused: 0
627 627 data-unused: 0.000%
628 628
629 629 Running unrelated upgrade
630 630
631 631 $ hg debugupgraderepo --run --no-backup --quiet --optimize re-delta-all
632 632 upgrade will perform the following actions:
633 633
634 634 requirements
635 635 preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store
636 636
637 637 optimisations: re-delta-all
638 638
639 639 processed revlogs:
640 640 - all-filelogs
641 641 - changelog
642 642 - manifest
643 643
644 644 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
645 645 00changelog-*.nd (glob)
646 646 00changelog.n
647 647 00manifest-*.nd (glob)
648 648 00manifest.n
649 649
650 650 $ hg debugnodemap --metadata
651 651 uid: * (glob)
652 652 tip-rev: 5005
653 653 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
654 654 data-length: 121088
655 655 data-unused: 0
656 656 data-unused: 0.000%
657 657
658 658 Persistent nodemap and local/streaming clone
659 659 ============================================
660 660
661 661 $ cd ..
662 662
663 663 standard clone
664 664 --------------
665 665
666 666 The persistent nodemap should exist after a streaming clone
667 667
668 668 $ hg clone --pull --quiet -U test-repo standard-clone
669 669 $ ls -1 standard-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
670 670 00changelog-*.nd (glob)
671 671 00changelog.n
672 672 00manifest-*.nd (glob)
673 673 00manifest.n
674 674 $ hg -R standard-clone debugnodemap --metadata
675 675 uid: * (glob)
676 676 tip-rev: 5005
677 677 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
678 678 data-length: 121088
679 679 data-unused: 0
680 680 data-unused: 0.000%
681 681
682 682
683 683 local clone
684 684 ------------
685 685
686 686 The persistent nodemap should exist after a streaming clone
687 687
688 688 $ hg clone -U test-repo local-clone
689 689 $ ls -1 local-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
690 690 00changelog-*.nd (glob)
691 691 00changelog.n
692 692 00manifest-*.nd (glob)
693 693 00manifest.n
694 694 $ hg -R local-clone debugnodemap --metadata
695 695 uid: * (glob)
696 696 tip-rev: 5005
697 697 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
698 698 data-length: 121088
699 699 data-unused: 0
700 700 data-unused: 0.000%
701 701
702 702 stream clone
703 703 ------------
704 704
705 705 The persistent nodemap should exist after a streaming clone
706 706
707 707 $ hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone --debug | egrep '00(changelog|manifest)'
708 708 adding [s] 00manifest.n (70 bytes)
709 709 adding [s] 00manifest.i (313 KB)
710 710 adding [s] 00manifest.d (452 KB)
711 adding [s] 00manifest-*.nd (118 KB) (glob)
711 712 adding [s] 00changelog.n (70 bytes)
712 713 adding [s] 00changelog.i (313 KB)
713 714 adding [s] 00changelog.d (360 KB)
715 adding [s] 00changelog-*.nd (118 KB) (glob)
714 716 $ ls -1 stream-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
715 717 00changelog-*.nd (glob)
716 718 00changelog.n
717 719 00manifest-*.nd (glob)
718 720 00manifest.n
719 721 $ hg -R stream-clone debugnodemap --metadata
720 722 uid: * (glob)
721 723 tip-rev: 5005
722 724 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
723 725 data-length: 121088
724 726 data-unused: 0
725 727 data-unused: 0.000%
General Comments 0
You need to be logged in to leave comments. Login now