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