##// END OF EJS Templates
treemanifests: make _loadlazy tolerate item not on _lazydirs...
spectral -
r40017:da0319e0 default
parent child Browse files
Show More
@@ -1,2033 +1,2035
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 704 for k, (path, node, readsubtree) in self._lazydirs.iteritems():
705 705 self._dirs[k] = readsubtree(path, node)
706 706 self._lazydirs = {}
707 707
708 708 def _loadlazy(self, d):
709 path, node, readsubtree = self._lazydirs[d]
709 v = self._lazydirs.get(d)
710 if v:
711 path, node, readsubtree = v
710 712 self._dirs[d] = readsubtree(path, node)
711 713 del self._lazydirs[d]
712 714
713 715 def _loadchildrensetlazy(self, visit):
714 716 if not visit:
715 717 return None
716 718 if visit == 'all' or visit == 'this':
717 719 self._loadalllazy()
718 720 return None
719 721
720 722 todel = []
721 723 for k in visit:
722 724 kslash = k + '/'
723 725 ld = self._lazydirs.get(kslash)
724 726 if ld:
725 727 path, node, readsubtree = ld
726 728 self._dirs[kslash] = readsubtree(path, node)
727 729 todel.append(kslash)
728 730 for kslash in todel:
729 731 del self._lazydirs[kslash]
730 732 return visit
731 733
732 734 def __len__(self):
733 735 self._load()
734 736 size = len(self._files)
735 737 self._loadalllazy()
736 738 for m in self._dirs.values():
737 739 size += m.__len__()
738 740 return size
739 741
740 742 def __nonzero__(self):
741 743 # Faster than "__len() != 0" since it avoids loading sub-manifests
742 744 return not self._isempty()
743 745
744 746 __bool__ = __nonzero__
745 747
746 748 def _isempty(self):
747 749 self._load() # for consistency; already loaded by all callers
748 750 # See if we can skip loading everything.
749 751 if self._files or (self._dirs and
750 752 any(not m._isempty() for m in self._dirs.values())):
751 753 return False
752 754 self._loadalllazy()
753 755 return (not self._dirs or
754 756 all(m._isempty() for m in self._dirs.values()))
755 757
756 758 def __repr__(self):
757 759 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
758 760 (self._dir, hex(self._node),
759 761 bool(self._loadfunc is _noop),
760 762 self._dirty, id(self)))
761 763
762 764 def dir(self):
763 765 '''The directory that this tree manifest represents, including a
764 766 trailing '/'. Empty string for the repo root directory.'''
765 767 return self._dir
766 768
767 769 def node(self):
768 770 '''This node of this instance. nullid for unsaved instances. Should
769 771 be updated when the instance is read or written from a revlog.
770 772 '''
771 773 assert not self._dirty
772 774 return self._node
773 775
774 776 def setnode(self, node):
775 777 self._node = node
776 778 self._dirty = False
777 779
778 780 def iterentries(self):
779 781 self._load()
780 782 self._loadalllazy()
781 783 for p, n in sorted(itertools.chain(self._dirs.items(),
782 784 self._files.items())):
783 785 if p in self._files:
784 786 yield self._subpath(p), n, self._flags.get(p, '')
785 787 else:
786 788 for x in n.iterentries():
787 789 yield x
788 790
789 791 def items(self):
790 792 self._load()
791 793 self._loadalllazy()
792 794 for p, n in sorted(itertools.chain(self._dirs.items(),
793 795 self._files.items())):
794 796 if p in self._files:
795 797 yield self._subpath(p), n
796 798 else:
797 799 for f, sn in n.iteritems():
798 800 yield f, sn
799 801
800 802 iteritems = items
801 803
802 804 def iterkeys(self):
803 805 self._load()
804 806 self._loadalllazy()
805 807 for p in sorted(itertools.chain(self._dirs, self._files)):
806 808 if p in self._files:
807 809 yield self._subpath(p)
808 810 else:
809 811 for f in self._dirs[p]:
810 812 yield f
811 813
812 814 def keys(self):
813 815 return list(self.iterkeys())
814 816
815 817 def __iter__(self):
816 818 return self.iterkeys()
817 819
818 820 def __contains__(self, f):
819 821 if f is None:
820 822 return False
821 823 self._load()
822 824 dir, subpath = _splittopdir(f)
823 825 if dir:
824 826 if dir in self._lazydirs:
825 827 self._loadlazy(dir)
826 828
827 829 if dir not in self._dirs:
828 830 return False
829 831
830 832 return self._dirs[dir].__contains__(subpath)
831 833 else:
832 834 return f in self._files
833 835
834 836 def get(self, f, default=None):
835 837 self._load()
836 838 dir, subpath = _splittopdir(f)
837 839 if dir:
838 840 if dir in self._lazydirs:
839 841 self._loadlazy(dir)
840 842
841 843 if dir not in self._dirs:
842 844 return default
843 845 return self._dirs[dir].get(subpath, default)
844 846 else:
845 847 return self._files.get(f, default)
846 848
847 849 def __getitem__(self, f):
848 850 self._load()
849 851 dir, subpath = _splittopdir(f)
850 852 if dir:
851 853 if dir in self._lazydirs:
852 854 self._loadlazy(dir)
853 855
854 856 return self._dirs[dir].__getitem__(subpath)
855 857 else:
856 858 return self._files[f]
857 859
858 860 def flags(self, f):
859 861 self._load()
860 862 dir, subpath = _splittopdir(f)
861 863 if dir:
862 864 if dir in self._lazydirs:
863 865 self._loadlazy(dir)
864 866
865 867 if dir not in self._dirs:
866 868 return ''
867 869 return self._dirs[dir].flags(subpath)
868 870 else:
869 871 if f in self._lazydirs or f in self._dirs:
870 872 return ''
871 873 return self._flags.get(f, '')
872 874
873 875 def find(self, f):
874 876 self._load()
875 877 dir, subpath = _splittopdir(f)
876 878 if dir:
877 879 if dir in self._lazydirs:
878 880 self._loadlazy(dir)
879 881
880 882 return self._dirs[dir].find(subpath)
881 883 else:
882 884 return self._files[f], self._flags.get(f, '')
883 885
884 886 def __delitem__(self, f):
885 887 self._load()
886 888 dir, subpath = _splittopdir(f)
887 889 if dir:
888 890 if dir in self._lazydirs:
889 891 self._loadlazy(dir)
890 892
891 893 self._dirs[dir].__delitem__(subpath)
892 894 # If the directory is now empty, remove it
893 895 if self._dirs[dir]._isempty():
894 896 del self._dirs[dir]
895 897 else:
896 898 del self._files[f]
897 899 if f in self._flags:
898 900 del self._flags[f]
899 901 self._dirty = True
900 902
901 903 def __setitem__(self, f, n):
902 904 assert n is not None
903 905 self._load()
904 906 dir, subpath = _splittopdir(f)
905 907 if dir:
906 908 if dir in self._lazydirs:
907 909 self._loadlazy(dir)
908 910 if dir not in self._dirs:
909 911 self._dirs[dir] = treemanifest(self._subpath(dir))
910 912 self._dirs[dir].__setitem__(subpath, n)
911 913 else:
912 914 self._files[f] = n[:21] # to match manifestdict's behavior
913 915 self._dirty = True
914 916
915 917 def _load(self):
916 918 if self._loadfunc is not _noop:
917 919 lf, self._loadfunc = self._loadfunc, _noop
918 920 lf(self)
919 921 elif self._copyfunc is not _noop:
920 922 cf, self._copyfunc = self._copyfunc, _noop
921 923 cf(self)
922 924
923 925 def setflag(self, f, flags):
924 926 """Set the flags (symlink, executable) for path f."""
925 927 self._load()
926 928 dir, subpath = _splittopdir(f)
927 929 if dir:
928 930 if dir in self._lazydirs:
929 931 self._loadlazy(dir)
930 932 if dir not in self._dirs:
931 933 self._dirs[dir] = treemanifest(self._subpath(dir))
932 934 self._dirs[dir].setflag(subpath, flags)
933 935 else:
934 936 self._flags[f] = flags
935 937 self._dirty = True
936 938
937 939 def copy(self):
938 940 copy = treemanifest(self._dir)
939 941 copy._node = self._node
940 942 copy._dirty = self._dirty
941 943 if self._copyfunc is _noop:
942 944 def _copyfunc(s):
943 945 self._load()
944 946 # OPT: it'd be nice to not load everything here. Unfortunately
945 947 # this makes a mess of the "dirty" state tracking if we don't.
946 948 self._loadalllazy()
947 949 sdirs = s._dirs
948 950 for d, v in self._dirs.iteritems():
949 951 sdirs[d] = v.copy()
950 952 s._files = dict.copy(self._files)
951 953 s._flags = dict.copy(self._flags)
952 954 if self._loadfunc is _noop:
953 955 _copyfunc(copy)
954 956 else:
955 957 copy._copyfunc = _copyfunc
956 958 else:
957 959 copy._copyfunc = self._copyfunc
958 960 return copy
959 961
960 962 def filesnotin(self, m2, match=None):
961 963 '''Set of files in this manifest that are not in the other'''
962 964 if match and not match.always():
963 965 m1 = self.matches(match)
964 966 m2 = m2.matches(match)
965 967 return m1.filesnotin(m2)
966 968
967 969 files = set()
968 970 def _filesnotin(t1, t2):
969 971 if t1._node == t2._node and not t1._dirty and not t2._dirty:
970 972 return
971 973 t1._load()
972 974 t2._load()
973 975 t1._loadalllazy()
974 976 t2._loadalllazy()
975 977 for d, m1 in t1._dirs.iteritems():
976 978 if d in t2._dirs:
977 979 m2 = t2._dirs[d]
978 980 _filesnotin(m1, m2)
979 981 else:
980 982 files.update(m1.iterkeys())
981 983
982 984 for fn in t1._files:
983 985 if fn not in t2._files:
984 986 files.add(t1._subpath(fn))
985 987
986 988 _filesnotin(self, m2)
987 989 return files
988 990
989 991 @propertycache
990 992 def _alldirs(self):
991 993 return util.dirs(self)
992 994
993 995 def dirs(self):
994 996 return self._alldirs
995 997
996 998 def hasdir(self, dir):
997 999 self._load()
998 1000 topdir, subdir = _splittopdir(dir)
999 1001 if topdir:
1000 1002 if topdir in self._lazydirs:
1001 1003 self._loadlazy(topdir)
1002 1004 if topdir in self._dirs:
1003 1005 return self._dirs[topdir].hasdir(subdir)
1004 1006 return False
1005 1007 dirslash = dir + '/'
1006 1008 return dirslash in self._dirs or dirslash in self._lazydirs
1007 1009
1008 1010 def walk(self, match):
1009 1011 '''Generates matching file names.
1010 1012
1011 1013 Equivalent to manifest.matches(match).iterkeys(), but without creating
1012 1014 an entirely new manifest.
1013 1015
1014 1016 It also reports nonexistent files by marking them bad with match.bad().
1015 1017 '''
1016 1018 if match.always():
1017 1019 for f in iter(self):
1018 1020 yield f
1019 1021 return
1020 1022
1021 1023 fset = set(match.files())
1022 1024
1023 1025 for fn in self._walk(match):
1024 1026 if fn in fset:
1025 1027 # specified pattern is the exact name
1026 1028 fset.remove(fn)
1027 1029 yield fn
1028 1030
1029 1031 # for dirstate.walk, files=['.'] means "walk the whole tree".
1030 1032 # follow that here, too
1031 1033 fset.discard('.')
1032 1034
1033 1035 for fn in sorted(fset):
1034 1036 if not self.hasdir(fn):
1035 1037 match.bad(fn, None)
1036 1038
1037 1039 def _walk(self, match):
1038 1040 '''Recursively generates matching file names for walk().'''
1039 1041 visit = match.visitchildrenset(self._dir[:-1] or '.')
1040 1042 if not visit:
1041 1043 return
1042 1044
1043 1045 # yield this dir's files and walk its submanifests
1044 1046 self._load()
1045 1047 visit = self._loadchildrensetlazy(visit)
1046 1048 for p in sorted(list(self._dirs) + list(self._files)):
1047 1049 if p in self._files:
1048 1050 fullp = self._subpath(p)
1049 1051 if match(fullp):
1050 1052 yield fullp
1051 1053 else:
1052 1054 if not visit or p[:-1] in visit:
1053 1055 for f in self._dirs[p]._walk(match):
1054 1056 yield f
1055 1057
1056 1058 def matches(self, match):
1057 1059 '''generate a new manifest filtered by the match argument'''
1058 1060 if match.always():
1059 1061 return self.copy()
1060 1062
1061 1063 return self._matches(match)
1062 1064
1063 1065 def _matches(self, match):
1064 1066 '''recursively generate a new manifest filtered by the match argument.
1065 1067 '''
1066 1068
1067 1069 visit = match.visitchildrenset(self._dir[:-1] or '.')
1068 1070 if visit == 'all':
1069 1071 return self.copy()
1070 1072 ret = treemanifest(self._dir)
1071 1073 if not visit:
1072 1074 return ret
1073 1075
1074 1076 self._load()
1075 1077 for fn in self._files:
1076 1078 # While visitchildrenset *usually* lists only subdirs, this is
1077 1079 # actually up to the matcher and may have some files in the set().
1078 1080 # If visit == 'this', we should obviously look at the files in this
1079 1081 # directory; if visit is a set, and fn is in it, we should inspect
1080 1082 # fn (but no need to inspect things not in the set).
1081 1083 if visit != 'this' and fn not in visit:
1082 1084 continue
1083 1085 fullp = self._subpath(fn)
1084 1086 # visitchildrenset isn't perfect, we still need to call the regular
1085 1087 # matcher code to further filter results.
1086 1088 if not match(fullp):
1087 1089 continue
1088 1090 ret._files[fn] = self._files[fn]
1089 1091 if fn in self._flags:
1090 1092 ret._flags[fn] = self._flags[fn]
1091 1093
1092 1094 visit = self._loadchildrensetlazy(visit)
1093 1095 for dir, subm in self._dirs.iteritems():
1094 1096 if visit and dir[:-1] not in visit:
1095 1097 continue
1096 1098 m = subm._matches(match)
1097 1099 if not m._isempty():
1098 1100 ret._dirs[dir] = m
1099 1101
1100 1102 if not ret._isempty():
1101 1103 ret._dirty = True
1102 1104 return ret
1103 1105
1104 1106 def diff(self, m2, match=None, clean=False):
1105 1107 '''Finds changes between the current manifest and m2.
1106 1108
1107 1109 Args:
1108 1110 m2: the manifest to which this manifest should be compared.
1109 1111 clean: if true, include files unchanged between these manifests
1110 1112 with a None value in the returned dictionary.
1111 1113
1112 1114 The result is returned as a dict with filename as key and
1113 1115 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1114 1116 nodeid in the current/other manifest and fl1/fl2 is the flag
1115 1117 in the current/other manifest. Where the file does not exist,
1116 1118 the nodeid will be None and the flags will be the empty
1117 1119 string.
1118 1120 '''
1119 1121 if match and not match.always():
1120 1122 m1 = self.matches(match)
1121 1123 m2 = m2.matches(match)
1122 1124 return m1.diff(m2, clean=clean)
1123 1125 result = {}
1124 1126 emptytree = treemanifest()
1125 1127 def _diff(t1, t2):
1126 1128 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1127 1129 return
1128 1130 t1._load()
1129 1131 t2._load()
1130 1132 # OPT: do we need to load everything?
1131 1133 t1._loadalllazy()
1132 1134 t2._loadalllazy()
1133 1135 for d, m1 in t1._dirs.iteritems():
1134 1136 m2 = t2._dirs.get(d, emptytree)
1135 1137 _diff(m1, m2)
1136 1138
1137 1139 for d, m2 in t2._dirs.iteritems():
1138 1140 if d not in t1._dirs:
1139 1141 _diff(emptytree, m2)
1140 1142
1141 1143 for fn, n1 in t1._files.iteritems():
1142 1144 fl1 = t1._flags.get(fn, '')
1143 1145 n2 = t2._files.get(fn, None)
1144 1146 fl2 = t2._flags.get(fn, '')
1145 1147 if n1 != n2 or fl1 != fl2:
1146 1148 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1147 1149 elif clean:
1148 1150 result[t1._subpath(fn)] = None
1149 1151
1150 1152 for fn, n2 in t2._files.iteritems():
1151 1153 if fn not in t1._files:
1152 1154 fl2 = t2._flags.get(fn, '')
1153 1155 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1154 1156
1155 1157 _diff(self, m2)
1156 1158 return result
1157 1159
1158 1160 def unmodifiedsince(self, m2):
1159 1161 return not self._dirty and not m2._dirty and self._node == m2._node
1160 1162
1161 1163 def parse(self, text, readsubtree):
1162 1164 selflazy = self._lazydirs
1163 1165 subpath = self._subpath
1164 1166 for f, n, fl in _parse(text):
1165 1167 if fl == 't':
1166 1168 f = f + '/'
1167 1169 selflazy[f] = (subpath(f), n, readsubtree)
1168 1170 elif '/' in f:
1169 1171 # This is a flat manifest, so use __setitem__ and setflag rather
1170 1172 # than assigning directly to _files and _flags, so we can
1171 1173 # assign a path in a subdirectory, and to mark dirty (compared
1172 1174 # to nullid).
1173 1175 self[f] = n
1174 1176 if fl:
1175 1177 self.setflag(f, fl)
1176 1178 else:
1177 1179 # Assigning to _files and _flags avoids marking as dirty,
1178 1180 # and should be a little faster.
1179 1181 self._files[f] = n
1180 1182 if fl:
1181 1183 self._flags[f] = fl
1182 1184
1183 1185 def text(self):
1184 1186 """Get the full data of this manifest as a bytestring."""
1185 1187 self._load()
1186 1188 return _text(self.iterentries())
1187 1189
1188 1190 def dirtext(self):
1189 1191 """Get the full data of this directory as a bytestring. Make sure that
1190 1192 any submanifests have been written first, so their nodeids are correct.
1191 1193 """
1192 1194 self._load()
1193 1195 flags = self.flags
1194 1196 lazydirs = [(d[:-1], node, 't') for
1195 1197 d, (path, node, readsubtree) in self._lazydirs.iteritems()]
1196 1198 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1197 1199 files = [(f, self._files[f], flags(f)) for f in self._files]
1198 1200 return _text(sorted(dirs + files + lazydirs))
1199 1201
1200 1202 def read(self, gettext, readsubtree):
1201 1203 def _load_for_read(s):
1202 1204 s.parse(gettext(), readsubtree)
1203 1205 s._dirty = False
1204 1206 self._loadfunc = _load_for_read
1205 1207
1206 1208 def writesubtrees(self, m1, m2, writesubtree, match):
1207 1209 self._load() # for consistency; should never have any effect here
1208 1210 m1._load()
1209 1211 m2._load()
1210 1212 emptytree = treemanifest()
1211 1213 def getnode(m, d):
1212 1214 ld = m._lazydirs.get(d)
1213 1215 if ld:
1214 1216 return ld[1]
1215 1217 return m._dirs.get(d, emptytree)._node
1216 1218
1217 1219 # we should have always loaded everything by the time we get here for
1218 1220 # `self`, but possibly not in `m1` or `m2`.
1219 1221 assert not self._lazydirs
1220 1222 # let's skip investigating things that `match` says we do not need.
1221 1223 visit = match.visitchildrenset(self._dir[:-1] or '.')
1222 1224 if visit == 'this' or visit == 'all':
1223 1225 visit = None
1224 1226 for d, subm in self._dirs.iteritems():
1225 1227 if visit and d[:-1] not in visit:
1226 1228 continue
1227 1229 subp1 = getnode(m1, d)
1228 1230 subp2 = getnode(m2, d)
1229 1231 if subp1 == nullid:
1230 1232 subp1, subp2 = subp2, subp1
1231 1233 writesubtree(subm, subp1, subp2, match)
1232 1234
1233 1235 def walksubtrees(self, matcher=None):
1234 1236 """Returns an iterator of the subtrees of this manifest, including this
1235 1237 manifest itself.
1236 1238
1237 1239 If `matcher` is provided, it only returns subtrees that match.
1238 1240 """
1239 1241 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1240 1242 return
1241 1243 if not matcher or matcher(self._dir[:-1]):
1242 1244 yield self
1243 1245
1244 1246 self._load()
1245 1247 # OPT: use visitchildrenset to avoid loading everything.
1246 1248 self._loadalllazy()
1247 1249 for d, subm in self._dirs.iteritems():
1248 1250 for subtree in subm.walksubtrees(matcher=matcher):
1249 1251 yield subtree
1250 1252
1251 1253 class manifestfulltextcache(util.lrucachedict):
1252 1254 """File-backed LRU cache for the manifest cache
1253 1255
1254 1256 File consists of entries, up to EOF:
1255 1257
1256 1258 - 20 bytes node, 4 bytes length, <length> manifest data
1257 1259
1258 1260 These are written in reverse cache order (oldest to newest).
1259 1261
1260 1262 """
1261 1263 def __init__(self, max):
1262 1264 super(manifestfulltextcache, self).__init__(max)
1263 1265 self._dirty = False
1264 1266 self._read = False
1265 1267 self._opener = None
1266 1268
1267 1269 def read(self):
1268 1270 if self._read or self._opener is None:
1269 1271 return
1270 1272
1271 1273 try:
1272 1274 with self._opener('manifestfulltextcache') as fp:
1273 1275 set = super(manifestfulltextcache, self).__setitem__
1274 1276 # ignore trailing data, this is a cache, corruption is skipped
1275 1277 while True:
1276 1278 node = fp.read(20)
1277 1279 if len(node) < 20:
1278 1280 break
1279 1281 try:
1280 1282 size = struct.unpack('>L', fp.read(4))[0]
1281 1283 except struct.error:
1282 1284 break
1283 1285 value = bytearray(fp.read(size))
1284 1286 if len(value) != size:
1285 1287 break
1286 1288 set(node, value)
1287 1289 except IOError:
1288 1290 # the file is allowed to be missing
1289 1291 pass
1290 1292
1291 1293 self._read = True
1292 1294 self._dirty = False
1293 1295
1294 1296 def write(self):
1295 1297 if not self._dirty or self._opener is None:
1296 1298 return
1297 1299 # rotate backwards to the first used node
1298 1300 with self._opener(
1299 1301 'manifestfulltextcache', 'w', atomictemp=True, checkambig=True
1300 1302 ) as fp:
1301 1303 node = self._head.prev
1302 1304 while True:
1303 1305 if node.key in self._cache:
1304 1306 fp.write(node.key)
1305 1307 fp.write(struct.pack('>L', len(node.value)))
1306 1308 fp.write(node.value)
1307 1309 if node is self._head:
1308 1310 break
1309 1311 node = node.prev
1310 1312
1311 1313 def __len__(self):
1312 1314 if not self._read:
1313 1315 self.read()
1314 1316 return super(manifestfulltextcache, self).__len__()
1315 1317
1316 1318 def __contains__(self, k):
1317 1319 if not self._read:
1318 1320 self.read()
1319 1321 return super(manifestfulltextcache, self).__contains__(k)
1320 1322
1321 1323 def __iter__(self):
1322 1324 if not self._read:
1323 1325 self.read()
1324 1326 return super(manifestfulltextcache, self).__iter__()
1325 1327
1326 1328 def __getitem__(self, k):
1327 1329 if not self._read:
1328 1330 self.read()
1329 1331 # the cache lru order can change on read
1330 1332 setdirty = self._cache.get(k) is not self._head
1331 1333 value = super(manifestfulltextcache, self).__getitem__(k)
1332 1334 if setdirty:
1333 1335 self._dirty = True
1334 1336 return value
1335 1337
1336 1338 def __setitem__(self, k, v):
1337 1339 if not self._read:
1338 1340 self.read()
1339 1341 super(manifestfulltextcache, self).__setitem__(k, v)
1340 1342 self._dirty = True
1341 1343
1342 1344 def __delitem__(self, k):
1343 1345 if not self._read:
1344 1346 self.read()
1345 1347 super(manifestfulltextcache, self).__delitem__(k)
1346 1348 self._dirty = True
1347 1349
1348 1350 def get(self, k, default=None):
1349 1351 if not self._read:
1350 1352 self.read()
1351 1353 return super(manifestfulltextcache, self).get(k, default=default)
1352 1354
1353 1355 def clear(self, clear_persisted_data=False):
1354 1356 super(manifestfulltextcache, self).clear()
1355 1357 if clear_persisted_data:
1356 1358 self._dirty = True
1357 1359 self.write()
1358 1360 self._read = False
1359 1361
1360 1362 @interfaceutil.implementer(repository.imanifeststorage)
1361 1363 class manifestrevlog(object):
1362 1364 '''A revlog that stores manifest texts. This is responsible for caching the
1363 1365 full-text manifest contents.
1364 1366 '''
1365 1367 def __init__(self, opener, tree='', dirlogcache=None, indexfile=None,
1366 1368 treemanifest=False):
1367 1369 """Constructs a new manifest revlog
1368 1370
1369 1371 `indexfile` - used by extensions to have two manifests at once, like
1370 1372 when transitioning between flatmanifeset and treemanifests.
1371 1373
1372 1374 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1373 1375 options can also be used to make this a tree manifest revlog. The opener
1374 1376 option takes precedence, so if it is set to True, we ignore whatever
1375 1377 value is passed in to the constructor.
1376 1378 """
1377 1379 # During normal operations, we expect to deal with not more than four
1378 1380 # revs at a time (such as during commit --amend). When rebasing large
1379 1381 # stacks of commits, the number can go up, hence the config knob below.
1380 1382 cachesize = 4
1381 1383 optiontreemanifest = False
1382 1384 opts = getattr(opener, 'options', None)
1383 1385 if opts is not None:
1384 1386 cachesize = opts.get('manifestcachesize', cachesize)
1385 1387 optiontreemanifest = opts.get('treemanifest', False)
1386 1388
1387 1389 self._treeondisk = optiontreemanifest or treemanifest
1388 1390
1389 1391 self._fulltextcache = manifestfulltextcache(cachesize)
1390 1392
1391 1393 if tree:
1392 1394 assert self._treeondisk, 'opts is %r' % opts
1393 1395
1394 1396 if indexfile is None:
1395 1397 indexfile = '00manifest.i'
1396 1398 if tree:
1397 1399 indexfile = "meta/" + tree + indexfile
1398 1400
1399 1401 self.tree = tree
1400 1402
1401 1403 # The dirlogcache is kept on the root manifest log
1402 1404 if tree:
1403 1405 self._dirlogcache = dirlogcache
1404 1406 else:
1405 1407 self._dirlogcache = {'': self}
1406 1408
1407 1409 self._revlog = revlog.revlog(opener, indexfile,
1408 1410 # only root indexfile is cached
1409 1411 checkambig=not bool(tree),
1410 1412 mmaplargeindex=True)
1411 1413
1412 1414 self.index = self._revlog.index
1413 1415 self.version = self._revlog.version
1414 1416 self._generaldelta = self._revlog._generaldelta
1415 1417
1416 1418 def _setupmanifestcachehooks(self, repo):
1417 1419 """Persist the manifestfulltextcache on lock release"""
1418 1420 if not util.safehasattr(repo, '_lockref'):
1419 1421 return
1420 1422
1421 1423 self._fulltextcache._opener = repo.cachevfs
1422 1424 reporef = weakref.ref(repo)
1423 1425 manifestrevlogref = weakref.ref(self)
1424 1426
1425 1427 def persistmanifestcache():
1426 1428 repo = reporef()
1427 1429 self = manifestrevlogref()
1428 1430 if repo is None or self is None:
1429 1431 return
1430 1432 if repo.manifestlog.getstorage(b'') is not self:
1431 1433 # there's a different manifest in play now, abort
1432 1434 return
1433 1435 self._fulltextcache.write()
1434 1436
1435 1437 if repo._currentlock(repo._lockref) is not None:
1436 1438 repo._afterlock(persistmanifestcache)
1437 1439
1438 1440 @property
1439 1441 def fulltextcache(self):
1440 1442 return self._fulltextcache
1441 1443
1442 1444 def clearcaches(self, clear_persisted_data=False):
1443 1445 self._revlog.clearcaches()
1444 1446 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1445 1447 self._dirlogcache = {self.tree: self}
1446 1448
1447 1449 def dirlog(self, d):
1448 1450 if d:
1449 1451 assert self._treeondisk
1450 1452 if d not in self._dirlogcache:
1451 1453 mfrevlog = manifestrevlog(self.opener, d,
1452 1454 self._dirlogcache,
1453 1455 treemanifest=self._treeondisk)
1454 1456 self._dirlogcache[d] = mfrevlog
1455 1457 return self._dirlogcache[d]
1456 1458
1457 1459 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None,
1458 1460 match=None):
1459 1461 if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'):
1460 1462 # If our first parent is in the manifest cache, we can
1461 1463 # compute a delta here using properties we know about the
1462 1464 # manifest up-front, which may save time later for the
1463 1465 # revlog layer.
1464 1466
1465 1467 _checkforbidden(added)
1466 1468 # combine the changed lists into one sorted iterator
1467 1469 work = heapq.merge([(x, False) for x in added],
1468 1470 [(x, True) for x in removed])
1469 1471
1470 1472 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1471 1473 cachedelta = self._revlog.rev(p1), deltatext
1472 1474 text = util.buffer(arraytext)
1473 1475 n = self._revlog.addrevision(text, transaction, link, p1, p2,
1474 1476 cachedelta)
1475 1477 else:
1476 1478 # The first parent manifest isn't already loaded, so we'll
1477 1479 # just encode a fulltext of the manifest and pass that
1478 1480 # through to the revlog layer, and let it handle the delta
1479 1481 # process.
1480 1482 if self._treeondisk:
1481 1483 assert readtree, "readtree must be set for treemanifest writes"
1482 1484 assert match, "match must be specified for treemanifest writes"
1483 1485 m1 = readtree(self.tree, p1)
1484 1486 m2 = readtree(self.tree, p2)
1485 1487 n = self._addtree(m, transaction, link, m1, m2, readtree,
1486 1488 match=match)
1487 1489 arraytext = None
1488 1490 else:
1489 1491 text = m.text()
1490 1492 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1491 1493 arraytext = bytearray(text)
1492 1494
1493 1495 if arraytext is not None:
1494 1496 self.fulltextcache[n] = arraytext
1495 1497
1496 1498 return n
1497 1499
1498 1500 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1499 1501 # If the manifest is unchanged compared to one parent,
1500 1502 # don't write a new revision
1501 1503 if self.tree != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(
1502 1504 m2)):
1503 1505 return m.node()
1504 1506 def writesubtree(subm, subp1, subp2, match):
1505 1507 sublog = self.dirlog(subm.dir())
1506 1508 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1507 1509 readtree=readtree, match=match)
1508 1510 m.writesubtrees(m1, m2, writesubtree, match)
1509 1511 text = m.dirtext()
1510 1512 n = None
1511 1513 if self.tree != '':
1512 1514 # Double-check whether contents are unchanged to one parent
1513 1515 if text == m1.dirtext():
1514 1516 n = m1.node()
1515 1517 elif text == m2.dirtext():
1516 1518 n = m2.node()
1517 1519
1518 1520 if not n:
1519 1521 n = self._revlog.addrevision(text, transaction, link, m1.node(),
1520 1522 m2.node())
1521 1523
1522 1524 # Save nodeid so parent manifest can calculate its nodeid
1523 1525 m.setnode(n)
1524 1526 return n
1525 1527
1526 1528 def __len__(self):
1527 1529 return len(self._revlog)
1528 1530
1529 1531 def __iter__(self):
1530 1532 return self._revlog.__iter__()
1531 1533
1532 1534 def rev(self, node):
1533 1535 return self._revlog.rev(node)
1534 1536
1535 1537 def node(self, rev):
1536 1538 return self._revlog.node(rev)
1537 1539
1538 1540 def lookup(self, value):
1539 1541 return self._revlog.lookup(value)
1540 1542
1541 1543 def parentrevs(self, rev):
1542 1544 return self._revlog.parentrevs(rev)
1543 1545
1544 1546 def parents(self, node):
1545 1547 return self._revlog.parents(node)
1546 1548
1547 1549 def linkrev(self, rev):
1548 1550 return self._revlog.linkrev(rev)
1549 1551
1550 1552 def checksize(self):
1551 1553 return self._revlog.checksize()
1552 1554
1553 1555 def revision(self, node, _df=None, raw=False):
1554 1556 return self._revlog.revision(node, _df=_df, raw=raw)
1555 1557
1556 1558 def revdiff(self, rev1, rev2):
1557 1559 return self._revlog.revdiff(rev1, rev2)
1558 1560
1559 1561 def cmp(self, node, text):
1560 1562 return self._revlog.cmp(node, text)
1561 1563
1562 1564 def deltaparent(self, rev):
1563 1565 return self._revlog.deltaparent(rev)
1564 1566
1565 1567 def emitrevisions(self, nodes, nodesorder=None,
1566 1568 revisiondata=False, assumehaveparentrevisions=False,
1567 1569 deltaprevious=False):
1568 1570 return self._revlog.emitrevisions(
1569 1571 nodes, nodesorder=nodesorder, revisiondata=revisiondata,
1570 1572 assumehaveparentrevisions=assumehaveparentrevisions,
1571 1573 deltaprevious=deltaprevious)
1572 1574
1573 1575 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1574 1576 return self._revlog.addgroup(deltas, linkmapper, transaction,
1575 1577 addrevisioncb=addrevisioncb)
1576 1578
1577 1579 def rawsize(self, rev):
1578 1580 return self._revlog.rawsize(rev)
1579 1581
1580 1582 def getstrippoint(self, minlink):
1581 1583 return self._revlog.getstrippoint(minlink)
1582 1584
1583 1585 def strip(self, minlink, transaction):
1584 1586 return self._revlog.strip(minlink, transaction)
1585 1587
1586 1588 def files(self):
1587 1589 return self._revlog.files()
1588 1590
1589 1591 def clone(self, tr, destrevlog, **kwargs):
1590 1592 if not isinstance(destrevlog, manifestrevlog):
1591 1593 raise error.ProgrammingError('expected manifestrevlog to clone()')
1592 1594
1593 1595 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1594 1596
1595 1597 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
1596 1598 revisionscount=False, trackedsize=False,
1597 1599 storedsize=False):
1598 1600 return self._revlog.storageinfo(
1599 1601 exclusivefiles=exclusivefiles, sharedfiles=sharedfiles,
1600 1602 revisionscount=revisionscount, trackedsize=trackedsize,
1601 1603 storedsize=storedsize)
1602 1604
1603 1605 @property
1604 1606 def indexfile(self):
1605 1607 return self._revlog.indexfile
1606 1608
1607 1609 @indexfile.setter
1608 1610 def indexfile(self, value):
1609 1611 self._revlog.indexfile = value
1610 1612
1611 1613 @property
1612 1614 def opener(self):
1613 1615 return self._revlog.opener
1614 1616
1615 1617 @opener.setter
1616 1618 def opener(self, value):
1617 1619 self._revlog.opener = value
1618 1620
1619 1621 @interfaceutil.implementer(repository.imanifestlog)
1620 1622 class manifestlog(object):
1621 1623 """A collection class representing the collection of manifest snapshots
1622 1624 referenced by commits in the repository.
1623 1625
1624 1626 In this situation, 'manifest' refers to the abstract concept of a snapshot
1625 1627 of the list of files in the given commit. Consumers of the output of this
1626 1628 class do not care about the implementation details of the actual manifests
1627 1629 they receive (i.e. tree or flat or lazily loaded, etc)."""
1628 1630 def __init__(self, opener, repo, rootstore):
1629 1631 usetreemanifest = False
1630 1632 cachesize = 4
1631 1633
1632 1634 opts = getattr(opener, 'options', None)
1633 1635 if opts is not None:
1634 1636 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1635 1637 cachesize = opts.get('manifestcachesize', cachesize)
1636 1638
1637 1639 self._treemanifests = usetreemanifest
1638 1640
1639 1641 self._rootstore = rootstore
1640 1642 self._rootstore._setupmanifestcachehooks(repo)
1641 1643 self._narrowmatch = repo.narrowmatch()
1642 1644
1643 1645 # A cache of the manifestctx or treemanifestctx for each directory
1644 1646 self._dirmancache = {}
1645 1647 self._dirmancache[''] = util.lrucachedict(cachesize)
1646 1648
1647 1649 self._cachesize = cachesize
1648 1650
1649 1651 def __getitem__(self, node):
1650 1652 """Retrieves the manifest instance for the given node. Throws a
1651 1653 LookupError if not found.
1652 1654 """
1653 1655 return self.get('', node)
1654 1656
1655 1657 def get(self, tree, node, verify=True):
1656 1658 """Retrieves the manifest instance for the given node. Throws a
1657 1659 LookupError if not found.
1658 1660
1659 1661 `verify` - if True an exception will be thrown if the node is not in
1660 1662 the revlog
1661 1663 """
1662 1664 if node in self._dirmancache.get(tree, ()):
1663 1665 return self._dirmancache[tree][node]
1664 1666
1665 1667 if not self._narrowmatch.always():
1666 1668 if not self._narrowmatch.visitdir(tree[:-1] or '.'):
1667 1669 return excludeddirmanifestctx(tree, node)
1668 1670 if tree:
1669 1671 if self._rootstore._treeondisk:
1670 1672 if verify:
1671 1673 # Side-effect is LookupError is raised if node doesn't
1672 1674 # exist.
1673 1675 self.getstorage(tree).rev(node)
1674 1676
1675 1677 m = treemanifestctx(self, tree, node)
1676 1678 else:
1677 1679 raise error.Abort(
1678 1680 _("cannot ask for manifest directory '%s' in a flat "
1679 1681 "manifest") % tree)
1680 1682 else:
1681 1683 if verify:
1682 1684 # Side-effect is LookupError is raised if node doesn't exist.
1683 1685 self._rootstore.rev(node)
1684 1686
1685 1687 if self._treemanifests:
1686 1688 m = treemanifestctx(self, '', node)
1687 1689 else:
1688 1690 m = manifestctx(self, node)
1689 1691
1690 1692 if node != nullid:
1691 1693 mancache = self._dirmancache.get(tree)
1692 1694 if not mancache:
1693 1695 mancache = util.lrucachedict(self._cachesize)
1694 1696 self._dirmancache[tree] = mancache
1695 1697 mancache[node] = m
1696 1698 return m
1697 1699
1698 1700 def getstorage(self, tree):
1699 1701 return self._rootstore.dirlog(tree)
1700 1702
1701 1703 def clearcaches(self, clear_persisted_data=False):
1702 1704 self._dirmancache.clear()
1703 1705 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1704 1706
1705 1707 def rev(self, node):
1706 1708 return self._rootstore.rev(node)
1707 1709
1708 1710 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1709 1711 class memmanifestctx(object):
1710 1712 def __init__(self, manifestlog):
1711 1713 self._manifestlog = manifestlog
1712 1714 self._manifestdict = manifestdict()
1713 1715
1714 1716 def _storage(self):
1715 1717 return self._manifestlog.getstorage(b'')
1716 1718
1717 1719 def new(self):
1718 1720 return memmanifestctx(self._manifestlog)
1719 1721
1720 1722 def copy(self):
1721 1723 memmf = memmanifestctx(self._manifestlog)
1722 1724 memmf._manifestdict = self.read().copy()
1723 1725 return memmf
1724 1726
1725 1727 def read(self):
1726 1728 return self._manifestdict
1727 1729
1728 1730 def write(self, transaction, link, p1, p2, added, removed, match=None):
1729 1731 return self._storage().add(self._manifestdict, transaction, link,
1730 1732 p1, p2, added, removed, match=match)
1731 1733
1732 1734 @interfaceutil.implementer(repository.imanifestrevisionstored)
1733 1735 class manifestctx(object):
1734 1736 """A class representing a single revision of a manifest, including its
1735 1737 contents, its parent revs, and its linkrev.
1736 1738 """
1737 1739 def __init__(self, manifestlog, node):
1738 1740 self._manifestlog = manifestlog
1739 1741 self._data = None
1740 1742
1741 1743 self._node = node
1742 1744
1743 1745 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1744 1746 # but let's add it later when something needs it and we can load it
1745 1747 # lazily.
1746 1748 #self.p1, self.p2 = store.parents(node)
1747 1749 #rev = store.rev(node)
1748 1750 #self.linkrev = store.linkrev(rev)
1749 1751
1750 1752 def _storage(self):
1751 1753 return self._manifestlog.getstorage(b'')
1752 1754
1753 1755 def node(self):
1754 1756 return self._node
1755 1757
1756 1758 def new(self):
1757 1759 return memmanifestctx(self._manifestlog)
1758 1760
1759 1761 def copy(self):
1760 1762 memmf = memmanifestctx(self._manifestlog)
1761 1763 memmf._manifestdict = self.read().copy()
1762 1764 return memmf
1763 1765
1764 1766 @propertycache
1765 1767 def parents(self):
1766 1768 return self._storage().parents(self._node)
1767 1769
1768 1770 def read(self):
1769 1771 if self._data is None:
1770 1772 if self._node == nullid:
1771 1773 self._data = manifestdict()
1772 1774 else:
1773 1775 store = self._storage()
1774 1776 if self._node in store.fulltextcache:
1775 1777 text = pycompat.bytestr(store.fulltextcache[self._node])
1776 1778 else:
1777 1779 text = store.revision(self._node)
1778 1780 arraytext = bytearray(text)
1779 1781 store.fulltextcache[self._node] = arraytext
1780 1782 self._data = manifestdict(text)
1781 1783 return self._data
1782 1784
1783 1785 def readfast(self, shallow=False):
1784 1786 '''Calls either readdelta or read, based on which would be less work.
1785 1787 readdelta is called if the delta is against the p1, and therefore can be
1786 1788 read quickly.
1787 1789
1788 1790 If `shallow` is True, nothing changes since this is a flat manifest.
1789 1791 '''
1790 1792 store = self._storage()
1791 1793 r = store.rev(self._node)
1792 1794 deltaparent = store.deltaparent(r)
1793 1795 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
1794 1796 return self.readdelta()
1795 1797 return self.read()
1796 1798
1797 1799 def readdelta(self, shallow=False):
1798 1800 '''Returns a manifest containing just the entries that are present
1799 1801 in this manifest, but not in its p1 manifest. This is efficient to read
1800 1802 if the revlog delta is already p1.
1801 1803
1802 1804 Changing the value of `shallow` has no effect on flat manifests.
1803 1805 '''
1804 1806 store = self._storage()
1805 1807 r = store.rev(self._node)
1806 1808 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1807 1809 return manifestdict(d)
1808 1810
1809 1811 def find(self, key):
1810 1812 return self.read().find(key)
1811 1813
1812 1814 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1813 1815 class memtreemanifestctx(object):
1814 1816 def __init__(self, manifestlog, dir=''):
1815 1817 self._manifestlog = manifestlog
1816 1818 self._dir = dir
1817 1819 self._treemanifest = treemanifest()
1818 1820
1819 1821 def _storage(self):
1820 1822 return self._manifestlog.getstorage(b'')
1821 1823
1822 1824 def new(self, dir=''):
1823 1825 return memtreemanifestctx(self._manifestlog, dir=dir)
1824 1826
1825 1827 def copy(self):
1826 1828 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1827 1829 memmf._treemanifest = self._treemanifest.copy()
1828 1830 return memmf
1829 1831
1830 1832 def read(self):
1831 1833 return self._treemanifest
1832 1834
1833 1835 def write(self, transaction, link, p1, p2, added, removed, match=None):
1834 1836 def readtree(dir, node):
1835 1837 return self._manifestlog.get(dir, node).read()
1836 1838 return self._storage().add(self._treemanifest, transaction, link,
1837 1839 p1, p2, added, removed, readtree=readtree,
1838 1840 match=match)
1839 1841
1840 1842 @interfaceutil.implementer(repository.imanifestrevisionstored)
1841 1843 class treemanifestctx(object):
1842 1844 def __init__(self, manifestlog, dir, node):
1843 1845 self._manifestlog = manifestlog
1844 1846 self._dir = dir
1845 1847 self._data = None
1846 1848
1847 1849 self._node = node
1848 1850
1849 1851 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1850 1852 # we can instantiate treemanifestctx objects for directories we don't
1851 1853 # have on disk.
1852 1854 #self.p1, self.p2 = store.parents(node)
1853 1855 #rev = store.rev(node)
1854 1856 #self.linkrev = store.linkrev(rev)
1855 1857
1856 1858 def _storage(self):
1857 1859 narrowmatch = self._manifestlog._narrowmatch
1858 1860 if not narrowmatch.always():
1859 1861 if not narrowmatch.visitdir(self._dir[:-1] or '.'):
1860 1862 return excludedmanifestrevlog(self._dir)
1861 1863 return self._manifestlog.getstorage(self._dir)
1862 1864
1863 1865 def read(self):
1864 1866 if self._data is None:
1865 1867 store = self._storage()
1866 1868 if self._node == nullid:
1867 1869 self._data = treemanifest()
1868 1870 # TODO accessing non-public API
1869 1871 elif store._treeondisk:
1870 1872 m = treemanifest(dir=self._dir)
1871 1873 def gettext():
1872 1874 return store.revision(self._node)
1873 1875 def readsubtree(dir, subm):
1874 1876 # Set verify to False since we need to be able to create
1875 1877 # subtrees for trees that don't exist on disk.
1876 1878 return self._manifestlog.get(dir, subm, verify=False).read()
1877 1879 m.read(gettext, readsubtree)
1878 1880 m.setnode(self._node)
1879 1881 self._data = m
1880 1882 else:
1881 1883 if self._node in store.fulltextcache:
1882 1884 text = pycompat.bytestr(store.fulltextcache[self._node])
1883 1885 else:
1884 1886 text = store.revision(self._node)
1885 1887 arraytext = bytearray(text)
1886 1888 store.fulltextcache[self._node] = arraytext
1887 1889 self._data = treemanifest(dir=self._dir, text=text)
1888 1890
1889 1891 return self._data
1890 1892
1891 1893 def node(self):
1892 1894 return self._node
1893 1895
1894 1896 def new(self, dir=''):
1895 1897 return memtreemanifestctx(self._manifestlog, dir=dir)
1896 1898
1897 1899 def copy(self):
1898 1900 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1899 1901 memmf._treemanifest = self.read().copy()
1900 1902 return memmf
1901 1903
1902 1904 @propertycache
1903 1905 def parents(self):
1904 1906 return self._storage().parents(self._node)
1905 1907
1906 1908 def readdelta(self, shallow=False):
1907 1909 '''Returns a manifest containing just the entries that are present
1908 1910 in this manifest, but not in its p1 manifest. This is efficient to read
1909 1911 if the revlog delta is already p1.
1910 1912
1911 1913 If `shallow` is True, this will read the delta for this directory,
1912 1914 without recursively reading subdirectory manifests. Instead, any
1913 1915 subdirectory entry will be reported as it appears in the manifest, i.e.
1914 1916 the subdirectory will be reported among files and distinguished only by
1915 1917 its 't' flag.
1916 1918 '''
1917 1919 store = self._storage()
1918 1920 if shallow:
1919 1921 r = store.rev(self._node)
1920 1922 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1921 1923 return manifestdict(d)
1922 1924 else:
1923 1925 # Need to perform a slow delta
1924 1926 r0 = store.deltaparent(store.rev(self._node))
1925 1927 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
1926 1928 m1 = self.read()
1927 1929 md = treemanifest(dir=self._dir)
1928 1930 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1929 1931 if n1:
1930 1932 md[f] = n1
1931 1933 if fl1:
1932 1934 md.setflag(f, fl1)
1933 1935 return md
1934 1936
1935 1937 def readfast(self, shallow=False):
1936 1938 '''Calls either readdelta or read, based on which would be less work.
1937 1939 readdelta is called if the delta is against the p1, and therefore can be
1938 1940 read quickly.
1939 1941
1940 1942 If `shallow` is True, it only returns the entries from this manifest,
1941 1943 and not any submanifests.
1942 1944 '''
1943 1945 store = self._storage()
1944 1946 r = store.rev(self._node)
1945 1947 deltaparent = store.deltaparent(r)
1946 1948 if (deltaparent != nullrev and
1947 1949 deltaparent in store.parentrevs(r)):
1948 1950 return self.readdelta(shallow=shallow)
1949 1951
1950 1952 if shallow:
1951 1953 return manifestdict(store.revision(self._node))
1952 1954 else:
1953 1955 return self.read()
1954 1956
1955 1957 def find(self, key):
1956 1958 return self.read().find(key)
1957 1959
1958 1960 class excludeddir(treemanifest):
1959 1961 """Stand-in for a directory that is excluded from the repository.
1960 1962
1961 1963 With narrowing active on a repository that uses treemanifests,
1962 1964 some of the directory revlogs will be excluded from the resulting
1963 1965 clone. This is a huge storage win for clients, but means we need
1964 1966 some sort of pseudo-manifest to surface to internals so we can
1965 1967 detect a merge conflict outside the narrowspec. That's what this
1966 1968 class is: it stands in for a directory whose node is known, but
1967 1969 whose contents are unknown.
1968 1970 """
1969 1971 def __init__(self, dir, node):
1970 1972 super(excludeddir, self).__init__(dir)
1971 1973 self._node = node
1972 1974 # Add an empty file, which will be included by iterators and such,
1973 1975 # appearing as the directory itself (i.e. something like "dir/")
1974 1976 self._files[''] = node
1975 1977 self._flags[''] = 't'
1976 1978
1977 1979 # Manifests outside the narrowspec should never be modified, so avoid
1978 1980 # copying. This makes a noticeable difference when there are very many
1979 1981 # directories outside the narrowspec. Also, it makes sense for the copy to
1980 1982 # be of the same type as the original, which would not happen with the
1981 1983 # super type's copy().
1982 1984 def copy(self):
1983 1985 return self
1984 1986
1985 1987 class excludeddirmanifestctx(treemanifestctx):
1986 1988 """context wrapper for excludeddir - see that docstring for rationale"""
1987 1989 def __init__(self, dir, node):
1988 1990 self._dir = dir
1989 1991 self._node = node
1990 1992
1991 1993 def read(self):
1992 1994 return excludeddir(self._dir, self._node)
1993 1995
1994 1996 def write(self, *args):
1995 1997 raise error.ProgrammingError(
1996 1998 'attempt to write manifest from excluded dir %s' % self._dir)
1997 1999
1998 2000 class excludedmanifestrevlog(manifestrevlog):
1999 2001 """Stand-in for excluded treemanifest revlogs.
2000 2002
2001 2003 When narrowing is active on a treemanifest repository, we'll have
2002 2004 references to directories we can't see due to the revlog being
2003 2005 skipped. This class exists to conform to the manifestrevlog
2004 2006 interface for those directories and proactively prevent writes to
2005 2007 outside the narrowspec.
2006 2008 """
2007 2009
2008 2010 def __init__(self, dir):
2009 2011 self._dir = dir
2010 2012
2011 2013 def __len__(self):
2012 2014 raise error.ProgrammingError(
2013 2015 'attempt to get length of excluded dir %s' % self._dir)
2014 2016
2015 2017 def rev(self, node):
2016 2018 raise error.ProgrammingError(
2017 2019 'attempt to get rev from excluded dir %s' % self._dir)
2018 2020
2019 2021 def linkrev(self, node):
2020 2022 raise error.ProgrammingError(
2021 2023 'attempt to get linkrev from excluded dir %s' % self._dir)
2022 2024
2023 2025 def node(self, rev):
2024 2026 raise error.ProgrammingError(
2025 2027 'attempt to get node from excluded dir %s' % self._dir)
2026 2028
2027 2029 def add(self, *args, **kwargs):
2028 2030 # We should never write entries in dirlogs outside the narrow clone.
2029 2031 # However, the method still gets called from writesubtree() in
2030 2032 # _addtree(), so we need to handle it. We should possibly make that
2031 2033 # avoid calling add() with a clean manifest (_dirty is always False
2032 2034 # in excludeddir instances).
2033 2035 pass
General Comments 0
You need to be logged in to leave comments. Login now