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