##// END OF EJS Templates
nodemap: also use persistent nodemap for manifest...
marmoute -
r45290:640d5b3b default
parent child Browse files
Show More
@@ -1,2298 +1,2301 b''
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import heapq
11 11 import itertools
12 12 import struct
13 13 import weakref
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 bin,
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 )
22 22 from .pycompat import getattr
23 23 from . import (
24 24 encoding,
25 25 error,
26 26 match as matchmod,
27 27 mdiff,
28 28 pathutil,
29 29 policy,
30 30 pycompat,
31 31 revlog,
32 32 util,
33 33 )
34 34 from .interfaces import (
35 35 repository,
36 36 util as interfaceutil,
37 37 )
38 38
39 39 parsers = policy.importmod('parsers')
40 40 propertycache = util.propertycache
41 41
42 42 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
43 43 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
44 44
45 45
46 46 def _parse(data):
47 47 # This method does a little bit of excessive-looking
48 48 # precondition checking. This is so that the behavior of this
49 49 # class exactly matches its C counterpart to try and help
50 50 # prevent surprise breakage for anyone that develops against
51 51 # the pure version.
52 52 if data and data[-1:] != b'\n':
53 53 raise ValueError(b'Manifest did not end in a newline.')
54 54 prev = None
55 55 for l in data.splitlines():
56 56 if prev is not None and prev > l:
57 57 raise ValueError(b'Manifest lines not in sorted order.')
58 58 prev = l
59 59 f, n = l.split(b'\0')
60 60 nl = len(n)
61 61 if 64 < nl:
62 62 # modern hash, full width
63 63 yield f, bin(n[:64]), n[64:]
64 64 elif 40 < nl < 45:
65 65 # legacy hash, always sha1
66 66 yield f, bin(n[:40]), n[40:]
67 67 else:
68 68 yield f, bin(n), b''
69 69
70 70
71 71 def _text(it):
72 72 files = []
73 73 lines = []
74 74 for f, n, fl in it:
75 75 files.append(f)
76 76 # if this is changed to support newlines in filenames,
77 77 # be sure to check the templates/ dir again (especially *-raw.tmpl)
78 78 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
79 79
80 80 _checkforbidden(files)
81 81 return b''.join(lines)
82 82
83 83
84 84 class lazymanifestiter(object):
85 85 def __init__(self, lm):
86 86 self.pos = 0
87 87 self.lm = lm
88 88
89 89 def __iter__(self):
90 90 return self
91 91
92 92 def next(self):
93 93 try:
94 94 data, pos = self.lm._get(self.pos)
95 95 except IndexError:
96 96 raise StopIteration
97 97 if pos == -1:
98 98 self.pos += 1
99 99 return data[0]
100 100 self.pos += 1
101 101 zeropos = data.find(b'\x00', pos)
102 102 return data[pos:zeropos]
103 103
104 104 __next__ = next
105 105
106 106
107 107 class lazymanifestiterentries(object):
108 108 def __init__(self, lm):
109 109 self.lm = lm
110 110 self.pos = 0
111 111
112 112 def __iter__(self):
113 113 return self
114 114
115 115 def next(self):
116 116 try:
117 117 data, pos = self.lm._get(self.pos)
118 118 except IndexError:
119 119 raise StopIteration
120 120 if pos == -1:
121 121 self.pos += 1
122 122 return data
123 123 zeropos = data.find(b'\x00', pos)
124 124 hashval = unhexlify(data, self.lm.extrainfo[self.pos], zeropos + 1, 40)
125 125 flags = self.lm._getflags(data, self.pos, zeropos)
126 126 self.pos += 1
127 127 return (data[pos:zeropos], hashval, flags)
128 128
129 129 __next__ = next
130 130
131 131
132 132 def unhexlify(data, extra, pos, length):
133 133 s = bin(data[pos : pos + length])
134 134 if extra:
135 135 s += chr(extra & 0xFF)
136 136 return s
137 137
138 138
139 139 def _cmp(a, b):
140 140 return (a > b) - (a < b)
141 141
142 142
143 143 class _lazymanifest(object):
144 144 """A pure python manifest backed by a byte string. It is supplimented with
145 145 internal lists as it is modified, until it is compacted back to a pure byte
146 146 string.
147 147
148 148 ``data`` is the initial manifest data.
149 149
150 150 ``positions`` is a list of offsets, one per manifest entry. Positive
151 151 values are offsets into ``data``, negative values are offsets into the
152 152 ``extradata`` list. When an entry is removed, its entry is dropped from
153 153 ``positions``. The values are encoded such that when walking the list and
154 154 indexing into ``data`` or ``extradata`` as appropriate, the entries are
155 155 sorted by filename.
156 156
157 157 ``extradata`` is a list of (key, hash, flags) for entries that were added or
158 158 modified since the manifest was created or compacted.
159 159 """
160 160
161 161 def __init__(
162 162 self,
163 163 data,
164 164 positions=None,
165 165 extrainfo=None,
166 166 extradata=None,
167 167 hasremovals=False,
168 168 ):
169 169 if positions is None:
170 170 self.positions = self.findlines(data)
171 171 self.extrainfo = [0] * len(self.positions)
172 172 self.data = data
173 173 self.extradata = []
174 174 self.hasremovals = False
175 175 else:
176 176 self.positions = positions[:]
177 177 self.extrainfo = extrainfo[:]
178 178 self.extradata = extradata[:]
179 179 self.data = data
180 180 self.hasremovals = hasremovals
181 181
182 182 def findlines(self, data):
183 183 if not data:
184 184 return []
185 185 pos = data.find(b"\n")
186 186 if pos == -1 or data[-1:] != b'\n':
187 187 raise ValueError(b"Manifest did not end in a newline.")
188 188 positions = [0]
189 189 prev = data[: data.find(b'\x00')]
190 190 while pos < len(data) - 1 and pos != -1:
191 191 positions.append(pos + 1)
192 192 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
193 193 if nexts < prev:
194 194 raise ValueError(b"Manifest lines not in sorted order.")
195 195 prev = nexts
196 196 pos = data.find(b"\n", pos + 1)
197 197 return positions
198 198
199 199 def _get(self, index):
200 200 # get the position encoded in pos:
201 201 # positive number is an index in 'data'
202 202 # negative number is in extrapieces
203 203 pos = self.positions[index]
204 204 if pos >= 0:
205 205 return self.data, pos
206 206 return self.extradata[-pos - 1], -1
207 207
208 208 def _getkey(self, pos):
209 209 if pos >= 0:
210 210 return self.data[pos : self.data.find(b'\x00', pos + 1)]
211 211 return self.extradata[-pos - 1][0]
212 212
213 213 def bsearch(self, key):
214 214 first = 0
215 215 last = len(self.positions) - 1
216 216
217 217 while first <= last:
218 218 midpoint = (first + last) // 2
219 219 nextpos = self.positions[midpoint]
220 220 candidate = self._getkey(nextpos)
221 221 r = _cmp(key, candidate)
222 222 if r == 0:
223 223 return midpoint
224 224 else:
225 225 if r < 0:
226 226 last = midpoint - 1
227 227 else:
228 228 first = midpoint + 1
229 229 return -1
230 230
231 231 def bsearch2(self, key):
232 232 # same as the above, but will always return the position
233 233 # done for performance reasons
234 234 first = 0
235 235 last = len(self.positions) - 1
236 236
237 237 while first <= last:
238 238 midpoint = (first + last) // 2
239 239 nextpos = self.positions[midpoint]
240 240 candidate = self._getkey(nextpos)
241 241 r = _cmp(key, candidate)
242 242 if r == 0:
243 243 return (midpoint, True)
244 244 else:
245 245 if r < 0:
246 246 last = midpoint - 1
247 247 else:
248 248 first = midpoint + 1
249 249 return (first, False)
250 250
251 251 def __contains__(self, key):
252 252 return self.bsearch(key) != -1
253 253
254 254 def _getflags(self, data, needle, pos):
255 255 start = pos + 41
256 256 end = data.find(b"\n", start)
257 257 if end == -1:
258 258 end = len(data) - 1
259 259 if start == end:
260 260 return b''
261 261 return self.data[start:end]
262 262
263 263 def __getitem__(self, key):
264 264 if not isinstance(key, bytes):
265 265 raise TypeError(b"getitem: manifest keys must be a bytes.")
266 266 needle = self.bsearch(key)
267 267 if needle == -1:
268 268 raise KeyError
269 269 data, pos = self._get(needle)
270 270 if pos == -1:
271 271 return (data[1], data[2])
272 272 zeropos = data.find(b'\x00', pos)
273 273 nlpos = data.find(b'\n', zeropos)
274 274 assert 0 <= needle <= len(self.positions)
275 275 assert len(self.extrainfo) == len(self.positions)
276 276 hlen = nlpos - zeropos - 1
277 277 # Hashes sometimes have an extra byte tucked on the end, so
278 278 # detect that.
279 279 if hlen % 2:
280 280 hlen -= 1
281 281 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
282 282 flags = self._getflags(data, needle, zeropos)
283 283 return (hashval, flags)
284 284
285 285 def __delitem__(self, key):
286 286 needle, found = self.bsearch2(key)
287 287 if not found:
288 288 raise KeyError
289 289 cur = self.positions[needle]
290 290 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
291 291 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
292 292 if cur >= 0:
293 293 # This does NOT unsort the list as far as the search functions are
294 294 # concerned, as they only examine lines mapped by self.positions.
295 295 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
296 296 self.hasremovals = True
297 297
298 298 def __setitem__(self, key, value):
299 299 if not isinstance(key, bytes):
300 300 raise TypeError(b"setitem: manifest keys must be a byte string.")
301 301 if not isinstance(value, tuple) or len(value) != 2:
302 302 raise TypeError(
303 303 b"Manifest values must be a tuple of (node, flags)."
304 304 )
305 305 hashval = value[0]
306 306 # hashes are either 20 or 32 bytes (sha1 or its replacement),
307 307 # and allow one extra byte taht won't be persisted to disk but
308 308 # is sometimes used in memory.
309 309 if not isinstance(hashval, bytes) or not (
310 310 20 <= len(hashval) <= 22 or 32 <= len(hashval) <= 34
311 311 ):
312 312 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
313 313 flags = value[1]
314 314 if len(hashval) == 22:
315 315 hashval = hashval[:-1]
316 316 if not isinstance(flags, bytes) or len(flags) > 1:
317 317 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
318 318 needle, found = self.bsearch2(key)
319 319 if found:
320 320 # put the item
321 321 pos = self.positions[needle]
322 322 if pos < 0:
323 323 self.extradata[-pos - 1] = (key, hashval, value[1])
324 324 else:
325 325 # just don't bother
326 326 self.extradata.append((key, hashval, value[1]))
327 327 self.positions[needle] = -len(self.extradata)
328 328 else:
329 329 # not found, put it in with extra positions
330 330 self.extradata.append((key, hashval, value[1]))
331 331 self.positions = (
332 332 self.positions[:needle]
333 333 + [-len(self.extradata)]
334 334 + self.positions[needle:]
335 335 )
336 336 self.extrainfo = (
337 337 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
338 338 )
339 339
340 340 def copy(self):
341 341 # XXX call _compact like in C?
342 342 return _lazymanifest(
343 343 self.data,
344 344 self.positions,
345 345 self.extrainfo,
346 346 self.extradata,
347 347 self.hasremovals,
348 348 )
349 349
350 350 def _compact(self):
351 351 # hopefully not called TOO often
352 352 if len(self.extradata) == 0 and not self.hasremovals:
353 353 return
354 354 l = []
355 355 i = 0
356 356 offset = 0
357 357 self.extrainfo = [0] * len(self.positions)
358 358 while i < len(self.positions):
359 359 if self.positions[i] >= 0:
360 360 cur = self.positions[i]
361 361 last_cut = cur
362 362
363 363 # Collect all contiguous entries in the buffer at the current
364 364 # offset, breaking out only for added/modified items held in
365 365 # extradata, or a deleted line prior to the next position.
366 366 while True:
367 367 self.positions[i] = offset
368 368 i += 1
369 369 if i == len(self.positions) or self.positions[i] < 0:
370 370 break
371 371
372 372 # A removed file has no positions[] entry, but does have an
373 373 # overwritten first byte. Break out and find the end of the
374 374 # current good entry/entries if there is a removed file
375 375 # before the next position.
376 376 if (
377 377 self.hasremovals
378 378 and self.data.find(b'\n\x00', cur, self.positions[i])
379 379 != -1
380 380 ):
381 381 break
382 382
383 383 offset += self.positions[i] - cur
384 384 cur = self.positions[i]
385 385 end_cut = self.data.find(b'\n', cur)
386 386 if end_cut != -1:
387 387 end_cut += 1
388 388 offset += end_cut - cur
389 389 l.append(self.data[last_cut:end_cut])
390 390 else:
391 391 while i < len(self.positions) and self.positions[i] < 0:
392 392 cur = self.positions[i]
393 393 t = self.extradata[-cur - 1]
394 394 l.append(self._pack(t))
395 395 self.positions[i] = offset
396 396 # Hashes are either 20 bytes (old sha1s) or 32
397 397 # bytes (new non-sha1).
398 398 hlen = 20
399 399 if len(t[1]) > 25:
400 400 hlen = 32
401 401 if len(t[1]) > hlen:
402 402 self.extrainfo[i] = ord(t[1][hlen + 1])
403 403 offset += len(l[-1])
404 404 i += 1
405 405 self.data = b''.join(l)
406 406 self.hasremovals = False
407 407 self.extradata = []
408 408
409 409 def _pack(self, d):
410 410 n = d[1]
411 411 if len(n) == 21 or len(n) == 33:
412 412 n = n[:-1]
413 413 assert len(n) == 20 or len(n) == 32
414 414 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
415 415
416 416 def text(self):
417 417 self._compact()
418 418 return self.data
419 419
420 420 def diff(self, m2, clean=False):
421 421 '''Finds changes between the current manifest and m2.'''
422 422 # XXX think whether efficiency matters here
423 423 diff = {}
424 424
425 425 for fn, e1, flags in self.iterentries():
426 426 if fn not in m2:
427 427 diff[fn] = (e1, flags), (None, b'')
428 428 else:
429 429 e2 = m2[fn]
430 430 if (e1, flags) != e2:
431 431 diff[fn] = (e1, flags), e2
432 432 elif clean:
433 433 diff[fn] = None
434 434
435 435 for fn, e2, flags in m2.iterentries():
436 436 if fn not in self:
437 437 diff[fn] = (None, b''), (e2, flags)
438 438
439 439 return diff
440 440
441 441 def iterentries(self):
442 442 return lazymanifestiterentries(self)
443 443
444 444 def iterkeys(self):
445 445 return lazymanifestiter(self)
446 446
447 447 def __iter__(self):
448 448 return lazymanifestiter(self)
449 449
450 450 def __len__(self):
451 451 return len(self.positions)
452 452
453 453 def filtercopy(self, filterfn):
454 454 # XXX should be optimized
455 455 c = _lazymanifest(b'')
456 456 for f, n, fl in self.iterentries():
457 457 if filterfn(f):
458 458 c[f] = n, fl
459 459 return c
460 460
461 461
462 462 try:
463 463 _lazymanifest = parsers.lazymanifest
464 464 except AttributeError:
465 465 pass
466 466
467 467
468 468 @interfaceutil.implementer(repository.imanifestdict)
469 469 class manifestdict(object):
470 470 def __init__(self, data=b''):
471 471 self._lm = _lazymanifest(data)
472 472
473 473 def __getitem__(self, key):
474 474 return self._lm[key][0]
475 475
476 476 def find(self, key):
477 477 return self._lm[key]
478 478
479 479 def __len__(self):
480 480 return len(self._lm)
481 481
482 482 def __nonzero__(self):
483 483 # nonzero is covered by the __len__ function, but implementing it here
484 484 # makes it easier for extensions to override.
485 485 return len(self._lm) != 0
486 486
487 487 __bool__ = __nonzero__
488 488
489 489 def __setitem__(self, key, node):
490 490 self._lm[key] = node, self.flags(key)
491 491
492 492 def __contains__(self, key):
493 493 if key is None:
494 494 return False
495 495 return key in self._lm
496 496
497 497 def __delitem__(self, key):
498 498 del self._lm[key]
499 499
500 500 def __iter__(self):
501 501 return self._lm.__iter__()
502 502
503 503 def iterkeys(self):
504 504 return self._lm.iterkeys()
505 505
506 506 def keys(self):
507 507 return list(self.iterkeys())
508 508
509 509 def filesnotin(self, m2, match=None):
510 510 '''Set of files in this manifest that are not in the other'''
511 511 if match is not None:
512 512 match = matchmod.badmatch(match, lambda path, msg: None)
513 513 sm2 = set(m2.walk(match))
514 514 return {f for f in self.walk(match) if f not in sm2}
515 515 return {f for f in self if f not in m2}
516 516
517 517 @propertycache
518 518 def _dirs(self):
519 519 return pathutil.dirs(self)
520 520
521 521 def dirs(self):
522 522 return self._dirs
523 523
524 524 def hasdir(self, dir):
525 525 return dir in self._dirs
526 526
527 527 def _filesfastpath(self, match):
528 528 '''Checks whether we can correctly and quickly iterate over matcher
529 529 files instead of over manifest files.'''
530 530 files = match.files()
531 531 return len(files) < 100 and (
532 532 match.isexact()
533 533 or (match.prefix() and all(fn in self for fn in files))
534 534 )
535 535
536 536 def walk(self, match):
537 537 '''Generates matching file names.
538 538
539 539 Equivalent to manifest.matches(match).iterkeys(), but without creating
540 540 an entirely new manifest.
541 541
542 542 It also reports nonexistent files by marking them bad with match.bad().
543 543 '''
544 544 if match.always():
545 545 for f in iter(self):
546 546 yield f
547 547 return
548 548
549 549 fset = set(match.files())
550 550
551 551 # avoid the entire walk if we're only looking for specific files
552 552 if self._filesfastpath(match):
553 553 for fn in sorted(fset):
554 554 if fn in self:
555 555 yield fn
556 556 return
557 557
558 558 for fn in self:
559 559 if fn in fset:
560 560 # specified pattern is the exact name
561 561 fset.remove(fn)
562 562 if match(fn):
563 563 yield fn
564 564
565 565 # for dirstate.walk, files=[''] means "walk the whole tree".
566 566 # follow that here, too
567 567 fset.discard(b'')
568 568
569 569 for fn in sorted(fset):
570 570 if not self.hasdir(fn):
571 571 match.bad(fn, None)
572 572
573 573 def _matches(self, match):
574 574 '''generate a new manifest filtered by the match argument'''
575 575 if match.always():
576 576 return self.copy()
577 577
578 578 if self._filesfastpath(match):
579 579 m = manifestdict()
580 580 lm = self._lm
581 581 for fn in match.files():
582 582 if fn in lm:
583 583 m._lm[fn] = lm[fn]
584 584 return m
585 585
586 586 m = manifestdict()
587 587 m._lm = self._lm.filtercopy(match)
588 588 return m
589 589
590 590 def diff(self, m2, match=None, clean=False):
591 591 '''Finds changes between the current manifest and m2.
592 592
593 593 Args:
594 594 m2: the manifest to which this manifest should be compared.
595 595 clean: if true, include files unchanged between these manifests
596 596 with a None value in the returned dictionary.
597 597
598 598 The result is returned as a dict with filename as key and
599 599 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
600 600 nodeid in the current/other manifest and fl1/fl2 is the flag
601 601 in the current/other manifest. Where the file does not exist,
602 602 the nodeid will be None and the flags will be the empty
603 603 string.
604 604 '''
605 605 if match:
606 606 m1 = self._matches(match)
607 607 m2 = m2._matches(match)
608 608 return m1.diff(m2, clean=clean)
609 609 return self._lm.diff(m2._lm, clean)
610 610
611 611 def setflag(self, key, flag):
612 612 self._lm[key] = self[key], flag
613 613
614 614 def get(self, key, default=None):
615 615 try:
616 616 return self._lm[key][0]
617 617 except KeyError:
618 618 return default
619 619
620 620 def flags(self, key):
621 621 try:
622 622 return self._lm[key][1]
623 623 except KeyError:
624 624 return b''
625 625
626 626 def copy(self):
627 627 c = manifestdict()
628 628 c._lm = self._lm.copy()
629 629 return c
630 630
631 631 def items(self):
632 632 return (x[:2] for x in self._lm.iterentries())
633 633
634 634 def iteritems(self):
635 635 return (x[:2] for x in self._lm.iterentries())
636 636
637 637 def iterentries(self):
638 638 return self._lm.iterentries()
639 639
640 640 def text(self):
641 641 # most likely uses native version
642 642 return self._lm.text()
643 643
644 644 def fastdelta(self, base, changes):
645 645 """Given a base manifest text as a bytearray and a list of changes
646 646 relative to that text, compute a delta that can be used by revlog.
647 647 """
648 648 delta = []
649 649 dstart = None
650 650 dend = None
651 651 dline = [b""]
652 652 start = 0
653 653 # zero copy representation of base as a buffer
654 654 addbuf = util.buffer(base)
655 655
656 656 changes = list(changes)
657 657 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
658 658 # start with a readonly loop that finds the offset of
659 659 # each line and creates the deltas
660 660 for f, todelete in changes:
661 661 # bs will either be the index of the item or the insert point
662 662 start, end = _msearch(addbuf, f, start)
663 663 if not todelete:
664 664 h, fl = self._lm[f]
665 665 l = b"%s\0%s%s\n" % (f, hex(h), fl)
666 666 else:
667 667 if start == end:
668 668 # item we want to delete was not found, error out
669 669 raise AssertionError(
670 670 _(b"failed to remove %s from manifest") % f
671 671 )
672 672 l = b""
673 673 if dstart is not None and dstart <= start and dend >= start:
674 674 if dend < end:
675 675 dend = end
676 676 if l:
677 677 dline.append(l)
678 678 else:
679 679 if dstart is not None:
680 680 delta.append([dstart, dend, b"".join(dline)])
681 681 dstart = start
682 682 dend = end
683 683 dline = [l]
684 684
685 685 if dstart is not None:
686 686 delta.append([dstart, dend, b"".join(dline)])
687 687 # apply the delta to the base, and get a delta for addrevision
688 688 deltatext, arraytext = _addlistdelta(base, delta)
689 689 else:
690 690 # For large changes, it's much cheaper to just build the text and
691 691 # diff it.
692 692 arraytext = bytearray(self.text())
693 693 deltatext = mdiff.textdiff(
694 694 util.buffer(base), util.buffer(arraytext)
695 695 )
696 696
697 697 return arraytext, deltatext
698 698
699 699
700 700 def _msearch(m, s, lo=0, hi=None):
701 701 '''return a tuple (start, end) that says where to find s within m.
702 702
703 703 If the string is found m[start:end] are the line containing
704 704 that string. If start == end the string was not found and
705 705 they indicate the proper sorted insertion point.
706 706
707 707 m should be a buffer, a memoryview or a byte string.
708 708 s is a byte string'''
709 709
710 710 def advance(i, c):
711 711 while i < lenm and m[i : i + 1] != c:
712 712 i += 1
713 713 return i
714 714
715 715 if not s:
716 716 return (lo, lo)
717 717 lenm = len(m)
718 718 if not hi:
719 719 hi = lenm
720 720 while lo < hi:
721 721 mid = (lo + hi) // 2
722 722 start = mid
723 723 while start > 0 and m[start - 1 : start] != b'\n':
724 724 start -= 1
725 725 end = advance(start, b'\0')
726 726 if bytes(m[start:end]) < s:
727 727 # we know that after the null there are 40 bytes of sha1
728 728 # this translates to the bisect lo = mid + 1
729 729 lo = advance(end + 40, b'\n') + 1
730 730 else:
731 731 # this translates to the bisect hi = mid
732 732 hi = start
733 733 end = advance(lo, b'\0')
734 734 found = m[lo:end]
735 735 if s == found:
736 736 # we know that after the null there are 40 bytes of sha1
737 737 end = advance(end + 40, b'\n')
738 738 return (lo, end + 1)
739 739 else:
740 740 return (lo, lo)
741 741
742 742
743 743 def _checkforbidden(l):
744 744 """Check filenames for illegal characters."""
745 745 for f in l:
746 746 if b'\n' in f or b'\r' in f:
747 747 raise error.StorageError(
748 748 _(b"'\\n' and '\\r' disallowed in filenames: %r")
749 749 % pycompat.bytestr(f)
750 750 )
751 751
752 752
753 753 # apply the changes collected during the bisect loop to our addlist
754 754 # return a delta suitable for addrevision
755 755 def _addlistdelta(addlist, x):
756 756 # for large addlist arrays, building a new array is cheaper
757 757 # than repeatedly modifying the existing one
758 758 currentposition = 0
759 759 newaddlist = bytearray()
760 760
761 761 for start, end, content in x:
762 762 newaddlist += addlist[currentposition:start]
763 763 if content:
764 764 newaddlist += bytearray(content)
765 765
766 766 currentposition = end
767 767
768 768 newaddlist += addlist[currentposition:]
769 769
770 770 deltatext = b"".join(
771 771 struct.pack(b">lll", start, end, len(content)) + content
772 772 for start, end, content in x
773 773 )
774 774 return deltatext, newaddlist
775 775
776 776
777 777 def _splittopdir(f):
778 778 if b'/' in f:
779 779 dir, subpath = f.split(b'/', 1)
780 780 return dir + b'/', subpath
781 781 else:
782 782 return b'', f
783 783
784 784
785 785 _noop = lambda s: None
786 786
787 787
788 788 @interfaceutil.implementer(repository.imanifestdict)
789 789 class treemanifest(object):
790 790 def __init__(self, dir=b'', text=b''):
791 791 self._dir = dir
792 792 self._node = nullid
793 793 self._loadfunc = _noop
794 794 self._copyfunc = _noop
795 795 self._dirty = False
796 796 self._dirs = {}
797 797 self._lazydirs = {}
798 798 # Using _lazymanifest here is a little slower than plain old dicts
799 799 self._files = {}
800 800 self._flags = {}
801 801 if text:
802 802
803 803 def readsubtree(subdir, subm):
804 804 raise AssertionError(
805 805 b'treemanifest constructor only accepts flat manifests'
806 806 )
807 807
808 808 self.parse(text, readsubtree)
809 809 self._dirty = True # Mark flat manifest dirty after parsing
810 810
811 811 def _subpath(self, path):
812 812 return self._dir + path
813 813
814 814 def _loadalllazy(self):
815 815 selfdirs = self._dirs
816 816 for d, (path, node, readsubtree, docopy) in pycompat.iteritems(
817 817 self._lazydirs
818 818 ):
819 819 if docopy:
820 820 selfdirs[d] = readsubtree(path, node).copy()
821 821 else:
822 822 selfdirs[d] = readsubtree(path, node)
823 823 self._lazydirs = {}
824 824
825 825 def _loadlazy(self, d):
826 826 v = self._lazydirs.get(d)
827 827 if v:
828 828 path, node, readsubtree, docopy = v
829 829 if docopy:
830 830 self._dirs[d] = readsubtree(path, node).copy()
831 831 else:
832 832 self._dirs[d] = readsubtree(path, node)
833 833 del self._lazydirs[d]
834 834
835 835 def _loadchildrensetlazy(self, visit):
836 836 if not visit:
837 837 return None
838 838 if visit == b'all' or visit == b'this':
839 839 self._loadalllazy()
840 840 return None
841 841
842 842 loadlazy = self._loadlazy
843 843 for k in visit:
844 844 loadlazy(k + b'/')
845 845 return visit
846 846
847 847 def _loaddifflazy(self, t1, t2):
848 848 """load items in t1 and t2 if they're needed for diffing.
849 849
850 850 The criteria currently is:
851 851 - if it's not present in _lazydirs in either t1 or t2, load it in the
852 852 other (it may already be loaded or it may not exist, doesn't matter)
853 853 - if it's present in _lazydirs in both, compare the nodeid; if it
854 854 differs, load it in both
855 855 """
856 856 toloadlazy = []
857 857 for d, v1 in pycompat.iteritems(t1._lazydirs):
858 858 v2 = t2._lazydirs.get(d)
859 859 if not v2 or v2[1] != v1[1]:
860 860 toloadlazy.append(d)
861 861 for d, v1 in pycompat.iteritems(t2._lazydirs):
862 862 if d not in t1._lazydirs:
863 863 toloadlazy.append(d)
864 864
865 865 for d in toloadlazy:
866 866 t1._loadlazy(d)
867 867 t2._loadlazy(d)
868 868
869 869 def __len__(self):
870 870 self._load()
871 871 size = len(self._files)
872 872 self._loadalllazy()
873 873 for m in self._dirs.values():
874 874 size += m.__len__()
875 875 return size
876 876
877 877 def __nonzero__(self):
878 878 # Faster than "__len() != 0" since it avoids loading sub-manifests
879 879 return not self._isempty()
880 880
881 881 __bool__ = __nonzero__
882 882
883 883 def _isempty(self):
884 884 self._load() # for consistency; already loaded by all callers
885 885 # See if we can skip loading everything.
886 886 if self._files or (
887 887 self._dirs and any(not m._isempty() for m in self._dirs.values())
888 888 ):
889 889 return False
890 890 self._loadalllazy()
891 891 return not self._dirs or all(m._isempty() for m in self._dirs.values())
892 892
893 893 @encoding.strmethod
894 894 def __repr__(self):
895 895 return (
896 896 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
897 897 % (
898 898 self._dir,
899 899 hex(self._node),
900 900 bool(self._loadfunc is _noop),
901 901 self._dirty,
902 902 id(self),
903 903 )
904 904 )
905 905
906 906 def dir(self):
907 907 '''The directory that this tree manifest represents, including a
908 908 trailing '/'. Empty string for the repo root directory.'''
909 909 return self._dir
910 910
911 911 def node(self):
912 912 '''This node of this instance. nullid for unsaved instances. Should
913 913 be updated when the instance is read or written from a revlog.
914 914 '''
915 915 assert not self._dirty
916 916 return self._node
917 917
918 918 def setnode(self, node):
919 919 self._node = node
920 920 self._dirty = False
921 921
922 922 def iterentries(self):
923 923 self._load()
924 924 self._loadalllazy()
925 925 for p, n in sorted(
926 926 itertools.chain(self._dirs.items(), self._files.items())
927 927 ):
928 928 if p in self._files:
929 929 yield self._subpath(p), n, self._flags.get(p, b'')
930 930 else:
931 931 for x in n.iterentries():
932 932 yield x
933 933
934 934 def items(self):
935 935 self._load()
936 936 self._loadalllazy()
937 937 for p, n in sorted(
938 938 itertools.chain(self._dirs.items(), self._files.items())
939 939 ):
940 940 if p in self._files:
941 941 yield self._subpath(p), n
942 942 else:
943 943 for f, sn in pycompat.iteritems(n):
944 944 yield f, sn
945 945
946 946 iteritems = items
947 947
948 948 def iterkeys(self):
949 949 self._load()
950 950 self._loadalllazy()
951 951 for p in sorted(itertools.chain(self._dirs, self._files)):
952 952 if p in self._files:
953 953 yield self._subpath(p)
954 954 else:
955 955 for f in self._dirs[p]:
956 956 yield f
957 957
958 958 def keys(self):
959 959 return list(self.iterkeys())
960 960
961 961 def __iter__(self):
962 962 return self.iterkeys()
963 963
964 964 def __contains__(self, f):
965 965 if f is None:
966 966 return False
967 967 self._load()
968 968 dir, subpath = _splittopdir(f)
969 969 if dir:
970 970 self._loadlazy(dir)
971 971
972 972 if dir not in self._dirs:
973 973 return False
974 974
975 975 return self._dirs[dir].__contains__(subpath)
976 976 else:
977 977 return f in self._files
978 978
979 979 def get(self, f, default=None):
980 980 self._load()
981 981 dir, subpath = _splittopdir(f)
982 982 if dir:
983 983 self._loadlazy(dir)
984 984
985 985 if dir not in self._dirs:
986 986 return default
987 987 return self._dirs[dir].get(subpath, default)
988 988 else:
989 989 return self._files.get(f, default)
990 990
991 991 def __getitem__(self, f):
992 992 self._load()
993 993 dir, subpath = _splittopdir(f)
994 994 if dir:
995 995 self._loadlazy(dir)
996 996
997 997 return self._dirs[dir].__getitem__(subpath)
998 998 else:
999 999 return self._files[f]
1000 1000
1001 1001 def flags(self, f):
1002 1002 self._load()
1003 1003 dir, subpath = _splittopdir(f)
1004 1004 if dir:
1005 1005 self._loadlazy(dir)
1006 1006
1007 1007 if dir not in self._dirs:
1008 1008 return b''
1009 1009 return self._dirs[dir].flags(subpath)
1010 1010 else:
1011 1011 if f in self._lazydirs or f in self._dirs:
1012 1012 return b''
1013 1013 return self._flags.get(f, b'')
1014 1014
1015 1015 def find(self, f):
1016 1016 self._load()
1017 1017 dir, subpath = _splittopdir(f)
1018 1018 if dir:
1019 1019 self._loadlazy(dir)
1020 1020
1021 1021 return self._dirs[dir].find(subpath)
1022 1022 else:
1023 1023 return self._files[f], self._flags.get(f, b'')
1024 1024
1025 1025 def __delitem__(self, f):
1026 1026 self._load()
1027 1027 dir, subpath = _splittopdir(f)
1028 1028 if dir:
1029 1029 self._loadlazy(dir)
1030 1030
1031 1031 self._dirs[dir].__delitem__(subpath)
1032 1032 # If the directory is now empty, remove it
1033 1033 if self._dirs[dir]._isempty():
1034 1034 del self._dirs[dir]
1035 1035 else:
1036 1036 del self._files[f]
1037 1037 if f in self._flags:
1038 1038 del self._flags[f]
1039 1039 self._dirty = True
1040 1040
1041 1041 def __setitem__(self, f, n):
1042 1042 assert n is not None
1043 1043 self._load()
1044 1044 dir, subpath = _splittopdir(f)
1045 1045 if dir:
1046 1046 self._loadlazy(dir)
1047 1047 if dir not in self._dirs:
1048 1048 self._dirs[dir] = treemanifest(self._subpath(dir))
1049 1049 self._dirs[dir].__setitem__(subpath, n)
1050 1050 else:
1051 1051 # manifest nodes are either 20 bytes or 32 bytes,
1052 1052 # depending on the hash in use. An extra byte is
1053 1053 # occasionally used by hg, but won't ever be
1054 1054 # persisted. Trim to 21 or 33 bytes as appropriate.
1055 1055 trim = 21 if len(n) < 25 else 33
1056 1056 self._files[f] = n[:trim] # to match manifestdict's behavior
1057 1057 self._dirty = True
1058 1058
1059 1059 def _load(self):
1060 1060 if self._loadfunc is not _noop:
1061 1061 lf, self._loadfunc = self._loadfunc, _noop
1062 1062 lf(self)
1063 1063 elif self._copyfunc is not _noop:
1064 1064 cf, self._copyfunc = self._copyfunc, _noop
1065 1065 cf(self)
1066 1066
1067 1067 def setflag(self, f, flags):
1068 1068 """Set the flags (symlink, executable) for path f."""
1069 1069 self._load()
1070 1070 dir, subpath = _splittopdir(f)
1071 1071 if dir:
1072 1072 self._loadlazy(dir)
1073 1073 if dir not in self._dirs:
1074 1074 self._dirs[dir] = treemanifest(self._subpath(dir))
1075 1075 self._dirs[dir].setflag(subpath, flags)
1076 1076 else:
1077 1077 self._flags[f] = flags
1078 1078 self._dirty = True
1079 1079
1080 1080 def copy(self):
1081 1081 copy = treemanifest(self._dir)
1082 1082 copy._node = self._node
1083 1083 copy._dirty = self._dirty
1084 1084 if self._copyfunc is _noop:
1085 1085
1086 1086 def _copyfunc(s):
1087 1087 self._load()
1088 1088 s._lazydirs = {
1089 1089 d: (p, n, r, True)
1090 1090 for d, (p, n, r, c) in pycompat.iteritems(self._lazydirs)
1091 1091 }
1092 1092 sdirs = s._dirs
1093 1093 for d, v in pycompat.iteritems(self._dirs):
1094 1094 sdirs[d] = v.copy()
1095 1095 s._files = dict.copy(self._files)
1096 1096 s._flags = dict.copy(self._flags)
1097 1097
1098 1098 if self._loadfunc is _noop:
1099 1099 _copyfunc(copy)
1100 1100 else:
1101 1101 copy._copyfunc = _copyfunc
1102 1102 else:
1103 1103 copy._copyfunc = self._copyfunc
1104 1104 return copy
1105 1105
1106 1106 def filesnotin(self, m2, match=None):
1107 1107 '''Set of files in this manifest that are not in the other'''
1108 1108 if match and not match.always():
1109 1109 m1 = self._matches(match)
1110 1110 m2 = m2._matches(match)
1111 1111 return m1.filesnotin(m2)
1112 1112
1113 1113 files = set()
1114 1114
1115 1115 def _filesnotin(t1, t2):
1116 1116 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1117 1117 return
1118 1118 t1._load()
1119 1119 t2._load()
1120 1120 self._loaddifflazy(t1, t2)
1121 1121 for d, m1 in pycompat.iteritems(t1._dirs):
1122 1122 if d in t2._dirs:
1123 1123 m2 = t2._dirs[d]
1124 1124 _filesnotin(m1, m2)
1125 1125 else:
1126 1126 files.update(m1.iterkeys())
1127 1127
1128 1128 for fn in t1._files:
1129 1129 if fn not in t2._files:
1130 1130 files.add(t1._subpath(fn))
1131 1131
1132 1132 _filesnotin(self, m2)
1133 1133 return files
1134 1134
1135 1135 @propertycache
1136 1136 def _alldirs(self):
1137 1137 return pathutil.dirs(self)
1138 1138
1139 1139 def dirs(self):
1140 1140 return self._alldirs
1141 1141
1142 1142 def hasdir(self, dir):
1143 1143 self._load()
1144 1144 topdir, subdir = _splittopdir(dir)
1145 1145 if topdir:
1146 1146 self._loadlazy(topdir)
1147 1147 if topdir in self._dirs:
1148 1148 return self._dirs[topdir].hasdir(subdir)
1149 1149 return False
1150 1150 dirslash = dir + b'/'
1151 1151 return dirslash in self._dirs or dirslash in self._lazydirs
1152 1152
1153 1153 def walk(self, match):
1154 1154 '''Generates matching file names.
1155 1155
1156 1156 It also reports nonexistent files by marking them bad with match.bad().
1157 1157 '''
1158 1158 if match.always():
1159 1159 for f in iter(self):
1160 1160 yield f
1161 1161 return
1162 1162
1163 1163 fset = set(match.files())
1164 1164
1165 1165 for fn in self._walk(match):
1166 1166 if fn in fset:
1167 1167 # specified pattern is the exact name
1168 1168 fset.remove(fn)
1169 1169 yield fn
1170 1170
1171 1171 # for dirstate.walk, files=[''] means "walk the whole tree".
1172 1172 # follow that here, too
1173 1173 fset.discard(b'')
1174 1174
1175 1175 for fn in sorted(fset):
1176 1176 if not self.hasdir(fn):
1177 1177 match.bad(fn, None)
1178 1178
1179 1179 def _walk(self, match):
1180 1180 '''Recursively generates matching file names for walk().'''
1181 1181 visit = match.visitchildrenset(self._dir[:-1])
1182 1182 if not visit:
1183 1183 return
1184 1184
1185 1185 # yield this dir's files and walk its submanifests
1186 1186 self._load()
1187 1187 visit = self._loadchildrensetlazy(visit)
1188 1188 for p in sorted(list(self._dirs) + list(self._files)):
1189 1189 if p in self._files:
1190 1190 fullp = self._subpath(p)
1191 1191 if match(fullp):
1192 1192 yield fullp
1193 1193 else:
1194 1194 if not visit or p[:-1] in visit:
1195 1195 for f in self._dirs[p]._walk(match):
1196 1196 yield f
1197 1197
1198 1198 def _matches(self, match):
1199 1199 '''recursively generate a new manifest filtered by the match argument.
1200 1200 '''
1201 1201 if match.always():
1202 1202 return self.copy()
1203 1203 return self._matches_inner(match)
1204 1204
1205 1205 def _matches_inner(self, match):
1206 1206 if match.always():
1207 1207 return self.copy()
1208 1208
1209 1209 visit = match.visitchildrenset(self._dir[:-1])
1210 1210 if visit == b'all':
1211 1211 return self.copy()
1212 1212 ret = treemanifest(self._dir)
1213 1213 if not visit:
1214 1214 return ret
1215 1215
1216 1216 self._load()
1217 1217 for fn in self._files:
1218 1218 # While visitchildrenset *usually* lists only subdirs, this is
1219 1219 # actually up to the matcher and may have some files in the set().
1220 1220 # If visit == 'this', we should obviously look at the files in this
1221 1221 # directory; if visit is a set, and fn is in it, we should inspect
1222 1222 # fn (but no need to inspect things not in the set).
1223 1223 if visit != b'this' and fn not in visit:
1224 1224 continue
1225 1225 fullp = self._subpath(fn)
1226 1226 # visitchildrenset isn't perfect, we still need to call the regular
1227 1227 # matcher code to further filter results.
1228 1228 if not match(fullp):
1229 1229 continue
1230 1230 ret._files[fn] = self._files[fn]
1231 1231 if fn in self._flags:
1232 1232 ret._flags[fn] = self._flags[fn]
1233 1233
1234 1234 visit = self._loadchildrensetlazy(visit)
1235 1235 for dir, subm in pycompat.iteritems(self._dirs):
1236 1236 if visit and dir[:-1] not in visit:
1237 1237 continue
1238 1238 m = subm._matches_inner(match)
1239 1239 if not m._isempty():
1240 1240 ret._dirs[dir] = m
1241 1241
1242 1242 if not ret._isempty():
1243 1243 ret._dirty = True
1244 1244 return ret
1245 1245
1246 1246 def fastdelta(self, base, changes):
1247 1247 raise FastdeltaUnavailable()
1248 1248
1249 1249 def diff(self, m2, match=None, clean=False):
1250 1250 '''Finds changes between the current manifest and m2.
1251 1251
1252 1252 Args:
1253 1253 m2: the manifest to which this manifest should be compared.
1254 1254 clean: if true, include files unchanged between these manifests
1255 1255 with a None value in the returned dictionary.
1256 1256
1257 1257 The result is returned as a dict with filename as key and
1258 1258 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1259 1259 nodeid in the current/other manifest and fl1/fl2 is the flag
1260 1260 in the current/other manifest. Where the file does not exist,
1261 1261 the nodeid will be None and the flags will be the empty
1262 1262 string.
1263 1263 '''
1264 1264 if match and not match.always():
1265 1265 m1 = self._matches(match)
1266 1266 m2 = m2._matches(match)
1267 1267 return m1.diff(m2, clean=clean)
1268 1268 result = {}
1269 1269 emptytree = treemanifest()
1270 1270
1271 1271 def _iterativediff(t1, t2, stack):
1272 1272 """compares two tree manifests and append new tree-manifests which
1273 1273 needs to be compared to stack"""
1274 1274 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1275 1275 return
1276 1276 t1._load()
1277 1277 t2._load()
1278 1278 self._loaddifflazy(t1, t2)
1279 1279
1280 1280 for d, m1 in pycompat.iteritems(t1._dirs):
1281 1281 m2 = t2._dirs.get(d, emptytree)
1282 1282 stack.append((m1, m2))
1283 1283
1284 1284 for d, m2 in pycompat.iteritems(t2._dirs):
1285 1285 if d not in t1._dirs:
1286 1286 stack.append((emptytree, m2))
1287 1287
1288 1288 for fn, n1 in pycompat.iteritems(t1._files):
1289 1289 fl1 = t1._flags.get(fn, b'')
1290 1290 n2 = t2._files.get(fn, None)
1291 1291 fl2 = t2._flags.get(fn, b'')
1292 1292 if n1 != n2 or fl1 != fl2:
1293 1293 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1294 1294 elif clean:
1295 1295 result[t1._subpath(fn)] = None
1296 1296
1297 1297 for fn, n2 in pycompat.iteritems(t2._files):
1298 1298 if fn not in t1._files:
1299 1299 fl2 = t2._flags.get(fn, b'')
1300 1300 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1301 1301
1302 1302 stackls = []
1303 1303 _iterativediff(self, m2, stackls)
1304 1304 while stackls:
1305 1305 t1, t2 = stackls.pop()
1306 1306 # stackls is populated in the function call
1307 1307 _iterativediff(t1, t2, stackls)
1308 1308 return result
1309 1309
1310 1310 def unmodifiedsince(self, m2):
1311 1311 return not self._dirty and not m2._dirty and self._node == m2._node
1312 1312
1313 1313 def parse(self, text, readsubtree):
1314 1314 selflazy = self._lazydirs
1315 1315 subpath = self._subpath
1316 1316 for f, n, fl in _parse(text):
1317 1317 if fl == b't':
1318 1318 f = f + b'/'
1319 1319 # False below means "doesn't need to be copied" and can use the
1320 1320 # cached value from readsubtree directly.
1321 1321 selflazy[f] = (subpath(f), n, readsubtree, False)
1322 1322 elif b'/' in f:
1323 1323 # This is a flat manifest, so use __setitem__ and setflag rather
1324 1324 # than assigning directly to _files and _flags, so we can
1325 1325 # assign a path in a subdirectory, and to mark dirty (compared
1326 1326 # to nullid).
1327 1327 self[f] = n
1328 1328 if fl:
1329 1329 self.setflag(f, fl)
1330 1330 else:
1331 1331 # Assigning to _files and _flags avoids marking as dirty,
1332 1332 # and should be a little faster.
1333 1333 self._files[f] = n
1334 1334 if fl:
1335 1335 self._flags[f] = fl
1336 1336
1337 1337 def text(self):
1338 1338 """Get the full data of this manifest as a bytestring."""
1339 1339 self._load()
1340 1340 return _text(self.iterentries())
1341 1341
1342 1342 def dirtext(self):
1343 1343 """Get the full data of this directory as a bytestring. Make sure that
1344 1344 any submanifests have been written first, so their nodeids are correct.
1345 1345 """
1346 1346 self._load()
1347 1347 flags = self.flags
1348 1348 lazydirs = [
1349 1349 (d[:-1], v[1], b't') for d, v in pycompat.iteritems(self._lazydirs)
1350 1350 ]
1351 1351 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1352 1352 files = [(f, self._files[f], flags(f)) for f in self._files]
1353 1353 return _text(sorted(dirs + files + lazydirs))
1354 1354
1355 1355 def read(self, gettext, readsubtree):
1356 1356 def _load_for_read(s):
1357 1357 s.parse(gettext(), readsubtree)
1358 1358 s._dirty = False
1359 1359
1360 1360 self._loadfunc = _load_for_read
1361 1361
1362 1362 def writesubtrees(self, m1, m2, writesubtree, match):
1363 1363 self._load() # for consistency; should never have any effect here
1364 1364 m1._load()
1365 1365 m2._load()
1366 1366 emptytree = treemanifest()
1367 1367
1368 1368 def getnode(m, d):
1369 1369 ld = m._lazydirs.get(d)
1370 1370 if ld:
1371 1371 return ld[1]
1372 1372 return m._dirs.get(d, emptytree)._node
1373 1373
1374 1374 # let's skip investigating things that `match` says we do not need.
1375 1375 visit = match.visitchildrenset(self._dir[:-1])
1376 1376 visit = self._loadchildrensetlazy(visit)
1377 1377 if visit == b'this' or visit == b'all':
1378 1378 visit = None
1379 1379 for d, subm in pycompat.iteritems(self._dirs):
1380 1380 if visit and d[:-1] not in visit:
1381 1381 continue
1382 1382 subp1 = getnode(m1, d)
1383 1383 subp2 = getnode(m2, d)
1384 1384 if subp1 == nullid:
1385 1385 subp1, subp2 = subp2, subp1
1386 1386 writesubtree(subm, subp1, subp2, match)
1387 1387
1388 1388 def walksubtrees(self, matcher=None):
1389 1389 """Returns an iterator of the subtrees of this manifest, including this
1390 1390 manifest itself.
1391 1391
1392 1392 If `matcher` is provided, it only returns subtrees that match.
1393 1393 """
1394 1394 if matcher and not matcher.visitdir(self._dir[:-1]):
1395 1395 return
1396 1396 if not matcher or matcher(self._dir[:-1]):
1397 1397 yield self
1398 1398
1399 1399 self._load()
1400 1400 # OPT: use visitchildrenset to avoid loading everything.
1401 1401 self._loadalllazy()
1402 1402 for d, subm in pycompat.iteritems(self._dirs):
1403 1403 for subtree in subm.walksubtrees(matcher=matcher):
1404 1404 yield subtree
1405 1405
1406 1406
1407 1407 class manifestfulltextcache(util.lrucachedict):
1408 1408 """File-backed LRU cache for the manifest cache
1409 1409
1410 1410 File consists of entries, up to EOF:
1411 1411
1412 1412 - 20 bytes node, 4 bytes length, <length> manifest data
1413 1413
1414 1414 These are written in reverse cache order (oldest to newest).
1415 1415
1416 1416 """
1417 1417
1418 1418 _file = b'manifestfulltextcache'
1419 1419
1420 1420 def __init__(self, max):
1421 1421 super(manifestfulltextcache, self).__init__(max)
1422 1422 self._dirty = False
1423 1423 self._read = False
1424 1424 self._opener = None
1425 1425
1426 1426 def read(self):
1427 1427 if self._read or self._opener is None:
1428 1428 return
1429 1429
1430 1430 try:
1431 1431 with self._opener(self._file) as fp:
1432 1432 set = super(manifestfulltextcache, self).__setitem__
1433 1433 # ignore trailing data, this is a cache, corruption is skipped
1434 1434 while True:
1435 1435 # TODO do we need to do work here for sha1 portability?
1436 1436 node = fp.read(20)
1437 1437 if len(node) < 20:
1438 1438 break
1439 1439 try:
1440 1440 size = struct.unpack(b'>L', fp.read(4))[0]
1441 1441 except struct.error:
1442 1442 break
1443 1443 value = bytearray(fp.read(size))
1444 1444 if len(value) != size:
1445 1445 break
1446 1446 set(node, value)
1447 1447 except IOError:
1448 1448 # the file is allowed to be missing
1449 1449 pass
1450 1450
1451 1451 self._read = True
1452 1452 self._dirty = False
1453 1453
1454 1454 def write(self):
1455 1455 if not self._dirty or self._opener is None:
1456 1456 return
1457 1457 # rotate backwards to the first used node
1458 1458 with self._opener(
1459 1459 self._file, b'w', atomictemp=True, checkambig=True
1460 1460 ) as fp:
1461 1461 node = self._head.prev
1462 1462 while True:
1463 1463 if node.key in self._cache:
1464 1464 fp.write(node.key)
1465 1465 fp.write(struct.pack(b'>L', len(node.value)))
1466 1466 fp.write(node.value)
1467 1467 if node is self._head:
1468 1468 break
1469 1469 node = node.prev
1470 1470
1471 1471 def __len__(self):
1472 1472 if not self._read:
1473 1473 self.read()
1474 1474 return super(manifestfulltextcache, self).__len__()
1475 1475
1476 1476 def __contains__(self, k):
1477 1477 if not self._read:
1478 1478 self.read()
1479 1479 return super(manifestfulltextcache, self).__contains__(k)
1480 1480
1481 1481 def __iter__(self):
1482 1482 if not self._read:
1483 1483 self.read()
1484 1484 return super(manifestfulltextcache, self).__iter__()
1485 1485
1486 1486 def __getitem__(self, k):
1487 1487 if not self._read:
1488 1488 self.read()
1489 1489 # the cache lru order can change on read
1490 1490 setdirty = self._cache.get(k) is not self._head
1491 1491 value = super(manifestfulltextcache, self).__getitem__(k)
1492 1492 if setdirty:
1493 1493 self._dirty = True
1494 1494 return value
1495 1495
1496 1496 def __setitem__(self, k, v):
1497 1497 if not self._read:
1498 1498 self.read()
1499 1499 super(manifestfulltextcache, self).__setitem__(k, v)
1500 1500 self._dirty = True
1501 1501
1502 1502 def __delitem__(self, k):
1503 1503 if not self._read:
1504 1504 self.read()
1505 1505 super(manifestfulltextcache, self).__delitem__(k)
1506 1506 self._dirty = True
1507 1507
1508 1508 def get(self, k, default=None):
1509 1509 if not self._read:
1510 1510 self.read()
1511 1511 return super(manifestfulltextcache, self).get(k, default=default)
1512 1512
1513 1513 def clear(self, clear_persisted_data=False):
1514 1514 super(manifestfulltextcache, self).clear()
1515 1515 if clear_persisted_data:
1516 1516 self._dirty = True
1517 1517 self.write()
1518 1518 self._read = False
1519 1519
1520 1520
1521 1521 # and upper bound of what we expect from compression
1522 1522 # (real live value seems to be "3")
1523 1523 MAXCOMPRESSION = 3
1524 1524
1525 1525
1526 1526 class FastdeltaUnavailable(Exception):
1527 1527 """Exception raised when fastdelta isn't usable on a manifest."""
1528 1528
1529 1529
1530 1530 @interfaceutil.implementer(repository.imanifeststorage)
1531 1531 class manifestrevlog(object):
1532 1532 '''A revlog that stores manifest texts. This is responsible for caching the
1533 1533 full-text manifest contents.
1534 1534 '''
1535 1535
1536 1536 def __init__(
1537 1537 self,
1538 1538 opener,
1539 1539 tree=b'',
1540 1540 dirlogcache=None,
1541 1541 indexfile=None,
1542 1542 treemanifest=False,
1543 1543 ):
1544 1544 """Constructs a new manifest revlog
1545 1545
1546 1546 `indexfile` - used by extensions to have two manifests at once, like
1547 1547 when transitioning between flatmanifeset and treemanifests.
1548 1548
1549 1549 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1550 1550 options can also be used to make this a tree manifest revlog. The opener
1551 1551 option takes precedence, so if it is set to True, we ignore whatever
1552 1552 value is passed in to the constructor.
1553 1553 """
1554 1554 # During normal operations, we expect to deal with not more than four
1555 1555 # revs at a time (such as during commit --amend). When rebasing large
1556 1556 # stacks of commits, the number can go up, hence the config knob below.
1557 1557 cachesize = 4
1558 1558 optiontreemanifest = False
1559 1559 opts = getattr(opener, 'options', None)
1560 1560 if opts is not None:
1561 1561 cachesize = opts.get(b'manifestcachesize', cachesize)
1562 1562 optiontreemanifest = opts.get(b'treemanifest', False)
1563 1563
1564 1564 self._treeondisk = optiontreemanifest or treemanifest
1565 1565
1566 1566 self._fulltextcache = manifestfulltextcache(cachesize)
1567 1567
1568 1568 if tree:
1569 1569 assert self._treeondisk, b'opts is %r' % opts
1570 1570
1571 1571 if indexfile is None:
1572 1572 indexfile = b'00manifest.i'
1573 1573 if tree:
1574 1574 indexfile = b"meta/" + tree + indexfile
1575 1575
1576 1576 self.tree = tree
1577 1577
1578 1578 # The dirlogcache is kept on the root manifest log
1579 1579 if tree:
1580 1580 self._dirlogcache = dirlogcache
1581 1581 else:
1582 1582 self._dirlogcache = {b'': self}
1583 1583
1584 1584 self._revlog = revlog.revlog(
1585 1585 opener,
1586 1586 indexfile,
1587 1587 # only root indexfile is cached
1588 1588 checkambig=not bool(tree),
1589 1589 mmaplargeindex=True,
1590 1590 upperboundcomp=MAXCOMPRESSION,
1591 persistentnodemap=opener.options.get(
1592 b'exp-persistent-nodemap', False
1593 ),
1591 1594 )
1592 1595
1593 1596 self.index = self._revlog.index
1594 1597 self.version = self._revlog.version
1595 1598 self._generaldelta = self._revlog._generaldelta
1596 1599
1597 1600 def _setupmanifestcachehooks(self, repo):
1598 1601 """Persist the manifestfulltextcache on lock release"""
1599 1602 if not util.safehasattr(repo, b'_wlockref'):
1600 1603 return
1601 1604
1602 1605 self._fulltextcache._opener = repo.wcachevfs
1603 1606 if repo._currentlock(repo._wlockref) is None:
1604 1607 return
1605 1608
1606 1609 reporef = weakref.ref(repo)
1607 1610 manifestrevlogref = weakref.ref(self)
1608 1611
1609 1612 def persistmanifestcache(success):
1610 1613 # Repo is in an unknown state, do not persist.
1611 1614 if not success:
1612 1615 return
1613 1616
1614 1617 repo = reporef()
1615 1618 self = manifestrevlogref()
1616 1619 if repo is None or self is None:
1617 1620 return
1618 1621 if repo.manifestlog.getstorage(b'') is not self:
1619 1622 # there's a different manifest in play now, abort
1620 1623 return
1621 1624 self._fulltextcache.write()
1622 1625
1623 1626 repo._afterlock(persistmanifestcache)
1624 1627
1625 1628 @property
1626 1629 def fulltextcache(self):
1627 1630 return self._fulltextcache
1628 1631
1629 1632 def clearcaches(self, clear_persisted_data=False):
1630 1633 self._revlog.clearcaches()
1631 1634 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1632 1635 self._dirlogcache = {self.tree: self}
1633 1636
1634 1637 def dirlog(self, d):
1635 1638 if d:
1636 1639 assert self._treeondisk
1637 1640 if d not in self._dirlogcache:
1638 1641 mfrevlog = manifestrevlog(
1639 1642 self.opener, d, self._dirlogcache, treemanifest=self._treeondisk
1640 1643 )
1641 1644 self._dirlogcache[d] = mfrevlog
1642 1645 return self._dirlogcache[d]
1643 1646
1644 1647 def add(
1645 1648 self,
1646 1649 m,
1647 1650 transaction,
1648 1651 link,
1649 1652 p1,
1650 1653 p2,
1651 1654 added,
1652 1655 removed,
1653 1656 readtree=None,
1654 1657 match=None,
1655 1658 ):
1656 1659 try:
1657 1660 if p1 not in self.fulltextcache:
1658 1661 raise FastdeltaUnavailable()
1659 1662 # If our first parent is in the manifest cache, we can
1660 1663 # compute a delta here using properties we know about the
1661 1664 # manifest up-front, which may save time later for the
1662 1665 # revlog layer.
1663 1666
1664 1667 _checkforbidden(added)
1665 1668 # combine the changed lists into one sorted iterator
1666 1669 work = heapq.merge(
1667 1670 [(x, False) for x in sorted(added)],
1668 1671 [(x, True) for x in sorted(removed)],
1669 1672 )
1670 1673
1671 1674 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1672 1675 cachedelta = self._revlog.rev(p1), deltatext
1673 1676 text = util.buffer(arraytext)
1674 1677 n = self._revlog.addrevision(
1675 1678 text, transaction, link, p1, p2, cachedelta
1676 1679 )
1677 1680 except FastdeltaUnavailable:
1678 1681 # The first parent manifest isn't already loaded or the
1679 1682 # manifest implementation doesn't support fastdelta, so
1680 1683 # we'll just encode a fulltext of the manifest and pass
1681 1684 # that through to the revlog layer, and let it handle the
1682 1685 # delta process.
1683 1686 if self._treeondisk:
1684 1687 assert readtree, b"readtree must be set for treemanifest writes"
1685 1688 assert match, b"match must be specified for treemanifest writes"
1686 1689 m1 = readtree(self.tree, p1)
1687 1690 m2 = readtree(self.tree, p2)
1688 1691 n = self._addtree(
1689 1692 m, transaction, link, m1, m2, readtree, match=match
1690 1693 )
1691 1694 arraytext = None
1692 1695 else:
1693 1696 text = m.text()
1694 1697 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1695 1698 arraytext = bytearray(text)
1696 1699
1697 1700 if arraytext is not None:
1698 1701 self.fulltextcache[n] = arraytext
1699 1702
1700 1703 return n
1701 1704
1702 1705 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1703 1706 # If the manifest is unchanged compared to one parent,
1704 1707 # don't write a new revision
1705 1708 if self.tree != b'' and (
1706 1709 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1707 1710 ):
1708 1711 return m.node()
1709 1712
1710 1713 def writesubtree(subm, subp1, subp2, match):
1711 1714 sublog = self.dirlog(subm.dir())
1712 1715 sublog.add(
1713 1716 subm,
1714 1717 transaction,
1715 1718 link,
1716 1719 subp1,
1717 1720 subp2,
1718 1721 None,
1719 1722 None,
1720 1723 readtree=readtree,
1721 1724 match=match,
1722 1725 )
1723 1726
1724 1727 m.writesubtrees(m1, m2, writesubtree, match)
1725 1728 text = m.dirtext()
1726 1729 n = None
1727 1730 if self.tree != b'':
1728 1731 # Double-check whether contents are unchanged to one parent
1729 1732 if text == m1.dirtext():
1730 1733 n = m1.node()
1731 1734 elif text == m2.dirtext():
1732 1735 n = m2.node()
1733 1736
1734 1737 if not n:
1735 1738 n = self._revlog.addrevision(
1736 1739 text, transaction, link, m1.node(), m2.node()
1737 1740 )
1738 1741
1739 1742 # Save nodeid so parent manifest can calculate its nodeid
1740 1743 m.setnode(n)
1741 1744 return n
1742 1745
1743 1746 def __len__(self):
1744 1747 return len(self._revlog)
1745 1748
1746 1749 def __iter__(self):
1747 1750 return self._revlog.__iter__()
1748 1751
1749 1752 def rev(self, node):
1750 1753 return self._revlog.rev(node)
1751 1754
1752 1755 def node(self, rev):
1753 1756 return self._revlog.node(rev)
1754 1757
1755 1758 def lookup(self, value):
1756 1759 return self._revlog.lookup(value)
1757 1760
1758 1761 def parentrevs(self, rev):
1759 1762 return self._revlog.parentrevs(rev)
1760 1763
1761 1764 def parents(self, node):
1762 1765 return self._revlog.parents(node)
1763 1766
1764 1767 def linkrev(self, rev):
1765 1768 return self._revlog.linkrev(rev)
1766 1769
1767 1770 def checksize(self):
1768 1771 return self._revlog.checksize()
1769 1772
1770 1773 def revision(self, node, _df=None, raw=False):
1771 1774 return self._revlog.revision(node, _df=_df, raw=raw)
1772 1775
1773 1776 def rawdata(self, node, _df=None):
1774 1777 return self._revlog.rawdata(node, _df=_df)
1775 1778
1776 1779 def revdiff(self, rev1, rev2):
1777 1780 return self._revlog.revdiff(rev1, rev2)
1778 1781
1779 1782 def cmp(self, node, text):
1780 1783 return self._revlog.cmp(node, text)
1781 1784
1782 1785 def deltaparent(self, rev):
1783 1786 return self._revlog.deltaparent(rev)
1784 1787
1785 1788 def emitrevisions(
1786 1789 self,
1787 1790 nodes,
1788 1791 nodesorder=None,
1789 1792 revisiondata=False,
1790 1793 assumehaveparentrevisions=False,
1791 1794 deltamode=repository.CG_DELTAMODE_STD,
1792 1795 ):
1793 1796 return self._revlog.emitrevisions(
1794 1797 nodes,
1795 1798 nodesorder=nodesorder,
1796 1799 revisiondata=revisiondata,
1797 1800 assumehaveparentrevisions=assumehaveparentrevisions,
1798 1801 deltamode=deltamode,
1799 1802 )
1800 1803
1801 1804 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1802 1805 return self._revlog.addgroup(
1803 1806 deltas, linkmapper, transaction, addrevisioncb=addrevisioncb
1804 1807 )
1805 1808
1806 1809 def rawsize(self, rev):
1807 1810 return self._revlog.rawsize(rev)
1808 1811
1809 1812 def getstrippoint(self, minlink):
1810 1813 return self._revlog.getstrippoint(minlink)
1811 1814
1812 1815 def strip(self, minlink, transaction):
1813 1816 return self._revlog.strip(minlink, transaction)
1814 1817
1815 1818 def files(self):
1816 1819 return self._revlog.files()
1817 1820
1818 1821 def clone(self, tr, destrevlog, **kwargs):
1819 1822 if not isinstance(destrevlog, manifestrevlog):
1820 1823 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
1821 1824
1822 1825 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1823 1826
1824 1827 def storageinfo(
1825 1828 self,
1826 1829 exclusivefiles=False,
1827 1830 sharedfiles=False,
1828 1831 revisionscount=False,
1829 1832 trackedsize=False,
1830 1833 storedsize=False,
1831 1834 ):
1832 1835 return self._revlog.storageinfo(
1833 1836 exclusivefiles=exclusivefiles,
1834 1837 sharedfiles=sharedfiles,
1835 1838 revisionscount=revisionscount,
1836 1839 trackedsize=trackedsize,
1837 1840 storedsize=storedsize,
1838 1841 )
1839 1842
1840 1843 @property
1841 1844 def indexfile(self):
1842 1845 return self._revlog.indexfile
1843 1846
1844 1847 @indexfile.setter
1845 1848 def indexfile(self, value):
1846 1849 self._revlog.indexfile = value
1847 1850
1848 1851 @property
1849 1852 def opener(self):
1850 1853 return self._revlog.opener
1851 1854
1852 1855 @opener.setter
1853 1856 def opener(self, value):
1854 1857 self._revlog.opener = value
1855 1858
1856 1859
1857 1860 @interfaceutil.implementer(repository.imanifestlog)
1858 1861 class manifestlog(object):
1859 1862 """A collection class representing the collection of manifest snapshots
1860 1863 referenced by commits in the repository.
1861 1864
1862 1865 In this situation, 'manifest' refers to the abstract concept of a snapshot
1863 1866 of the list of files in the given commit. Consumers of the output of this
1864 1867 class do not care about the implementation details of the actual manifests
1865 1868 they receive (i.e. tree or flat or lazily loaded, etc)."""
1866 1869
1867 1870 def __init__(self, opener, repo, rootstore, narrowmatch):
1868 1871 usetreemanifest = False
1869 1872 cachesize = 4
1870 1873
1871 1874 opts = getattr(opener, 'options', None)
1872 1875 if opts is not None:
1873 1876 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
1874 1877 cachesize = opts.get(b'manifestcachesize', cachesize)
1875 1878
1876 1879 self._treemanifests = usetreemanifest
1877 1880
1878 1881 self._rootstore = rootstore
1879 1882 self._rootstore._setupmanifestcachehooks(repo)
1880 1883 self._narrowmatch = narrowmatch
1881 1884
1882 1885 # A cache of the manifestctx or treemanifestctx for each directory
1883 1886 self._dirmancache = {}
1884 1887 self._dirmancache[b''] = util.lrucachedict(cachesize)
1885 1888
1886 1889 self._cachesize = cachesize
1887 1890
1888 1891 def __getitem__(self, node):
1889 1892 """Retrieves the manifest instance for the given node. Throws a
1890 1893 LookupError if not found.
1891 1894 """
1892 1895 return self.get(b'', node)
1893 1896
1894 1897 def get(self, tree, node, verify=True):
1895 1898 """Retrieves the manifest instance for the given node. Throws a
1896 1899 LookupError if not found.
1897 1900
1898 1901 `verify` - if True an exception will be thrown if the node is not in
1899 1902 the revlog
1900 1903 """
1901 1904 if node in self._dirmancache.get(tree, ()):
1902 1905 return self._dirmancache[tree][node]
1903 1906
1904 1907 if not self._narrowmatch.always():
1905 1908 if not self._narrowmatch.visitdir(tree[:-1]):
1906 1909 return excludeddirmanifestctx(tree, node)
1907 1910 if tree:
1908 1911 if self._rootstore._treeondisk:
1909 1912 if verify:
1910 1913 # Side-effect is LookupError is raised if node doesn't
1911 1914 # exist.
1912 1915 self.getstorage(tree).rev(node)
1913 1916
1914 1917 m = treemanifestctx(self, tree, node)
1915 1918 else:
1916 1919 raise error.Abort(
1917 1920 _(
1918 1921 b"cannot ask for manifest directory '%s' in a flat "
1919 1922 b"manifest"
1920 1923 )
1921 1924 % tree
1922 1925 )
1923 1926 else:
1924 1927 if verify:
1925 1928 # Side-effect is LookupError is raised if node doesn't exist.
1926 1929 self._rootstore.rev(node)
1927 1930
1928 1931 if self._treemanifests:
1929 1932 m = treemanifestctx(self, b'', node)
1930 1933 else:
1931 1934 m = manifestctx(self, node)
1932 1935
1933 1936 if node != nullid:
1934 1937 mancache = self._dirmancache.get(tree)
1935 1938 if not mancache:
1936 1939 mancache = util.lrucachedict(self._cachesize)
1937 1940 self._dirmancache[tree] = mancache
1938 1941 mancache[node] = m
1939 1942 return m
1940 1943
1941 1944 def getstorage(self, tree):
1942 1945 return self._rootstore.dirlog(tree)
1943 1946
1944 1947 def clearcaches(self, clear_persisted_data=False):
1945 1948 self._dirmancache.clear()
1946 1949 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1947 1950
1948 1951 def rev(self, node):
1949 1952 return self._rootstore.rev(node)
1950 1953
1951 1954
1952 1955 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1953 1956 class memmanifestctx(object):
1954 1957 def __init__(self, manifestlog):
1955 1958 self._manifestlog = manifestlog
1956 1959 self._manifestdict = manifestdict()
1957 1960
1958 1961 def _storage(self):
1959 1962 return self._manifestlog.getstorage(b'')
1960 1963
1961 1964 def copy(self):
1962 1965 memmf = memmanifestctx(self._manifestlog)
1963 1966 memmf._manifestdict = self.read().copy()
1964 1967 return memmf
1965 1968
1966 1969 def read(self):
1967 1970 return self._manifestdict
1968 1971
1969 1972 def write(self, transaction, link, p1, p2, added, removed, match=None):
1970 1973 return self._storage().add(
1971 1974 self._manifestdict,
1972 1975 transaction,
1973 1976 link,
1974 1977 p1,
1975 1978 p2,
1976 1979 added,
1977 1980 removed,
1978 1981 match=match,
1979 1982 )
1980 1983
1981 1984
1982 1985 @interfaceutil.implementer(repository.imanifestrevisionstored)
1983 1986 class manifestctx(object):
1984 1987 """A class representing a single revision of a manifest, including its
1985 1988 contents, its parent revs, and its linkrev.
1986 1989 """
1987 1990
1988 1991 def __init__(self, manifestlog, node):
1989 1992 self._manifestlog = manifestlog
1990 1993 self._data = None
1991 1994
1992 1995 self._node = node
1993 1996
1994 1997 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1995 1998 # but let's add it later when something needs it and we can load it
1996 1999 # lazily.
1997 2000 # self.p1, self.p2 = store.parents(node)
1998 2001 # rev = store.rev(node)
1999 2002 # self.linkrev = store.linkrev(rev)
2000 2003
2001 2004 def _storage(self):
2002 2005 return self._manifestlog.getstorage(b'')
2003 2006
2004 2007 def node(self):
2005 2008 return self._node
2006 2009
2007 2010 def copy(self):
2008 2011 memmf = memmanifestctx(self._manifestlog)
2009 2012 memmf._manifestdict = self.read().copy()
2010 2013 return memmf
2011 2014
2012 2015 @propertycache
2013 2016 def parents(self):
2014 2017 return self._storage().parents(self._node)
2015 2018
2016 2019 def read(self):
2017 2020 if self._data is None:
2018 2021 if self._node == nullid:
2019 2022 self._data = manifestdict()
2020 2023 else:
2021 2024 store = self._storage()
2022 2025 if self._node in store.fulltextcache:
2023 2026 text = pycompat.bytestr(store.fulltextcache[self._node])
2024 2027 else:
2025 2028 text = store.revision(self._node)
2026 2029 arraytext = bytearray(text)
2027 2030 store.fulltextcache[self._node] = arraytext
2028 2031 self._data = manifestdict(text)
2029 2032 return self._data
2030 2033
2031 2034 def readfast(self, shallow=False):
2032 2035 '''Calls either readdelta or read, based on which would be less work.
2033 2036 readdelta is called if the delta is against the p1, and therefore can be
2034 2037 read quickly.
2035 2038
2036 2039 If `shallow` is True, nothing changes since this is a flat manifest.
2037 2040 '''
2038 2041 store = self._storage()
2039 2042 r = store.rev(self._node)
2040 2043 deltaparent = store.deltaparent(r)
2041 2044 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2042 2045 return self.readdelta()
2043 2046 return self.read()
2044 2047
2045 2048 def readdelta(self, shallow=False):
2046 2049 '''Returns a manifest containing just the entries that are present
2047 2050 in this manifest, but not in its p1 manifest. This is efficient to read
2048 2051 if the revlog delta is already p1.
2049 2052
2050 2053 Changing the value of `shallow` has no effect on flat manifests.
2051 2054 '''
2052 2055 store = self._storage()
2053 2056 r = store.rev(self._node)
2054 2057 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2055 2058 return manifestdict(d)
2056 2059
2057 2060 def find(self, key):
2058 2061 return self.read().find(key)
2059 2062
2060 2063
2061 2064 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2062 2065 class memtreemanifestctx(object):
2063 2066 def __init__(self, manifestlog, dir=b''):
2064 2067 self._manifestlog = manifestlog
2065 2068 self._dir = dir
2066 2069 self._treemanifest = treemanifest()
2067 2070
2068 2071 def _storage(self):
2069 2072 return self._manifestlog.getstorage(b'')
2070 2073
2071 2074 def copy(self):
2072 2075 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2073 2076 memmf._treemanifest = self._treemanifest.copy()
2074 2077 return memmf
2075 2078
2076 2079 def read(self):
2077 2080 return self._treemanifest
2078 2081
2079 2082 def write(self, transaction, link, p1, p2, added, removed, match=None):
2080 2083 def readtree(dir, node):
2081 2084 return self._manifestlog.get(dir, node).read()
2082 2085
2083 2086 return self._storage().add(
2084 2087 self._treemanifest,
2085 2088 transaction,
2086 2089 link,
2087 2090 p1,
2088 2091 p2,
2089 2092 added,
2090 2093 removed,
2091 2094 readtree=readtree,
2092 2095 match=match,
2093 2096 )
2094 2097
2095 2098
2096 2099 @interfaceutil.implementer(repository.imanifestrevisionstored)
2097 2100 class treemanifestctx(object):
2098 2101 def __init__(self, manifestlog, dir, node):
2099 2102 self._manifestlog = manifestlog
2100 2103 self._dir = dir
2101 2104 self._data = None
2102 2105
2103 2106 self._node = node
2104 2107
2105 2108 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2106 2109 # we can instantiate treemanifestctx objects for directories we don't
2107 2110 # have on disk.
2108 2111 # self.p1, self.p2 = store.parents(node)
2109 2112 # rev = store.rev(node)
2110 2113 # self.linkrev = store.linkrev(rev)
2111 2114
2112 2115 def _storage(self):
2113 2116 narrowmatch = self._manifestlog._narrowmatch
2114 2117 if not narrowmatch.always():
2115 2118 if not narrowmatch.visitdir(self._dir[:-1]):
2116 2119 return excludedmanifestrevlog(self._dir)
2117 2120 return self._manifestlog.getstorage(self._dir)
2118 2121
2119 2122 def read(self):
2120 2123 if self._data is None:
2121 2124 store = self._storage()
2122 2125 if self._node == nullid:
2123 2126 self._data = treemanifest()
2124 2127 # TODO accessing non-public API
2125 2128 elif store._treeondisk:
2126 2129 m = treemanifest(dir=self._dir)
2127 2130
2128 2131 def gettext():
2129 2132 return store.revision(self._node)
2130 2133
2131 2134 def readsubtree(dir, subm):
2132 2135 # Set verify to False since we need to be able to create
2133 2136 # subtrees for trees that don't exist on disk.
2134 2137 return self._manifestlog.get(dir, subm, verify=False).read()
2135 2138
2136 2139 m.read(gettext, readsubtree)
2137 2140 m.setnode(self._node)
2138 2141 self._data = m
2139 2142 else:
2140 2143 if self._node in store.fulltextcache:
2141 2144 text = pycompat.bytestr(store.fulltextcache[self._node])
2142 2145 else:
2143 2146 text = store.revision(self._node)
2144 2147 arraytext = bytearray(text)
2145 2148 store.fulltextcache[self._node] = arraytext
2146 2149 self._data = treemanifest(dir=self._dir, text=text)
2147 2150
2148 2151 return self._data
2149 2152
2150 2153 def node(self):
2151 2154 return self._node
2152 2155
2153 2156 def copy(self):
2154 2157 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2155 2158 memmf._treemanifest = self.read().copy()
2156 2159 return memmf
2157 2160
2158 2161 @propertycache
2159 2162 def parents(self):
2160 2163 return self._storage().parents(self._node)
2161 2164
2162 2165 def readdelta(self, shallow=False):
2163 2166 '''Returns a manifest containing just the entries that are present
2164 2167 in this manifest, but not in its p1 manifest. This is efficient to read
2165 2168 if the revlog delta is already p1.
2166 2169
2167 2170 If `shallow` is True, this will read the delta for this directory,
2168 2171 without recursively reading subdirectory manifests. Instead, any
2169 2172 subdirectory entry will be reported as it appears in the manifest, i.e.
2170 2173 the subdirectory will be reported among files and distinguished only by
2171 2174 its 't' flag.
2172 2175 '''
2173 2176 store = self._storage()
2174 2177 if shallow:
2175 2178 r = store.rev(self._node)
2176 2179 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2177 2180 return manifestdict(d)
2178 2181 else:
2179 2182 # Need to perform a slow delta
2180 2183 r0 = store.deltaparent(store.rev(self._node))
2181 2184 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2182 2185 m1 = self.read()
2183 2186 md = treemanifest(dir=self._dir)
2184 2187 for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
2185 2188 if n1:
2186 2189 md[f] = n1
2187 2190 if fl1:
2188 2191 md.setflag(f, fl1)
2189 2192 return md
2190 2193
2191 2194 def readfast(self, shallow=False):
2192 2195 '''Calls either readdelta or read, based on which would be less work.
2193 2196 readdelta is called if the delta is against the p1, and therefore can be
2194 2197 read quickly.
2195 2198
2196 2199 If `shallow` is True, it only returns the entries from this manifest,
2197 2200 and not any submanifests.
2198 2201 '''
2199 2202 store = self._storage()
2200 2203 r = store.rev(self._node)
2201 2204 deltaparent = store.deltaparent(r)
2202 2205 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2203 2206 return self.readdelta(shallow=shallow)
2204 2207
2205 2208 if shallow:
2206 2209 return manifestdict(store.revision(self._node))
2207 2210 else:
2208 2211 return self.read()
2209 2212
2210 2213 def find(self, key):
2211 2214 return self.read().find(key)
2212 2215
2213 2216
2214 2217 class excludeddir(treemanifest):
2215 2218 """Stand-in for a directory that is excluded from the repository.
2216 2219
2217 2220 With narrowing active on a repository that uses treemanifests,
2218 2221 some of the directory revlogs will be excluded from the resulting
2219 2222 clone. This is a huge storage win for clients, but means we need
2220 2223 some sort of pseudo-manifest to surface to internals so we can
2221 2224 detect a merge conflict outside the narrowspec. That's what this
2222 2225 class is: it stands in for a directory whose node is known, but
2223 2226 whose contents are unknown.
2224 2227 """
2225 2228
2226 2229 def __init__(self, dir, node):
2227 2230 super(excludeddir, self).__init__(dir)
2228 2231 self._node = node
2229 2232 # Add an empty file, which will be included by iterators and such,
2230 2233 # appearing as the directory itself (i.e. something like "dir/")
2231 2234 self._files[b''] = node
2232 2235 self._flags[b''] = b't'
2233 2236
2234 2237 # Manifests outside the narrowspec should never be modified, so avoid
2235 2238 # copying. This makes a noticeable difference when there are very many
2236 2239 # directories outside the narrowspec. Also, it makes sense for the copy to
2237 2240 # be of the same type as the original, which would not happen with the
2238 2241 # super type's copy().
2239 2242 def copy(self):
2240 2243 return self
2241 2244
2242 2245
2243 2246 class excludeddirmanifestctx(treemanifestctx):
2244 2247 """context wrapper for excludeddir - see that docstring for rationale"""
2245 2248
2246 2249 def __init__(self, dir, node):
2247 2250 self._dir = dir
2248 2251 self._node = node
2249 2252
2250 2253 def read(self):
2251 2254 return excludeddir(self._dir, self._node)
2252 2255
2253 2256 def write(self, *args):
2254 2257 raise error.ProgrammingError(
2255 2258 b'attempt to write manifest from excluded dir %s' % self._dir
2256 2259 )
2257 2260
2258 2261
2259 2262 class excludedmanifestrevlog(manifestrevlog):
2260 2263 """Stand-in for excluded treemanifest revlogs.
2261 2264
2262 2265 When narrowing is active on a treemanifest repository, we'll have
2263 2266 references to directories we can't see due to the revlog being
2264 2267 skipped. This class exists to conform to the manifestrevlog
2265 2268 interface for those directories and proactively prevent writes to
2266 2269 outside the narrowspec.
2267 2270 """
2268 2271
2269 2272 def __init__(self, dir):
2270 2273 self._dir = dir
2271 2274
2272 2275 def __len__(self):
2273 2276 raise error.ProgrammingError(
2274 2277 b'attempt to get length of excluded dir %s' % self._dir
2275 2278 )
2276 2279
2277 2280 def rev(self, node):
2278 2281 raise error.ProgrammingError(
2279 2282 b'attempt to get rev from excluded dir %s' % self._dir
2280 2283 )
2281 2284
2282 2285 def linkrev(self, node):
2283 2286 raise error.ProgrammingError(
2284 2287 b'attempt to get linkrev from excluded dir %s' % self._dir
2285 2288 )
2286 2289
2287 2290 def node(self, rev):
2288 2291 raise error.ProgrammingError(
2289 2292 b'attempt to get node from excluded dir %s' % self._dir
2290 2293 )
2291 2294
2292 2295 def add(self, *args, **kwargs):
2293 2296 # We should never write entries in dirlogs outside the narrow clone.
2294 2297 # However, the method still gets called from writesubtree() in
2295 2298 # _addtree(), so we need to handle it. We should possibly make that
2296 2299 # avoid calling add() with a clean manifest (_dirty is always False
2297 2300 # in excludeddir instances).
2298 2301 pass
@@ -1,413 +1,416 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
14 14 $ hg debugnodemap --metadata
15 15 uid: ???????????????? (glob)
16 16 tip-rev: 5000
17 17 tip-node: 6b02b8c7b96654c25e86ba69eda198d7e6ad8b3c
18 18 data-length: 121088
19 19 data-unused: 0
20 20 data-unused: 0.000%
21 21 $ f --size .hg/store/00changelog.n
22 22 .hg/store/00changelog.n: size=70
23 23
24 24 Simple lookup works
25 25
26 26 $ ANYNODE=`hg log --template '{node|short}\n' --rev tip`
27 27 $ hg log -r "$ANYNODE" --template '{rev}\n'
28 28 5000
29 29
30 30
31 31 #if rust
32 32
33 33 $ f --sha256 .hg/store/00changelog-*.nd
34 34 .hg/store/00changelog-????????????????.nd: sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd (glob)
35
36 $ f --sha256 .hg/store/00manifest-*.nd
37 .hg/store/00manifest-????????????????.nd: sha256=97117b1c064ea2f86664a124589e47db0e254e8d34739b5c5cc5bf31c9da2b51 (glob)
35 38 $ hg debugnodemap --dump-new | f --sha256 --size
36 39 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
37 40 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
38 41 size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
39 42 0000: 00 00 00 91 00 00 00 20 00 00 00 bb 00 00 00 e7 |....... ........|
40 43 0010: 00 00 00 66 00 00 00 a1 00 00 01 13 00 00 01 22 |...f..........."|
41 44 0020: 00 00 00 23 00 00 00 fc 00 00 00 ba 00 00 00 5e |...#...........^|
42 45 0030: 00 00 00 df 00 00 01 4e 00 00 01 65 00 00 00 ab |.......N...e....|
43 46 0040: 00 00 00 a9 00 00 00 95 00 00 00 73 00 00 00 38 |...........s...8|
44 47 0050: 00 00 00 cc 00 00 00 92 00 00 00 90 00 00 00 69 |...............i|
45 48 0060: 00 00 00 ec 00 00 00 8d 00 00 01 4f 00 00 00 12 |...........O....|
46 49 0070: 00 00 02 0c 00 00 00 77 00 00 00 9c 00 00 00 8f |.......w........|
47 50 0080: 00 00 00 d5 00 00 00 6b 00 00 00 48 00 00 00 b3 |.......k...H....|
48 51 0090: 00 00 00 e5 00 00 00 b5 00 00 00 8e 00 00 00 ad |................|
49 52 00a0: 00 00 00 7b 00 00 00 7c 00 00 00 0b 00 00 00 2b |...{...|.......+|
50 53 00b0: 00 00 00 c6 00 00 00 1e 00 00 01 08 00 00 00 11 |................|
51 54 00c0: 00 00 01 30 00 00 00 26 00 00 01 9c 00 00 00 35 |...0...&.......5|
52 55 00d0: 00 00 00 b8 00 00 01 31 00 00 00 2c 00 00 00 55 |.......1...,...U|
53 56 00e0: 00 00 00 8a 00 00 00 9a 00 00 00 0c 00 00 01 1e |................|
54 57 00f0: 00 00 00 a4 00 00 00 83 00 00 00 c9 00 00 00 8c |................|
55 58
56 59
57 60 #else
58 61
59 62 $ f --sha256 .hg/store/00changelog-*.nd
60 63 .hg/store/00changelog-????????????????.nd: sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 (glob)
61 64 $ hg debugnodemap --dump-new | f --sha256 --size
62 65 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
63 66 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
64 67 size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
65 68 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
66 69 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
67 70 0020: ff ff ff ff ff ff f5 06 ff ff ff ff ff ff f3 e7 |................|
68 71 0030: ff ff ef ca ff ff ff ff ff ff ff ff ff ff ff ff |................|
69 72 0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
70 73 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ed 08 |................|
71 74 0060: ff ff ed 66 ff ff ff ff ff ff ff ff ff ff ff ff |...f............|
72 75 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
73 76 0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
74 77 0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f6 ed |................|
75 78 00a0: ff ff ff ff ff ff fe 61 ff ff ff ff ff ff ff ff |.......a........|
76 79 00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
77 80 00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
78 81 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
79 82 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f1 02 |................|
80 83 00f0: ff ff ff ff ff ff ed 1b ff ff ff ff ff ff ff ff |................|
81 84
82 85 #endif
83 86
84 87 $ hg debugnodemap --check
85 88 revision in index: 5001
86 89 revision in nodemap: 5001
87 90
88 91 add a new commit
89 92
90 93 $ hg up
91 94 5001 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 95 $ echo foo > foo
93 96 $ hg add foo
94 97 $ hg ci -m 'foo'
95 98
96 99 #if no-pure no-rust
97 100 $ hg debugnodemap --metadata
98 101 uid: ???????????????? (glob)
99 102 tip-rev: 5001
100 103 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
101 104 data-length: 121088
102 105 data-unused: 0
103 106 data-unused: 0.000%
104 107 #else
105 108 $ hg debugnodemap --metadata
106 109 uid: ???????????????? (glob)
107 110 tip-rev: 5001
108 111 tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
109 112 data-length: 121344
110 113 data-unused: 256
111 114 data-unused: 0.211%
112 115 #endif
113 116
114 117 $ f --size .hg/store/00changelog.n
115 118 .hg/store/00changelog.n: size=70
116 119
117 120 (The pure code use the debug code that perform incremental update, the C code reencode from scratch)
118 121
119 122 #if pure
120 123 $ f --sha256 .hg/store/00changelog-*.nd --size
121 124 .hg/store/00changelog-????????????????.nd: size=121344, sha256=cce54c5da5bde3ad72a4938673ed4064c86231b9c64376b082b163fdb20f8f66 (glob)
122 125 #endif
123 126
124 127 #if rust
125 128 $ f --sha256 .hg/store/00changelog-*.nd --size
126 129 .hg/store/00changelog-????????????????.nd: size=121344, sha256=952b042fcf614ceb37b542b1b723e04f18f83efe99bee4e0f5ccd232ef470e58 (glob)
127 130 #endif
128 131
129 132 #if no-pure no-rust
130 133 $ f --sha256 .hg/store/00changelog-*.nd --size
131 134 .hg/store/00changelog-????????????????.nd: size=121088, sha256=df7c06a035b96cb28c7287d349d603baef43240be7736fe34eea419a49702e17 (glob)
132 135 #endif
133 136
134 137 $ hg debugnodemap --check
135 138 revision in index: 5002
136 139 revision in nodemap: 5002
137 140
138 141 Test code path without mmap
139 142 ---------------------------
140 143
141 144 $ echo bar > bar
142 145 $ hg add bar
143 146 $ hg ci -m 'bar' --config experimental.exp-persistent-nodemap.mmap=no
144 147
145 148 $ hg debugnodemap --check --config experimental.exp-persistent-nodemap.mmap=yes
146 149 revision in index: 5003
147 150 revision in nodemap: 5003
148 151 $ hg debugnodemap --check --config experimental.exp-persistent-nodemap.mmap=no
149 152 revision in index: 5003
150 153 revision in nodemap: 5003
151 154
152 155
153 156 #if pure
154 157 $ hg debugnodemap --metadata
155 158 uid: ???????????????? (glob)
156 159 tip-rev: 5002
157 160 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
158 161 data-length: 121600
159 162 data-unused: 512
160 163 data-unused: 0.421%
161 164 $ f --sha256 .hg/store/00changelog-*.nd --size
162 165 .hg/store/00changelog-????????????????.nd: size=121600, sha256=def52503d049ccb823974af313a98a935319ba61f40f3aa06a8be4d35c215054 (glob)
163 166 #endif
164 167 #if rust
165 168 $ hg debugnodemap --metadata
166 169 uid: ???????????????? (glob)
167 170 tip-rev: 5002
168 171 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
169 172 data-length: 121600
170 173 data-unused: 512
171 174 data-unused: 0.421%
172 175 $ f --sha256 .hg/store/00changelog-*.nd --size
173 176 .hg/store/00changelog-????????????????.nd: size=121600, sha256=dacf5b5f1d4585fee7527d0e67cad5b1ba0930e6a0928f650f779aefb04ce3fb (glob)
174 177 #endif
175 178 #if no-pure no-rust
176 179 $ hg debugnodemap --metadata
177 180 uid: ???????????????? (glob)
178 181 tip-rev: 5002
179 182 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
180 183 data-length: 121088
181 184 data-unused: 0
182 185 data-unused: 0.000%
183 186 $ f --sha256 .hg/store/00changelog-*.nd --size
184 187 .hg/store/00changelog-????????????????.nd: size=121088, sha256=59fcede3e3cc587755916ceed29e3c33748cd1aa7d2f91828ac83e7979d935e8 (glob)
185 188 #endif
186 189
187 190 Test force warming the cache
188 191
189 192 $ rm .hg/store/00changelog.n
190 193 $ hg debugnodemap --metadata
191 194 $ hg debugupdatecache
192 195 #if pure
193 196 $ hg debugnodemap --metadata
194 197 uid: ???????????????? (glob)
195 198 tip-rev: 5002
196 199 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
197 200 data-length: 121088
198 201 data-unused: 0
199 202 data-unused: 0.000%
200 203 #else
201 204 $ hg debugnodemap --metadata
202 205 uid: ???????????????? (glob)
203 206 tip-rev: 5002
204 207 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
205 208 data-length: 121088
206 209 data-unused: 0
207 210 data-unused: 0.000%
208 211 #endif
209 212
210 213 Check out of sync nodemap
211 214 =========================
212 215
213 216 First copy old data on the side.
214 217
215 218 $ mkdir ../tmp-copies
216 219 $ cp .hg/store/00changelog-????????????????.nd .hg/store/00changelog.n ../tmp-copies
217 220
218 221 Nodemap lagging behind
219 222 ----------------------
220 223
221 224 make a new commit
222 225
223 226 $ echo bar2 > bar
224 227 $ hg ci -m 'bar2'
225 228 $ NODE=`hg log -r tip -T '{node}\n'`
226 229 $ hg log -r "$NODE" -T '{rev}\n'
227 230 5003
228 231
229 232 If the nodemap is lagging behind, it can catch up fine
230 233
231 234 $ hg debugnodemap --metadata
232 235 uid: ???????????????? (glob)
233 236 tip-rev: 5003
234 237 tip-node: c9329770f979ade2d16912267c38ba5f82fd37b3
235 238 data-length: 121344 (pure !)
236 239 data-length: 121344 (rust !)
237 240 data-length: 121152 (no-rust no-pure !)
238 241 data-unused: 192 (pure !)
239 242 data-unused: 192 (rust !)
240 243 data-unused: 0 (no-rust no-pure !)
241 244 data-unused: 0.158% (pure !)
242 245 data-unused: 0.158% (rust !)
243 246 data-unused: 0.000% (no-rust no-pure !)
244 247 $ cp -f ../tmp-copies/* .hg/store/
245 248 $ hg debugnodemap --metadata
246 249 uid: ???????????????? (glob)
247 250 tip-rev: 5002
248 251 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
249 252 data-length: 121088
250 253 data-unused: 0
251 254 data-unused: 0.000%
252 255 $ hg log -r "$NODE" -T '{rev}\n'
253 256 5003
254 257
255 258 changelog altered
256 259 -----------------
257 260
258 261 If the nodemap is not gated behind a requirements, an unaware client can alter
259 262 the repository so the revlog used to generate the nodemap is not longer
260 263 compatible with the persistent nodemap. We need to detect that.
261 264
262 265 $ hg up "$NODE~5"
263 266 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
264 267 $ echo bar > babar
265 268 $ hg add babar
266 269 $ hg ci -m 'babar'
267 270 created new head
268 271 $ OTHERNODE=`hg log -r tip -T '{node}\n'`
269 272 $ hg log -r "$OTHERNODE" -T '{rev}\n'
270 273 5004
271 274
272 275 $ hg --config extensions.strip= strip --rev "$NODE~1" --no-backup
273 276
274 277 the nodemap should detect the changelog have been tampered with and recover.
275 278
276 279 $ hg debugnodemap --metadata
277 280 uid: ???????????????? (glob)
278 281 tip-rev: 5002
279 282 tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
280 283 data-length: 121536 (pure !)
281 284 data-length: 121088 (rust !)
282 285 data-length: 121088 (no-pure no-rust !)
283 286 data-unused: 448 (pure !)
284 287 data-unused: 0 (rust !)
285 288 data-unused: 0 (no-pure no-rust !)
286 289 data-unused: 0.000% (rust !)
287 290 data-unused: 0.369% (pure !)
288 291 data-unused: 0.000% (no-pure no-rust !)
289 292
290 293 $ cp -f ../tmp-copies/* .hg/store/
291 294 $ hg debugnodemap --metadata
292 295 uid: ???????????????? (glob)
293 296 tip-rev: 5002
294 297 tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
295 298 data-length: 121088
296 299 data-unused: 0
297 300 data-unused: 0.000%
298 301 $ hg log -r "$OTHERNODE" -T '{rev}\n'
299 302 5002
300 303
301 304 Check transaction related property
302 305 ==================================
303 306
304 307 An up to date nodemap should be available to shell hooks,
305 308
306 309 $ echo dsljfl > a
307 310 $ hg add a
308 311 $ hg ci -m a
309 312 $ hg debugnodemap --metadata
310 313 uid: ???????????????? (glob)
311 314 tip-rev: 5003
312 315 tip-node: a52c5079765b5865d97b993b303a18740113bbb2
313 316 data-length: 121088
314 317 data-unused: 0
315 318 data-unused: 0.000%
316 319 $ echo babar2 > babar
317 320 $ hg ci -m 'babar2' --config "hooks.pretxnclose.nodemap-test=hg debugnodemap --metadata"
318 321 uid: ???????????????? (glob)
319 322 tip-rev: 5004
320 323 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
321 324 data-length: 121280 (pure !)
322 325 data-length: 121280 (rust !)
323 326 data-length: 121088 (no-pure no-rust !)
324 327 data-unused: 192 (pure !)
325 328 data-unused: 192 (rust !)
326 329 data-unused: 0 (no-pure no-rust !)
327 330 data-unused: 0.158% (pure !)
328 331 data-unused: 0.158% (rust !)
329 332 data-unused: 0.000% (no-pure no-rust !)
330 333 $ hg debugnodemap --metadata
331 334 uid: ???????????????? (glob)
332 335 tip-rev: 5004
333 336 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
334 337 data-length: 121280 (pure !)
335 338 data-length: 121280 (rust !)
336 339 data-length: 121088 (no-pure no-rust !)
337 340 data-unused: 192 (pure !)
338 341 data-unused: 192 (rust !)
339 342 data-unused: 0 (no-pure no-rust !)
340 343 data-unused: 0.158% (pure !)
341 344 data-unused: 0.158% (rust !)
342 345 data-unused: 0.000% (no-pure no-rust !)
343 346
344 347 Another process does not see the pending nodemap content during run.
345 348
346 349 $ PATH=$RUNTESTDIR/testlib/:$PATH
347 350 $ echo qpoasp > a
348 351 $ hg ci -m a2 \
349 352 > --config "hooks.pretxnclose=wait-on-file 20 sync-repo-read sync-txn-pending" \
350 353 > --config "hooks.txnclose=touch sync-txn-close" > output.txt 2>&1 &
351 354
352 355 (read the repository while the commit transaction is pending)
353 356
354 357 $ wait-on-file 20 sync-txn-pending && \
355 358 > hg debugnodemap --metadata && \
356 359 > wait-on-file 20 sync-txn-close sync-repo-read
357 360 uid: ???????????????? (glob)
358 361 tip-rev: 5004
359 362 tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
360 363 data-length: 121280 (pure !)
361 364 data-length: 121280 (rust !)
362 365 data-length: 121088 (no-pure no-rust !)
363 366 data-unused: 192 (pure !)
364 367 data-unused: 192 (rust !)
365 368 data-unused: 0 (no-pure no-rust !)
366 369 data-unused: 0.158% (pure !)
367 370 data-unused: 0.158% (rust !)
368 371 data-unused: 0.000% (no-pure no-rust !)
369 372 $ hg debugnodemap --metadata
370 373 uid: ???????????????? (glob)
371 374 tip-rev: 5005
372 375 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
373 376 data-length: 121536 (pure !)
374 377 data-length: 121536 (rust !)
375 378 data-length: 121088 (no-pure no-rust !)
376 379 data-unused: 448 (pure !)
377 380 data-unused: 448 (rust !)
378 381 data-unused: 0 (no-pure no-rust !)
379 382 data-unused: 0.369% (pure !)
380 383 data-unused: 0.369% (rust !)
381 384 data-unused: 0.000% (no-pure no-rust !)
382 385
383 386 $ cat output.txt
384 387
385 388 Check that a failing transaction will properly revert the data
386 389
387 390 $ echo plakfe > a
388 391 $ f --size --sha256 .hg/store/00changelog-*.nd
389 392 .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
390 393 .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
391 394 .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
392 395 $ hg ci -m a3 --config "extensions.abort=$RUNTESTDIR/testlib/crash_transaction_late.py"
393 396 transaction abort!
394 397 rollback completed
395 398 abort: This is a late abort
396 399 [255]
397 400 $ hg debugnodemap --metadata
398 401 uid: ???????????????? (glob)
399 402 tip-rev: 5005
400 403 tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
401 404 data-length: 121536 (pure !)
402 405 data-length: 121536 (rust !)
403 406 data-length: 121088 (no-pure no-rust !)
404 407 data-unused: 448 (pure !)
405 408 data-unused: 448 (rust !)
406 409 data-unused: 0 (no-pure no-rust !)
407 410 data-unused: 0.369% (pure !)
408 411 data-unused: 0.369% (rust !)
409 412 data-unused: 0.000% (no-pure no-rust !)
410 413 $ f --size --sha256 .hg/store/00changelog-*.nd
411 414 .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
412 415 .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
413 416 .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