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