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