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