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