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