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