##// END OF EJS Templates
store: exclude `undo.` nodemap's file from `walk`...
marmoute -
r47752:0b569c75 default
parent child Browse files
Show More
@@ -1,810 +1,814 b''
1 1 # store.py - repository store handling for Mercurial
2 2 #
3 3 # Copyright 2008 Olivia Mackall <olivia@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 import re
13 14 import stat
14 15
15 16 from .i18n import _
16 17 from .pycompat import getattr
17 18 from .node import hex
18 19 from . import (
19 20 changelog,
20 21 error,
21 22 manifest,
22 23 policy,
23 24 pycompat,
24 25 util,
25 26 vfs as vfsmod,
26 27 )
27 28 from .utils import hashutil
28 29
29 30 parsers = policy.importmod('parsers')
30 31 # how much bytes should be read from fncache in one read
31 32 # It is done to prevent loading large fncache files into memory
32 33 fncache_chunksize = 10 ** 6
33 34
34 35
35 36 def _matchtrackedpath(path, matcher):
36 37 """parses a fncache entry and returns whether the entry is tracking a path
37 38 matched by matcher or not.
38 39
39 40 If matcher is None, returns True"""
40 41
41 42 if matcher is None:
42 43 return True
43 44 path = decodedir(path)
44 45 if path.startswith(b'data/'):
45 46 return matcher(path[len(b'data/') : -len(b'.i')])
46 47 elif path.startswith(b'meta/'):
47 48 return matcher.visitdir(path[len(b'meta/') : -len(b'/00manifest.i')])
48 49
49 50 raise error.ProgrammingError(b"cannot decode path %s" % path)
50 51
51 52
52 53 # This avoids a collision between a file named foo and a dir named
53 54 # foo.i or foo.d
54 55 def _encodedir(path):
55 56 """
56 57 >>> _encodedir(b'data/foo.i')
57 58 'data/foo.i'
58 59 >>> _encodedir(b'data/foo.i/bla.i')
59 60 'data/foo.i.hg/bla.i'
60 61 >>> _encodedir(b'data/foo.i.hg/bla.i')
61 62 'data/foo.i.hg.hg/bla.i'
62 63 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
63 64 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
64 65 """
65 66 return (
66 67 path.replace(b".hg/", b".hg.hg/")
67 68 .replace(b".i/", b".i.hg/")
68 69 .replace(b".d/", b".d.hg/")
69 70 )
70 71
71 72
72 73 encodedir = getattr(parsers, 'encodedir', _encodedir)
73 74
74 75
75 76 def decodedir(path):
76 77 """
77 78 >>> decodedir(b'data/foo.i')
78 79 'data/foo.i'
79 80 >>> decodedir(b'data/foo.i.hg/bla.i')
80 81 'data/foo.i/bla.i'
81 82 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
82 83 'data/foo.i.hg/bla.i'
83 84 """
84 85 if b".hg/" not in path:
85 86 return path
86 87 return (
87 88 path.replace(b".d.hg/", b".d/")
88 89 .replace(b".i.hg/", b".i/")
89 90 .replace(b".hg.hg/", b".hg/")
90 91 )
91 92
92 93
93 94 def _reserved():
94 95 """characters that are problematic for filesystems
95 96
96 97 * ascii escapes (0..31)
97 98 * ascii hi (126..255)
98 99 * windows specials
99 100
100 101 these characters will be escaped by encodefunctions
101 102 """
102 103 winreserved = [ord(x) for x in u'\\:*?"<>|']
103 104 for x in range(32):
104 105 yield x
105 106 for x in range(126, 256):
106 107 yield x
107 108 for x in winreserved:
108 109 yield x
109 110
110 111
111 112 def _buildencodefun():
112 113 """
113 114 >>> enc, dec = _buildencodefun()
114 115
115 116 >>> enc(b'nothing/special.txt')
116 117 'nothing/special.txt'
117 118 >>> dec(b'nothing/special.txt')
118 119 'nothing/special.txt'
119 120
120 121 >>> enc(b'HELLO')
121 122 '_h_e_l_l_o'
122 123 >>> dec(b'_h_e_l_l_o')
123 124 'HELLO'
124 125
125 126 >>> enc(b'hello:world?')
126 127 'hello~3aworld~3f'
127 128 >>> dec(b'hello~3aworld~3f')
128 129 'hello:world?'
129 130
130 131 >>> enc(b'the\\x07quick\\xADshot')
131 132 'the~07quick~adshot'
132 133 >>> dec(b'the~07quick~adshot')
133 134 'the\\x07quick\\xadshot'
134 135 """
135 136 e = b'_'
136 137 xchr = pycompat.bytechr
137 138 asciistr = list(map(xchr, range(127)))
138 139 capitals = list(range(ord(b"A"), ord(b"Z") + 1))
139 140
140 141 cmap = {x: x for x in asciistr}
141 142 for x in _reserved():
142 143 cmap[xchr(x)] = b"~%02x" % x
143 144 for x in capitals + [ord(e)]:
144 145 cmap[xchr(x)] = e + xchr(x).lower()
145 146
146 147 dmap = {}
147 148 for k, v in pycompat.iteritems(cmap):
148 149 dmap[v] = k
149 150
150 151 def decode(s):
151 152 i = 0
152 153 while i < len(s):
153 154 for l in pycompat.xrange(1, 4):
154 155 try:
155 156 yield dmap[s[i : i + l]]
156 157 i += l
157 158 break
158 159 except KeyError:
159 160 pass
160 161 else:
161 162 raise KeyError
162 163
163 164 return (
164 165 lambda s: b''.join(
165 166 [cmap[s[c : c + 1]] for c in pycompat.xrange(len(s))]
166 167 ),
167 168 lambda s: b''.join(list(decode(s))),
168 169 )
169 170
170 171
171 172 _encodefname, _decodefname = _buildencodefun()
172 173
173 174
174 175 def encodefilename(s):
175 176 """
176 177 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
177 178 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
178 179 """
179 180 return _encodefname(encodedir(s))
180 181
181 182
182 183 def decodefilename(s):
183 184 """
184 185 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
185 186 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
186 187 """
187 188 return decodedir(_decodefname(s))
188 189
189 190
190 191 def _buildlowerencodefun():
191 192 """
192 193 >>> f = _buildlowerencodefun()
193 194 >>> f(b'nothing/special.txt')
194 195 'nothing/special.txt'
195 196 >>> f(b'HELLO')
196 197 'hello'
197 198 >>> f(b'hello:world?')
198 199 'hello~3aworld~3f'
199 200 >>> f(b'the\\x07quick\\xADshot')
200 201 'the~07quick~adshot'
201 202 """
202 203 xchr = pycompat.bytechr
203 204 cmap = {xchr(x): xchr(x) for x in pycompat.xrange(127)}
204 205 for x in _reserved():
205 206 cmap[xchr(x)] = b"~%02x" % x
206 207 for x in range(ord(b"A"), ord(b"Z") + 1):
207 208 cmap[xchr(x)] = xchr(x).lower()
208 209
209 210 def lowerencode(s):
210 211 return b"".join([cmap[c] for c in pycompat.iterbytestr(s)])
211 212
212 213 return lowerencode
213 214
214 215
215 216 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
216 217
217 218 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
218 219 _winres3 = (b'aux', b'con', b'prn', b'nul') # length 3
219 220 _winres4 = (b'com', b'lpt') # length 4 (with trailing 1..9)
220 221
221 222
222 223 def _auxencode(path, dotencode):
223 224 """
224 225 Encodes filenames containing names reserved by Windows or which end in
225 226 period or space. Does not touch other single reserved characters c.
226 227 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
227 228 Additionally encodes space or period at the beginning, if dotencode is
228 229 True. Parameter path is assumed to be all lowercase.
229 230 A segment only needs encoding if a reserved name appears as a
230 231 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
231 232 doesn't need encoding.
232 233
233 234 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
234 235 >>> _auxencode(s.split(b'/'), True)
235 236 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
236 237 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
237 238 >>> _auxencode(s.split(b'/'), False)
238 239 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
239 240 >>> _auxencode([b'foo. '], True)
240 241 ['foo.~20']
241 242 >>> _auxencode([b' .foo'], True)
242 243 ['~20.foo']
243 244 """
244 245 for i, n in enumerate(path):
245 246 if not n:
246 247 continue
247 248 if dotencode and n[0] in b'. ':
248 249 n = b"~%02x" % ord(n[0:1]) + n[1:]
249 250 path[i] = n
250 251 else:
251 252 l = n.find(b'.')
252 253 if l == -1:
253 254 l = len(n)
254 255 if (l == 3 and n[:3] in _winres3) or (
255 256 l == 4
256 257 and n[3:4] <= b'9'
257 258 and n[3:4] >= b'1'
258 259 and n[:3] in _winres4
259 260 ):
260 261 # encode third letter ('aux' -> 'au~78')
261 262 ec = b"~%02x" % ord(n[2:3])
262 263 n = n[0:2] + ec + n[3:]
263 264 path[i] = n
264 265 if n[-1] in b'. ':
265 266 # encode last period or space ('foo...' -> 'foo..~2e')
266 267 path[i] = n[:-1] + b"~%02x" % ord(n[-1:])
267 268 return path
268 269
269 270
270 271 _maxstorepathlen = 120
271 272 _dirprefixlen = 8
272 273 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
273 274
274 275
275 276 def _hashencode(path, dotencode):
276 277 digest = hex(hashutil.sha1(path).digest())
277 278 le = lowerencode(path[5:]).split(b'/') # skips prefix 'data/' or 'meta/'
278 279 parts = _auxencode(le, dotencode)
279 280 basename = parts[-1]
280 281 _root, ext = os.path.splitext(basename)
281 282 sdirs = []
282 283 sdirslen = 0
283 284 for p in parts[:-1]:
284 285 d = p[:_dirprefixlen]
285 286 if d[-1] in b'. ':
286 287 # Windows can't access dirs ending in period or space
287 288 d = d[:-1] + b'_'
288 289 if sdirslen == 0:
289 290 t = len(d)
290 291 else:
291 292 t = sdirslen + 1 + len(d)
292 293 if t > _maxshortdirslen:
293 294 break
294 295 sdirs.append(d)
295 296 sdirslen = t
296 297 dirs = b'/'.join(sdirs)
297 298 if len(dirs) > 0:
298 299 dirs += b'/'
299 300 res = b'dh/' + dirs + digest + ext
300 301 spaceleft = _maxstorepathlen - len(res)
301 302 if spaceleft > 0:
302 303 filler = basename[:spaceleft]
303 304 res = b'dh/' + dirs + filler + digest + ext
304 305 return res
305 306
306 307
307 308 def _hybridencode(path, dotencode):
308 309 """encodes path with a length limit
309 310
310 311 Encodes all paths that begin with 'data/', according to the following.
311 312
312 313 Default encoding (reversible):
313 314
314 315 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
315 316 characters are encoded as '~xx', where xx is the two digit hex code
316 317 of the character (see encodefilename).
317 318 Relevant path components consisting of Windows reserved filenames are
318 319 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
319 320
320 321 Hashed encoding (not reversible):
321 322
322 323 If the default-encoded path is longer than _maxstorepathlen, a
323 324 non-reversible hybrid hashing of the path is done instead.
324 325 This encoding uses up to _dirprefixlen characters of all directory
325 326 levels of the lowerencoded path, but not more levels than can fit into
326 327 _maxshortdirslen.
327 328 Then follows the filler followed by the sha digest of the full path.
328 329 The filler is the beginning of the basename of the lowerencoded path
329 330 (the basename is everything after the last path separator). The filler
330 331 is as long as possible, filling in characters from the basename until
331 332 the encoded path has _maxstorepathlen characters (or all chars of the
332 333 basename have been taken).
333 334 The extension (e.g. '.i' or '.d') is preserved.
334 335
335 336 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
336 337 encoding was used.
337 338 """
338 339 path = encodedir(path)
339 340 ef = _encodefname(path).split(b'/')
340 341 res = b'/'.join(_auxencode(ef, dotencode))
341 342 if len(res) > _maxstorepathlen:
342 343 res = _hashencode(path, dotencode)
343 344 return res
344 345
345 346
346 347 def _pathencode(path):
347 348 de = encodedir(path)
348 349 if len(path) > _maxstorepathlen:
349 350 return _hashencode(de, True)
350 351 ef = _encodefname(de).split(b'/')
351 352 res = b'/'.join(_auxencode(ef, True))
352 353 if len(res) > _maxstorepathlen:
353 354 return _hashencode(de, True)
354 355 return res
355 356
356 357
357 358 _pathencode = getattr(parsers, 'pathencode', _pathencode)
358 359
359 360
360 361 def _plainhybridencode(f):
361 362 return _hybridencode(f, False)
362 363
363 364
364 365 def _calcmode(vfs):
365 366 try:
366 367 # files in .hg/ will be created using this mode
367 368 mode = vfs.stat().st_mode
368 369 # avoid some useless chmods
369 370 if (0o777 & ~util.umask) == (0o777 & mode):
370 371 mode = None
371 372 except OSError:
372 373 mode = None
373 374 return mode
374 375
375 376
376 377 _data = [
377 378 b'bookmarks',
378 379 b'narrowspec',
379 380 b'data',
380 381 b'meta',
381 382 b'00manifest.d',
382 383 b'00manifest.i',
383 384 b'00changelog.d',
384 385 b'00changelog.i',
385 386 b'phaseroots',
386 387 b'obsstore',
387 388 b'requires',
388 389 ]
389 390
390 391 REVLOG_FILES_MAIN_EXT = (b'.i', b'i.tmpcensored')
391 392 REVLOG_FILES_OTHER_EXT = (b'.d', b'.n', b'.nd', b'd.tmpcensored')
392 393 # files that are "volatile" and might change between listing and streaming
393 394 #
394 395 # note: the ".nd" file are nodemap data and won't "change" but they might be
395 396 # deleted.
396 397 REVLOG_FILES_VOLATILE_EXT = (b'.n', b'.nd')
397 398
399 # some exception to the above matching
400 EXCLUDED = re.compile(b'.*undo\.[^/]+\.nd?$')
401
398 402
399 403 def is_revlog(f, kind, st):
400 404 if kind != stat.S_IFREG:
401 405 return None
402 406 return revlog_type(f)
403 407
404 408
405 409 def revlog_type(f):
406 410 if f.endswith(REVLOG_FILES_MAIN_EXT):
407 411 return FILEFLAGS_REVLOG_MAIN
408 elif f.endswith(REVLOG_FILES_OTHER_EXT):
412 elif f.endswith(REVLOG_FILES_OTHER_EXT) and EXCLUDED.match(f) is None:
409 413 t = FILETYPE_FILELOG_OTHER
410 414 if f.endswith(REVLOG_FILES_VOLATILE_EXT):
411 415 t |= FILEFLAGS_VOLATILE
412 416 return t
413 417
414 418
415 419 # the file is part of changelog data
416 420 FILEFLAGS_CHANGELOG = 1 << 13
417 421 # the file is part of manifest data
418 422 FILEFLAGS_MANIFESTLOG = 1 << 12
419 423 # the file is part of filelog data
420 424 FILEFLAGS_FILELOG = 1 << 11
421 425 # file that are not directly part of a revlog
422 426 FILEFLAGS_OTHER = 1 << 10
423 427
424 428 # the main entry point for a revlog
425 429 FILEFLAGS_REVLOG_MAIN = 1 << 1
426 430 # a secondary file for a revlog
427 431 FILEFLAGS_REVLOG_OTHER = 1 << 0
428 432
429 433 # files that are "volatile" and might change between listing and streaming
430 434 FILEFLAGS_VOLATILE = 1 << 20
431 435
432 436 FILETYPE_CHANGELOG_MAIN = FILEFLAGS_CHANGELOG | FILEFLAGS_REVLOG_MAIN
433 437 FILETYPE_CHANGELOG_OTHER = FILEFLAGS_CHANGELOG | FILEFLAGS_REVLOG_OTHER
434 438 FILETYPE_MANIFESTLOG_MAIN = FILEFLAGS_MANIFESTLOG | FILEFLAGS_REVLOG_MAIN
435 439 FILETYPE_MANIFESTLOG_OTHER = FILEFLAGS_MANIFESTLOG | FILEFLAGS_REVLOG_OTHER
436 440 FILETYPE_FILELOG_MAIN = FILEFLAGS_FILELOG | FILEFLAGS_REVLOG_MAIN
437 441 FILETYPE_FILELOG_OTHER = FILEFLAGS_FILELOG | FILEFLAGS_REVLOG_OTHER
438 442 FILETYPE_OTHER = FILEFLAGS_OTHER
439 443
440 444
441 445 class basicstore(object):
442 446 '''base class for local repository stores'''
443 447
444 448 def __init__(self, path, vfstype):
445 449 vfs = vfstype(path)
446 450 self.path = vfs.base
447 451 self.createmode = _calcmode(vfs)
448 452 vfs.createmode = self.createmode
449 453 self.rawvfs = vfs
450 454 self.vfs = vfsmod.filtervfs(vfs, encodedir)
451 455 self.opener = self.vfs
452 456
453 457 def join(self, f):
454 458 return self.path + b'/' + encodedir(f)
455 459
456 460 def _walk(self, relpath, recurse):
457 461 '''yields (unencoded, encoded, size)'''
458 462 path = self.path
459 463 if relpath:
460 464 path += b'/' + relpath
461 465 striplen = len(self.path) + 1
462 466 l = []
463 467 if self.rawvfs.isdir(path):
464 468 visit = [path]
465 469 readdir = self.rawvfs.readdir
466 470 while visit:
467 471 p = visit.pop()
468 472 for f, kind, st in readdir(p, stat=True):
469 473 fp = p + b'/' + f
470 474 rl_type = is_revlog(f, kind, st)
471 475 if rl_type is not None:
472 476 n = util.pconvert(fp[striplen:])
473 477 l.append((rl_type, decodedir(n), n, st.st_size))
474 478 elif kind == stat.S_IFDIR and recurse:
475 479 visit.append(fp)
476 480 l.sort()
477 481 return l
478 482
479 483 def changelog(self, trypending, concurrencychecker=None):
480 484 return changelog.changelog(
481 485 self.vfs,
482 486 trypending=trypending,
483 487 concurrencychecker=concurrencychecker,
484 488 )
485 489
486 490 def manifestlog(self, repo, storenarrowmatch):
487 491 rootstore = manifest.manifestrevlog(repo.nodeconstants, self.vfs)
488 492 return manifest.manifestlog(self.vfs, repo, rootstore, storenarrowmatch)
489 493
490 494 def datafiles(self, matcher=None):
491 495 files = self._walk(b'data', True) + self._walk(b'meta', True)
492 496 for (t, u, e, s) in files:
493 497 yield (FILEFLAGS_FILELOG | t, u, e, s)
494 498
495 499 def topfiles(self):
496 500 # yield manifest before changelog
497 501 files = reversed(self._walk(b'', False))
498 502 for (t, u, e, s) in files:
499 503 if u.startswith(b'00changelog'):
500 504 yield (FILEFLAGS_CHANGELOG | t, u, e, s)
501 505 elif u.startswith(b'00manifest'):
502 506 yield (FILEFLAGS_MANIFESTLOG | t, u, e, s)
503 507 else:
504 508 yield (FILETYPE_OTHER | t, u, e, s)
505 509
506 510 def walk(self, matcher=None):
507 511 """return file related to data storage (ie: revlogs)
508 512
509 513 yields (file_type, unencoded, encoded, size)
510 514
511 515 if a matcher is passed, storage files of only those tracked paths
512 516 are passed with matches the matcher
513 517 """
514 518 # yield data files first
515 519 for x in self.datafiles(matcher):
516 520 yield x
517 521 for x in self.topfiles():
518 522 yield x
519 523
520 524 def copylist(self):
521 525 return _data
522 526
523 527 def write(self, tr):
524 528 pass
525 529
526 530 def invalidatecaches(self):
527 531 pass
528 532
529 533 def markremoved(self, fn):
530 534 pass
531 535
532 536 def __contains__(self, path):
533 537 '''Checks if the store contains path'''
534 538 path = b"/".join((b"data", path))
535 539 # file?
536 540 if self.vfs.exists(path + b".i"):
537 541 return True
538 542 # dir?
539 543 if not path.endswith(b"/"):
540 544 path = path + b"/"
541 545 return self.vfs.exists(path)
542 546
543 547
544 548 class encodedstore(basicstore):
545 549 def __init__(self, path, vfstype):
546 550 vfs = vfstype(path + b'/store')
547 551 self.path = vfs.base
548 552 self.createmode = _calcmode(vfs)
549 553 vfs.createmode = self.createmode
550 554 self.rawvfs = vfs
551 555 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
552 556 self.opener = self.vfs
553 557
554 558 def datafiles(self, matcher=None):
555 559 for t, a, b, size in super(encodedstore, self).datafiles():
556 560 try:
557 561 a = decodefilename(a)
558 562 except KeyError:
559 563 a = None
560 564 if a is not None and not _matchtrackedpath(a, matcher):
561 565 continue
562 566 yield t, a, b, size
563 567
564 568 def join(self, f):
565 569 return self.path + b'/' + encodefilename(f)
566 570
567 571 def copylist(self):
568 572 return [b'requires', b'00changelog.i'] + [b'store/' + f for f in _data]
569 573
570 574
571 575 class fncache(object):
572 576 # the filename used to be partially encoded
573 577 # hence the encodedir/decodedir dance
574 578 def __init__(self, vfs):
575 579 self.vfs = vfs
576 580 self.entries = None
577 581 self._dirty = False
578 582 # set of new additions to fncache
579 583 self.addls = set()
580 584
581 585 def ensureloaded(self, warn=None):
582 586 """read the fncache file if not already read.
583 587
584 588 If the file on disk is corrupted, raise. If warn is provided,
585 589 warn and keep going instead."""
586 590 if self.entries is None:
587 591 self._load(warn)
588 592
589 593 def _load(self, warn=None):
590 594 '''fill the entries from the fncache file'''
591 595 self._dirty = False
592 596 try:
593 597 fp = self.vfs(b'fncache', mode=b'rb')
594 598 except IOError:
595 599 # skip nonexistent file
596 600 self.entries = set()
597 601 return
598 602
599 603 self.entries = set()
600 604 chunk = b''
601 605 for c in iter(functools.partial(fp.read, fncache_chunksize), b''):
602 606 chunk += c
603 607 try:
604 608 p = chunk.rindex(b'\n')
605 609 self.entries.update(decodedir(chunk[: p + 1]).splitlines())
606 610 chunk = chunk[p + 1 :]
607 611 except ValueError:
608 612 # substring '\n' not found, maybe the entry is bigger than the
609 613 # chunksize, so let's keep iterating
610 614 pass
611 615
612 616 if chunk:
613 617 msg = _(b"fncache does not ends with a newline")
614 618 if warn:
615 619 warn(msg + b'\n')
616 620 else:
617 621 raise error.Abort(
618 622 msg,
619 623 hint=_(
620 624 b"use 'hg debugrebuildfncache' to "
621 625 b"rebuild the fncache"
622 626 ),
623 627 )
624 628 self._checkentries(fp, warn)
625 629 fp.close()
626 630
627 631 def _checkentries(self, fp, warn):
628 632 """ make sure there is no empty string in entries """
629 633 if b'' in self.entries:
630 634 fp.seek(0)
631 635 for n, line in enumerate(util.iterfile(fp)):
632 636 if not line.rstrip(b'\n'):
633 637 t = _(b'invalid entry in fncache, line %d') % (n + 1)
634 638 if warn:
635 639 warn(t + b'\n')
636 640 else:
637 641 raise error.Abort(t)
638 642
639 643 def write(self, tr):
640 644 if self._dirty:
641 645 assert self.entries is not None
642 646 self.entries = self.entries | self.addls
643 647 self.addls = set()
644 648 tr.addbackup(b'fncache')
645 649 fp = self.vfs(b'fncache', mode=b'wb', atomictemp=True)
646 650 if self.entries:
647 651 fp.write(encodedir(b'\n'.join(self.entries) + b'\n'))
648 652 fp.close()
649 653 self._dirty = False
650 654 if self.addls:
651 655 # if we have just new entries, let's append them to the fncache
652 656 tr.addbackup(b'fncache')
653 657 fp = self.vfs(b'fncache', mode=b'ab', atomictemp=True)
654 658 if self.addls:
655 659 fp.write(encodedir(b'\n'.join(self.addls) + b'\n'))
656 660 fp.close()
657 661 self.entries = None
658 662 self.addls = set()
659 663
660 664 def add(self, fn):
661 665 if self.entries is None:
662 666 self._load()
663 667 if fn not in self.entries:
664 668 self.addls.add(fn)
665 669
666 670 def remove(self, fn):
667 671 if self.entries is None:
668 672 self._load()
669 673 if fn in self.addls:
670 674 self.addls.remove(fn)
671 675 return
672 676 try:
673 677 self.entries.remove(fn)
674 678 self._dirty = True
675 679 except KeyError:
676 680 pass
677 681
678 682 def __contains__(self, fn):
679 683 if fn in self.addls:
680 684 return True
681 685 if self.entries is None:
682 686 self._load()
683 687 return fn in self.entries
684 688
685 689 def __iter__(self):
686 690 if self.entries is None:
687 691 self._load()
688 692 return iter(self.entries | self.addls)
689 693
690 694
691 695 class _fncachevfs(vfsmod.proxyvfs):
692 696 def __init__(self, vfs, fnc, encode):
693 697 vfsmod.proxyvfs.__init__(self, vfs)
694 698 self.fncache = fnc
695 699 self.encode = encode
696 700
697 701 def __call__(self, path, mode=b'r', *args, **kw):
698 702 encoded = self.encode(path)
699 703 if mode not in (b'r', b'rb') and (
700 704 path.startswith(b'data/') or path.startswith(b'meta/')
701 705 ):
702 706 # do not trigger a fncache load when adding a file that already is
703 707 # known to exist.
704 708 notload = self.fncache.entries is None and self.vfs.exists(encoded)
705 709 if notload and b'a' in mode and not self.vfs.stat(encoded).st_size:
706 710 # when appending to an existing file, if the file has size zero,
707 711 # it should be considered as missing. Such zero-size files are
708 712 # the result of truncation when a transaction is aborted.
709 713 notload = False
710 714 if not notload:
711 715 self.fncache.add(path)
712 716 return self.vfs(encoded, mode, *args, **kw)
713 717
714 718 def join(self, path):
715 719 if path:
716 720 return self.vfs.join(self.encode(path))
717 721 else:
718 722 return self.vfs.join(path)
719 723
720 724
721 725 class fncachestore(basicstore):
722 726 def __init__(self, path, vfstype, dotencode):
723 727 if dotencode:
724 728 encode = _pathencode
725 729 else:
726 730 encode = _plainhybridencode
727 731 self.encode = encode
728 732 vfs = vfstype(path + b'/store')
729 733 self.path = vfs.base
730 734 self.pathsep = self.path + b'/'
731 735 self.createmode = _calcmode(vfs)
732 736 vfs.createmode = self.createmode
733 737 self.rawvfs = vfs
734 738 fnc = fncache(vfs)
735 739 self.fncache = fnc
736 740 self.vfs = _fncachevfs(vfs, fnc, encode)
737 741 self.opener = self.vfs
738 742
739 743 def join(self, f):
740 744 return self.pathsep + self.encode(f)
741 745
742 746 def getsize(self, path):
743 747 return self.rawvfs.stat(path).st_size
744 748
745 749 def datafiles(self, matcher=None):
746 750 for f in sorted(self.fncache):
747 751 if not _matchtrackedpath(f, matcher):
748 752 continue
749 753 ef = self.encode(f)
750 754 try:
751 755 t = revlog_type(f)
752 756 t |= FILEFLAGS_FILELOG
753 757 yield t, f, ef, self.getsize(ef)
754 758 except OSError as err:
755 759 if err.errno != errno.ENOENT:
756 760 raise
757 761
758 762 def copylist(self):
759 763 d = (
760 764 b'bookmarks',
761 765 b'narrowspec',
762 766 b'data',
763 767 b'meta',
764 768 b'dh',
765 769 b'fncache',
766 770 b'phaseroots',
767 771 b'obsstore',
768 772 b'00manifest.d',
769 773 b'00manifest.i',
770 774 b'00changelog.d',
771 775 b'00changelog.i',
772 776 b'requires',
773 777 )
774 778 return [b'requires', b'00changelog.i'] + [b'store/' + f for f in d]
775 779
776 780 def write(self, tr):
777 781 self.fncache.write(tr)
778 782
779 783 def invalidatecaches(self):
780 784 self.fncache.entries = None
781 785 self.fncache.addls = set()
782 786
783 787 def markremoved(self, fn):
784 788 self.fncache.remove(fn)
785 789
786 790 def _exists(self, f):
787 791 ef = self.encode(f)
788 792 try:
789 793 self.getsize(ef)
790 794 return True
791 795 except OSError as err:
792 796 if err.errno != errno.ENOENT:
793 797 raise
794 798 # nonexistent entry
795 799 return False
796 800
797 801 def __contains__(self, path):
798 802 '''Checks if the store contains path'''
799 803 path = b"/".join((b"data", path))
800 804 # check for files (exact match)
801 805 e = path + b'.i'
802 806 if e in self.fncache and self._exists(e):
803 807 return True
804 808 # now check for directories (prefix match)
805 809 if not path.endswith(b'/'):
806 810 path += b'/'
807 811 for e in self.fncache:
808 812 if e.startswith(path) and self._exists(e):
809 813 return True
810 814 return False
@@ -1,1054 +1,1052 b''
1 1 ===================================
2 2 Test the persistent on-disk nodemap
3 3 ===================================
4 4
5 5
6 6 #if no-rust
7 7
8 8 $ cat << EOF >> $HGRCPATH
9 9 > [format]
10 10 > use-persistent-nodemap=yes
11 11 > [devel]
12 12 > persistent-nodemap=yes
13 13 > EOF
14 14
15 15 #endif
16 16
17 17 $ hg init test-repo --config storage.revlog.persistent-nodemap.slow-path=allow
18 18 $ cd test-repo
19 19
20 20 Check handling of the default slow-path value
21 21
22 22 #if no-pure no-rust
23 23
24 24 $ hg id
25 25 abort: accessing `persistent-nodemap` repository without associated fast implementation.
26 26 (check `hg help config.format.use-persistent-nodemap` for details)
27 27 [255]
28 28
29 29 Unlock further check (we are here to test the feature)
30 30
31 31 $ cat << EOF >> $HGRCPATH
32 32 > [storage]
33 33 > # to avoid spamming the test
34 34 > revlog.persistent-nodemap.slow-path=allow
35 35 > EOF
36 36
37 37 #endif
38 38
39 39 #if rust
40 40
41 41 Regression test for a previous bug in Rust/C FFI for the `Revlog_CAPI` capsule:
42 42 in places where `mercurial/cext/revlog.c` function signatures use `Py_ssize_t`
43 43 (64 bits on Linux x86_64), corresponding declarations in `rust/hg-cpython/src/cindex.rs`
44 44 incorrectly used `libc::c_int` (32 bits).
45 45 As a result, -1 passed from Rust for the null revision became 4294967295 in C.
46 46
47 47 $ hg log -r 00000000
48 48 changeset: -1:000000000000
49 49 tag: tip
50 50 user:
51 51 date: Thu Jan 01 00:00:00 1970 +0000
52 52
53 53
54 54 #endif
55 55
56 56
57 57 $ hg debugformat
58 58 format-variant repo
59 59 fncache: yes
60 60 dotencode: yes
61 61 generaldelta: yes
62 62 share-safe: no
63 63 sparserevlog: yes
64 64 persistent-nodemap: yes
65 65 copies-sdc: no
66 66 revlog-v2: no
67 67 plain-cl-delta: yes
68 68 compression: zlib (no-zstd !)
69 69 compression: zstd (zstd !)
70 70 compression-level: default
71 71 $ hg debugbuilddag .+5000 --new-file
72 72
73 73 $ hg debugnodemap --metadata
74 74 uid: ???????????????? (glob)
75 75 tip-rev: 5000
76 76 tip-node: 6b02b8c7b96654c25e86ba69eda198d7e6ad8b3c
77 77 data-length: 121088
78 78 data-unused: 0
79 79 data-unused: 0.000%
80 80 $ f --size .hg/store/00changelog.n
81 81 .hg/store/00changelog.n: size=70
82 82
83 83 Simple lookup works
84 84
85 85 $ ANYNODE=`hg log --template '{node|short}\n' --rev tip`
86 86 $ hg log -r "$ANYNODE" --template '{rev}\n'
87 87 5000
88 88
89 89
90 90 #if rust
91 91
92 92 $ f --sha256 .hg/store/00changelog-*.nd
93 93 .hg/store/00changelog-????????????????.nd: sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd (glob)
94 94
95 95 $ f --sha256 .hg/store/00manifest-*.nd
96 96 .hg/store/00manifest-????????????????.nd: sha256=97117b1c064ea2f86664a124589e47db0e254e8d34739b5c5cc5bf31c9da2b51 (glob)
97 97 $ hg debugnodemap --dump-new | f --sha256 --size
98 98 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
99 99 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
100 100 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
101 101 0000: 00 00 00 91 00 00 00 20 00 00 00 bb 00 00 00 e7 |....... ........|
102 102 0010: 00 00 00 66 00 00 00 a1 00 00 01 13 00 00 01 22 |...f..........."|
103 103 0020: 00 00 00 23 00 00 00 fc 00 00 00 ba 00 00 00 5e |...#...........^|
104 104 0030: 00 00 00 df 00 00 01 4e 00 00 01 65 00 00 00 ab |.......N...e....|
105 105 0040: 00 00 00 a9 00 00 00 95 00 00 00 73 00 00 00 38 |...........s...8|
106 106 0050: 00 00 00 cc 00 00 00 92 00 00 00 90 00 00 00 69 |...............i|
107 107 0060: 00 00 00 ec 00 00 00 8d 00 00 01 4f 00 00 00 12 |...........O....|
108 108 0070: 00 00 02 0c 00 00 00 77 00 00 00 9c 00 00 00 8f |.......w........|
109 109 0080: 00 00 00 d5 00 00 00 6b 00 00 00 48 00 00 00 b3 |.......k...H....|
110 110 0090: 00 00 00 e5 00 00 00 b5 00 00 00 8e 00 00 00 ad |................|
111 111 00a0: 00 00 00 7b 00 00 00 7c 00 00 00 0b 00 00 00 2b |...{...|.......+|
112 112 00b0: 00 00 00 c6 00 00 00 1e 00 00 01 08 00 00 00 11 |................|
113 113 00c0: 00 00 01 30 00 00 00 26 00 00 01 9c 00 00 00 35 |...0...&.......5|
114 114 00d0: 00 00 00 b8 00 00 01 31 00 00 00 2c 00 00 00 55 |.......1...,...U|
115 115 00e0: 00 00 00 8a 00 00 00 9a 00 00 00 0c 00 00 01 1e |................|
116 116 00f0: 00 00 00 a4 00 00 00 83 00 00 00 c9 00 00 00 8c |................|
117 117
118 118
119 119 #else
120 120
121 121 $ f --sha256 .hg/store/00changelog-*.nd
122 122 .hg/store/00changelog-????????????????.nd: sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 (glob)
123 123 $ hg debugnodemap --dump-new | f --sha256 --size
124 124 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
125 125 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
126 126 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
127 127 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
128 128 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
129 129 0020: ff ff ff ff ff ff f5 06 ff ff ff ff ff ff f3 e7 |................|
130 130 0030: ff ff ef ca ff ff ff ff ff ff ff ff ff ff ff ff |................|
131 131 0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
132 132 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ed 08 |................|
133 133 0060: ff ff ed 66 ff ff ff ff ff ff ff ff ff ff ff ff |...f............|
134 134 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
135 135 0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
136 136 0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f6 ed |................|
137 137 00a0: ff ff ff ff ff ff fe 61 ff ff ff ff ff ff ff ff |.......a........|
138 138 00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
139 139 00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
140 140 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
141 141 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f1 02 |................|
142 142 00f0: ff ff ff ff ff ff ed 1b ff ff ff ff ff ff ff ff |................|
143 143
144 144 #endif
145 145
146 146 $ hg debugnodemap --check
147 147 revision in index: 5001
148 148 revision in nodemap: 5001
149 149
150 150 add a new commit
151 151
152 152 $ hg up
153 153 5001 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 154 $ echo foo > foo
155 155 $ hg add foo
156 156
157 157
158 158 Check slow-path config value handling
159 159 -------------------------------------
160 160
161 161 #if no-pure no-rust
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 abort: accessing `persistent-nodemap` repository without associated fast implementation.
167 167 (check `hg help config.format.use-persistent-nodemap` for details)
168 168 [255]
169 169
170 170 $ hg log -r . --config "storage.revlog.persistent-nodemap.slow-path=warn"
171 171 warning: accessing `persistent-nodemap` repository without associated fast implementation.
172 172 (check `hg help config.format.use-persistent-nodemap` for details)
173 173 changeset: 5000:6b02b8c7b966
174 174 tag: tip
175 175 user: debugbuilddag
176 176 date: Thu Jan 01 01:23:20 1970 +0000
177 177 summary: r5000
178 178
179 179 $ hg ci -m 'foo' --config "storage.revlog.persistent-nodemap.slow-path=abort"
180 180 abort: accessing `persistent-nodemap` repository without associated fast implementation.
181 181 (check `hg help config.format.use-persistent-nodemap` for details)
182 182 [255]
183 183
184 184 #else
185 185
186 186 $ hg id --config "storage.revlog.persistent-nodemap.slow-path=invalid-value"
187 187 unknown value for config "storage.revlog.persistent-nodemap.slow-path": "invalid-value"
188 188 falling back to default value: abort
189 189 6b02b8c7b966+ tip
190 190
191 191 #endif
192 192
193 193 $ hg ci -m 'foo'
194 194
195 195 #if no-pure no-rust
196 196 $ hg debugnodemap --metadata
197 197 uid: ???????????????? (glob)
198 198 tip-rev: 5001
199 199 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
200 200 data-length: 121088
201 201 data-unused: 0
202 202 data-unused: 0.000%
203 203 #else
204 204 $ hg debugnodemap --metadata
205 205 uid: ???????????????? (glob)
206 206 tip-rev: 5001
207 207 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
208 208 data-length: 121344
209 209 data-unused: 256
210 210 data-unused: 0.211%
211 211 #endif
212 212
213 213 $ f --size .hg/store/00changelog.n
214 214 .hg/store/00changelog.n: size=70
215 215
216 216 (The pure code use the debug code that perform incremental update, the C code reencode from scratch)
217 217
218 218 #if pure
219 219 $ f --sha256 .hg/store/00changelog-*.nd --size
220 220 .hg/store/00changelog-????????????????.nd: size=121344, sha256=cce54c5da5bde3ad72a4938673ed4064c86231b9c64376b082b163fdb20f8f66 (glob)
221 221 #endif
222 222
223 223 #if rust
224 224 $ f --sha256 .hg/store/00changelog-*.nd --size
225 225 .hg/store/00changelog-????????????????.nd: size=121344, sha256=952b042fcf614ceb37b542b1b723e04f18f83efe99bee4e0f5ccd232ef470e58 (glob)
226 226 #endif
227 227
228 228 #if no-pure no-rust
229 229 $ f --sha256 .hg/store/00changelog-*.nd --size
230 230 .hg/store/00changelog-????????????????.nd: size=121088, sha256=df7c06a035b96cb28c7287d349d603baef43240be7736fe34eea419a49702e17 (glob)
231 231 #endif
232 232
233 233 $ hg debugnodemap --check
234 234 revision in index: 5002
235 235 revision in nodemap: 5002
236 236
237 237 Test code path without mmap
238 238 ---------------------------
239 239
240 240 $ echo bar > bar
241 241 $ hg add bar
242 242 $ hg ci -m 'bar' --config storage.revlog.persistent-nodemap.mmap=no
243 243
244 244 $ hg debugnodemap --check --config storage.revlog.persistent-nodemap.mmap=yes
245 245 revision in index: 5003
246 246 revision in nodemap: 5003
247 247 $ hg debugnodemap --check --config storage.revlog.persistent-nodemap.mmap=no
248 248 revision in index: 5003
249 249 revision in nodemap: 5003
250 250
251 251
252 252 #if pure
253 253 $ hg debugnodemap --metadata
254 254 uid: ???????????????? (glob)
255 255 tip-rev: 5002
256 256 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
257 257 data-length: 121600
258 258 data-unused: 512
259 259 data-unused: 0.421%
260 260 $ f --sha256 .hg/store/00changelog-*.nd --size
261 261 .hg/store/00changelog-????????????????.nd: size=121600, sha256=def52503d049ccb823974af313a98a935319ba61f40f3aa06a8be4d35c215054 (glob)
262 262 #endif
263 263 #if rust
264 264 $ hg debugnodemap --metadata
265 265 uid: ???????????????? (glob)
266 266 tip-rev: 5002
267 267 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
268 268 data-length: 121600
269 269 data-unused: 512
270 270 data-unused: 0.421%
271 271 $ f --sha256 .hg/store/00changelog-*.nd --size
272 272 .hg/store/00changelog-????????????????.nd: size=121600, sha256=dacf5b5f1d4585fee7527d0e67cad5b1ba0930e6a0928f650f779aefb04ce3fb (glob)
273 273 #endif
274 274 #if no-pure no-rust
275 275 $ hg debugnodemap --metadata
276 276 uid: ???????????????? (glob)
277 277 tip-rev: 5002
278 278 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
279 279 data-length: 121088
280 280 data-unused: 0
281 281 data-unused: 0.000%
282 282 $ f --sha256 .hg/store/00changelog-*.nd --size
283 283 .hg/store/00changelog-????????????????.nd: size=121088, sha256=59fcede3e3cc587755916ceed29e3c33748cd1aa7d2f91828ac83e7979d935e8 (glob)
284 284 #endif
285 285
286 286 Test force warming the cache
287 287
288 288 $ rm .hg/store/00changelog.n
289 289 $ hg debugnodemap --metadata
290 290 $ hg debugupdatecache
291 291 #if pure
292 292 $ hg debugnodemap --metadata
293 293 uid: ???????????????? (glob)
294 294 tip-rev: 5002
295 295 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
296 296 data-length: 121088
297 297 data-unused: 0
298 298 data-unused: 0.000%
299 299 #else
300 300 $ hg debugnodemap --metadata
301 301 uid: ???????????????? (glob)
302 302 tip-rev: 5002
303 303 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
304 304 data-length: 121088
305 305 data-unused: 0
306 306 data-unused: 0.000%
307 307 #endif
308 308
309 309 Check out of sync nodemap
310 310 =========================
311 311
312 312 First copy old data on the side.
313 313
314 314 $ mkdir ../tmp-copies
315 315 $ cp .hg/store/00changelog-????????????????.nd .hg/store/00changelog.n ../tmp-copies
316 316
317 317 Nodemap lagging behind
318 318 ----------------------
319 319
320 320 make a new commit
321 321
322 322 $ echo bar2 > bar
323 323 $ hg ci -m 'bar2'
324 324 $ NODE=`hg log -r tip -T '{node}\n'`
325 325 $ hg log -r "$NODE" -T '{rev}\n'
326 326 5003
327 327
328 328 If the nodemap is lagging behind, it can catch up fine
329 329
330 330 $ hg debugnodemap --metadata
331 331 uid: ???????????????? (glob)
332 332 tip-rev: 5003
333 333 tip-node: c9329770f979ade2d16912267c38ba5f82fd37b3
334 334 data-length: 121344 (pure !)
335 335 data-length: 121344 (rust !)
336 336 data-length: 121152 (no-rust no-pure !)
337 337 data-unused: 192 (pure !)
338 338 data-unused: 192 (rust !)
339 339 data-unused: 0 (no-rust no-pure !)
340 340 data-unused: 0.158% (pure !)
341 341 data-unused: 0.158% (rust !)
342 342 data-unused: 0.000% (no-rust no-pure !)
343 343 $ cp -f ../tmp-copies/* .hg/store/
344 344 $ hg debugnodemap --metadata
345 345 uid: ???????????????? (glob)
346 346 tip-rev: 5002
347 347 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
348 348 data-length: 121088
349 349 data-unused: 0
350 350 data-unused: 0.000%
351 351 $ hg log -r "$NODE" -T '{rev}\n'
352 352 5003
353 353
354 354 changelog altered
355 355 -----------------
356 356
357 357 If the nodemap is not gated behind a requirements, an unaware client can alter
358 358 the repository so the revlog used to generate the nodemap is not longer
359 359 compatible with the persistent nodemap. We need to detect that.
360 360
361 361 $ hg up "$NODE~5"
362 362 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
363 363 $ echo bar > babar
364 364 $ hg add babar
365 365 $ hg ci -m 'babar'
366 366 created new head
367 367 $ OTHERNODE=`hg log -r tip -T '{node}\n'`
368 368 $ hg log -r "$OTHERNODE" -T '{rev}\n'
369 369 5004
370 370
371 371 $ hg --config extensions.strip= strip --rev "$NODE~1" --no-backup
372 372
373 373 the nodemap should detect the changelog have been tampered with and recover.
374 374
375 375 $ hg debugnodemap --metadata
376 376 uid: ???????????????? (glob)
377 377 tip-rev: 5002
378 378 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
379 379 data-length: 121536 (pure !)
380 380 data-length: 121088 (rust !)
381 381 data-length: 121088 (no-pure no-rust !)
382 382 data-unused: 448 (pure !)
383 383 data-unused: 0 (rust !)
384 384 data-unused: 0 (no-pure no-rust !)
385 385 data-unused: 0.000% (rust !)
386 386 data-unused: 0.369% (pure !)
387 387 data-unused: 0.000% (no-pure no-rust !)
388 388
389 389 $ cp -f ../tmp-copies/* .hg/store/
390 390 $ hg debugnodemap --metadata
391 391 uid: ???????????????? (glob)
392 392 tip-rev: 5002
393 393 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
394 394 data-length: 121088
395 395 data-unused: 0
396 396 data-unused: 0.000%
397 397 $ hg log -r "$OTHERNODE" -T '{rev}\n'
398 398 5002
399 399
400 400 missing data file
401 401 -----------------
402 402
403 403 $ UUID=`hg debugnodemap --metadata| grep 'uid:' | \
404 404 > sed 's/uid: //'`
405 405 $ FILE=.hg/store/00changelog-"${UUID}".nd
406 406 $ mv $FILE ../tmp-data-file
407 407 $ cp .hg/store/00changelog.n ../tmp-docket
408 408
409 409 mercurial don't crash
410 410
411 411 $ hg log -r .
412 412 changeset: 5002:b355ef8adce0
413 413 tag: tip
414 414 parent: 4998:d918ad6d18d3
415 415 user: test
416 416 date: Thu Jan 01 00:00:00 1970 +0000
417 417 summary: babar
418 418
419 419 $ hg debugnodemap --metadata
420 420
421 421 $ hg debugupdatecache
422 422 $ hg debugnodemap --metadata
423 423 uid: * (glob)
424 424 tip-rev: 5002
425 425 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
426 426 data-length: 121088
427 427 data-unused: 0
428 428 data-unused: 0.000%
429 429 $ mv ../tmp-data-file $FILE
430 430 $ mv ../tmp-docket .hg/store/00changelog.n
431 431
432 432 Check transaction related property
433 433 ==================================
434 434
435 435 An up to date nodemap should be available to shell hooks,
436 436
437 437 $ echo dsljfl > a
438 438 $ hg add a
439 439 $ hg ci -m a
440 440 $ hg debugnodemap --metadata
441 441 uid: ???????????????? (glob)
442 442 tip-rev: 5003
443 443 tip-node: a52c5079765b5865d97b993b303a18740113bbb2
444 444 data-length: 121088
445 445 data-unused: 0
446 446 data-unused: 0.000%
447 447 $ echo babar2 > babar
448 448 $ hg ci -m 'babar2' --config "hooks.pretxnclose.nodemap-test=hg debugnodemap --metadata"
449 449 uid: ???????????????? (glob)
450 450 tip-rev: 5004
451 451 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
452 452 data-length: 121280 (pure !)
453 453 data-length: 121280 (rust !)
454 454 data-length: 121088 (no-pure no-rust !)
455 455 data-unused: 192 (pure !)
456 456 data-unused: 192 (rust !)
457 457 data-unused: 0 (no-pure no-rust !)
458 458 data-unused: 0.158% (pure !)
459 459 data-unused: 0.158% (rust !)
460 460 data-unused: 0.000% (no-pure no-rust !)
461 461 $ hg debugnodemap --metadata
462 462 uid: ???????????????? (glob)
463 463 tip-rev: 5004
464 464 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
465 465 data-length: 121280 (pure !)
466 466 data-length: 121280 (rust !)
467 467 data-length: 121088 (no-pure no-rust !)
468 468 data-unused: 192 (pure !)
469 469 data-unused: 192 (rust !)
470 470 data-unused: 0 (no-pure no-rust !)
471 471 data-unused: 0.158% (pure !)
472 472 data-unused: 0.158% (rust !)
473 473 data-unused: 0.000% (no-pure no-rust !)
474 474
475 475 Another process does not see the pending nodemap content during run.
476 476
477 477 $ PATH=$RUNTESTDIR/testlib/:$PATH
478 478 $ echo qpoasp > a
479 479 $ hg ci -m a2 \
480 480 > --config "hooks.pretxnclose=wait-on-file 20 sync-repo-read sync-txn-pending" \
481 481 > --config "hooks.txnclose=touch sync-txn-close" > output.txt 2>&1 &
482 482
483 483 (read the repository while the commit transaction is pending)
484 484
485 485 $ wait-on-file 20 sync-txn-pending && \
486 486 > hg debugnodemap --metadata && \
487 487 > wait-on-file 20 sync-txn-close sync-repo-read
488 488 uid: ???????????????? (glob)
489 489 tip-rev: 5004
490 490 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
491 491 data-length: 121280 (pure !)
492 492 data-length: 121280 (rust !)
493 493 data-length: 121088 (no-pure no-rust !)
494 494 data-unused: 192 (pure !)
495 495 data-unused: 192 (rust !)
496 496 data-unused: 0 (no-pure no-rust !)
497 497 data-unused: 0.158% (pure !)
498 498 data-unused: 0.158% (rust !)
499 499 data-unused: 0.000% (no-pure no-rust !)
500 500 $ hg debugnodemap --metadata
501 501 uid: ???????????????? (glob)
502 502 tip-rev: 5005
503 503 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
504 504 data-length: 121536 (pure !)
505 505 data-length: 121536 (rust !)
506 506 data-length: 121088 (no-pure no-rust !)
507 507 data-unused: 448 (pure !)
508 508 data-unused: 448 (rust !)
509 509 data-unused: 0 (no-pure no-rust !)
510 510 data-unused: 0.369% (pure !)
511 511 data-unused: 0.369% (rust !)
512 512 data-unused: 0.000% (no-pure no-rust !)
513 513
514 514 $ cat output.txt
515 515
516 516 Check that a failing transaction will properly revert the data
517 517
518 518 $ echo plakfe > a
519 519 $ f --size --sha256 .hg/store/00changelog-*.nd
520 520 .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
521 521 .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
522 522 .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
523 523 $ hg ci -m a3 --config "extensions.abort=$RUNTESTDIR/testlib/crash_transaction_late.py"
524 524 transaction abort!
525 525 rollback completed
526 526 abort: This is a late abort
527 527 [255]
528 528 $ hg debugnodemap --metadata
529 529 uid: ???????????????? (glob)
530 530 tip-rev: 5005
531 531 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
532 532 data-length: 121536 (pure !)
533 533 data-length: 121536 (rust !)
534 534 data-length: 121088 (no-pure no-rust !)
535 535 data-unused: 448 (pure !)
536 536 data-unused: 448 (rust !)
537 537 data-unused: 0 (no-pure no-rust !)
538 538 data-unused: 0.369% (pure !)
539 539 data-unused: 0.369% (rust !)
540 540 data-unused: 0.000% (no-pure no-rust !)
541 541 $ f --size --sha256 .hg/store/00changelog-*.nd
542 542 .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
543 543 .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
544 544 .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
545 545
546 546 Check that removing content does not confuse the nodemap
547 547 --------------------------------------------------------
548 548
549 549 removing data with rollback
550 550
551 551 $ echo aso > a
552 552 $ hg ci -m a4
553 553 $ hg rollback
554 554 repository tip rolled back to revision 5005 (undo commit)
555 555 working directory now based on revision 5005
556 556 $ hg id -r .
557 557 90d5d3ba2fc4 tip
558 558
559 559 roming data with strip
560 560
561 561 $ echo aso > a
562 562 $ hg ci -m a4
563 563 $ hg --config extensions.strip= strip -r . --no-backup
564 564 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
565 565 $ hg id -r . --traceback
566 566 90d5d3ba2fc4 tip
567 567
568 568 Test upgrade / downgrade
569 569 ========================
570 570
571 571 downgrading
572 572
573 573 $ cat << EOF >> .hg/hgrc
574 574 > [format]
575 575 > use-persistent-nodemap=no
576 576 > EOF
577 577 $ hg debugformat -v
578 578 format-variant repo config default
579 579 fncache: yes yes yes
580 580 dotencode: yes yes yes
581 581 generaldelta: yes yes yes
582 582 share-safe: no no no
583 583 sparserevlog: yes yes yes
584 584 persistent-nodemap: yes no no
585 585 copies-sdc: no no no
586 586 revlog-v2: no no no
587 587 plain-cl-delta: yes yes yes
588 588 compression: zlib zlib zlib (no-zstd !)
589 589 compression: zstd zstd zstd (zstd !)
590 590 compression-level: default default default
591 591 $ hg debugupgraderepo --run --no-backup
592 592 upgrade will perform the following actions:
593 593
594 594 requirements
595 595 preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !)
596 596 preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !)
597 597 removed: persistent-nodemap
598 598
599 599 processed revlogs:
600 600 - all-filelogs
601 601 - changelog
602 602 - manifest
603 603
604 604 beginning upgrade...
605 605 repository locked and read-only
606 606 creating temporary repository to stage upgraded data: $TESTTMP/test-repo/.hg/upgrade.* (glob)
607 607 (it is safe to interrupt this process any time before data migration completes)
608 608 downgrading repository to not use persistent nodemap feature
609 609 removing temporary repository $TESTTMP/test-repo/.hg/upgrade.* (glob)
610 610 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
611 611 00changelog-*.nd (glob)
612 612 00manifest-*.nd (glob)
613 613 undo.backup.00changelog.n
614 614 undo.backup.00manifest.n
615 615 $ hg debugnodemap --metadata
616 616
617 617
618 618 upgrading
619 619
620 620 $ cat << EOF >> .hg/hgrc
621 621 > [format]
622 622 > use-persistent-nodemap=yes
623 623 > EOF
624 624 $ hg debugformat -v
625 625 format-variant repo config default
626 626 fncache: yes yes yes
627 627 dotencode: yes yes yes
628 628 generaldelta: yes yes yes
629 629 share-safe: no no no
630 630 sparserevlog: yes yes yes
631 631 persistent-nodemap: no yes no
632 632 copies-sdc: no no no
633 633 revlog-v2: no no no
634 634 plain-cl-delta: yes yes yes
635 635 compression: zlib zlib zlib (no-zstd !)
636 636 compression: zstd zstd zstd (zstd !)
637 637 compression-level: default default default
638 638 $ hg debugupgraderepo --run --no-backup
639 639 upgrade will perform the following actions:
640 640
641 641 requirements
642 642 preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !)
643 643 preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !)
644 644 added: persistent-nodemap
645 645
646 646 persistent-nodemap
647 647 Speedup revision lookup by node id.
648 648
649 649 processed revlogs:
650 650 - all-filelogs
651 651 - changelog
652 652 - manifest
653 653
654 654 beginning upgrade...
655 655 repository locked and read-only
656 656 creating temporary repository to stage upgraded data: $TESTTMP/test-repo/.hg/upgrade.* (glob)
657 657 (it is safe to interrupt this process any time before data migration completes)
658 658 upgrading repository to use persistent nodemap feature
659 659 removing temporary repository $TESTTMP/test-repo/.hg/upgrade.* (glob)
660 660 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
661 661 00changelog-*.nd (glob)
662 662 00changelog.n
663 663 00manifest-*.nd (glob)
664 664 00manifest.n
665 665 undo.backup.00changelog.n
666 666 undo.backup.00manifest.n
667 667
668 668 $ hg debugnodemap --metadata
669 669 uid: * (glob)
670 670 tip-rev: 5005
671 671 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
672 672 data-length: 121088
673 673 data-unused: 0
674 674 data-unused: 0.000%
675 675
676 676 Running unrelated upgrade
677 677
678 678 $ hg debugupgraderepo --run --no-backup --quiet --optimize re-delta-all
679 679 upgrade will perform the following actions:
680 680
681 681 requirements
682 682 preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (no-zstd !)
683 683 preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !)
684 684
685 685 optimisations: re-delta-all
686 686
687 687 processed revlogs:
688 688 - all-filelogs
689 689 - changelog
690 690 - manifest
691 691
692 692 $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
693 693 00changelog-*.nd (glob)
694 694 00changelog.n
695 695 00manifest-*.nd (glob)
696 696 00manifest.n
697 697
698 698 $ hg debugnodemap --metadata
699 699 uid: * (glob)
700 700 tip-rev: 5005
701 701 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
702 702 data-length: 121088
703 703 data-unused: 0
704 704 data-unused: 0.000%
705 705
706 706 Persistent nodemap and local/streaming clone
707 707 ============================================
708 708
709 709 $ cd ..
710 710
711 711 standard clone
712 712 --------------
713 713
714 714 The persistent nodemap should exist after a streaming clone
715 715
716 716 $ hg clone --pull --quiet -U test-repo standard-clone
717 717 $ ls -1 standard-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
718 718 00changelog-*.nd (glob)
719 719 00changelog.n
720 720 00manifest-*.nd (glob)
721 721 00manifest.n
722 722 $ hg -R standard-clone debugnodemap --metadata
723 723 uid: * (glob)
724 724 tip-rev: 5005
725 725 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
726 726 data-length: 121088
727 727 data-unused: 0
728 728 data-unused: 0.000%
729 729
730 730
731 731 local clone
732 732 ------------
733 733
734 734 The persistent nodemap should exist after a streaming clone
735 735
736 736 $ hg clone -U test-repo local-clone
737 737 $ ls -1 local-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
738 738 00changelog-*.nd (glob)
739 739 00changelog.n
740 740 00manifest-*.nd (glob)
741 741 00manifest.n
742 742 $ hg -R local-clone debugnodemap --metadata
743 743 uid: * (glob)
744 744 tip-rev: 5005
745 745 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
746 746 data-length: 121088
747 747 data-unused: 0
748 748 data-unused: 0.000%
749 749
750 750 Test various corruption case
751 751 ============================
752 752
753 753 Missing datafile
754 754 ----------------
755 755
756 756 Test behavior with a missing datafile
757 757
758 758 $ hg clone --quiet --pull test-repo corruption-test-repo
759 759 $ ls -1 corruption-test-repo/.hg/store/00changelog*
760 760 corruption-test-repo/.hg/store/00changelog-*.nd (glob)
761 761 corruption-test-repo/.hg/store/00changelog.d
762 762 corruption-test-repo/.hg/store/00changelog.i
763 763 corruption-test-repo/.hg/store/00changelog.n
764 764 $ rm corruption-test-repo/.hg/store/00changelog*.nd
765 765 $ hg log -R corruption-test-repo -r .
766 766 changeset: 5005:90d5d3ba2fc4
767 767 tag: tip
768 768 user: test
769 769 date: Thu Jan 01 00:00:00 1970 +0000
770 770 summary: a2
771 771
772 772 $ ls -1 corruption-test-repo/.hg/store/00changelog*
773 773 corruption-test-repo/.hg/store/00changelog.d
774 774 corruption-test-repo/.hg/store/00changelog.i
775 775 corruption-test-repo/.hg/store/00changelog.n
776 776
777 777 Truncated data file
778 778 -------------------
779 779
780 780 Test behavior with a too short datafile
781 781
782 782 rebuild the missing data
783 783 $ hg -R corruption-test-repo debugupdatecache
784 784 $ ls -1 corruption-test-repo/.hg/store/00changelog*
785 785 corruption-test-repo/.hg/store/00changelog-*.nd (glob)
786 786 corruption-test-repo/.hg/store/00changelog.d
787 787 corruption-test-repo/.hg/store/00changelog.i
788 788 corruption-test-repo/.hg/store/00changelog.n
789 789
790 790 truncate the file
791 791
792 792 $ datafilepath=`ls corruption-test-repo/.hg/store/00changelog*.nd`
793 793 $ f -s $datafilepath
794 794 corruption-test-repo/.hg/store/00changelog-*.nd: size=121088 (glob)
795 795 $ dd if=$datafilepath bs=1000 count=10 of=$datafilepath-tmp status=none
796 796 $ mv $datafilepath-tmp $datafilepath
797 797 $ f -s $datafilepath
798 798 corruption-test-repo/.hg/store/00changelog-*.nd: size=10000 (glob)
799 799
800 800 Check that Mercurial reaction to this event
801 801
802 802 $ hg -R corruption-test-repo log -r . --traceback
803 803 changeset: 5005:90d5d3ba2fc4
804 804 tag: tip
805 805 user: test
806 806 date: Thu Jan 01 00:00:00 1970 +0000
807 807 summary: a2
808 808
809 809
810 810
811 811 stream clone
812 812 ============
813 813
814 814 The persistent nodemap should exist after a streaming clone
815 815
816 816 Simple case
817 817 -----------
818 818
819 819 No race condition
820 820
821 821 $ hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone --debug | egrep '00(changelog|manifest)'
822 822 adding [s] 00manifest.n (70 bytes)
823 823 adding [s] 00manifest-*.nd (118 KB) (glob)
824 824 adding [s] 00changelog.n (70 bytes)
825 825 adding [s] 00changelog-*.nd (118 KB) (glob)
826 826 adding [s] 00manifest.d (452 KB) (no-zstd !)
827 827 adding [s] 00manifest.d (491 KB) (zstd !)
828 828 adding [s] 00changelog.d (360 KB) (no-zstd !)
829 829 adding [s] 00changelog.d (368 KB) (zstd !)
830 830 adding [s] 00manifest.i (313 KB)
831 831 adding [s] 00changelog.i (313 KB)
832 832 $ ls -1 stream-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
833 833 00changelog-*.nd (glob)
834 834 00changelog.n
835 835 00manifest-*.nd (glob)
836 836 00manifest.n
837 837 $ hg -R stream-clone debugnodemap --metadata
838 838 uid: * (glob)
839 839 tip-rev: 5005
840 840 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
841 841 data-length: 121088
842 842 data-unused: 0
843 843 data-unused: 0.000%
844 844
845 845 new data appened
846 846 -----------------
847 847
848 848 Other commit happening on the server during the stream clone
849 849
850 850 setup the step-by-step stream cloning
851 851
852 852 $ HG_TEST_STREAM_WALKED_FILE_1="$TESTTMP/sync_file_walked_1"
853 853 $ export HG_TEST_STREAM_WALKED_FILE_1
854 854 $ HG_TEST_STREAM_WALKED_FILE_2="$TESTTMP/sync_file_walked_2"
855 855 $ export HG_TEST_STREAM_WALKED_FILE_2
856 856 $ HG_TEST_STREAM_WALKED_FILE_3="$TESTTMP/sync_file_walked_3"
857 857 $ export HG_TEST_STREAM_WALKED_FILE_3
858 858 $ cat << EOF >> test-repo/.hg/hgrc
859 859 > [extensions]
860 860 > steps=$RUNTESTDIR/testlib/ext-stream-clone-steps.py
861 861 > EOF
862 862
863 863 Check and record file state beforehand
864 864
865 865 $ f --size test-repo/.hg/store/00changelog*
866 866 test-repo/.hg/store/00changelog-*.nd: size=121088 (glob)
867 867 test-repo/.hg/store/00changelog.d: size=376891 (zstd !)
868 868 test-repo/.hg/store/00changelog.d: size=368890 (no-zstd !)
869 869 test-repo/.hg/store/00changelog.i: size=320384
870 870 test-repo/.hg/store/00changelog.n: size=70
871 871 $ hg -R test-repo debugnodemap --metadata | tee server-metadata.txt
872 872 uid: * (glob)
873 873 tip-rev: 5005
874 874 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
875 875 data-length: 121088
876 876 data-unused: 0
877 877 data-unused: 0.000%
878 878
879 879 Prepare a commit
880 880
881 881 $ echo foo >> test-repo/foo
882 882 $ hg -R test-repo/ add test-repo/foo
883 883
884 884 Do a mix of clone and commit at the same time so that the file listed on disk differ at actual transfer time.
885 885
886 886 $ (hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone-race-1 --debug 2>> clone-output | egrep '00(changelog|manifest)' >> clone-output; touch $HG_TEST_STREAM_WALKED_FILE_3) &
887 887 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1
888 888 $ hg -R test-repo/ commit -m foo
889 889 $ touch $HG_TEST_STREAM_WALKED_FILE_2
890 890 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3
891 891 $ cat clone-output
892 892 adding [s] 00manifest.n (70 bytes)
893 893 adding [s] 00manifest-*.nd (118 KB) (glob)
894 894 adding [s] 00changelog.n (70 bytes)
895 895 adding [s] 00changelog-*.nd (118 KB) (glob)
896 896 adding [s] 00manifest.d (452 KB) (no-zstd !)
897 897 adding [s] 00manifest.d (491 KB) (zstd !)
898 898 adding [s] 00changelog.d (360 KB) (no-zstd !)
899 899 adding [s] 00changelog.d (368 KB) (zstd !)
900 900 adding [s] 00manifest.i (313 KB)
901 901 adding [s] 00changelog.i (313 KB)
902 902
903 903 Check the result state
904 904
905 905 $ f --size stream-clone-race-1/.hg/store/00changelog*
906 906 stream-clone-race-1/.hg/store/00changelog-*.nd: size=121088 (glob)
907 907 stream-clone-race-1/.hg/store/00changelog.d: size=368890 (no-zstd !)
908 908 stream-clone-race-1/.hg/store/00changelog.d: size=376891 (zstd !)
909 909 stream-clone-race-1/.hg/store/00changelog.i: size=320384
910 910 stream-clone-race-1/.hg/store/00changelog.n: size=70
911 911
912 912 $ hg -R stream-clone-race-1 debugnodemap --metadata | tee client-metadata.txt
913 913 uid: * (glob)
914 914 tip-rev: 5005
915 915 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
916 916 data-length: 121088
917 917 data-unused: 0
918 918 data-unused: 0.000%
919 919
920 920 We get a usable nodemap, so no rewrite would be needed and the metadata should be identical
921 921 (ie: the following diff should be empty)
922 922
923 923 This isn't the case for the `no-rust` `no-pure` implementation as it use a very minimal nodemap implementation that unconditionnaly rewrite the nodemap "all the time".
924 924
925 925 #if no-rust no-pure
926 926 $ diff -u server-metadata.txt client-metadata.txt
927 927 --- server-metadata.txt * (glob)
928 928 +++ client-metadata.txt * (glob)
929 929 @@ -1,4 +1,4 @@
930 930 -uid: * (glob)
931 931 +uid: * (glob)
932 932 tip-rev: 5005
933 933 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
934 934 data-length: 121088
935 935 [1]
936 936 #else
937 937 $ diff -u server-metadata.txt client-metadata.txt
938 938 #endif
939 939
940 940
941 941 Clean up after the test.
942 942
943 943 $ rm -f "$HG_TEST_STREAM_WALKED_FILE_1"
944 944 $ rm -f "$HG_TEST_STREAM_WALKED_FILE_2"
945 945 $ rm -f "$HG_TEST_STREAM_WALKED_FILE_3"
946 946
947 947 full regeneration
948 948 -----------------
949 949
950 950 A full nodemap is generated
951 951
952 952 (ideally this test would append enough data to make sure the nodemap data file
953 953 get changed, however to make thing simpler we will force the regeneration for
954 954 this test.
955 955
956 956 Check the initial state
957 957
958 958 $ f --size test-repo/.hg/store/00changelog*
959 959 test-repo/.hg/store/00changelog-*.nd: size=121344 (glob) (rust !)
960 960 test-repo/.hg/store/00changelog-*.nd: size=121344 (glob) (pure !)
961 961 test-repo/.hg/store/00changelog-*.nd: size=121152 (glob) (no-rust no-pure !)
962 962 test-repo/.hg/store/00changelog.d: size=376950 (zstd !)
963 963 test-repo/.hg/store/00changelog.d: size=368949 (no-zstd !)
964 964 test-repo/.hg/store/00changelog.i: size=320448
965 965 test-repo/.hg/store/00changelog.n: size=70
966 966 $ hg -R test-repo debugnodemap --metadata | tee server-metadata-2.txt
967 967 uid: * (glob)
968 968 tip-rev: 5006
969 969 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b
970 970 data-length: 121344 (rust !)
971 971 data-length: 121344 (pure !)
972 972 data-length: 121152 (no-rust no-pure !)
973 973 data-unused: 192 (rust !)
974 974 data-unused: 192 (pure !)
975 975 data-unused: 0 (no-rust no-pure !)
976 976 data-unused: 0.158% (rust !)
977 977 data-unused: 0.158% (pure !)
978 978 data-unused: 0.000% (no-rust no-pure !)
979 979
980 980 Performe the mix of clone and full refresh of the nodemap, so that the files
981 981 (and filenames) are different between listing time and actual transfer time.
982 982
983 983 $ (hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone-race-2 --debug 2>> clone-output-2 | egrep '00(changelog|manifest)' >> clone-output-2; touch $HG_TEST_STREAM_WALKED_FILE_3) &
984 984 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1
985 985 $ rm test-repo/.hg/store/00changelog.n
986 986 $ rm test-repo/.hg/store/00changelog-*.nd
987 987 $ hg -R test-repo/ debugupdatecache
988 988 $ touch $HG_TEST_STREAM_WALKED_FILE_2
989 989 $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3
990 990 $ cat clone-output-2
991 adding [s] undo.backup.00manifest.n (70 bytes) (known-bad-output !)
992 adding [s] undo.backup.00changelog.n (70 bytes) (known-bad-output !)
993 991 adding [s] 00manifest.n (70 bytes)
994 992 adding [s] 00manifest-*.nd (118 KB) (glob)
995 993 adding [s] 00changelog.n (70 bytes)
996 994 adding [s] 00changelog-*.nd (118 KB) (glob)
997 995 adding [s] 00manifest.d (492 KB) (zstd !)
998 996 adding [s] 00manifest.d (452 KB) (no-zstd !)
999 997 adding [s] 00changelog.d (360 KB) (no-zstd !)
1000 998 adding [s] 00changelog.d (368 KB) (zstd !)
1001 999 adding [s] 00manifest.i (313 KB)
1002 1000 adding [s] 00changelog.i (313 KB)
1003 1001
1004 1002 Check the result.
1005 1003
1006 1004 $ f --size stream-clone-race-2/.hg/store/00changelog*
1007 1005 stream-clone-race-2/.hg/store/00changelog-*.nd: size=121344 (glob) (rust !)
1008 1006 stream-clone-race-2/.hg/store/00changelog-*.nd: size=121344 (glob) (pure !)
1009 1007 stream-clone-race-2/.hg/store/00changelog-*.nd: size=121152 (glob) (no-rust no-pure !)
1010 1008 stream-clone-race-2/.hg/store/00changelog.d: size=376950 (zstd !)
1011 1009 stream-clone-race-2/.hg/store/00changelog.d: size=368949 (no-zstd !)
1012 1010 stream-clone-race-2/.hg/store/00changelog.i: size=320448
1013 1011 stream-clone-race-2/.hg/store/00changelog.n: size=70
1014 1012
1015 1013 $ hg -R stream-clone-race-2 debugnodemap --metadata | tee client-metadata-2.txt
1016 1014 uid: * (glob)
1017 1015 tip-rev: 5006
1018 1016 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b
1019 1017 data-length: 121344 (rust !)
1020 1018 data-unused: 192 (rust !)
1021 1019 data-unused: 0.158% (rust !)
1022 1020 data-length: 121152 (no-rust no-pure !)
1023 1021 data-unused: 0 (no-rust no-pure !)
1024 1022 data-unused: 0.000% (no-rust no-pure !)
1025 1023 data-length: 121344 (pure !)
1026 1024 data-unused: 192 (pure !)
1027 1025 data-unused: 0.158% (pure !)
1028 1026
1029 1027 We get a usable nodemap, so no rewrite would be needed and the metadata should be identical
1030 1028 (ie: the following diff should be empty)
1031 1029
1032 1030 This isn't the case for the `no-rust` `no-pure` implementation as it use a very minimal nodemap implementation that unconditionnaly rewrite the nodemap "all the time".
1033 1031
1034 1032 #if no-rust no-pure
1035 1033 $ diff -u server-metadata-2.txt client-metadata-2.txt
1036 1034 --- server-metadata-2.txt * (glob)
1037 1035 +++ client-metadata-2.txt * (glob)
1038 1036 @@ -1,4 +1,4 @@
1039 1037 -uid: * (glob)
1040 1038 +uid: * (glob)
1041 1039 tip-rev: 5006
1042 1040 tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b
1043 1041 data-length: 121152
1044 1042 [1]
1045 1043 #else
1046 1044 $ diff -u server-metadata-2.txt client-metadata-2.txt
1047 1045 #endif
1048 1046
1049 1047 Clean up after the test
1050 1048
1051 1049 $ rm -f $HG_TEST_STREAM_WALKED_FILE_1
1052 1050 $ rm -f $HG_TEST_STREAM_WALKED_FILE_2
1053 1051 $ rm -f $HG_TEST_STREAM_WALKED_FILE_3
1054 1052
General Comments 0
You need to be logged in to leave comments. Login now