##// END OF EJS Templates
py3: return False early while checking whether None is a key in lazymanifest...
Pulkit Goyal -
r34347:05167447 default
parent child Browse files
Show More
@@ -1,1641 +1,1643
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 if key is None:
446 return False
445 447 return key in self._lm
446 448
447 449 def __delitem__(self, key):
448 450 del self._lm[key]
449 451
450 452 def __iter__(self):
451 453 return self._lm.__iter__()
452 454
453 455 def iterkeys(self):
454 456 return self._lm.iterkeys()
455 457
456 458 def keys(self):
457 459 return list(self.iterkeys())
458 460
459 461 def filesnotin(self, m2, match=None):
460 462 '''Set of files in this manifest that are not in the other'''
461 463 if match:
462 464 m1 = self.matches(match)
463 465 m2 = m2.matches(match)
464 466 return m1.filesnotin(m2)
465 467 diff = self.diff(m2)
466 468 files = set(filepath
467 469 for filepath, hashflags in diff.iteritems()
468 470 if hashflags[1][0] is None)
469 471 return files
470 472
471 473 @propertycache
472 474 def _dirs(self):
473 475 return util.dirs(self)
474 476
475 477 def dirs(self):
476 478 return self._dirs
477 479
478 480 def hasdir(self, dir):
479 481 return dir in self._dirs
480 482
481 483 def _filesfastpath(self, match):
482 484 '''Checks whether we can correctly and quickly iterate over matcher
483 485 files instead of over manifest files.'''
484 486 files = match.files()
485 487 return (len(files) < 100 and (match.isexact() or
486 488 (match.prefix() and all(fn in self for fn in files))))
487 489
488 490 def walk(self, match):
489 491 '''Generates matching file names.
490 492
491 493 Equivalent to manifest.matches(match).iterkeys(), but without creating
492 494 an entirely new manifest.
493 495
494 496 It also reports nonexistent files by marking them bad with match.bad().
495 497 '''
496 498 if match.always():
497 499 for f in iter(self):
498 500 yield f
499 501 return
500 502
501 503 fset = set(match.files())
502 504
503 505 # avoid the entire walk if we're only looking for specific files
504 506 if self._filesfastpath(match):
505 507 for fn in sorted(fset):
506 508 yield fn
507 509 return
508 510
509 511 for fn in self:
510 512 if fn in fset:
511 513 # specified pattern is the exact name
512 514 fset.remove(fn)
513 515 if match(fn):
514 516 yield fn
515 517
516 518 # for dirstate.walk, files=['.'] means "walk the whole tree".
517 519 # follow that here, too
518 520 fset.discard('.')
519 521
520 522 for fn in sorted(fset):
521 523 if not self.hasdir(fn):
522 524 match.bad(fn, None)
523 525
524 526 def matches(self, match):
525 527 '''generate a new manifest filtered by the match argument'''
526 528 if match.always():
527 529 return self.copy()
528 530
529 531 if self._filesfastpath(match):
530 532 m = manifestdict()
531 533 lm = self._lm
532 534 for fn in match.files():
533 535 if fn in lm:
534 536 m._lm[fn] = lm[fn]
535 537 return m
536 538
537 539 m = manifestdict()
538 540 m._lm = self._lm.filtercopy(match)
539 541 return m
540 542
541 543 def diff(self, m2, match=None, clean=False):
542 544 '''Finds changes between the current manifest and m2.
543 545
544 546 Args:
545 547 m2: the manifest to which this manifest should be compared.
546 548 clean: if true, include files unchanged between these manifests
547 549 with a None value in the returned dictionary.
548 550
549 551 The result is returned as a dict with filename as key and
550 552 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
551 553 nodeid in the current/other manifest and fl1/fl2 is the flag
552 554 in the current/other manifest. Where the file does not exist,
553 555 the nodeid will be None and the flags will be the empty
554 556 string.
555 557 '''
556 558 if match:
557 559 m1 = self.matches(match)
558 560 m2 = m2.matches(match)
559 561 return m1.diff(m2, clean=clean)
560 562 return self._lm.diff(m2._lm, clean)
561 563
562 564 def setflag(self, key, flag):
563 565 self._lm[key] = self[key], flag
564 566
565 567 def get(self, key, default=None):
566 568 try:
567 569 return self._lm[key][0]
568 570 except KeyError:
569 571 return default
570 572
571 573 def flags(self, key, default=''):
572 574 try:
573 575 return self._lm[key][1]
574 576 except KeyError:
575 577 return default
576 578
577 579 def copy(self):
578 580 c = manifestdict()
579 581 c._lm = self._lm.copy()
580 582 return c
581 583
582 584 def items(self):
583 585 return (x[:2] for x in self._lm.iterentries())
584 586
585 587 iteritems = items
586 588
587 589 def iterentries(self):
588 590 return self._lm.iterentries()
589 591
590 592 def text(self, usemanifestv2=False):
591 593 if usemanifestv2:
592 594 return _textv2(self._lm.iterentries())
593 595 else:
594 596 # use (probably) native version for v1
595 597 return self._lm.text()
596 598
597 599 def fastdelta(self, base, changes):
598 600 """Given a base manifest text as a bytearray and a list of changes
599 601 relative to that text, compute a delta that can be used by revlog.
600 602 """
601 603 delta = []
602 604 dstart = None
603 605 dend = None
604 606 dline = [""]
605 607 start = 0
606 608 # zero copy representation of base as a buffer
607 609 addbuf = util.buffer(base)
608 610
609 611 changes = list(changes)
610 612 if len(changes) < 1000:
611 613 # start with a readonly loop that finds the offset of
612 614 # each line and creates the deltas
613 615 for f, todelete in changes:
614 616 # bs will either be the index of the item or the insert point
615 617 start, end = _msearch(addbuf, f, start)
616 618 if not todelete:
617 619 h, fl = self._lm[f]
618 620 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
619 621 else:
620 622 if start == end:
621 623 # item we want to delete was not found, error out
622 624 raise AssertionError(
623 625 _("failed to remove %s from manifest") % f)
624 626 l = ""
625 627 if dstart is not None and dstart <= start and dend >= start:
626 628 if dend < end:
627 629 dend = end
628 630 if l:
629 631 dline.append(l)
630 632 else:
631 633 if dstart is not None:
632 634 delta.append([dstart, dend, "".join(dline)])
633 635 dstart = start
634 636 dend = end
635 637 dline = [l]
636 638
637 639 if dstart is not None:
638 640 delta.append([dstart, dend, "".join(dline)])
639 641 # apply the delta to the base, and get a delta for addrevision
640 642 deltatext, arraytext = _addlistdelta(base, delta)
641 643 else:
642 644 # For large changes, it's much cheaper to just build the text and
643 645 # diff it.
644 646 arraytext = bytearray(self.text())
645 647 deltatext = mdiff.textdiff(
646 648 util.buffer(base), util.buffer(arraytext))
647 649
648 650 return arraytext, deltatext
649 651
650 652 def _msearch(m, s, lo=0, hi=None):
651 653 '''return a tuple (start, end) that says where to find s within m.
652 654
653 655 If the string is found m[start:end] are the line containing
654 656 that string. If start == end the string was not found and
655 657 they indicate the proper sorted insertion point.
656 658
657 659 m should be a buffer, a memoryview or a byte string.
658 660 s is a byte string'''
659 661 def advance(i, c):
660 662 while i < lenm and m[i:i + 1] != c:
661 663 i += 1
662 664 return i
663 665 if not s:
664 666 return (lo, lo)
665 667 lenm = len(m)
666 668 if not hi:
667 669 hi = lenm
668 670 while lo < hi:
669 671 mid = (lo + hi) // 2
670 672 start = mid
671 673 while start > 0 and m[start - 1:start] != '\n':
672 674 start -= 1
673 675 end = advance(start, '\0')
674 676 if bytes(m[start:end]) < s:
675 677 # we know that after the null there are 40 bytes of sha1
676 678 # this translates to the bisect lo = mid + 1
677 679 lo = advance(end + 40, '\n') + 1
678 680 else:
679 681 # this translates to the bisect hi = mid
680 682 hi = start
681 683 end = advance(lo, '\0')
682 684 found = m[lo:end]
683 685 if s == found:
684 686 # we know that after the null there are 40 bytes of sha1
685 687 end = advance(end + 40, '\n')
686 688 return (lo, end + 1)
687 689 else:
688 690 return (lo, lo)
689 691
690 692 def _checkforbidden(l):
691 693 """Check filenames for illegal characters."""
692 694 for f in l:
693 695 if '\n' in f or '\r' in f:
694 696 raise error.RevlogError(
695 697 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
696 698
697 699
698 700 # apply the changes collected during the bisect loop to our addlist
699 701 # return a delta suitable for addrevision
700 702 def _addlistdelta(addlist, x):
701 703 # for large addlist arrays, building a new array is cheaper
702 704 # than repeatedly modifying the existing one
703 705 currentposition = 0
704 706 newaddlist = bytearray()
705 707
706 708 for start, end, content in x:
707 709 newaddlist += addlist[currentposition:start]
708 710 if content:
709 711 newaddlist += bytearray(content)
710 712
711 713 currentposition = end
712 714
713 715 newaddlist += addlist[currentposition:]
714 716
715 717 deltatext = "".join(struct.pack(">lll", start, end, len(content))
716 718 + content for start, end, content in x)
717 719 return deltatext, newaddlist
718 720
719 721 def _splittopdir(f):
720 722 if '/' in f:
721 723 dir, subpath = f.split('/', 1)
722 724 return dir + '/', subpath
723 725 else:
724 726 return '', f
725 727
726 728 _noop = lambda s: None
727 729
728 730 class treemanifest(object):
729 731 def __init__(self, dir='', text=''):
730 732 self._dir = dir
731 733 self._node = revlog.nullid
732 734 self._loadfunc = _noop
733 735 self._copyfunc = _noop
734 736 self._dirty = False
735 737 self._dirs = {}
736 738 # Using _lazymanifest here is a little slower than plain old dicts
737 739 self._files = {}
738 740 self._flags = {}
739 741 if text:
740 742 def readsubtree(subdir, subm):
741 743 raise AssertionError('treemanifest constructor only accepts '
742 744 'flat manifests')
743 745 self.parse(text, readsubtree)
744 746 self._dirty = True # Mark flat manifest dirty after parsing
745 747
746 748 def _subpath(self, path):
747 749 return self._dir + path
748 750
749 751 def __len__(self):
750 752 self._load()
751 753 size = len(self._files)
752 754 for m in self._dirs.values():
753 755 size += m.__len__()
754 756 return size
755 757
756 758 def _isempty(self):
757 759 self._load() # for consistency; already loaded by all callers
758 760 return (not self._files and (not self._dirs or
759 761 all(m._isempty() for m in self._dirs.values())))
760 762
761 763 def __repr__(self):
762 764 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
763 765 (self._dir, revlog.hex(self._node),
764 766 bool(self._loadfunc is _noop),
765 767 self._dirty, id(self)))
766 768
767 769 def dir(self):
768 770 '''The directory that this tree manifest represents, including a
769 771 trailing '/'. Empty string for the repo root directory.'''
770 772 return self._dir
771 773
772 774 def node(self):
773 775 '''This node of this instance. nullid for unsaved instances. Should
774 776 be updated when the instance is read or written from a revlog.
775 777 '''
776 778 assert not self._dirty
777 779 return self._node
778 780
779 781 def setnode(self, node):
780 782 self._node = node
781 783 self._dirty = False
782 784
783 785 def iterentries(self):
784 786 self._load()
785 787 for p, n in sorted(itertools.chain(self._dirs.items(),
786 788 self._files.items())):
787 789 if p in self._files:
788 790 yield self._subpath(p), n, self._flags.get(p, '')
789 791 else:
790 792 for x in n.iterentries():
791 793 yield x
792 794
793 795 def items(self):
794 796 self._load()
795 797 for p, n in sorted(itertools.chain(self._dirs.items(),
796 798 self._files.items())):
797 799 if p in self._files:
798 800 yield self._subpath(p), n
799 801 else:
800 802 for f, sn in n.iteritems():
801 803 yield f, sn
802 804
803 805 iteritems = items
804 806
805 807 def iterkeys(self):
806 808 self._load()
807 809 for p in sorted(itertools.chain(self._dirs, self._files)):
808 810 if p in self._files:
809 811 yield self._subpath(p)
810 812 else:
811 813 for f in self._dirs[p].iterkeys():
812 814 yield f
813 815
814 816 def keys(self):
815 817 return list(self.iterkeys())
816 818
817 819 def __iter__(self):
818 820 return self.iterkeys()
819 821
820 822 def __contains__(self, f):
821 823 if f is None:
822 824 return False
823 825 self._load()
824 826 dir, subpath = _splittopdir(f)
825 827 if dir:
826 828 if dir not in self._dirs:
827 829 return False
828 830 return self._dirs[dir].__contains__(subpath)
829 831 else:
830 832 return f in self._files
831 833
832 834 def get(self, f, default=None):
833 835 self._load()
834 836 dir, subpath = _splittopdir(f)
835 837 if dir:
836 838 if dir not in self._dirs:
837 839 return default
838 840 return self._dirs[dir].get(subpath, default)
839 841 else:
840 842 return self._files.get(f, default)
841 843
842 844 def __getitem__(self, f):
843 845 self._load()
844 846 dir, subpath = _splittopdir(f)
845 847 if dir:
846 848 return self._dirs[dir].__getitem__(subpath)
847 849 else:
848 850 return self._files[f]
849 851
850 852 def flags(self, f):
851 853 self._load()
852 854 dir, subpath = _splittopdir(f)
853 855 if dir:
854 856 if dir not in self._dirs:
855 857 return ''
856 858 return self._dirs[dir].flags(subpath)
857 859 else:
858 860 if f in self._dirs:
859 861 return ''
860 862 return self._flags.get(f, '')
861 863
862 864 def find(self, f):
863 865 self._load()
864 866 dir, subpath = _splittopdir(f)
865 867 if dir:
866 868 return self._dirs[dir].find(subpath)
867 869 else:
868 870 return self._files[f], self._flags.get(f, '')
869 871
870 872 def __delitem__(self, f):
871 873 self._load()
872 874 dir, subpath = _splittopdir(f)
873 875 if dir:
874 876 self._dirs[dir].__delitem__(subpath)
875 877 # If the directory is now empty, remove it
876 878 if self._dirs[dir]._isempty():
877 879 del self._dirs[dir]
878 880 else:
879 881 del self._files[f]
880 882 if f in self._flags:
881 883 del self._flags[f]
882 884 self._dirty = True
883 885
884 886 def __setitem__(self, f, n):
885 887 assert n is not None
886 888 self._load()
887 889 dir, subpath = _splittopdir(f)
888 890 if dir:
889 891 if dir not in self._dirs:
890 892 self._dirs[dir] = treemanifest(self._subpath(dir))
891 893 self._dirs[dir].__setitem__(subpath, n)
892 894 else:
893 895 self._files[f] = n[:21] # to match manifestdict's behavior
894 896 self._dirty = True
895 897
896 898 def _load(self):
897 899 if self._loadfunc is not _noop:
898 900 lf, self._loadfunc = self._loadfunc, _noop
899 901 lf(self)
900 902 elif self._copyfunc is not _noop:
901 903 cf, self._copyfunc = self._copyfunc, _noop
902 904 cf(self)
903 905
904 906 def setflag(self, f, flags):
905 907 """Set the flags (symlink, executable) for path f."""
906 908 self._load()
907 909 dir, subpath = _splittopdir(f)
908 910 if dir:
909 911 if dir not in self._dirs:
910 912 self._dirs[dir] = treemanifest(self._subpath(dir))
911 913 self._dirs[dir].setflag(subpath, flags)
912 914 else:
913 915 self._flags[f] = flags
914 916 self._dirty = True
915 917
916 918 def copy(self):
917 919 copy = treemanifest(self._dir)
918 920 copy._node = self._node
919 921 copy._dirty = self._dirty
920 922 if self._copyfunc is _noop:
921 923 def _copyfunc(s):
922 924 self._load()
923 925 for d in self._dirs:
924 926 s._dirs[d] = self._dirs[d].copy()
925 927 s._files = dict.copy(self._files)
926 928 s._flags = dict.copy(self._flags)
927 929 if self._loadfunc is _noop:
928 930 _copyfunc(copy)
929 931 else:
930 932 copy._copyfunc = _copyfunc
931 933 else:
932 934 copy._copyfunc = self._copyfunc
933 935 return copy
934 936
935 937 def filesnotin(self, m2, match=None):
936 938 '''Set of files in this manifest that are not in the other'''
937 939 if match:
938 940 m1 = self.matches(match)
939 941 m2 = m2.matches(match)
940 942 return m1.filesnotin(m2)
941 943
942 944 files = set()
943 945 def _filesnotin(t1, t2):
944 946 if t1._node == t2._node and not t1._dirty and not t2._dirty:
945 947 return
946 948 t1._load()
947 949 t2._load()
948 950 for d, m1 in t1._dirs.iteritems():
949 951 if d in t2._dirs:
950 952 m2 = t2._dirs[d]
951 953 _filesnotin(m1, m2)
952 954 else:
953 955 files.update(m1.iterkeys())
954 956
955 957 for fn in t1._files.iterkeys():
956 958 if fn not in t2._files:
957 959 files.add(t1._subpath(fn))
958 960
959 961 _filesnotin(self, m2)
960 962 return files
961 963
962 964 @propertycache
963 965 def _alldirs(self):
964 966 return util.dirs(self)
965 967
966 968 def dirs(self):
967 969 return self._alldirs
968 970
969 971 def hasdir(self, dir):
970 972 self._load()
971 973 topdir, subdir = _splittopdir(dir)
972 974 if topdir:
973 975 if topdir in self._dirs:
974 976 return self._dirs[topdir].hasdir(subdir)
975 977 return False
976 978 return (dir + '/') in self._dirs
977 979
978 980 def walk(self, match):
979 981 '''Generates matching file names.
980 982
981 983 Equivalent to manifest.matches(match).iterkeys(), but without creating
982 984 an entirely new manifest.
983 985
984 986 It also reports nonexistent files by marking them bad with match.bad().
985 987 '''
986 988 if match.always():
987 989 for f in iter(self):
988 990 yield f
989 991 return
990 992
991 993 fset = set(match.files())
992 994
993 995 for fn in self._walk(match):
994 996 if fn in fset:
995 997 # specified pattern is the exact name
996 998 fset.remove(fn)
997 999 yield fn
998 1000
999 1001 # for dirstate.walk, files=['.'] means "walk the whole tree".
1000 1002 # follow that here, too
1001 1003 fset.discard('.')
1002 1004
1003 1005 for fn in sorted(fset):
1004 1006 if not self.hasdir(fn):
1005 1007 match.bad(fn, None)
1006 1008
1007 1009 def _walk(self, match):
1008 1010 '''Recursively generates matching file names for walk().'''
1009 1011 if not match.visitdir(self._dir[:-1] or '.'):
1010 1012 return
1011 1013
1012 1014 # yield this dir's files and walk its submanifests
1013 1015 self._load()
1014 1016 for p in sorted(self._dirs.keys() + self._files.keys()):
1015 1017 if p in self._files:
1016 1018 fullp = self._subpath(p)
1017 1019 if match(fullp):
1018 1020 yield fullp
1019 1021 else:
1020 1022 for f in self._dirs[p]._walk(match):
1021 1023 yield f
1022 1024
1023 1025 def matches(self, match):
1024 1026 '''generate a new manifest filtered by the match argument'''
1025 1027 if match.always():
1026 1028 return self.copy()
1027 1029
1028 1030 return self._matches(match)
1029 1031
1030 1032 def _matches(self, match):
1031 1033 '''recursively generate a new manifest filtered by the match argument.
1032 1034 '''
1033 1035
1034 1036 visit = match.visitdir(self._dir[:-1] or '.')
1035 1037 if visit == 'all':
1036 1038 return self.copy()
1037 1039 ret = treemanifest(self._dir)
1038 1040 if not visit:
1039 1041 return ret
1040 1042
1041 1043 self._load()
1042 1044 for fn in self._files:
1043 1045 fullp = self._subpath(fn)
1044 1046 if not match(fullp):
1045 1047 continue
1046 1048 ret._files[fn] = self._files[fn]
1047 1049 if fn in self._flags:
1048 1050 ret._flags[fn] = self._flags[fn]
1049 1051
1050 1052 for dir, subm in self._dirs.iteritems():
1051 1053 m = subm._matches(match)
1052 1054 if not m._isempty():
1053 1055 ret._dirs[dir] = m
1054 1056
1055 1057 if not ret._isempty():
1056 1058 ret._dirty = True
1057 1059 return ret
1058 1060
1059 1061 def diff(self, m2, match=None, clean=False):
1060 1062 '''Finds changes between the current manifest and m2.
1061 1063
1062 1064 Args:
1063 1065 m2: the manifest to which this manifest should be compared.
1064 1066 clean: if true, include files unchanged between these manifests
1065 1067 with a None value in the returned dictionary.
1066 1068
1067 1069 The result is returned as a dict with filename as key and
1068 1070 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1069 1071 nodeid in the current/other manifest and fl1/fl2 is the flag
1070 1072 in the current/other manifest. Where the file does not exist,
1071 1073 the nodeid will be None and the flags will be the empty
1072 1074 string.
1073 1075 '''
1074 1076 if match:
1075 1077 m1 = self.matches(match)
1076 1078 m2 = m2.matches(match)
1077 1079 return m1.diff(m2, clean=clean)
1078 1080 result = {}
1079 1081 emptytree = treemanifest()
1080 1082 def _diff(t1, t2):
1081 1083 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1082 1084 return
1083 1085 t1._load()
1084 1086 t2._load()
1085 1087 for d, m1 in t1._dirs.iteritems():
1086 1088 m2 = t2._dirs.get(d, emptytree)
1087 1089 _diff(m1, m2)
1088 1090
1089 1091 for d, m2 in t2._dirs.iteritems():
1090 1092 if d not in t1._dirs:
1091 1093 _diff(emptytree, m2)
1092 1094
1093 1095 for fn, n1 in t1._files.iteritems():
1094 1096 fl1 = t1._flags.get(fn, '')
1095 1097 n2 = t2._files.get(fn, None)
1096 1098 fl2 = t2._flags.get(fn, '')
1097 1099 if n1 != n2 or fl1 != fl2:
1098 1100 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1099 1101 elif clean:
1100 1102 result[t1._subpath(fn)] = None
1101 1103
1102 1104 for fn, n2 in t2._files.iteritems():
1103 1105 if fn not in t1._files:
1104 1106 fl2 = t2._flags.get(fn, '')
1105 1107 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1106 1108
1107 1109 _diff(self, m2)
1108 1110 return result
1109 1111
1110 1112 def unmodifiedsince(self, m2):
1111 1113 return not self._dirty and not m2._dirty and self._node == m2._node
1112 1114
1113 1115 def parse(self, text, readsubtree):
1114 1116 for f, n, fl in _parse(text):
1115 1117 if fl == 't':
1116 1118 f = f + '/'
1117 1119 self._dirs[f] = readsubtree(self._subpath(f), n)
1118 1120 elif '/' in f:
1119 1121 # This is a flat manifest, so use __setitem__ and setflag rather
1120 1122 # than assigning directly to _files and _flags, so we can
1121 1123 # assign a path in a subdirectory, and to mark dirty (compared
1122 1124 # to nullid).
1123 1125 self[f] = n
1124 1126 if fl:
1125 1127 self.setflag(f, fl)
1126 1128 else:
1127 1129 # Assigning to _files and _flags avoids marking as dirty,
1128 1130 # and should be a little faster.
1129 1131 self._files[f] = n
1130 1132 if fl:
1131 1133 self._flags[f] = fl
1132 1134
1133 1135 def text(self, usemanifestv2=False):
1134 1136 """Get the full data of this manifest as a bytestring."""
1135 1137 self._load()
1136 1138 return _text(self.iterentries(), usemanifestv2)
1137 1139
1138 1140 def dirtext(self, usemanifestv2=False):
1139 1141 """Get the full data of this directory as a bytestring. Make sure that
1140 1142 any submanifests have been written first, so their nodeids are correct.
1141 1143 """
1142 1144 self._load()
1143 1145 flags = self.flags
1144 1146 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1145 1147 files = [(f, self._files[f], flags(f)) for f in self._files]
1146 1148 return _text(sorted(dirs + files), usemanifestv2)
1147 1149
1148 1150 def read(self, gettext, readsubtree):
1149 1151 def _load_for_read(s):
1150 1152 s.parse(gettext(), readsubtree)
1151 1153 s._dirty = False
1152 1154 self._loadfunc = _load_for_read
1153 1155
1154 1156 def writesubtrees(self, m1, m2, writesubtree):
1155 1157 self._load() # for consistency; should never have any effect here
1156 1158 m1._load()
1157 1159 m2._load()
1158 1160 emptytree = treemanifest()
1159 1161 for d, subm in self._dirs.iteritems():
1160 1162 subp1 = m1._dirs.get(d, emptytree)._node
1161 1163 subp2 = m2._dirs.get(d, emptytree)._node
1162 1164 if subp1 == revlog.nullid:
1163 1165 subp1, subp2 = subp2, subp1
1164 1166 writesubtree(subm, subp1, subp2)
1165 1167
1166 1168 def walksubtrees(self, matcher=None):
1167 1169 """Returns an iterator of the subtrees of this manifest, including this
1168 1170 manifest itself.
1169 1171
1170 1172 If `matcher` is provided, it only returns subtrees that match.
1171 1173 """
1172 1174 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1173 1175 return
1174 1176 if not matcher or matcher(self._dir[:-1]):
1175 1177 yield self
1176 1178
1177 1179 self._load()
1178 1180 for d, subm in self._dirs.iteritems():
1179 1181 for subtree in subm.walksubtrees(matcher=matcher):
1180 1182 yield subtree
1181 1183
1182 1184 class manifestrevlog(revlog.revlog):
1183 1185 '''A revlog that stores manifest texts. This is responsible for caching the
1184 1186 full-text manifest contents.
1185 1187 '''
1186 1188 def __init__(self, opener, dir='', dirlogcache=None, indexfile=None,
1187 1189 treemanifest=False):
1188 1190 """Constructs a new manifest revlog
1189 1191
1190 1192 `indexfile` - used by extensions to have two manifests at once, like
1191 1193 when transitioning between flatmanifeset and treemanifests.
1192 1194
1193 1195 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1194 1196 options can also be used to make this a tree manifest revlog. The opener
1195 1197 option takes precedence, so if it is set to True, we ignore whatever
1196 1198 value is passed in to the constructor.
1197 1199 """
1198 1200 # During normal operations, we expect to deal with not more than four
1199 1201 # revs at a time (such as during commit --amend). When rebasing large
1200 1202 # stacks of commits, the number can go up, hence the config knob below.
1201 1203 cachesize = 4
1202 1204 optiontreemanifest = False
1203 1205 usemanifestv2 = False
1204 1206 opts = getattr(opener, 'options', None)
1205 1207 if opts is not None:
1206 1208 cachesize = opts.get('manifestcachesize', cachesize)
1207 1209 optiontreemanifest = opts.get('treemanifest', False)
1208 1210 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1209 1211
1210 1212 self._treeondisk = optiontreemanifest or treemanifest
1211 1213 self._usemanifestv2 = usemanifestv2
1212 1214
1213 1215 self._fulltextcache = util.lrucachedict(cachesize)
1214 1216
1215 1217 if dir:
1216 1218 assert self._treeondisk, 'opts is %r' % opts
1217 1219 if not dir.endswith('/'):
1218 1220 dir = dir + '/'
1219 1221
1220 1222 if indexfile is None:
1221 1223 indexfile = '00manifest.i'
1222 1224 if dir:
1223 1225 indexfile = "meta/" + dir + indexfile
1224 1226
1225 1227 self._dir = dir
1226 1228 # The dirlogcache is kept on the root manifest log
1227 1229 if dir:
1228 1230 self._dirlogcache = dirlogcache
1229 1231 else:
1230 1232 self._dirlogcache = {'': self}
1231 1233
1232 1234 super(manifestrevlog, self).__init__(opener, indexfile,
1233 1235 # only root indexfile is cached
1234 1236 checkambig=not bool(dir),
1235 1237 mmaplargeindex=True)
1236 1238
1237 1239 @property
1238 1240 def fulltextcache(self):
1239 1241 return self._fulltextcache
1240 1242
1241 1243 def clearcaches(self):
1242 1244 super(manifestrevlog, self).clearcaches()
1243 1245 self._fulltextcache.clear()
1244 1246 self._dirlogcache = {'': self}
1245 1247
1246 1248 def dirlog(self, dir):
1247 1249 if dir:
1248 1250 assert self._treeondisk
1249 1251 if dir not in self._dirlogcache:
1250 1252 mfrevlog = manifestrevlog(self.opener, dir,
1251 1253 self._dirlogcache,
1252 1254 treemanifest=self._treeondisk)
1253 1255 self._dirlogcache[dir] = mfrevlog
1254 1256 return self._dirlogcache[dir]
1255 1257
1256 1258 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1257 1259 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1258 1260 and not self._usemanifestv2):
1259 1261 # If our first parent is in the manifest cache, we can
1260 1262 # compute a delta here using properties we know about the
1261 1263 # manifest up-front, which may save time later for the
1262 1264 # revlog layer.
1263 1265
1264 1266 _checkforbidden(added)
1265 1267 # combine the changed lists into one sorted iterator
1266 1268 work = heapq.merge([(x, False) for x in added],
1267 1269 [(x, True) for x in removed])
1268 1270
1269 1271 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1270 1272 cachedelta = self.rev(p1), deltatext
1271 1273 text = util.buffer(arraytext)
1272 1274 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1273 1275 else:
1274 1276 # The first parent manifest isn't already loaded, so we'll
1275 1277 # just encode a fulltext of the manifest and pass that
1276 1278 # through to the revlog layer, and let it handle the delta
1277 1279 # process.
1278 1280 if self._treeondisk:
1279 1281 assert readtree, "readtree must be set for treemanifest writes"
1280 1282 m1 = readtree(self._dir, p1)
1281 1283 m2 = readtree(self._dir, p2)
1282 1284 n = self._addtree(m, transaction, link, m1, m2, readtree)
1283 1285 arraytext = None
1284 1286 else:
1285 1287 text = m.text(self._usemanifestv2)
1286 1288 n = self.addrevision(text, transaction, link, p1, p2)
1287 1289 arraytext = bytearray(text)
1288 1290
1289 1291 if arraytext is not None:
1290 1292 self.fulltextcache[n] = arraytext
1291 1293
1292 1294 return n
1293 1295
1294 1296 def _addtree(self, m, transaction, link, m1, m2, readtree):
1295 1297 # If the manifest is unchanged compared to one parent,
1296 1298 # don't write a new revision
1297 1299 if self._dir != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(m2)):
1298 1300 return m.node()
1299 1301 def writesubtree(subm, subp1, subp2):
1300 1302 sublog = self.dirlog(subm.dir())
1301 1303 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1302 1304 readtree=readtree)
1303 1305 m.writesubtrees(m1, m2, writesubtree)
1304 1306 text = m.dirtext(self._usemanifestv2)
1305 1307 n = None
1306 1308 if self._dir != '':
1307 1309 # Double-check whether contents are unchanged to one parent
1308 1310 if text == m1.dirtext(self._usemanifestv2):
1309 1311 n = m1.node()
1310 1312 elif text == m2.dirtext(self._usemanifestv2):
1311 1313 n = m2.node()
1312 1314
1313 1315 if not n:
1314 1316 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1315 1317
1316 1318 # Save nodeid so parent manifest can calculate its nodeid
1317 1319 m.setnode(n)
1318 1320 return n
1319 1321
1320 1322 class manifestlog(object):
1321 1323 """A collection class representing the collection of manifest snapshots
1322 1324 referenced by commits in the repository.
1323 1325
1324 1326 In this situation, 'manifest' refers to the abstract concept of a snapshot
1325 1327 of the list of files in the given commit. Consumers of the output of this
1326 1328 class do not care about the implementation details of the actual manifests
1327 1329 they receive (i.e. tree or flat or lazily loaded, etc)."""
1328 1330 def __init__(self, opener, repo):
1329 1331 usetreemanifest = False
1330 1332 cachesize = 4
1331 1333
1332 1334 opts = getattr(opener, 'options', None)
1333 1335 if opts is not None:
1334 1336 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1335 1337 cachesize = opts.get('manifestcachesize', cachesize)
1336 1338 self._treeinmem = usetreemanifest
1337 1339
1338 1340 self._revlog = repo._constructmanifest()
1339 1341
1340 1342 # A cache of the manifestctx or treemanifestctx for each directory
1341 1343 self._dirmancache = {}
1342 1344 self._dirmancache[''] = util.lrucachedict(cachesize)
1343 1345
1344 1346 self.cachesize = cachesize
1345 1347
1346 1348 def __getitem__(self, node):
1347 1349 """Retrieves the manifest instance for the given node. Throws a
1348 1350 LookupError if not found.
1349 1351 """
1350 1352 return self.get('', node)
1351 1353
1352 1354 def get(self, dir, node, verify=True):
1353 1355 """Retrieves the manifest instance for the given node. Throws a
1354 1356 LookupError if not found.
1355 1357
1356 1358 `verify` - if True an exception will be thrown if the node is not in
1357 1359 the revlog
1358 1360 """
1359 1361 if node in self._dirmancache.get(dir, ()):
1360 1362 return self._dirmancache[dir][node]
1361 1363
1362 1364 if dir:
1363 1365 if self._revlog._treeondisk:
1364 1366 if verify:
1365 1367 dirlog = self._revlog.dirlog(dir)
1366 1368 if node not in dirlog.nodemap:
1367 1369 raise LookupError(node, dirlog.indexfile,
1368 1370 _('no node'))
1369 1371 m = treemanifestctx(self, dir, node)
1370 1372 else:
1371 1373 raise error.Abort(
1372 1374 _("cannot ask for manifest directory '%s' in a flat "
1373 1375 "manifest") % dir)
1374 1376 else:
1375 1377 if verify:
1376 1378 if node not in self._revlog.nodemap:
1377 1379 raise LookupError(node, self._revlog.indexfile,
1378 1380 _('no node'))
1379 1381 if self._treeinmem:
1380 1382 m = treemanifestctx(self, '', node)
1381 1383 else:
1382 1384 m = manifestctx(self, node)
1383 1385
1384 1386 if node != revlog.nullid:
1385 1387 mancache = self._dirmancache.get(dir)
1386 1388 if not mancache:
1387 1389 mancache = util.lrucachedict(self.cachesize)
1388 1390 self._dirmancache[dir] = mancache
1389 1391 mancache[node] = m
1390 1392 return m
1391 1393
1392 1394 def clearcaches(self):
1393 1395 self._dirmancache.clear()
1394 1396 self._revlog.clearcaches()
1395 1397
1396 1398 class memmanifestctx(object):
1397 1399 def __init__(self, manifestlog):
1398 1400 self._manifestlog = manifestlog
1399 1401 self._manifestdict = manifestdict()
1400 1402
1401 1403 def _revlog(self):
1402 1404 return self._manifestlog._revlog
1403 1405
1404 1406 def new(self):
1405 1407 return memmanifestctx(self._manifestlog)
1406 1408
1407 1409 def copy(self):
1408 1410 memmf = memmanifestctx(self._manifestlog)
1409 1411 memmf._manifestdict = self.read().copy()
1410 1412 return memmf
1411 1413
1412 1414 def read(self):
1413 1415 return self._manifestdict
1414 1416
1415 1417 def write(self, transaction, link, p1, p2, added, removed):
1416 1418 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1417 1419 added, removed)
1418 1420
1419 1421 class manifestctx(object):
1420 1422 """A class representing a single revision of a manifest, including its
1421 1423 contents, its parent revs, and its linkrev.
1422 1424 """
1423 1425 def __init__(self, manifestlog, node):
1424 1426 self._manifestlog = manifestlog
1425 1427 self._data = None
1426 1428
1427 1429 self._node = node
1428 1430
1429 1431 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1430 1432 # but let's add it later when something needs it and we can load it
1431 1433 # lazily.
1432 1434 #self.p1, self.p2 = revlog.parents(node)
1433 1435 #rev = revlog.rev(node)
1434 1436 #self.linkrev = revlog.linkrev(rev)
1435 1437
1436 1438 def _revlog(self):
1437 1439 return self._manifestlog._revlog
1438 1440
1439 1441 def node(self):
1440 1442 return self._node
1441 1443
1442 1444 def new(self):
1443 1445 return memmanifestctx(self._manifestlog)
1444 1446
1445 1447 def copy(self):
1446 1448 memmf = memmanifestctx(self._manifestlog)
1447 1449 memmf._manifestdict = self.read().copy()
1448 1450 return memmf
1449 1451
1450 1452 @propertycache
1451 1453 def parents(self):
1452 1454 return self._revlog().parents(self._node)
1453 1455
1454 1456 def read(self):
1455 1457 if self._data is None:
1456 1458 if self._node == revlog.nullid:
1457 1459 self._data = manifestdict()
1458 1460 else:
1459 1461 rl = self._revlog()
1460 1462 text = rl.revision(self._node)
1461 1463 arraytext = bytearray(text)
1462 1464 rl._fulltextcache[self._node] = arraytext
1463 1465 self._data = manifestdict(text)
1464 1466 return self._data
1465 1467
1466 1468 def readfast(self, shallow=False):
1467 1469 '''Calls either readdelta or read, based on which would be less work.
1468 1470 readdelta is called if the delta is against the p1, and therefore can be
1469 1471 read quickly.
1470 1472
1471 1473 If `shallow` is True, nothing changes since this is a flat manifest.
1472 1474 '''
1473 1475 rl = self._revlog()
1474 1476 r = rl.rev(self._node)
1475 1477 deltaparent = rl.deltaparent(r)
1476 1478 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1477 1479 return self.readdelta()
1478 1480 return self.read()
1479 1481
1480 1482 def readdelta(self, shallow=False):
1481 1483 '''Returns a manifest containing just the entries that are present
1482 1484 in this manifest, but not in its p1 manifest. This is efficient to read
1483 1485 if the revlog delta is already p1.
1484 1486
1485 1487 Changing the value of `shallow` has no effect on flat manifests.
1486 1488 '''
1487 1489 revlog = self._revlog()
1488 1490 if revlog._usemanifestv2:
1489 1491 # Need to perform a slow delta
1490 1492 r0 = revlog.deltaparent(revlog.rev(self._node))
1491 1493 m0 = self._manifestlog[revlog.node(r0)].read()
1492 1494 m1 = self.read()
1493 1495 md = manifestdict()
1494 1496 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1495 1497 if n1:
1496 1498 md[f] = n1
1497 1499 if fl1:
1498 1500 md.setflag(f, fl1)
1499 1501 return md
1500 1502
1501 1503 r = revlog.rev(self._node)
1502 1504 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1503 1505 return manifestdict(d)
1504 1506
1505 1507 def find(self, key):
1506 1508 return self.read().find(key)
1507 1509
1508 1510 class memtreemanifestctx(object):
1509 1511 def __init__(self, manifestlog, dir=''):
1510 1512 self._manifestlog = manifestlog
1511 1513 self._dir = dir
1512 1514 self._treemanifest = treemanifest()
1513 1515
1514 1516 def _revlog(self):
1515 1517 return self._manifestlog._revlog
1516 1518
1517 1519 def new(self, dir=''):
1518 1520 return memtreemanifestctx(self._manifestlog, dir=dir)
1519 1521
1520 1522 def copy(self):
1521 1523 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1522 1524 memmf._treemanifest = self._treemanifest.copy()
1523 1525 return memmf
1524 1526
1525 1527 def read(self):
1526 1528 return self._treemanifest
1527 1529
1528 1530 def write(self, transaction, link, p1, p2, added, removed):
1529 1531 def readtree(dir, node):
1530 1532 return self._manifestlog.get(dir, node).read()
1531 1533 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1532 1534 added, removed, readtree=readtree)
1533 1535
1534 1536 class treemanifestctx(object):
1535 1537 def __init__(self, manifestlog, dir, node):
1536 1538 self._manifestlog = manifestlog
1537 1539 self._dir = dir
1538 1540 self._data = None
1539 1541
1540 1542 self._node = node
1541 1543
1542 1544 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1543 1545 # we can instantiate treemanifestctx objects for directories we don't
1544 1546 # have on disk.
1545 1547 #self.p1, self.p2 = revlog.parents(node)
1546 1548 #rev = revlog.rev(node)
1547 1549 #self.linkrev = revlog.linkrev(rev)
1548 1550
1549 1551 def _revlog(self):
1550 1552 return self._manifestlog._revlog.dirlog(self._dir)
1551 1553
1552 1554 def read(self):
1553 1555 if self._data is None:
1554 1556 rl = self._revlog()
1555 1557 if self._node == revlog.nullid:
1556 1558 self._data = treemanifest()
1557 1559 elif rl._treeondisk:
1558 1560 m = treemanifest(dir=self._dir)
1559 1561 def gettext():
1560 1562 return rl.revision(self._node)
1561 1563 def readsubtree(dir, subm):
1562 1564 # Set verify to False since we need to be able to create
1563 1565 # subtrees for trees that don't exist on disk.
1564 1566 return self._manifestlog.get(dir, subm, verify=False).read()
1565 1567 m.read(gettext, readsubtree)
1566 1568 m.setnode(self._node)
1567 1569 self._data = m
1568 1570 else:
1569 1571 text = rl.revision(self._node)
1570 1572 arraytext = bytearray(text)
1571 1573 rl.fulltextcache[self._node] = arraytext
1572 1574 self._data = treemanifest(dir=self._dir, text=text)
1573 1575
1574 1576 return self._data
1575 1577
1576 1578 def node(self):
1577 1579 return self._node
1578 1580
1579 1581 def new(self, dir=''):
1580 1582 return memtreemanifestctx(self._manifestlog, dir=dir)
1581 1583
1582 1584 def copy(self):
1583 1585 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1584 1586 memmf._treemanifest = self.read().copy()
1585 1587 return memmf
1586 1588
1587 1589 @propertycache
1588 1590 def parents(self):
1589 1591 return self._revlog().parents(self._node)
1590 1592
1591 1593 def readdelta(self, shallow=False):
1592 1594 '''Returns a manifest containing just the entries that are present
1593 1595 in this manifest, but not in its p1 manifest. This is efficient to read
1594 1596 if the revlog delta is already p1.
1595 1597
1596 1598 If `shallow` is True, this will read the delta for this directory,
1597 1599 without recursively reading subdirectory manifests. Instead, any
1598 1600 subdirectory entry will be reported as it appears in the manifest, i.e.
1599 1601 the subdirectory will be reported among files and distinguished only by
1600 1602 its 't' flag.
1601 1603 '''
1602 1604 revlog = self._revlog()
1603 1605 if shallow and not revlog._usemanifestv2:
1604 1606 r = revlog.rev(self._node)
1605 1607 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1606 1608 return manifestdict(d)
1607 1609 else:
1608 1610 # Need to perform a slow delta
1609 1611 r0 = revlog.deltaparent(revlog.rev(self._node))
1610 1612 m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
1611 1613 m1 = self.read()
1612 1614 md = treemanifest(dir=self._dir)
1613 1615 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1614 1616 if n1:
1615 1617 md[f] = n1
1616 1618 if fl1:
1617 1619 md.setflag(f, fl1)
1618 1620 return md
1619 1621
1620 1622 def readfast(self, shallow=False):
1621 1623 '''Calls either readdelta or read, based on which would be less work.
1622 1624 readdelta is called if the delta is against the p1, and therefore can be
1623 1625 read quickly.
1624 1626
1625 1627 If `shallow` is True, it only returns the entries from this manifest,
1626 1628 and not any submanifests.
1627 1629 '''
1628 1630 rl = self._revlog()
1629 1631 r = rl.rev(self._node)
1630 1632 deltaparent = rl.deltaparent(r)
1631 1633 if (deltaparent != revlog.nullrev and
1632 1634 deltaparent in rl.parentrevs(r)):
1633 1635 return self.readdelta(shallow=shallow)
1634 1636
1635 1637 if shallow:
1636 1638 return manifestdict(rl.revision(self._node))
1637 1639 else:
1638 1640 return self.read()
1639 1641
1640 1642 def find(self, key):
1641 1643 return self.read().find(key)
General Comments 0
You need to be logged in to leave comments. Login now