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