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