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