##// END OF EJS Templates
treemanifest: avoid unnecessary copies/processing when using alwaysmatcher...
Kyle Lippincott -
r39553:8798be5f default
parent child Browse files
Show More
@@ -1,1972 +1,1972
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 if match:
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 if match:
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 1180 # OPT: Do we really need to load everything? Presumably things in lazy
1181 1181 # aren't dirty and don't need to be written.
1182 1182 self._loadalllazy()
1183 1183 m1._loadalllazy()
1184 1184 m2._loadalllazy()
1185 1185 for d, subm in self._dirs.iteritems():
1186 1186 subp1 = m1._dirs.get(d, emptytree)._node
1187 1187 subp2 = m2._dirs.get(d, emptytree)._node
1188 1188 if subp1 == nullid:
1189 1189 subp1, subp2 = subp2, subp1
1190 1190 writesubtree(subm, subp1, subp2)
1191 1191
1192 1192 def walksubtrees(self, matcher=None):
1193 1193 """Returns an iterator of the subtrees of this manifest, including this
1194 1194 manifest itself.
1195 1195
1196 1196 If `matcher` is provided, it only returns subtrees that match.
1197 1197 """
1198 1198 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1199 1199 return
1200 1200 if not matcher or matcher(self._dir[:-1]):
1201 1201 yield self
1202 1202
1203 1203 self._load()
1204 1204 # OPT: use visitchildrenset to avoid loading everything.
1205 1205 self._loadalllazy()
1206 1206 for d, subm in self._dirs.iteritems():
1207 1207 for subtree in subm.walksubtrees(matcher=matcher):
1208 1208 yield subtree
1209 1209
1210 1210 class manifestfulltextcache(util.lrucachedict):
1211 1211 """File-backed LRU cache for the manifest cache
1212 1212
1213 1213 File consists of entries, up to EOF:
1214 1214
1215 1215 - 20 bytes node, 4 bytes length, <length> manifest data
1216 1216
1217 1217 These are written in reverse cache order (oldest to newest).
1218 1218
1219 1219 """
1220 1220 def __init__(self, max):
1221 1221 super(manifestfulltextcache, self).__init__(max)
1222 1222 self._dirty = False
1223 1223 self._read = False
1224 1224 self._opener = None
1225 1225
1226 1226 def read(self):
1227 1227 if self._read or self._opener is None:
1228 1228 return
1229 1229
1230 1230 try:
1231 1231 with self._opener('manifestfulltextcache') as fp:
1232 1232 set = super(manifestfulltextcache, self).__setitem__
1233 1233 # ignore trailing data, this is a cache, corruption is skipped
1234 1234 while True:
1235 1235 node = fp.read(20)
1236 1236 if len(node) < 20:
1237 1237 break
1238 1238 try:
1239 1239 size = struct.unpack('>L', fp.read(4))[0]
1240 1240 except struct.error:
1241 1241 break
1242 1242 value = bytearray(fp.read(size))
1243 1243 if len(value) != size:
1244 1244 break
1245 1245 set(node, value)
1246 1246 except IOError:
1247 1247 # the file is allowed to be missing
1248 1248 pass
1249 1249
1250 1250 self._read = True
1251 1251 self._dirty = False
1252 1252
1253 1253 def write(self):
1254 1254 if not self._dirty or self._opener is None:
1255 1255 return
1256 1256 # rotate backwards to the first used node
1257 1257 with self._opener(
1258 1258 'manifestfulltextcache', 'w', atomictemp=True, checkambig=True
1259 1259 ) as fp:
1260 1260 node = self._head.prev
1261 1261 while True:
1262 1262 if node.key in self._cache:
1263 1263 fp.write(node.key)
1264 1264 fp.write(struct.pack('>L', len(node.value)))
1265 1265 fp.write(node.value)
1266 1266 if node is self._head:
1267 1267 break
1268 1268 node = node.prev
1269 1269
1270 1270 def __len__(self):
1271 1271 if not self._read:
1272 1272 self.read()
1273 1273 return super(manifestfulltextcache, self).__len__()
1274 1274
1275 1275 def __contains__(self, k):
1276 1276 if not self._read:
1277 1277 self.read()
1278 1278 return super(manifestfulltextcache, self).__contains__(k)
1279 1279
1280 1280 def __iter__(self):
1281 1281 if not self._read:
1282 1282 self.read()
1283 1283 return super(manifestfulltextcache, self).__iter__()
1284 1284
1285 1285 def __getitem__(self, k):
1286 1286 if not self._read:
1287 1287 self.read()
1288 1288 # the cache lru order can change on read
1289 1289 setdirty = self._cache.get(k) is not self._head
1290 1290 value = super(manifestfulltextcache, self).__getitem__(k)
1291 1291 if setdirty:
1292 1292 self._dirty = True
1293 1293 return value
1294 1294
1295 1295 def __setitem__(self, k, v):
1296 1296 if not self._read:
1297 1297 self.read()
1298 1298 super(manifestfulltextcache, self).__setitem__(k, v)
1299 1299 self._dirty = True
1300 1300
1301 1301 def __delitem__(self, k):
1302 1302 if not self._read:
1303 1303 self.read()
1304 1304 super(manifestfulltextcache, self).__delitem__(k)
1305 1305 self._dirty = True
1306 1306
1307 1307 def get(self, k, default=None):
1308 1308 if not self._read:
1309 1309 self.read()
1310 1310 return super(manifestfulltextcache, self).get(k, default=default)
1311 1311
1312 1312 def clear(self, clear_persisted_data=False):
1313 1313 super(manifestfulltextcache, self).clear()
1314 1314 if clear_persisted_data:
1315 1315 self._dirty = True
1316 1316 self.write()
1317 1317 self._read = False
1318 1318
1319 1319 @interfaceutil.implementer(repository.imanifeststorage)
1320 1320 class manifestrevlog(object):
1321 1321 '''A revlog that stores manifest texts. This is responsible for caching the
1322 1322 full-text manifest contents.
1323 1323 '''
1324 1324 def __init__(self, opener, tree='', dirlogcache=None, indexfile=None,
1325 1325 treemanifest=False):
1326 1326 """Constructs a new manifest revlog
1327 1327
1328 1328 `indexfile` - used by extensions to have two manifests at once, like
1329 1329 when transitioning between flatmanifeset and treemanifests.
1330 1330
1331 1331 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1332 1332 options can also be used to make this a tree manifest revlog. The opener
1333 1333 option takes precedence, so if it is set to True, we ignore whatever
1334 1334 value is passed in to the constructor.
1335 1335 """
1336 1336 # During normal operations, we expect to deal with not more than four
1337 1337 # revs at a time (such as during commit --amend). When rebasing large
1338 1338 # stacks of commits, the number can go up, hence the config knob below.
1339 1339 cachesize = 4
1340 1340 optiontreemanifest = False
1341 1341 opts = getattr(opener, 'options', None)
1342 1342 if opts is not None:
1343 1343 cachesize = opts.get('manifestcachesize', cachesize)
1344 1344 optiontreemanifest = opts.get('treemanifest', False)
1345 1345
1346 1346 self._treeondisk = optiontreemanifest or treemanifest
1347 1347
1348 1348 self._fulltextcache = manifestfulltextcache(cachesize)
1349 1349
1350 1350 if tree:
1351 1351 assert self._treeondisk, 'opts is %r' % opts
1352 1352
1353 1353 if indexfile is None:
1354 1354 indexfile = '00manifest.i'
1355 1355 if tree:
1356 1356 indexfile = "meta/" + tree + indexfile
1357 1357
1358 1358 self.tree = tree
1359 1359
1360 1360 # The dirlogcache is kept on the root manifest log
1361 1361 if tree:
1362 1362 self._dirlogcache = dirlogcache
1363 1363 else:
1364 1364 self._dirlogcache = {'': self}
1365 1365
1366 1366 self._revlog = revlog.revlog(opener, indexfile,
1367 1367 # only root indexfile is cached
1368 1368 checkambig=not bool(tree),
1369 1369 mmaplargeindex=True)
1370 1370
1371 1371 self.index = self._revlog.index
1372 1372 self.version = self._revlog.version
1373 1373 self._generaldelta = self._revlog._generaldelta
1374 1374
1375 1375 def _setupmanifestcachehooks(self, repo):
1376 1376 """Persist the manifestfulltextcache on lock release"""
1377 1377 if not util.safehasattr(repo, '_lockref'):
1378 1378 return
1379 1379
1380 1380 self._fulltextcache._opener = repo.cachevfs
1381 1381 reporef = weakref.ref(repo)
1382 1382 manifestrevlogref = weakref.ref(self)
1383 1383
1384 1384 def persistmanifestcache():
1385 1385 repo = reporef()
1386 1386 self = manifestrevlogref()
1387 1387 if repo is None or self is None:
1388 1388 return
1389 1389 if repo.manifestlog.getstorage(b'') is not self:
1390 1390 # there's a different manifest in play now, abort
1391 1391 return
1392 1392 self._fulltextcache.write()
1393 1393
1394 1394 if repo._currentlock(repo._lockref) is not None:
1395 1395 repo._afterlock(persistmanifestcache)
1396 1396
1397 1397 @property
1398 1398 def fulltextcache(self):
1399 1399 return self._fulltextcache
1400 1400
1401 1401 def clearcaches(self, clear_persisted_data=False):
1402 1402 self._revlog.clearcaches()
1403 1403 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1404 1404 self._dirlogcache = {self.tree: self}
1405 1405
1406 1406 def dirlog(self, d):
1407 1407 if d:
1408 1408 assert self._treeondisk
1409 1409 if d not in self._dirlogcache:
1410 1410 mfrevlog = manifestrevlog(self.opener, d,
1411 1411 self._dirlogcache,
1412 1412 treemanifest=self._treeondisk)
1413 1413 self._dirlogcache[d] = mfrevlog
1414 1414 return self._dirlogcache[d]
1415 1415
1416 1416 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1417 1417 if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'):
1418 1418 # If our first parent is in the manifest cache, we can
1419 1419 # compute a delta here using properties we know about the
1420 1420 # manifest up-front, which may save time later for the
1421 1421 # revlog layer.
1422 1422
1423 1423 _checkforbidden(added)
1424 1424 # combine the changed lists into one sorted iterator
1425 1425 work = heapq.merge([(x, False) for x in added],
1426 1426 [(x, True) for x in removed])
1427 1427
1428 1428 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1429 1429 cachedelta = self._revlog.rev(p1), deltatext
1430 1430 text = util.buffer(arraytext)
1431 1431 n = self._revlog.addrevision(text, transaction, link, p1, p2,
1432 1432 cachedelta)
1433 1433 else:
1434 1434 # The first parent manifest isn't already loaded, so we'll
1435 1435 # just encode a fulltext of the manifest and pass that
1436 1436 # through to the revlog layer, and let it handle the delta
1437 1437 # process.
1438 1438 if self._treeondisk:
1439 1439 assert readtree, "readtree must be set for treemanifest writes"
1440 1440 m1 = readtree(self.tree, p1)
1441 1441 m2 = readtree(self.tree, p2)
1442 1442 n = self._addtree(m, transaction, link, m1, m2, readtree)
1443 1443 arraytext = None
1444 1444 else:
1445 1445 text = m.text()
1446 1446 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1447 1447 arraytext = bytearray(text)
1448 1448
1449 1449 if arraytext is not None:
1450 1450 self.fulltextcache[n] = arraytext
1451 1451
1452 1452 return n
1453 1453
1454 1454 def _addtree(self, m, transaction, link, m1, m2, readtree):
1455 1455 # If the manifest is unchanged compared to one parent,
1456 1456 # don't write a new revision
1457 1457 if self.tree != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(
1458 1458 m2)):
1459 1459 return m.node()
1460 1460 def writesubtree(subm, subp1, subp2):
1461 1461 sublog = self.dirlog(subm.dir())
1462 1462 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1463 1463 readtree=readtree)
1464 1464 m.writesubtrees(m1, m2, writesubtree)
1465 1465 text = m.dirtext()
1466 1466 n = None
1467 1467 if self.tree != '':
1468 1468 # Double-check whether contents are unchanged to one parent
1469 1469 if text == m1.dirtext():
1470 1470 n = m1.node()
1471 1471 elif text == m2.dirtext():
1472 1472 n = m2.node()
1473 1473
1474 1474 if not n:
1475 1475 n = self._revlog.addrevision(text, transaction, link, m1.node(),
1476 1476 m2.node())
1477 1477
1478 1478 # Save nodeid so parent manifest can calculate its nodeid
1479 1479 m.setnode(n)
1480 1480 return n
1481 1481
1482 1482 def __len__(self):
1483 1483 return len(self._revlog)
1484 1484
1485 1485 def __iter__(self):
1486 1486 return self._revlog.__iter__()
1487 1487
1488 1488 def rev(self, node):
1489 1489 return self._revlog.rev(node)
1490 1490
1491 1491 def node(self, rev):
1492 1492 return self._revlog.node(rev)
1493 1493
1494 1494 def lookup(self, value):
1495 1495 return self._revlog.lookup(value)
1496 1496
1497 1497 def parentrevs(self, rev):
1498 1498 return self._revlog.parentrevs(rev)
1499 1499
1500 1500 def parents(self, node):
1501 1501 return self._revlog.parents(node)
1502 1502
1503 1503 def linkrev(self, rev):
1504 1504 return self._revlog.linkrev(rev)
1505 1505
1506 1506 def checksize(self):
1507 1507 return self._revlog.checksize()
1508 1508
1509 1509 def revision(self, node, _df=None, raw=False):
1510 1510 return self._revlog.revision(node, _df=_df, raw=raw)
1511 1511
1512 1512 def revdiff(self, rev1, rev2):
1513 1513 return self._revlog.revdiff(rev1, rev2)
1514 1514
1515 1515 def cmp(self, node, text):
1516 1516 return self._revlog.cmp(node, text)
1517 1517
1518 1518 def deltaparent(self, rev):
1519 1519 return self._revlog.deltaparent(rev)
1520 1520
1521 1521 def emitrevisiondeltas(self, requests):
1522 1522 return self._revlog.emitrevisiondeltas(requests)
1523 1523
1524 1524 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1525 1525 return self._revlog.addgroup(deltas, linkmapper, transaction,
1526 1526 addrevisioncb=addrevisioncb)
1527 1527
1528 1528 def getstrippoint(self, minlink):
1529 1529 return self._revlog.getstrippoint(minlink)
1530 1530
1531 1531 def strip(self, minlink, transaction):
1532 1532 return self._revlog.strip(minlink, transaction)
1533 1533
1534 1534 def files(self):
1535 1535 return self._revlog.files()
1536 1536
1537 1537 def clone(self, tr, destrevlog, **kwargs):
1538 1538 if not isinstance(destrevlog, manifestrevlog):
1539 1539 raise error.ProgrammingError('expected manifestrevlog to clone()')
1540 1540
1541 1541 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1542 1542
1543 1543 @property
1544 1544 def indexfile(self):
1545 1545 return self._revlog.indexfile
1546 1546
1547 1547 @indexfile.setter
1548 1548 def indexfile(self, value):
1549 1549 self._revlog.indexfile = value
1550 1550
1551 1551 @property
1552 1552 def opener(self):
1553 1553 return self._revlog.opener
1554 1554
1555 1555 @opener.setter
1556 1556 def opener(self, value):
1557 1557 self._revlog.opener = value
1558 1558
1559 1559 @interfaceutil.implementer(repository.imanifestlog)
1560 1560 class manifestlog(object):
1561 1561 """A collection class representing the collection of manifest snapshots
1562 1562 referenced by commits in the repository.
1563 1563
1564 1564 In this situation, 'manifest' refers to the abstract concept of a snapshot
1565 1565 of the list of files in the given commit. Consumers of the output of this
1566 1566 class do not care about the implementation details of the actual manifests
1567 1567 they receive (i.e. tree or flat or lazily loaded, etc)."""
1568 1568 def __init__(self, opener, repo):
1569 1569 usetreemanifest = False
1570 1570 cachesize = 4
1571 1571
1572 1572 opts = getattr(opener, 'options', None)
1573 1573 if opts is not None:
1574 1574 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1575 1575 cachesize = opts.get('manifestcachesize', cachesize)
1576 1576
1577 1577 self._treemanifests = usetreemanifest
1578 1578
1579 1579 self._rootstore = repo._constructmanifest()
1580 1580 self._rootstore._setupmanifestcachehooks(repo)
1581 1581 self._narrowmatch = repo.narrowmatch()
1582 1582
1583 1583 # A cache of the manifestctx or treemanifestctx for each directory
1584 1584 self._dirmancache = {}
1585 1585 self._dirmancache[''] = util.lrucachedict(cachesize)
1586 1586
1587 1587 self._cachesize = cachesize
1588 1588
1589 1589 def __getitem__(self, node):
1590 1590 """Retrieves the manifest instance for the given node. Throws a
1591 1591 LookupError if not found.
1592 1592 """
1593 1593 return self.get('', node)
1594 1594
1595 1595 def get(self, tree, node, verify=True):
1596 1596 """Retrieves the manifest instance for the given node. Throws a
1597 1597 LookupError if not found.
1598 1598
1599 1599 `verify` - if True an exception will be thrown if the node is not in
1600 1600 the revlog
1601 1601 """
1602 1602 if node in self._dirmancache.get(tree, ()):
1603 1603 return self._dirmancache[tree][node]
1604 1604
1605 1605 if not self._narrowmatch.always():
1606 1606 if not self._narrowmatch.visitdir(tree[:-1] or '.'):
1607 1607 return excludeddirmanifestctx(tree, node)
1608 1608 if tree:
1609 1609 if self._rootstore._treeondisk:
1610 1610 if verify:
1611 1611 # Side-effect is LookupError is raised if node doesn't
1612 1612 # exist.
1613 1613 self.getstorage(tree).rev(node)
1614 1614
1615 1615 m = treemanifestctx(self, tree, node)
1616 1616 else:
1617 1617 raise error.Abort(
1618 1618 _("cannot ask for manifest directory '%s' in a flat "
1619 1619 "manifest") % tree)
1620 1620 else:
1621 1621 if verify:
1622 1622 # Side-effect is LookupError is raised if node doesn't exist.
1623 1623 self._rootstore.rev(node)
1624 1624
1625 1625 if self._treemanifests:
1626 1626 m = treemanifestctx(self, '', node)
1627 1627 else:
1628 1628 m = manifestctx(self, node)
1629 1629
1630 1630 if node != nullid:
1631 1631 mancache = self._dirmancache.get(tree)
1632 1632 if not mancache:
1633 1633 mancache = util.lrucachedict(self._cachesize)
1634 1634 self._dirmancache[tree] = mancache
1635 1635 mancache[node] = m
1636 1636 return m
1637 1637
1638 1638 def getstorage(self, tree):
1639 1639 return self._rootstore.dirlog(tree)
1640 1640
1641 1641 def clearcaches(self, clear_persisted_data=False):
1642 1642 self._dirmancache.clear()
1643 1643 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1644 1644
1645 1645 def rev(self, node):
1646 1646 return self._rootstore.rev(node)
1647 1647
1648 1648 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1649 1649 class memmanifestctx(object):
1650 1650 def __init__(self, manifestlog):
1651 1651 self._manifestlog = manifestlog
1652 1652 self._manifestdict = manifestdict()
1653 1653
1654 1654 def _storage(self):
1655 1655 return self._manifestlog.getstorage(b'')
1656 1656
1657 1657 def new(self):
1658 1658 return memmanifestctx(self._manifestlog)
1659 1659
1660 1660 def copy(self):
1661 1661 memmf = memmanifestctx(self._manifestlog)
1662 1662 memmf._manifestdict = self.read().copy()
1663 1663 return memmf
1664 1664
1665 1665 def read(self):
1666 1666 return self._manifestdict
1667 1667
1668 1668 def write(self, transaction, link, p1, p2, added, removed):
1669 1669 return self._storage().add(self._manifestdict, transaction, link,
1670 1670 p1, p2, added, removed)
1671 1671
1672 1672 @interfaceutil.implementer(repository.imanifestrevisionstored)
1673 1673 class manifestctx(object):
1674 1674 """A class representing a single revision of a manifest, including its
1675 1675 contents, its parent revs, and its linkrev.
1676 1676 """
1677 1677 def __init__(self, manifestlog, node):
1678 1678 self._manifestlog = manifestlog
1679 1679 self._data = None
1680 1680
1681 1681 self._node = node
1682 1682
1683 1683 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1684 1684 # but let's add it later when something needs it and we can load it
1685 1685 # lazily.
1686 1686 #self.p1, self.p2 = store.parents(node)
1687 1687 #rev = store.rev(node)
1688 1688 #self.linkrev = store.linkrev(rev)
1689 1689
1690 1690 def _storage(self):
1691 1691 return self._manifestlog.getstorage(b'')
1692 1692
1693 1693 def node(self):
1694 1694 return self._node
1695 1695
1696 1696 def new(self):
1697 1697 return memmanifestctx(self._manifestlog)
1698 1698
1699 1699 def copy(self):
1700 1700 memmf = memmanifestctx(self._manifestlog)
1701 1701 memmf._manifestdict = self.read().copy()
1702 1702 return memmf
1703 1703
1704 1704 @propertycache
1705 1705 def parents(self):
1706 1706 return self._storage().parents(self._node)
1707 1707
1708 1708 def read(self):
1709 1709 if self._data is None:
1710 1710 if self._node == nullid:
1711 1711 self._data = manifestdict()
1712 1712 else:
1713 1713 store = self._storage()
1714 1714 if self._node in store.fulltextcache:
1715 1715 text = pycompat.bytestr(store.fulltextcache[self._node])
1716 1716 else:
1717 1717 text = store.revision(self._node)
1718 1718 arraytext = bytearray(text)
1719 1719 store.fulltextcache[self._node] = arraytext
1720 1720 self._data = manifestdict(text)
1721 1721 return self._data
1722 1722
1723 1723 def readfast(self, shallow=False):
1724 1724 '''Calls either readdelta or read, based on which would be less work.
1725 1725 readdelta is called if the delta is against the p1, and therefore can be
1726 1726 read quickly.
1727 1727
1728 1728 If `shallow` is True, nothing changes since this is a flat manifest.
1729 1729 '''
1730 1730 store = self._storage()
1731 1731 r = store.rev(self._node)
1732 1732 deltaparent = store.deltaparent(r)
1733 1733 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
1734 1734 return self.readdelta()
1735 1735 return self.read()
1736 1736
1737 1737 def readdelta(self, shallow=False):
1738 1738 '''Returns a manifest containing just the entries that are present
1739 1739 in this manifest, but not in its p1 manifest. This is efficient to read
1740 1740 if the revlog delta is already p1.
1741 1741
1742 1742 Changing the value of `shallow` has no effect on flat manifests.
1743 1743 '''
1744 1744 store = self._storage()
1745 1745 r = store.rev(self._node)
1746 1746 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1747 1747 return manifestdict(d)
1748 1748
1749 1749 def find(self, key):
1750 1750 return self.read().find(key)
1751 1751
1752 1752 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1753 1753 class memtreemanifestctx(object):
1754 1754 def __init__(self, manifestlog, dir=''):
1755 1755 self._manifestlog = manifestlog
1756 1756 self._dir = dir
1757 1757 self._treemanifest = treemanifest()
1758 1758
1759 1759 def _storage(self):
1760 1760 return self._manifestlog.getstorage(b'')
1761 1761
1762 1762 def new(self, dir=''):
1763 1763 return memtreemanifestctx(self._manifestlog, dir=dir)
1764 1764
1765 1765 def copy(self):
1766 1766 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1767 1767 memmf._treemanifest = self._treemanifest.copy()
1768 1768 return memmf
1769 1769
1770 1770 def read(self):
1771 1771 return self._treemanifest
1772 1772
1773 1773 def write(self, transaction, link, p1, p2, added, removed):
1774 1774 def readtree(dir, node):
1775 1775 return self._manifestlog.get(dir, node).read()
1776 1776 return self._storage().add(self._treemanifest, transaction, link,
1777 1777 p1, p2, added, removed, readtree=readtree)
1778 1778
1779 1779 @interfaceutil.implementer(repository.imanifestrevisionstored)
1780 1780 class treemanifestctx(object):
1781 1781 def __init__(self, manifestlog, dir, node):
1782 1782 self._manifestlog = manifestlog
1783 1783 self._dir = dir
1784 1784 self._data = None
1785 1785
1786 1786 self._node = node
1787 1787
1788 1788 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1789 1789 # we can instantiate treemanifestctx objects for directories we don't
1790 1790 # have on disk.
1791 1791 #self.p1, self.p2 = store.parents(node)
1792 1792 #rev = store.rev(node)
1793 1793 #self.linkrev = store.linkrev(rev)
1794 1794
1795 1795 def _storage(self):
1796 1796 narrowmatch = self._manifestlog._narrowmatch
1797 1797 if not narrowmatch.always():
1798 1798 if not narrowmatch.visitdir(self._dir[:-1] or '.'):
1799 1799 return excludedmanifestrevlog(self._dir)
1800 1800 return self._manifestlog.getstorage(self._dir)
1801 1801
1802 1802 def read(self):
1803 1803 if self._data is None:
1804 1804 store = self._storage()
1805 1805 if self._node == nullid:
1806 1806 self._data = treemanifest()
1807 1807 # TODO accessing non-public API
1808 1808 elif store._treeondisk:
1809 1809 m = treemanifest(dir=self._dir)
1810 1810 def gettext():
1811 1811 return store.revision(self._node)
1812 1812 def readsubtree(dir, subm):
1813 1813 # Set verify to False since we need to be able to create
1814 1814 # subtrees for trees that don't exist on disk.
1815 1815 return self._manifestlog.get(dir, subm, verify=False).read()
1816 1816 m.read(gettext, readsubtree)
1817 1817 m.setnode(self._node)
1818 1818 self._data = m
1819 1819 else:
1820 1820 if self._node in store.fulltextcache:
1821 1821 text = pycompat.bytestr(store.fulltextcache[self._node])
1822 1822 else:
1823 1823 text = store.revision(self._node)
1824 1824 arraytext = bytearray(text)
1825 1825 store.fulltextcache[self._node] = arraytext
1826 1826 self._data = treemanifest(dir=self._dir, text=text)
1827 1827
1828 1828 return self._data
1829 1829
1830 1830 def node(self):
1831 1831 return self._node
1832 1832
1833 1833 def new(self, dir=''):
1834 1834 return memtreemanifestctx(self._manifestlog, dir=dir)
1835 1835
1836 1836 def copy(self):
1837 1837 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1838 1838 memmf._treemanifest = self.read().copy()
1839 1839 return memmf
1840 1840
1841 1841 @propertycache
1842 1842 def parents(self):
1843 1843 return self._storage().parents(self._node)
1844 1844
1845 1845 def readdelta(self, shallow=False):
1846 1846 '''Returns a manifest containing just the entries that are present
1847 1847 in this manifest, but not in its p1 manifest. This is efficient to read
1848 1848 if the revlog delta is already p1.
1849 1849
1850 1850 If `shallow` is True, this will read the delta for this directory,
1851 1851 without recursively reading subdirectory manifests. Instead, any
1852 1852 subdirectory entry will be reported as it appears in the manifest, i.e.
1853 1853 the subdirectory will be reported among files and distinguished only by
1854 1854 its 't' flag.
1855 1855 '''
1856 1856 store = self._storage()
1857 1857 if shallow:
1858 1858 r = store.rev(self._node)
1859 1859 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1860 1860 return manifestdict(d)
1861 1861 else:
1862 1862 # Need to perform a slow delta
1863 1863 r0 = store.deltaparent(store.rev(self._node))
1864 1864 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
1865 1865 m1 = self.read()
1866 1866 md = treemanifest(dir=self._dir)
1867 1867 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1868 1868 if n1:
1869 1869 md[f] = n1
1870 1870 if fl1:
1871 1871 md.setflag(f, fl1)
1872 1872 return md
1873 1873
1874 1874 def readfast(self, shallow=False):
1875 1875 '''Calls either readdelta or read, based on which would be less work.
1876 1876 readdelta is called if the delta is against the p1, and therefore can be
1877 1877 read quickly.
1878 1878
1879 1879 If `shallow` is True, it only returns the entries from this manifest,
1880 1880 and not any submanifests.
1881 1881 '''
1882 1882 store = self._storage()
1883 1883 r = store.rev(self._node)
1884 1884 deltaparent = store.deltaparent(r)
1885 1885 if (deltaparent != nullrev and
1886 1886 deltaparent in store.parentrevs(r)):
1887 1887 return self.readdelta(shallow=shallow)
1888 1888
1889 1889 if shallow:
1890 1890 return manifestdict(store.revision(self._node))
1891 1891 else:
1892 1892 return self.read()
1893 1893
1894 1894 def find(self, key):
1895 1895 return self.read().find(key)
1896 1896
1897 1897 class excludeddir(treemanifest):
1898 1898 """Stand-in for a directory that is excluded from the repository.
1899 1899
1900 1900 With narrowing active on a repository that uses treemanifests,
1901 1901 some of the directory revlogs will be excluded from the resulting
1902 1902 clone. This is a huge storage win for clients, but means we need
1903 1903 some sort of pseudo-manifest to surface to internals so we can
1904 1904 detect a merge conflict outside the narrowspec. That's what this
1905 1905 class is: it stands in for a directory whose node is known, but
1906 1906 whose contents are unknown.
1907 1907 """
1908 1908 def __init__(self, dir, node):
1909 1909 super(excludeddir, self).__init__(dir)
1910 1910 self._node = node
1911 1911 # Add an empty file, which will be included by iterators and such,
1912 1912 # appearing as the directory itself (i.e. something like "dir/")
1913 1913 self._files[''] = node
1914 1914 self._flags[''] = 't'
1915 1915
1916 1916 # Manifests outside the narrowspec should never be modified, so avoid
1917 1917 # copying. This makes a noticeable difference when there are very many
1918 1918 # directories outside the narrowspec. Also, it makes sense for the copy to
1919 1919 # be of the same type as the original, which would not happen with the
1920 1920 # super type's copy().
1921 1921 def copy(self):
1922 1922 return self
1923 1923
1924 1924 class excludeddirmanifestctx(treemanifestctx):
1925 1925 """context wrapper for excludeddir - see that docstring for rationale"""
1926 1926 def __init__(self, dir, node):
1927 1927 self._dir = dir
1928 1928 self._node = node
1929 1929
1930 1930 def read(self):
1931 1931 return excludeddir(self._dir, self._node)
1932 1932
1933 1933 def write(self, *args):
1934 1934 raise error.ProgrammingError(
1935 1935 'attempt to write manifest from excluded dir %s' % self._dir)
1936 1936
1937 1937 class excludedmanifestrevlog(manifestrevlog):
1938 1938 """Stand-in for excluded treemanifest revlogs.
1939 1939
1940 1940 When narrowing is active on a treemanifest repository, we'll have
1941 1941 references to directories we can't see due to the revlog being
1942 1942 skipped. This class exists to conform to the manifestrevlog
1943 1943 interface for those directories and proactively prevent writes to
1944 1944 outside the narrowspec.
1945 1945 """
1946 1946
1947 1947 def __init__(self, dir):
1948 1948 self._dir = dir
1949 1949
1950 1950 def __len__(self):
1951 1951 raise error.ProgrammingError(
1952 1952 'attempt to get length of excluded dir %s' % self._dir)
1953 1953
1954 1954 def rev(self, node):
1955 1955 raise error.ProgrammingError(
1956 1956 'attempt to get rev from excluded dir %s' % self._dir)
1957 1957
1958 1958 def linkrev(self, node):
1959 1959 raise error.ProgrammingError(
1960 1960 'attempt to get linkrev from excluded dir %s' % self._dir)
1961 1961
1962 1962 def node(self, rev):
1963 1963 raise error.ProgrammingError(
1964 1964 'attempt to get node from excluded dir %s' % self._dir)
1965 1965
1966 1966 def add(self, *args, **kwargs):
1967 1967 # We should never write entries in dirlogs outside the narrow clone.
1968 1968 # However, the method still gets called from writesubtree() in
1969 1969 # _addtree(), so we need to handle it. We should possibly make that
1970 1970 # avoid calling add() with a clean manifest (_dirty is always False
1971 1971 # in excludeddir instances).
1972 1972 pass
General Comments 0
You need to be logged in to leave comments. Login now