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