##// END OF EJS Templates
nodemap: deal with data mmap error...
marmoute -
r47733:a3720569 default
parent child Browse files
Show More
@@ -1,660 +1,664 b''
1 1 # nodemap.py - nodemap related code and utilities
2 2 #
3 3 # Copyright 2019 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 # Copyright 2019 George Racinet <georges.racinet@octobus.net>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import os
13 13 import re
14 14 import struct
15 15
16 16 from ..node import hex
17 17
18 18 from .. import (
19 19 error,
20 20 util,
21 21 )
22 22
23 23
24 24 class NodeMap(dict):
25 25 def __missing__(self, x):
26 26 raise error.RevlogError(b'unknown node: %s' % x)
27 27
28 28
29 29 def persisted_data(revlog):
30 30 """read the nodemap for a revlog from disk"""
31 31 if revlog.nodemap_file is None:
32 32 return None
33 33 pdata = revlog.opener.tryread(revlog.nodemap_file)
34 34 if not pdata:
35 35 return None
36 36 offset = 0
37 37 (version,) = S_VERSION.unpack(pdata[offset : offset + S_VERSION.size])
38 38 if version != ONDISK_VERSION:
39 39 return None
40 40 offset += S_VERSION.size
41 41 headers = S_HEADER.unpack(pdata[offset : offset + S_HEADER.size])
42 42 uid_size, tip_rev, data_length, data_unused, tip_node_size = headers
43 43 offset += S_HEADER.size
44 44 docket = NodeMapDocket(pdata[offset : offset + uid_size])
45 45 offset += uid_size
46 46 docket.tip_rev = tip_rev
47 47 docket.tip_node = pdata[offset : offset + tip_node_size]
48 48 docket.data_length = data_length
49 49 docket.data_unused = data_unused
50 50
51 51 filename = _rawdata_filepath(revlog, docket)
52 52 use_mmap = revlog.opener.options.get(b"persistent-nodemap.mmap")
53 53 try:
54 54 with revlog.opener(filename) as fd:
55 55 if use_mmap:
56 try:
56 57 data = util.buffer(util.mmapread(fd, data_length))
58 except ValueError:
59 # raised when the read file is too small
60 data = b''
57 61 else:
58 62 data = fd.read(data_length)
59 63 except (IOError, OSError) as e:
60 64 if e.errno == errno.ENOENT:
61 65 return None
62 66 else:
63 67 raise
64 68 if len(data) < data_length:
65 69 return None
66 70 return docket, data
67 71
68 72
69 73 def setup_persistent_nodemap(tr, revlog):
70 74 """Install whatever is needed transaction side to persist a nodemap on disk
71 75
72 76 (only actually persist the nodemap if this is relevant for this revlog)
73 77 """
74 78 if revlog._inline:
75 79 return # inlined revlog are too small for this to be relevant
76 80 if revlog.nodemap_file is None:
77 81 return # we do not use persistent_nodemap on this revlog
78 82
79 83 # we need to happen after the changelog finalization, in that use "cl-"
80 84 callback_id = b"nm-revlog-persistent-nodemap-%s" % revlog.nodemap_file
81 85 if tr.hasfinalize(callback_id):
82 86 return # no need to register again
83 87 tr.addpending(
84 88 callback_id, lambda tr: persist_nodemap(tr, revlog, pending=True)
85 89 )
86 90 tr.addfinalize(callback_id, lambda tr: persist_nodemap(tr, revlog))
87 91
88 92
89 93 class _NoTransaction(object):
90 94 """transaction like object to update the nodemap outside a transaction"""
91 95
92 96 def __init__(self):
93 97 self._postclose = {}
94 98
95 99 def addpostclose(self, callback_id, callback_func):
96 100 self._postclose[callback_id] = callback_func
97 101
98 102 def registertmp(self, *args, **kwargs):
99 103 pass
100 104
101 105 def addbackup(self, *args, **kwargs):
102 106 pass
103 107
104 108 def add(self, *args, **kwargs):
105 109 pass
106 110
107 111 def addabort(self, *args, **kwargs):
108 112 pass
109 113
110 114 def _report(self, *args):
111 115 pass
112 116
113 117
114 118 def update_persistent_nodemap(revlog):
115 119 """update the persistent nodemap right now
116 120
117 121 To be used for updating the nodemap on disk outside of a normal transaction
118 122 setup (eg, `debugupdatecache`).
119 123 """
120 124 if revlog._inline:
121 125 return # inlined revlog are too small for this to be relevant
122 126 if revlog.nodemap_file is None:
123 127 return # we do not use persistent_nodemap on this revlog
124 128
125 129 notr = _NoTransaction()
126 130 persist_nodemap(notr, revlog)
127 131 for k in sorted(notr._postclose):
128 132 notr._postclose[k](None)
129 133
130 134
131 135 def delete_nodemap(tr, repo, revlog):
132 136 """ Delete nodemap data on disk for a given revlog"""
133 137 if revlog.nodemap_file is None:
134 138 msg = "calling persist nodemap on a revlog without the feature enabled"
135 139 raise error.ProgrammingError(msg)
136 140 repo.svfs.unlink(revlog.nodemap_file)
137 141
138 142
139 143 def persist_nodemap(tr, revlog, pending=False, force=False):
140 144 """Write nodemap data on disk for a given revlog"""
141 145 if getattr(revlog, 'filteredrevs', ()):
142 146 raise error.ProgrammingError(
143 147 "cannot persist nodemap of a filtered changelog"
144 148 )
145 149 if revlog.nodemap_file is None:
146 150 if force:
147 151 revlog.nodemap_file = get_nodemap_file(
148 152 revlog.opener, revlog.indexfile
149 153 )
150 154 else:
151 155 msg = "calling persist nodemap on a revlog without the feature enabled"
152 156 raise error.ProgrammingError(msg)
153 157
154 158 can_incremental = util.safehasattr(revlog.index, "nodemap_data_incremental")
155 159 ondisk_docket = revlog._nodemap_docket
156 160 feed_data = util.safehasattr(revlog.index, "update_nodemap_data")
157 161 use_mmap = revlog.opener.options.get(b"persistent-nodemap.mmap")
158 162
159 163 data = None
160 164 # first attemp an incremental update of the data
161 165 if can_incremental and ondisk_docket is not None:
162 166 target_docket = revlog._nodemap_docket.copy()
163 167 (
164 168 src_docket,
165 169 data_changed_count,
166 170 data,
167 171 ) = revlog.index.nodemap_data_incremental()
168 172 new_length = target_docket.data_length + len(data)
169 173 new_unused = target_docket.data_unused + data_changed_count
170 174 if src_docket != target_docket:
171 175 data = None
172 176 elif new_length <= (new_unused * 10): # under 10% of unused data
173 177 data = None
174 178 else:
175 179 datafile = _rawdata_filepath(revlog, target_docket)
176 180 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
177 181 # store vfs
178 182 tr.add(datafile, target_docket.data_length)
179 183 with revlog.opener(datafile, b'r+') as fd:
180 184 fd.seek(target_docket.data_length)
181 185 fd.write(data)
182 186 if feed_data:
183 187 if use_mmap:
184 188 fd.seek(0)
185 189 new_data = fd.read(new_length)
186 190 else:
187 191 fd.flush()
188 192 new_data = util.buffer(util.mmapread(fd, new_length))
189 193 target_docket.data_length = new_length
190 194 target_docket.data_unused = new_unused
191 195
192 196 if data is None:
193 197 # otherwise fallback to a full new export
194 198 target_docket = NodeMapDocket()
195 199 datafile = _rawdata_filepath(revlog, target_docket)
196 200 if util.safehasattr(revlog.index, "nodemap_data_all"):
197 201 data = revlog.index.nodemap_data_all()
198 202 else:
199 203 data = persistent_data(revlog.index)
200 204 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
201 205 # store vfs
202 206
203 207 tryunlink = revlog.opener.tryunlink
204 208
205 209 def abortck(tr):
206 210 tryunlink(datafile)
207 211
208 212 callback_id = b"delete-%s" % datafile
209 213
210 214 # some flavor of the transaction abort does not cleanup new file, it
211 215 # simply empty them.
212 216 tr.addabort(callback_id, abortck)
213 217 with revlog.opener(datafile, b'w+') as fd:
214 218 fd.write(data)
215 219 if feed_data:
216 220 if use_mmap:
217 221 new_data = data
218 222 else:
219 223 fd.flush()
220 224 new_data = util.buffer(util.mmapread(fd, len(data)))
221 225 target_docket.data_length = len(data)
222 226 target_docket.tip_rev = revlog.tiprev()
223 227 target_docket.tip_node = revlog.node(target_docket.tip_rev)
224 228 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
225 229 # store vfs
226 230 file_path = revlog.nodemap_file
227 231 if pending:
228 232 file_path += b'.a'
229 233 tr.registertmp(file_path)
230 234 else:
231 235 tr.addbackup(file_path)
232 236
233 237 with revlog.opener(file_path, b'w', atomictemp=True) as fp:
234 238 fp.write(target_docket.serialize())
235 239 revlog._nodemap_docket = target_docket
236 240 if feed_data:
237 241 revlog.index.update_nodemap_data(target_docket, new_data)
238 242
239 243 # search for old index file in all cases, some older process might have
240 244 # left one behind.
241 245 olds = _other_rawdata_filepath(revlog, target_docket)
242 246 if olds:
243 247 realvfs = getattr(revlog, '_realopener', revlog.opener)
244 248
245 249 def cleanup(tr):
246 250 for oldfile in olds:
247 251 realvfs.tryunlink(oldfile)
248 252
249 253 callback_id = b"revlog-cleanup-nodemap-%s" % revlog.nodemap_file
250 254 tr.addpostclose(callback_id, cleanup)
251 255
252 256
253 257 ### Nodemap docket file
254 258 #
255 259 # The nodemap data are stored on disk using 2 files:
256 260 #
257 261 # * a raw data files containing a persistent nodemap
258 262 # (see `Nodemap Trie` section)
259 263 #
260 264 # * a small "docket" file containing medatadata
261 265 #
262 266 # While the nodemap data can be multiple tens of megabytes, the "docket" is
263 267 # small, it is easy to update it automatically or to duplicated its content
264 268 # during a transaction.
265 269 #
266 270 # Multiple raw data can exist at the same time (The currently valid one and a
267 271 # new one beind used by an in progress transaction). To accomodate this, the
268 272 # filename hosting the raw data has a variable parts. The exact filename is
269 273 # specified inside the "docket" file.
270 274 #
271 275 # The docket file contains information to find, qualify and validate the raw
272 276 # data. Its content is currently very light, but it will expand as the on disk
273 277 # nodemap gains the necessary features to be used in production.
274 278
275 279 ONDISK_VERSION = 1
276 280 S_VERSION = struct.Struct(">B")
277 281 S_HEADER = struct.Struct(">BQQQQ")
278 282
279 283 ID_SIZE = 8
280 284
281 285
282 286 def _make_uid():
283 287 """return a new unique identifier.
284 288
285 289 The identifier is random and composed of ascii characters."""
286 290 return hex(os.urandom(ID_SIZE))
287 291
288 292
289 293 class NodeMapDocket(object):
290 294 """metadata associated with persistent nodemap data
291 295
292 296 The persistent data may come from disk or be on their way to disk.
293 297 """
294 298
295 299 def __init__(self, uid=None):
296 300 if uid is None:
297 301 uid = _make_uid()
298 302 # a unique identifier for the data file:
299 303 # - When new data are appended, it is preserved.
300 304 # - When a new data file is created, a new identifier is generated.
301 305 self.uid = uid
302 306 # the tipmost revision stored in the data file. This revision and all
303 307 # revision before it are expected to be encoded in the data file.
304 308 self.tip_rev = None
305 309 # the node of that tipmost revision, if it mismatch the current index
306 310 # data the docket is not valid for the current index and should be
307 311 # discarded.
308 312 #
309 313 # note: this method is not perfect as some destructive operation could
310 314 # preserve the same tip_rev + tip_node while altering lower revision.
311 315 # However this multiple other caches have the same vulnerability (eg:
312 316 # brancmap cache).
313 317 self.tip_node = None
314 318 # the size (in bytes) of the persisted data to encode the nodemap valid
315 319 # for `tip_rev`.
316 320 # - data file shorter than this are corrupted,
317 321 # - any extra data should be ignored.
318 322 self.data_length = None
319 323 # the amount (in bytes) of "dead" data, still in the data file but no
320 324 # longer used for the nodemap.
321 325 self.data_unused = 0
322 326
323 327 def copy(self):
324 328 new = NodeMapDocket(uid=self.uid)
325 329 new.tip_rev = self.tip_rev
326 330 new.tip_node = self.tip_node
327 331 new.data_length = self.data_length
328 332 new.data_unused = self.data_unused
329 333 return new
330 334
331 335 def __cmp__(self, other):
332 336 if self.uid < other.uid:
333 337 return -1
334 338 if self.uid > other.uid:
335 339 return 1
336 340 elif self.data_length < other.data_length:
337 341 return -1
338 342 elif self.data_length > other.data_length:
339 343 return 1
340 344 return 0
341 345
342 346 def __eq__(self, other):
343 347 return self.uid == other.uid and self.data_length == other.data_length
344 348
345 349 def serialize(self):
346 350 """return serialized bytes for a docket using the passed uid"""
347 351 data = []
348 352 data.append(S_VERSION.pack(ONDISK_VERSION))
349 353 headers = (
350 354 len(self.uid),
351 355 self.tip_rev,
352 356 self.data_length,
353 357 self.data_unused,
354 358 len(self.tip_node),
355 359 )
356 360 data.append(S_HEADER.pack(*headers))
357 361 data.append(self.uid)
358 362 data.append(self.tip_node)
359 363 return b''.join(data)
360 364
361 365
362 366 def _rawdata_filepath(revlog, docket):
363 367 """The (vfs relative) nodemap's rawdata file for a given uid"""
364 368 if revlog.nodemap_file.endswith(b'.n.a'):
365 369 prefix = revlog.nodemap_file[:-4]
366 370 else:
367 371 prefix = revlog.nodemap_file[:-2]
368 372 return b"%s-%s.nd" % (prefix, docket.uid)
369 373
370 374
371 375 def _other_rawdata_filepath(revlog, docket):
372 376 prefix = revlog.nodemap_file[:-2]
373 377 pattern = re.compile(br"(^|/)%s-[0-9a-f]+\.nd$" % prefix)
374 378 new_file_path = _rawdata_filepath(revlog, docket)
375 379 new_file_name = revlog.opener.basename(new_file_path)
376 380 dirpath = revlog.opener.dirname(new_file_path)
377 381 others = []
378 382 for f in revlog.opener.listdir(dirpath):
379 383 if pattern.match(f) and f != new_file_name:
380 384 others.append(f)
381 385 return others
382 386
383 387
384 388 ### Nodemap Trie
385 389 #
386 390 # This is a simple reference implementation to compute and persist a nodemap
387 391 # trie. This reference implementation is write only. The python version of this
388 392 # is not expected to be actually used, since it wont provide performance
389 393 # improvement over existing non-persistent C implementation.
390 394 #
391 395 # The nodemap is persisted as Trie using 4bits-address/16-entries block. each
392 396 # revision can be adressed using its node shortest prefix.
393 397 #
394 398 # The trie is stored as a sequence of block. Each block contains 16 entries
395 399 # (signed 64bit integer, big endian). Each entry can be one of the following:
396 400 #
397 401 # * value >= 0 -> index of sub-block
398 402 # * value == -1 -> no value
399 403 # * value < -1 -> encoded revision: rev = -(value+2)
400 404 #
401 405 # See REV_OFFSET and _transform_rev below.
402 406 #
403 407 # The implementation focus on simplicity, not on performance. A Rust
404 408 # implementation should provide a efficient version of the same binary
405 409 # persistence. This reference python implementation is never meant to be
406 410 # extensively use in production.
407 411
408 412
409 413 def persistent_data(index):
410 414 """return the persistent binary form for a nodemap for a given index"""
411 415 trie = _build_trie(index)
412 416 return _persist_trie(trie)
413 417
414 418
415 419 def update_persistent_data(index, root, max_idx, last_rev):
416 420 """return the incremental update for persistent nodemap from a given index"""
417 421 changed_block, trie = _update_trie(index, root, last_rev)
418 422 return (
419 423 changed_block * S_BLOCK.size,
420 424 _persist_trie(trie, existing_idx=max_idx),
421 425 )
422 426
423 427
424 428 S_BLOCK = struct.Struct(">" + ("l" * 16))
425 429
426 430 NO_ENTRY = -1
427 431 # rev 0 need to be -2 because 0 is used by block, -1 is a special value.
428 432 REV_OFFSET = 2
429 433
430 434
431 435 def _transform_rev(rev):
432 436 """Return the number used to represent the rev in the tree.
433 437
434 438 (or retrieve a rev number from such representation)
435 439
436 440 Note that this is an involution, a function equal to its inverse (i.e.
437 441 which gives the identity when applied to itself).
438 442 """
439 443 return -(rev + REV_OFFSET)
440 444
441 445
442 446 def _to_int(hex_digit):
443 447 """turn an hexadecimal digit into a proper integer"""
444 448 return int(hex_digit, 16)
445 449
446 450
447 451 class Block(dict):
448 452 """represent a block of the Trie
449 453
450 454 contains up to 16 entry indexed from 0 to 15"""
451 455
452 456 def __init__(self):
453 457 super(Block, self).__init__()
454 458 # If this block exist on disk, here is its ID
455 459 self.ondisk_id = None
456 460
457 461 def __iter__(self):
458 462 return iter(self.get(i) for i in range(16))
459 463
460 464
461 465 def _build_trie(index):
462 466 """build a nodemap trie
463 467
464 468 The nodemap stores revision number for each unique prefix.
465 469
466 470 Each block is a dictionary with keys in `[0, 15]`. Values are either
467 471 another block or a revision number.
468 472 """
469 473 root = Block()
470 474 for rev in range(len(index)):
471 475 current_hex = hex(index[rev][7])
472 476 _insert_into_block(index, 0, root, rev, current_hex)
473 477 return root
474 478
475 479
476 480 def _update_trie(index, root, last_rev):
477 481 """consume"""
478 482 changed = 0
479 483 for rev in range(last_rev + 1, len(index)):
480 484 current_hex = hex(index[rev][7])
481 485 changed += _insert_into_block(index, 0, root, rev, current_hex)
482 486 return changed, root
483 487
484 488
485 489 def _insert_into_block(index, level, block, current_rev, current_hex):
486 490 """insert a new revision in a block
487 491
488 492 index: the index we are adding revision for
489 493 level: the depth of the current block in the trie
490 494 block: the block currently being considered
491 495 current_rev: the revision number we are adding
492 496 current_hex: the hexadecimal representation of the of that revision
493 497 """
494 498 changed = 1
495 499 if block.ondisk_id is not None:
496 500 block.ondisk_id = None
497 501 hex_digit = _to_int(current_hex[level : level + 1])
498 502 entry = block.get(hex_digit)
499 503 if entry is None:
500 504 # no entry, simply store the revision number
501 505 block[hex_digit] = current_rev
502 506 elif isinstance(entry, dict):
503 507 # need to recurse to an underlying block
504 508 changed += _insert_into_block(
505 509 index, level + 1, entry, current_rev, current_hex
506 510 )
507 511 else:
508 512 # collision with a previously unique prefix, inserting new
509 513 # vertices to fit both entry.
510 514 other_hex = hex(index[entry][7])
511 515 other_rev = entry
512 516 new = Block()
513 517 block[hex_digit] = new
514 518 _insert_into_block(index, level + 1, new, other_rev, other_hex)
515 519 _insert_into_block(index, level + 1, new, current_rev, current_hex)
516 520 return changed
517 521
518 522
519 523 def _persist_trie(root, existing_idx=None):
520 524 """turn a nodemap trie into persistent binary data
521 525
522 526 See `_build_trie` for nodemap trie structure"""
523 527 block_map = {}
524 528 if existing_idx is not None:
525 529 base_idx = existing_idx + 1
526 530 else:
527 531 base_idx = 0
528 532 chunks = []
529 533 for tn in _walk_trie(root):
530 534 if tn.ondisk_id is not None:
531 535 block_map[id(tn)] = tn.ondisk_id
532 536 else:
533 537 block_map[id(tn)] = len(chunks) + base_idx
534 538 chunks.append(_persist_block(tn, block_map))
535 539 return b''.join(chunks)
536 540
537 541
538 542 def _walk_trie(block):
539 543 """yield all the block in a trie
540 544
541 545 Children blocks are always yield before their parent block.
542 546 """
543 547 for (__, item) in sorted(block.items()):
544 548 if isinstance(item, dict):
545 549 for sub_block in _walk_trie(item):
546 550 yield sub_block
547 551 yield block
548 552
549 553
550 554 def _persist_block(block_node, block_map):
551 555 """produce persistent binary data for a single block
552 556
553 557 Children block are assumed to be already persisted and present in
554 558 block_map.
555 559 """
556 560 data = tuple(_to_value(v, block_map) for v in block_node)
557 561 return S_BLOCK.pack(*data)
558 562
559 563
560 564 def _to_value(item, block_map):
561 565 """persist any value as an integer"""
562 566 if item is None:
563 567 return NO_ENTRY
564 568 elif isinstance(item, dict):
565 569 return block_map[id(item)]
566 570 else:
567 571 return _transform_rev(item)
568 572
569 573
570 574 def parse_data(data):
571 575 """parse parse nodemap data into a nodemap Trie"""
572 576 if (len(data) % S_BLOCK.size) != 0:
573 577 msg = b"nodemap data size is not a multiple of block size (%d): %d"
574 578 raise error.Abort(msg % (S_BLOCK.size, len(data)))
575 579 if not data:
576 580 return Block(), None
577 581 block_map = {}
578 582 new_blocks = []
579 583 for i in range(0, len(data), S_BLOCK.size):
580 584 block = Block()
581 585 block.ondisk_id = len(block_map)
582 586 block_map[block.ondisk_id] = block
583 587 block_data = data[i : i + S_BLOCK.size]
584 588 values = S_BLOCK.unpack(block_data)
585 589 new_blocks.append((block, values))
586 590 for b, values in new_blocks:
587 591 for idx, v in enumerate(values):
588 592 if v == NO_ENTRY:
589 593 continue
590 594 elif v >= 0:
591 595 b[idx] = block_map[v]
592 596 else:
593 597 b[idx] = _transform_rev(v)
594 598 return block, i // S_BLOCK.size
595 599
596 600
597 601 # debug utility
598 602
599 603
600 604 def check_data(ui, index, data):
601 605 """verify that the provided nodemap data are valid for the given idex"""
602 606 ret = 0
603 607 ui.status((b"revision in index: %d\n") % len(index))
604 608 root, __ = parse_data(data)
605 609 all_revs = set(_all_revisions(root))
606 610 ui.status((b"revision in nodemap: %d\n") % len(all_revs))
607 611 for r in range(len(index)):
608 612 if r not in all_revs:
609 613 msg = b" revision missing from nodemap: %d\n" % r
610 614 ui.write_err(msg)
611 615 ret = 1
612 616 else:
613 617 all_revs.remove(r)
614 618 nm_rev = _find_node(root, hex(index[r][7]))
615 619 if nm_rev is None:
616 620 msg = b" revision node does not match any entries: %d\n" % r
617 621 ui.write_err(msg)
618 622 ret = 1
619 623 elif nm_rev != r:
620 624 msg = (
621 625 b" revision node does not match the expected revision: "
622 626 b"%d != %d\n" % (r, nm_rev)
623 627 )
624 628 ui.write_err(msg)
625 629 ret = 1
626 630
627 631 if all_revs:
628 632 for r in sorted(all_revs):
629 633 msg = b" extra revision in nodemap: %d\n" % r
630 634 ui.write_err(msg)
631 635 ret = 1
632 636 return ret
633 637
634 638
635 639 def _all_revisions(root):
636 640 """return all revisions stored in a Trie"""
637 641 for block in _walk_trie(root):
638 642 for v in block:
639 643 if v is None or isinstance(v, Block):
640 644 continue
641 645 yield v
642 646
643 647
644 648 def _find_node(block, node):
645 649 """find the revision associated with a given node"""
646 650 entry = block.get(_to_int(node[0:1]))
647 651 if isinstance(entry, dict):
648 652 return _find_node(entry, node[1:])
649 653 return entry
650 654
651 655
652 656 def get_nodemap_file(opener, indexfile):
653 657 if indexfile.endswith(b'.a'):
654 658 pending_path = indexfile[:-4] + b".n.a"
655 659 if opener.exists(pending_path):
656 660 return pending_path
657 661 else:
658 662 return indexfile[:-4] + b".n"
659 663 else:
660 664 return indexfile[:-2] + b".n"
@@ -1,834 +1,838 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 $ hg -R corruption-test-repo log -r .
803 abort: index 00changelog.i is corrupted
804 [50]
802 $ hg -R corruption-test-repo log -r . --traceback
803 changeset: 5005:90d5d3ba2fc4
804 tag: tip
805 user: test
806 date: Thu Jan 01 00:00:00 1970 +0000
807 summary: a2
808
805 809
806 810
807 811 stream clone
808 812 ------------
809 813
810 814 The persistent nodemap should exist after a streaming clone
811 815
812 816 $ hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone --debug | egrep '00(changelog|manifest)'
813 817 adding [s] 00manifest.n (70 bytes)
814 818 adding [s] 00manifest.d (452 KB) (no-zstd !)
815 819 adding [s] 00manifest.d (491 KB) (zstd !)
816 820 adding [s] 00manifest-*.nd (118 KB) (glob)
817 821 adding [s] 00changelog.n (70 bytes)
818 822 adding [s] 00changelog.d (360 KB) (no-zstd !)
819 823 adding [s] 00changelog.d (368 KB) (zstd !)
820 824 adding [s] 00changelog-*.nd (118 KB) (glob)
821 825 adding [s] 00manifest.i (313 KB)
822 826 adding [s] 00changelog.i (313 KB)
823 827 $ ls -1 stream-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
824 828 00changelog-*.nd (glob)
825 829 00changelog.n
826 830 00manifest-*.nd (glob)
827 831 00manifest.n
828 832 $ hg -R stream-clone debugnodemap --metadata
829 833 uid: * (glob)
830 834 tip-rev: 5005
831 835 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
832 836 data-length: 121088
833 837 data-unused: 0
834 838 data-unused: 0.000%
General Comments 0
You need to be logged in to leave comments. Login now