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