##// END OF EJS Templates
nodemap: add a new mode value, "strict"...
marmoute -
r45293:6b01799e default
parent child Browse files
Show More
@@ -1,643 +1,645 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 ..i18n import _
17 17
18 18 from .. import (
19 19 error,
20 20 node as nodemod,
21 21 util,
22 22 )
23 23
24 24
25 25 class NodeMap(dict):
26 26 def __missing__(self, x):
27 27 raise error.RevlogError(b'unknown node: %s' % x)
28 28
29 29
30 30 def persisted_data(revlog):
31 31 """read the nodemap for a revlog from disk"""
32 32 if revlog.nodemap_file is None:
33 33 return None
34 34 pdata = revlog.opener.tryread(revlog.nodemap_file)
35 35 if not pdata:
36 36 return None
37 37 offset = 0
38 38 (version,) = S_VERSION.unpack(pdata[offset : offset + S_VERSION.size])
39 39 if version != ONDISK_VERSION:
40 40 return None
41 41 offset += S_VERSION.size
42 42 headers = S_HEADER.unpack(pdata[offset : offset + S_HEADER.size])
43 43 uid_size, tip_rev, data_length, data_unused, tip_node_size = headers
44 44 offset += S_HEADER.size
45 45 docket = NodeMapDocket(pdata[offset : offset + uid_size])
46 46 offset += uid_size
47 47 docket.tip_rev = tip_rev
48 48 docket.tip_node = pdata[offset : offset + tip_node_size]
49 49 docket.data_length = data_length
50 50 docket.data_unused = data_unused
51 51
52 52 filename = _rawdata_filepath(revlog, docket)
53 53 use_mmap = revlog.opener.options.get(b"exp-persistent-nodemap.mmap")
54 54 try:
55 55 with revlog.opener(filename) as fd:
56 56 if use_mmap:
57 57 data = util.buffer(util.mmapread(fd, data_length))
58 58 else:
59 59 data = fd.read(data_length)
60 60 except OSError as e:
61 61 if e.errno != errno.ENOENT:
62 62 raise
63 63 if len(data) < data_length:
64 64 return None
65 65 return docket, data
66 66
67 67
68 68 def setup_persistent_nodemap(tr, revlog):
69 69 """Install whatever is needed transaction side to persist a nodemap on disk
70 70
71 71 (only actually persist the nodemap if this is relevant for this revlog)
72 72 """
73 73 if revlog._inline:
74 74 return # inlined revlog are too small for this to be relevant
75 75 if revlog.nodemap_file is None:
76 76 return # we do not use persistent_nodemap on this revlog
77 77
78 78 # we need to happen after the changelog finalization, in that use "cl-"
79 79 callback_id = b"nm-revlog-persistent-nodemap-%s" % revlog.nodemap_file
80 80 if tr.hasfinalize(callback_id):
81 81 return # no need to register again
82 82 tr.addpending(
83 83 callback_id, lambda tr: _persist_nodemap(tr, revlog, pending=True)
84 84 )
85 85 tr.addfinalize(callback_id, lambda tr: _persist_nodemap(tr, revlog))
86 86
87 87
88 88 class _NoTransaction(object):
89 89 """transaction like object to update the nodemap outside a transaction
90 90 """
91 91
92 92 def __init__(self):
93 93 self._postclose = {}
94 94
95 95 def addpostclose(self, callback_id, callback_func):
96 96 self._postclose[callback_id] = callback_func
97 97
98 98 def registertmp(self, *args, **kwargs):
99 99 pass
100 100
101 101 def addbackup(self, *args, **kwargs):
102 102 pass
103 103
104 104 def add(self, *args, **kwargs):
105 105 pass
106 106
107 107 def addabort(self, *args, **kwargs):
108 108 pass
109 109
110 110 def _report(self, *args):
111 111 pass
112 112
113 113
114 114 def update_persistent_nodemap(revlog):
115 115 """update the persistent nodemap right now
116 116
117 117 To be used for updating the nodemap on disk outside of a normal transaction
118 118 setup (eg, `debugupdatecache`).
119 119 """
120 120 if revlog._inline:
121 121 return # inlined revlog are too small for this to be relevant
122 122 if revlog.nodemap_file is None:
123 123 return # we do not use persistent_nodemap on this revlog
124 124
125 125 notr = _NoTransaction()
126 126 _persist_nodemap(notr, revlog)
127 127 for k in sorted(notr._postclose):
128 128 notr._postclose[k](None)
129 129
130 130
131 131 def _persist_nodemap(tr, revlog, pending=False):
132 132 """Write nodemap data on disk for a given revlog
133 133 """
134 134 if getattr(revlog, 'filteredrevs', ()):
135 135 raise error.ProgrammingError(
136 136 "cannot persist nodemap of a filtered changelog"
137 137 )
138 138 if revlog.nodemap_file is None:
139 139 msg = "calling persist nodemap on a revlog without the feature enableb"
140 140 raise error.ProgrammingError(msg)
141 141
142 142 can_incremental = util.safehasattr(revlog.index, "nodemap_data_incremental")
143 143 ondisk_docket = revlog._nodemap_docket
144 144 feed_data = util.safehasattr(revlog.index, "update_nodemap_data")
145 145 use_mmap = revlog.opener.options.get(b"exp-persistent-nodemap.mmap")
146 146 mode = revlog.opener.options.get(b"exp-persistent-nodemap.mode")
147 147 if not can_incremental:
148 148 msg = _(b"persistent nodemap in strict mode without efficient method")
149 149 if mode == b'warn':
150 150 tr._report(b"%s\n" % msg)
151 elif mode == b'strict':
152 raise error.Abort(msg)
151 153
152 154 data = None
153 155 # first attemp an incremental update of the data
154 156 if can_incremental and ondisk_docket is not None:
155 157 target_docket = revlog._nodemap_docket.copy()
156 158 (
157 159 src_docket,
158 160 data_changed_count,
159 161 data,
160 162 ) = revlog.index.nodemap_data_incremental()
161 163 new_length = target_docket.data_length + len(data)
162 164 new_unused = target_docket.data_unused + data_changed_count
163 165 if src_docket != target_docket:
164 166 data = None
165 167 elif new_length <= (new_unused * 10): # under 10% of unused data
166 168 data = None
167 169 else:
168 170 datafile = _rawdata_filepath(revlog, target_docket)
169 171 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
170 172 # store vfs
171 173 tr.add(datafile, target_docket.data_length)
172 174 with revlog.opener(datafile, b'r+') as fd:
173 175 fd.seek(target_docket.data_length)
174 176 fd.write(data)
175 177 if feed_data:
176 178 if use_mmap:
177 179 fd.seek(0)
178 180 new_data = fd.read(new_length)
179 181 else:
180 182 fd.flush()
181 183 new_data = util.buffer(util.mmapread(fd, new_length))
182 184 target_docket.data_length = new_length
183 185 target_docket.data_unused = new_unused
184 186
185 187 if data is None:
186 188 # otherwise fallback to a full new export
187 189 target_docket = NodeMapDocket()
188 190 datafile = _rawdata_filepath(revlog, target_docket)
189 191 if util.safehasattr(revlog.index, "nodemap_data_all"):
190 192 data = revlog.index.nodemap_data_all()
191 193 else:
192 194 data = persistent_data(revlog.index)
193 195 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
194 196 # store vfs
195 197
196 198 tryunlink = revlog.opener.tryunlink
197 199
198 200 def abortck(tr):
199 201 tryunlink(datafile)
200 202
201 203 callback_id = b"delete-%s" % datafile
202 204
203 205 # some flavor of the transaction abort does not cleanup new file, it
204 206 # simply empty them.
205 207 tr.addabort(callback_id, abortck)
206 208 with revlog.opener(datafile, b'w+') as fd:
207 209 fd.write(data)
208 210 if feed_data:
209 211 if use_mmap:
210 212 new_data = data
211 213 else:
212 214 fd.flush()
213 215 new_data = util.buffer(util.mmapread(fd, len(data)))
214 216 target_docket.data_length = len(data)
215 217 target_docket.tip_rev = revlog.tiprev()
216 218 target_docket.tip_node = revlog.node(target_docket.tip_rev)
217 219 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
218 220 # store vfs
219 221 file_path = revlog.nodemap_file
220 222 if pending:
221 223 file_path += b'.a'
222 224 tr.registertmp(file_path)
223 225 else:
224 226 tr.addbackup(file_path)
225 227
226 228 with revlog.opener(file_path, b'w', atomictemp=True) as fp:
227 229 fp.write(target_docket.serialize())
228 230 revlog._nodemap_docket = target_docket
229 231 if feed_data:
230 232 revlog.index.update_nodemap_data(target_docket, new_data)
231 233
232 234 # search for old index file in all cases, some older process might have
233 235 # left one behind.
234 236 olds = _other_rawdata_filepath(revlog, target_docket)
235 237 if olds:
236 238 realvfs = getattr(revlog, '_realopener', revlog.opener)
237 239
238 240 def cleanup(tr):
239 241 for oldfile in olds:
240 242 realvfs.tryunlink(oldfile)
241 243
242 244 callback_id = b"revlog-cleanup-nodemap-%s" % revlog.nodemap_file
243 245 tr.addpostclose(callback_id, cleanup)
244 246
245 247
246 248 ### Nodemap docket file
247 249 #
248 250 # The nodemap data are stored on disk using 2 files:
249 251 #
250 252 # * a raw data files containing a persistent nodemap
251 253 # (see `Nodemap Trie` section)
252 254 #
253 255 # * a small "docket" file containing medatadata
254 256 #
255 257 # While the nodemap data can be multiple tens of megabytes, the "docket" is
256 258 # small, it is easy to update it automatically or to duplicated its content
257 259 # during a transaction.
258 260 #
259 261 # Multiple raw data can exist at the same time (The currently valid one and a
260 262 # new one beind used by an in progress transaction). To accomodate this, the
261 263 # filename hosting the raw data has a variable parts. The exact filename is
262 264 # specified inside the "docket" file.
263 265 #
264 266 # The docket file contains information to find, qualify and validate the raw
265 267 # data. Its content is currently very light, but it will expand as the on disk
266 268 # nodemap gains the necessary features to be used in production.
267 269
268 270 # version 0 is experimental, no BC garantee, do no use outside of tests.
269 271 ONDISK_VERSION = 0
270 272 S_VERSION = struct.Struct(">B")
271 273 S_HEADER = struct.Struct(">BQQQQ")
272 274
273 275 ID_SIZE = 8
274 276
275 277
276 278 def _make_uid():
277 279 """return a new unique identifier.
278 280
279 281 The identifier is random and composed of ascii characters."""
280 282 return nodemod.hex(os.urandom(ID_SIZE))
281 283
282 284
283 285 class NodeMapDocket(object):
284 286 """metadata associated with persistent nodemap data
285 287
286 288 The persistent data may come from disk or be on their way to disk.
287 289 """
288 290
289 291 def __init__(self, uid=None):
290 292 if uid is None:
291 293 uid = _make_uid()
292 294 # a unique identifier for the data file:
293 295 # - When new data are appended, it is preserved.
294 296 # - When a new data file is created, a new identifier is generated.
295 297 self.uid = uid
296 298 # the tipmost revision stored in the data file. This revision and all
297 299 # revision before it are expected to be encoded in the data file.
298 300 self.tip_rev = None
299 301 # the node of that tipmost revision, if it mismatch the current index
300 302 # data the docket is not valid for the current index and should be
301 303 # discarded.
302 304 #
303 305 # note: this method is not perfect as some destructive operation could
304 306 # preserve the same tip_rev + tip_node while altering lower revision.
305 307 # However this multiple other caches have the same vulnerability (eg:
306 308 # brancmap cache).
307 309 self.tip_node = None
308 310 # the size (in bytes) of the persisted data to encode the nodemap valid
309 311 # for `tip_rev`.
310 312 # - data file shorter than this are corrupted,
311 313 # - any extra data should be ignored.
312 314 self.data_length = None
313 315 # the amount (in bytes) of "dead" data, still in the data file but no
314 316 # longer used for the nodemap.
315 317 self.data_unused = 0
316 318
317 319 def copy(self):
318 320 new = NodeMapDocket(uid=self.uid)
319 321 new.tip_rev = self.tip_rev
320 322 new.tip_node = self.tip_node
321 323 new.data_length = self.data_length
322 324 new.data_unused = self.data_unused
323 325 return new
324 326
325 327 def __cmp__(self, other):
326 328 if self.uid < other.uid:
327 329 return -1
328 330 if self.uid > other.uid:
329 331 return 1
330 332 elif self.data_length < other.data_length:
331 333 return -1
332 334 elif self.data_length > other.data_length:
333 335 return 1
334 336 return 0
335 337
336 338 def __eq__(self, other):
337 339 return self.uid == other.uid and self.data_length == other.data_length
338 340
339 341 def serialize(self):
340 342 """return serialized bytes for a docket using the passed uid"""
341 343 data = []
342 344 data.append(S_VERSION.pack(ONDISK_VERSION))
343 345 headers = (
344 346 len(self.uid),
345 347 self.tip_rev,
346 348 self.data_length,
347 349 self.data_unused,
348 350 len(self.tip_node),
349 351 )
350 352 data.append(S_HEADER.pack(*headers))
351 353 data.append(self.uid)
352 354 data.append(self.tip_node)
353 355 return b''.join(data)
354 356
355 357
356 358 def _rawdata_filepath(revlog, docket):
357 359 """The (vfs relative) nodemap's rawdata file for a given uid"""
358 360 if revlog.nodemap_file.endswith(b'.n.a'):
359 361 prefix = revlog.nodemap_file[:-4]
360 362 else:
361 363 prefix = revlog.nodemap_file[:-2]
362 364 return b"%s-%s.nd" % (prefix, docket.uid)
363 365
364 366
365 367 def _other_rawdata_filepath(revlog, docket):
366 368 prefix = revlog.nodemap_file[:-2]
367 369 pattern = re.compile(br"(^|/)%s-[0-9a-f]+\.nd$" % prefix)
368 370 new_file_path = _rawdata_filepath(revlog, docket)
369 371 new_file_name = revlog.opener.basename(new_file_path)
370 372 dirpath = revlog.opener.dirname(new_file_path)
371 373 others = []
372 374 for f in revlog.opener.listdir(dirpath):
373 375 if pattern.match(f) and f != new_file_name:
374 376 others.append(f)
375 377 return others
376 378
377 379
378 380 ### Nodemap Trie
379 381 #
380 382 # This is a simple reference implementation to compute and persist a nodemap
381 383 # trie. This reference implementation is write only. The python version of this
382 384 # is not expected to be actually used, since it wont provide performance
383 385 # improvement over existing non-persistent C implementation.
384 386 #
385 387 # The nodemap is persisted as Trie using 4bits-address/16-entries block. each
386 388 # revision can be adressed using its node shortest prefix.
387 389 #
388 390 # The trie is stored as a sequence of block. Each block contains 16 entries
389 391 # (signed 64bit integer, big endian). Each entry can be one of the following:
390 392 #
391 393 # * value >= 0 -> index of sub-block
392 394 # * value == -1 -> no value
393 395 # * value < -1 -> a revision value: rev = -(value+10)
394 396 #
395 397 # The implementation focus on simplicity, not on performance. A Rust
396 398 # implementation should provide a efficient version of the same binary
397 399 # persistence. This reference python implementation is never meant to be
398 400 # extensively use in production.
399 401
400 402
401 403 def persistent_data(index):
402 404 """return the persistent binary form for a nodemap for a given index
403 405 """
404 406 trie = _build_trie(index)
405 407 return _persist_trie(trie)
406 408
407 409
408 410 def update_persistent_data(index, root, max_idx, last_rev):
409 411 """return the incremental update for persistent nodemap from a given index
410 412 """
411 413 changed_block, trie = _update_trie(index, root, last_rev)
412 414 return (
413 415 changed_block * S_BLOCK.size,
414 416 _persist_trie(trie, existing_idx=max_idx),
415 417 )
416 418
417 419
418 420 S_BLOCK = struct.Struct(">" + ("l" * 16))
419 421
420 422 NO_ENTRY = -1
421 423 # rev 0 need to be -2 because 0 is used by block, -1 is a special value.
422 424 REV_OFFSET = 2
423 425
424 426
425 427 def _transform_rev(rev):
426 428 """Return the number used to represent the rev in the tree.
427 429
428 430 (or retrieve a rev number from such representation)
429 431
430 432 Note that this is an involution, a function equal to its inverse (i.e.
431 433 which gives the identity when applied to itself).
432 434 """
433 435 return -(rev + REV_OFFSET)
434 436
435 437
436 438 def _to_int(hex_digit):
437 439 """turn an hexadecimal digit into a proper integer"""
438 440 return int(hex_digit, 16)
439 441
440 442
441 443 class Block(dict):
442 444 """represent a block of the Trie
443 445
444 446 contains up to 16 entry indexed from 0 to 15"""
445 447
446 448 def __init__(self):
447 449 super(Block, self).__init__()
448 450 # If this block exist on disk, here is its ID
449 451 self.ondisk_id = None
450 452
451 453 def __iter__(self):
452 454 return iter(self.get(i) for i in range(16))
453 455
454 456
455 457 def _build_trie(index):
456 458 """build a nodemap trie
457 459
458 460 The nodemap stores revision number for each unique prefix.
459 461
460 462 Each block is a dictionary with keys in `[0, 15]`. Values are either
461 463 another block or a revision number.
462 464 """
463 465 root = Block()
464 466 for rev in range(len(index)):
465 467 hex = nodemod.hex(index[rev][7])
466 468 _insert_into_block(index, 0, root, rev, hex)
467 469 return root
468 470
469 471
470 472 def _update_trie(index, root, last_rev):
471 473 """consume"""
472 474 changed = 0
473 475 for rev in range(last_rev + 1, len(index)):
474 476 hex = nodemod.hex(index[rev][7])
475 477 changed += _insert_into_block(index, 0, root, rev, hex)
476 478 return changed, root
477 479
478 480
479 481 def _insert_into_block(index, level, block, current_rev, current_hex):
480 482 """insert a new revision in a block
481 483
482 484 index: the index we are adding revision for
483 485 level: the depth of the current block in the trie
484 486 block: the block currently being considered
485 487 current_rev: the revision number we are adding
486 488 current_hex: the hexadecimal representation of the of that revision
487 489 """
488 490 changed = 1
489 491 if block.ondisk_id is not None:
490 492 block.ondisk_id = None
491 493 hex_digit = _to_int(current_hex[level : level + 1])
492 494 entry = block.get(hex_digit)
493 495 if entry is None:
494 496 # no entry, simply store the revision number
495 497 block[hex_digit] = current_rev
496 498 elif isinstance(entry, dict):
497 499 # need to recurse to an underlying block
498 500 changed += _insert_into_block(
499 501 index, level + 1, entry, current_rev, current_hex
500 502 )
501 503 else:
502 504 # collision with a previously unique prefix, inserting new
503 505 # vertices to fit both entry.
504 506 other_hex = nodemod.hex(index[entry][7])
505 507 other_rev = entry
506 508 new = Block()
507 509 block[hex_digit] = new
508 510 _insert_into_block(index, level + 1, new, other_rev, other_hex)
509 511 _insert_into_block(index, level + 1, new, current_rev, current_hex)
510 512 return changed
511 513
512 514
513 515 def _persist_trie(root, existing_idx=None):
514 516 """turn a nodemap trie into persistent binary data
515 517
516 518 See `_build_trie` for nodemap trie structure"""
517 519 block_map = {}
518 520 if existing_idx is not None:
519 521 base_idx = existing_idx + 1
520 522 else:
521 523 base_idx = 0
522 524 chunks = []
523 525 for tn in _walk_trie(root):
524 526 if tn.ondisk_id is not None:
525 527 block_map[id(tn)] = tn.ondisk_id
526 528 else:
527 529 block_map[id(tn)] = len(chunks) + base_idx
528 530 chunks.append(_persist_block(tn, block_map))
529 531 return b''.join(chunks)
530 532
531 533
532 534 def _walk_trie(block):
533 535 """yield all the block in a trie
534 536
535 537 Children blocks are always yield before their parent block.
536 538 """
537 539 for (__, item) in sorted(block.items()):
538 540 if isinstance(item, dict):
539 541 for sub_block in _walk_trie(item):
540 542 yield sub_block
541 543 yield block
542 544
543 545
544 546 def _persist_block(block_node, block_map):
545 547 """produce persistent binary data for a single block
546 548
547 549 Children block are assumed to be already persisted and present in
548 550 block_map.
549 551 """
550 552 data = tuple(_to_value(v, block_map) for v in block_node)
551 553 return S_BLOCK.pack(*data)
552 554
553 555
554 556 def _to_value(item, block_map):
555 557 """persist any value as an integer"""
556 558 if item is None:
557 559 return NO_ENTRY
558 560 elif isinstance(item, dict):
559 561 return block_map[id(item)]
560 562 else:
561 563 return _transform_rev(item)
562 564
563 565
564 566 def parse_data(data):
565 567 """parse parse nodemap data into a nodemap Trie"""
566 568 if (len(data) % S_BLOCK.size) != 0:
567 569 msg = "nodemap data size is not a multiple of block size (%d): %d"
568 570 raise error.Abort(msg % (S_BLOCK.size, len(data)))
569 571 if not data:
570 572 return Block(), None
571 573 block_map = {}
572 574 new_blocks = []
573 575 for i in range(0, len(data), S_BLOCK.size):
574 576 block = Block()
575 577 block.ondisk_id = len(block_map)
576 578 block_map[block.ondisk_id] = block
577 579 block_data = data[i : i + S_BLOCK.size]
578 580 values = S_BLOCK.unpack(block_data)
579 581 new_blocks.append((block, values))
580 582 for b, values in new_blocks:
581 583 for idx, v in enumerate(values):
582 584 if v == NO_ENTRY:
583 585 continue
584 586 elif v >= 0:
585 587 b[idx] = block_map[v]
586 588 else:
587 589 b[idx] = _transform_rev(v)
588 590 return block, i // S_BLOCK.size
589 591
590 592
591 593 # debug utility
592 594
593 595
594 596 def check_data(ui, index, data):
595 597 """verify that the provided nodemap data are valid for the given idex"""
596 598 ret = 0
597 599 ui.status((b"revision in index: %d\n") % len(index))
598 600 root, __ = parse_data(data)
599 601 all_revs = set(_all_revisions(root))
600 602 ui.status((b"revision in nodemap: %d\n") % len(all_revs))
601 603 for r in range(len(index)):
602 604 if r not in all_revs:
603 605 msg = b" revision missing from nodemap: %d\n" % r
604 606 ui.write_err(msg)
605 607 ret = 1
606 608 else:
607 609 all_revs.remove(r)
608 610 nm_rev = _find_node(root, nodemod.hex(index[r][7]))
609 611 if nm_rev is None:
610 612 msg = b" revision node does not match any entries: %d\n" % r
611 613 ui.write_err(msg)
612 614 ret = 1
613 615 elif nm_rev != r:
614 616 msg = (
615 617 b" revision node does not match the expected revision: "
616 618 b"%d != %d\n" % (r, nm_rev)
617 619 )
618 620 ui.write_err(msg)
619 621 ret = 1
620 622
621 623 if all_revs:
622 624 for r in sorted(all_revs):
623 625 msg = b" extra revision in nodemap: %d\n" % r
624 626 ui.write_err(msg)
625 627 ret = 1
626 628 return ret
627 629
628 630
629 631 def _all_revisions(root):
630 632 """return all revisions stored in a Trie"""
631 633 for block in _walk_trie(root):
632 634 for v in block:
633 635 if v is None or isinstance(v, Block):
634 636 continue
635 637 yield v
636 638
637 639
638 640 def _find_node(block, node):
639 641 """find the revision associated with a given node"""
640 642 entry = block.get(_to_int(node[0:1]))
641 643 if isinstance(entry, dict):
642 644 return _find_node(entry, node[1:])
643 645 return entry
@@ -1,418 +1,429 b''
1 1 ===================================
2 2 Test the persistent on-disk nodemap
3 3 ===================================
4 4
5 5 $ hg init test-repo
6 6 $ cd test-repo
7 7 $ cat << EOF >> .hg/hgrc
8 8 > [experimental]
9 9 > exp-persistent-nodemap=yes
10 10 > [devel]
11 11 > persistent-nodemap=yes
12 12 > EOF
13 13 $ hg debugbuilddag .+5000 --new-file --config "experimental.exp-persistent-nodemap.mode=warn"
14 14 persistent nodemap in strict mode without efficient method (no-rust no-pure !)
15 15 persistent nodemap in strict mode without efficient method (no-rust no-pure !)
16 16 $ hg debugnodemap --metadata
17 17 uid: ???????????????? (glob)
18 18 tip-rev: 5000
19 19 tip-node: 6b02b8c7b96654c25e86ba69eda198d7e6ad8b3c
20 20 data-length: 121088
21 21 data-unused: 0
22 22 data-unused: 0.000%
23 23 $ f --size .hg/store/00changelog.n
24 24 .hg/store/00changelog.n: size=70
25 25
26 26 Simple lookup works
27 27
28 28 $ ANYNODE=`hg log --template '{node|short}\n' --rev tip`
29 29 $ hg log -r "$ANYNODE" --template '{rev}\n'
30 30 5000
31 31
32 32
33 33 #if rust
34 34
35 35 $ f --sha256 .hg/store/00changelog-*.nd
36 36 .hg/store/00changelog-????????????????.nd: sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd (glob)
37 37
38 38 $ f --sha256 .hg/store/00manifest-*.nd
39 39 .hg/store/00manifest-????????????????.nd: sha256=97117b1c064ea2f86664a124589e47db0e254e8d34739b5c5cc5bf31c9da2b51 (glob)
40 40 $ hg debugnodemap --dump-new | f --sha256 --size
41 41 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
42 42 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
43 43 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
44 44 0000: 00 00 00 91 00 00 00 20 00 00 00 bb 00 00 00 e7 |....... ........|
45 45 0010: 00 00 00 66 00 00 00 a1 00 00 01 13 00 00 01 22 |...f..........."|
46 46 0020: 00 00 00 23 00 00 00 fc 00 00 00 ba 00 00 00 5e |...#...........^|
47 47 0030: 00 00 00 df 00 00 01 4e 00 00 01 65 00 00 00 ab |.......N...e....|
48 48 0040: 00 00 00 a9 00 00 00 95 00 00 00 73 00 00 00 38 |...........s...8|
49 49 0050: 00 00 00 cc 00 00 00 92 00 00 00 90 00 00 00 69 |...............i|
50 50 0060: 00 00 00 ec 00 00 00 8d 00 00 01 4f 00 00 00 12 |...........O....|
51 51 0070: 00 00 02 0c 00 00 00 77 00 00 00 9c 00 00 00 8f |.......w........|
52 52 0080: 00 00 00 d5 00 00 00 6b 00 00 00 48 00 00 00 b3 |.......k...H....|
53 53 0090: 00 00 00 e5 00 00 00 b5 00 00 00 8e 00 00 00 ad |................|
54 54 00a0: 00 00 00 7b 00 00 00 7c 00 00 00 0b 00 00 00 2b |...{...|.......+|
55 55 00b0: 00 00 00 c6 00 00 00 1e 00 00 01 08 00 00 00 11 |................|
56 56 00c0: 00 00 01 30 00 00 00 26 00 00 01 9c 00 00 00 35 |...0...&.......5|
57 57 00d0: 00 00 00 b8 00 00 01 31 00 00 00 2c 00 00 00 55 |.......1...,...U|
58 58 00e0: 00 00 00 8a 00 00 00 9a 00 00 00 0c 00 00 01 1e |................|
59 59 00f0: 00 00 00 a4 00 00 00 83 00 00 00 c9 00 00 00 8c |................|
60 60
61 61
62 62 #else
63 63
64 64 $ f --sha256 .hg/store/00changelog-*.nd
65 65 .hg/store/00changelog-????????????????.nd: sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 (glob)
66 66 $ hg debugnodemap --dump-new | f --sha256 --size
67 67 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
68 68 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
69 69 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
70 70 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
71 71 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
72 72 0020: ff ff ff ff ff ff f5 06 ff ff ff ff ff ff f3 e7 |................|
73 73 0030: ff ff ef ca ff ff ff ff ff ff ff ff ff ff ff ff |................|
74 74 0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
75 75 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ed 08 |................|
76 76 0060: ff ff ed 66 ff ff ff ff ff ff ff ff ff ff ff ff |...f............|
77 77 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
78 78 0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
79 79 0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f6 ed |................|
80 80 00a0: ff ff ff ff ff ff fe 61 ff ff ff ff ff ff ff ff |.......a........|
81 81 00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
82 82 00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
83 83 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
84 84 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f1 02 |................|
85 85 00f0: ff ff ff ff ff ff ed 1b ff ff ff ff ff ff ff ff |................|
86 86
87 87 #endif
88 88
89 89 $ hg debugnodemap --check
90 90 revision in index: 5001
91 91 revision in nodemap: 5001
92 92
93 93 add a new commit
94 94
95 95 $ hg up
96 96 5001 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 97 $ echo foo > foo
98 98 $ hg add foo
99
100 #if no-pure no-rust
101
102 $ hg ci -m 'foo' --config "experimental.exp-persistent-nodemap.mode=strict"
103 transaction abort!
104 rollback completed
105 abort: persistent nodemap in strict mode without efficient method
106 [255]
107
108 #endif
109
99 110 $ hg ci -m 'foo'
100 111
101 112 #if no-pure no-rust
102 113 $ hg debugnodemap --metadata
103 114 uid: ???????????????? (glob)
104 115 tip-rev: 5001
105 116 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
106 117 data-length: 121088
107 118 data-unused: 0
108 119 data-unused: 0.000%
109 120 #else
110 121 $ hg debugnodemap --metadata
111 122 uid: ???????????????? (glob)
112 123 tip-rev: 5001
113 124 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
114 125 data-length: 121344
115 126 data-unused: 256
116 127 data-unused: 0.211%
117 128 #endif
118 129
119 130 $ f --size .hg/store/00changelog.n
120 131 .hg/store/00changelog.n: size=70
121 132
122 133 (The pure code use the debug code that perform incremental update, the C code reencode from scratch)
123 134
124 135 #if pure
125 136 $ f --sha256 .hg/store/00changelog-*.nd --size
126 137 .hg/store/00changelog-????????????????.nd: size=121344, sha256=cce54c5da5bde3ad72a4938673ed4064c86231b9c64376b082b163fdb20f8f66 (glob)
127 138 #endif
128 139
129 140 #if rust
130 141 $ f --sha256 .hg/store/00changelog-*.nd --size
131 142 .hg/store/00changelog-????????????????.nd: size=121344, sha256=952b042fcf614ceb37b542b1b723e04f18f83efe99bee4e0f5ccd232ef470e58 (glob)
132 143 #endif
133 144
134 145 #if no-pure no-rust
135 146 $ f --sha256 .hg/store/00changelog-*.nd --size
136 147 .hg/store/00changelog-????????????????.nd: size=121088, sha256=df7c06a035b96cb28c7287d349d603baef43240be7736fe34eea419a49702e17 (glob)
137 148 #endif
138 149
139 150 $ hg debugnodemap --check
140 151 revision in index: 5002
141 152 revision in nodemap: 5002
142 153
143 154 Test code path without mmap
144 155 ---------------------------
145 156
146 157 $ echo bar > bar
147 158 $ hg add bar
148 159 $ hg ci -m 'bar' --config experimental.exp-persistent-nodemap.mmap=no
149 160
150 161 $ hg debugnodemap --check --config experimental.exp-persistent-nodemap.mmap=yes
151 162 revision in index: 5003
152 163 revision in nodemap: 5003
153 164 $ hg debugnodemap --check --config experimental.exp-persistent-nodemap.mmap=no
154 165 revision in index: 5003
155 166 revision in nodemap: 5003
156 167
157 168
158 169 #if pure
159 170 $ hg debugnodemap --metadata
160 171 uid: ???????????????? (glob)
161 172 tip-rev: 5002
162 173 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
163 174 data-length: 121600
164 175 data-unused: 512
165 176 data-unused: 0.421%
166 177 $ f --sha256 .hg/store/00changelog-*.nd --size
167 178 .hg/store/00changelog-????????????????.nd: size=121600, sha256=def52503d049ccb823974af313a98a935319ba61f40f3aa06a8be4d35c215054 (glob)
168 179 #endif
169 180 #if rust
170 181 $ hg debugnodemap --metadata
171 182 uid: ???????????????? (glob)
172 183 tip-rev: 5002
173 184 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
174 185 data-length: 121600
175 186 data-unused: 512
176 187 data-unused: 0.421%
177 188 $ f --sha256 .hg/store/00changelog-*.nd --size
178 189 .hg/store/00changelog-????????????????.nd: size=121600, sha256=dacf5b5f1d4585fee7527d0e67cad5b1ba0930e6a0928f650f779aefb04ce3fb (glob)
179 190 #endif
180 191 #if no-pure no-rust
181 192 $ hg debugnodemap --metadata
182 193 uid: ???????????????? (glob)
183 194 tip-rev: 5002
184 195 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
185 196 data-length: 121088
186 197 data-unused: 0
187 198 data-unused: 0.000%
188 199 $ f --sha256 .hg/store/00changelog-*.nd --size
189 200 .hg/store/00changelog-????????????????.nd: size=121088, sha256=59fcede3e3cc587755916ceed29e3c33748cd1aa7d2f91828ac83e7979d935e8 (glob)
190 201 #endif
191 202
192 203 Test force warming the cache
193 204
194 205 $ rm .hg/store/00changelog.n
195 206 $ hg debugnodemap --metadata
196 207 $ hg debugupdatecache
197 208 #if pure
198 209 $ hg debugnodemap --metadata
199 210 uid: ???????????????? (glob)
200 211 tip-rev: 5002
201 212 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
202 213 data-length: 121088
203 214 data-unused: 0
204 215 data-unused: 0.000%
205 216 #else
206 217 $ hg debugnodemap --metadata
207 218 uid: ???????????????? (glob)
208 219 tip-rev: 5002
209 220 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
210 221 data-length: 121088
211 222 data-unused: 0
212 223 data-unused: 0.000%
213 224 #endif
214 225
215 226 Check out of sync nodemap
216 227 =========================
217 228
218 229 First copy old data on the side.
219 230
220 231 $ mkdir ../tmp-copies
221 232 $ cp .hg/store/00changelog-????????????????.nd .hg/store/00changelog.n ../tmp-copies
222 233
223 234 Nodemap lagging behind
224 235 ----------------------
225 236
226 237 make a new commit
227 238
228 239 $ echo bar2 > bar
229 240 $ hg ci -m 'bar2'
230 241 $ NODE=`hg log -r tip -T '{node}\n'`
231 242 $ hg log -r "$NODE" -T '{rev}\n'
232 243 5003
233 244
234 245 If the nodemap is lagging behind, it can catch up fine
235 246
236 247 $ hg debugnodemap --metadata
237 248 uid: ???????????????? (glob)
238 249 tip-rev: 5003
239 250 tip-node: c9329770f979ade2d16912267c38ba5f82fd37b3
240 251 data-length: 121344 (pure !)
241 252 data-length: 121344 (rust !)
242 253 data-length: 121152 (no-rust no-pure !)
243 254 data-unused: 192 (pure !)
244 255 data-unused: 192 (rust !)
245 256 data-unused: 0 (no-rust no-pure !)
246 257 data-unused: 0.158% (pure !)
247 258 data-unused: 0.158% (rust !)
248 259 data-unused: 0.000% (no-rust no-pure !)
249 260 $ cp -f ../tmp-copies/* .hg/store/
250 261 $ hg debugnodemap --metadata
251 262 uid: ???????????????? (glob)
252 263 tip-rev: 5002
253 264 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
254 265 data-length: 121088
255 266 data-unused: 0
256 267 data-unused: 0.000%
257 268 $ hg log -r "$NODE" -T '{rev}\n'
258 269 5003
259 270
260 271 changelog altered
261 272 -----------------
262 273
263 274 If the nodemap is not gated behind a requirements, an unaware client can alter
264 275 the repository so the revlog used to generate the nodemap is not longer
265 276 compatible with the persistent nodemap. We need to detect that.
266 277
267 278 $ hg up "$NODE~5"
268 279 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
269 280 $ echo bar > babar
270 281 $ hg add babar
271 282 $ hg ci -m 'babar'
272 283 created new head
273 284 $ OTHERNODE=`hg log -r tip -T '{node}\n'`
274 285 $ hg log -r "$OTHERNODE" -T '{rev}\n'
275 286 5004
276 287
277 288 $ hg --config extensions.strip= strip --rev "$NODE~1" --no-backup
278 289
279 290 the nodemap should detect the changelog have been tampered with and recover.
280 291
281 292 $ hg debugnodemap --metadata
282 293 uid: ???????????????? (glob)
283 294 tip-rev: 5002
284 295 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
285 296 data-length: 121536 (pure !)
286 297 data-length: 121088 (rust !)
287 298 data-length: 121088 (no-pure no-rust !)
288 299 data-unused: 448 (pure !)
289 300 data-unused: 0 (rust !)
290 301 data-unused: 0 (no-pure no-rust !)
291 302 data-unused: 0.000% (rust !)
292 303 data-unused: 0.369% (pure !)
293 304 data-unused: 0.000% (no-pure no-rust !)
294 305
295 306 $ cp -f ../tmp-copies/* .hg/store/
296 307 $ hg debugnodemap --metadata
297 308 uid: ???????????????? (glob)
298 309 tip-rev: 5002
299 310 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
300 311 data-length: 121088
301 312 data-unused: 0
302 313 data-unused: 0.000%
303 314 $ hg log -r "$OTHERNODE" -T '{rev}\n'
304 315 5002
305 316
306 317 Check transaction related property
307 318 ==================================
308 319
309 320 An up to date nodemap should be available to shell hooks,
310 321
311 322 $ echo dsljfl > a
312 323 $ hg add a
313 324 $ hg ci -m a
314 325 $ hg debugnodemap --metadata
315 326 uid: ???????????????? (glob)
316 327 tip-rev: 5003
317 328 tip-node: a52c5079765b5865d97b993b303a18740113bbb2
318 329 data-length: 121088
319 330 data-unused: 0
320 331 data-unused: 0.000%
321 332 $ echo babar2 > babar
322 333 $ hg ci -m 'babar2' --config "hooks.pretxnclose.nodemap-test=hg debugnodemap --metadata"
323 334 uid: ???????????????? (glob)
324 335 tip-rev: 5004
325 336 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
326 337 data-length: 121280 (pure !)
327 338 data-length: 121280 (rust !)
328 339 data-length: 121088 (no-pure no-rust !)
329 340 data-unused: 192 (pure !)
330 341 data-unused: 192 (rust !)
331 342 data-unused: 0 (no-pure no-rust !)
332 343 data-unused: 0.158% (pure !)
333 344 data-unused: 0.158% (rust !)
334 345 data-unused: 0.000% (no-pure no-rust !)
335 346 $ hg debugnodemap --metadata
336 347 uid: ???????????????? (glob)
337 348 tip-rev: 5004
338 349 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
339 350 data-length: 121280 (pure !)
340 351 data-length: 121280 (rust !)
341 352 data-length: 121088 (no-pure no-rust !)
342 353 data-unused: 192 (pure !)
343 354 data-unused: 192 (rust !)
344 355 data-unused: 0 (no-pure no-rust !)
345 356 data-unused: 0.158% (pure !)
346 357 data-unused: 0.158% (rust !)
347 358 data-unused: 0.000% (no-pure no-rust !)
348 359
349 360 Another process does not see the pending nodemap content during run.
350 361
351 362 $ PATH=$RUNTESTDIR/testlib/:$PATH
352 363 $ echo qpoasp > a
353 364 $ hg ci -m a2 \
354 365 > --config "hooks.pretxnclose=wait-on-file 20 sync-repo-read sync-txn-pending" \
355 366 > --config "hooks.txnclose=touch sync-txn-close" > output.txt 2>&1 &
356 367
357 368 (read the repository while the commit transaction is pending)
358 369
359 370 $ wait-on-file 20 sync-txn-pending && \
360 371 > hg debugnodemap --metadata && \
361 372 > wait-on-file 20 sync-txn-close sync-repo-read
362 373 uid: ???????????????? (glob)
363 374 tip-rev: 5004
364 375 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
365 376 data-length: 121280 (pure !)
366 377 data-length: 121280 (rust !)
367 378 data-length: 121088 (no-pure no-rust !)
368 379 data-unused: 192 (pure !)
369 380 data-unused: 192 (rust !)
370 381 data-unused: 0 (no-pure no-rust !)
371 382 data-unused: 0.158% (pure !)
372 383 data-unused: 0.158% (rust !)
373 384 data-unused: 0.000% (no-pure no-rust !)
374 385 $ hg debugnodemap --metadata
375 386 uid: ???????????????? (glob)
376 387 tip-rev: 5005
377 388 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
378 389 data-length: 121536 (pure !)
379 390 data-length: 121536 (rust !)
380 391 data-length: 121088 (no-pure no-rust !)
381 392 data-unused: 448 (pure !)
382 393 data-unused: 448 (rust !)
383 394 data-unused: 0 (no-pure no-rust !)
384 395 data-unused: 0.369% (pure !)
385 396 data-unused: 0.369% (rust !)
386 397 data-unused: 0.000% (no-pure no-rust !)
387 398
388 399 $ cat output.txt
389 400
390 401 Check that a failing transaction will properly revert the data
391 402
392 403 $ echo plakfe > a
393 404 $ f --size --sha256 .hg/store/00changelog-*.nd
394 405 .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
395 406 .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
396 407 .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
397 408 $ hg ci -m a3 --config "extensions.abort=$RUNTESTDIR/testlib/crash_transaction_late.py"
398 409 transaction abort!
399 410 rollback completed
400 411 abort: This is a late abort
401 412 [255]
402 413 $ hg debugnodemap --metadata
403 414 uid: ???????????????? (glob)
404 415 tip-rev: 5005
405 416 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
406 417 data-length: 121536 (pure !)
407 418 data-length: 121536 (rust !)
408 419 data-length: 121088 (no-pure no-rust !)
409 420 data-unused: 448 (pure !)
410 421 data-unused: 448 (rust !)
411 422 data-unused: 0 (no-pure no-rust !)
412 423 data-unused: 0.369% (pure !)
413 424 data-unused: 0.369% (rust !)
414 425 data-unused: 0.000% (no-pure no-rust !)
415 426 $ f --size --sha256 .hg/store/00changelog-*.nd
416 427 .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
417 428 .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
418 429 .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
General Comments 0
You need to be logged in to leave comments. Login now