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