##// END OF EJS Templates
manifest: don't store None in fulltextcache...
Martin von Zweigbergk -
r30209:9d06b65c default
parent child Browse files
Show More
@@ -1,1530 +1,1532 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 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 self.fulltextcache[n] = arraytext
1213 if arraytext is not None:
1214 self.fulltextcache[n] = arraytext
1214 1215
1215 1216 return n
1216 1217
1217 1218 def _addtree(self, m, transaction, link, m1, m2):
1218 1219 # If the manifest is unchanged compared to one parent,
1219 1220 # don't write a new revision
1220 1221 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1221 1222 return m.node()
1222 1223 def writesubtree(subm, subp1, subp2):
1223 1224 sublog = self.dirlog(subm.dir())
1224 1225 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1225 1226 m.writesubtrees(m1, m2, writesubtree)
1226 1227 text = m.dirtext(self._usemanifestv2)
1227 1228 # Double-check whether contents are unchanged to one parent
1228 1229 if text == m1.dirtext(self._usemanifestv2):
1229 1230 n = m1.node()
1230 1231 elif text == m2.dirtext(self._usemanifestv2):
1231 1232 n = m2.node()
1232 1233 else:
1233 1234 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1234 1235 # Save nodeid so parent manifest can calculate its nodeid
1235 1236 m.setnode(n)
1236 1237 return n
1237 1238
1238 1239 class manifestlog(object):
1239 1240 """A collection class representing the collection of manifest snapshots
1240 1241 referenced by commits in the repository.
1241 1242
1242 1243 In this situation, 'manifest' refers to the abstract concept of a snapshot
1243 1244 of the list of files in the given commit. Consumers of the output of this
1244 1245 class do not care about the implementation details of the actual manifests
1245 1246 they receive (i.e. tree or flat or lazily loaded, etc)."""
1246 1247 def __init__(self, opener, repo):
1247 1248 self._repo = repo
1248 1249
1249 1250 usetreemanifest = False
1250 1251
1251 1252 opts = getattr(opener, 'options', None)
1252 1253 if opts is not None:
1253 1254 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1254 1255 self._treeinmem = usetreemanifest
1255 1256
1256 1257 # We'll separate this into it's own cache once oldmanifest is no longer
1257 1258 # used
1258 1259 self._mancache = repo.manifest._mancache
1259 1260
1260 1261 @property
1261 1262 def _revlog(self):
1262 1263 return self._repo.manifest
1263 1264
1264 1265 def __getitem__(self, node):
1265 1266 """Retrieves the manifest instance for the given node. Throws a KeyError
1266 1267 if not found.
1267 1268 """
1268 1269 if node in self._mancache:
1269 1270 cachemf = self._mancache[node]
1270 1271 # The old manifest may put non-ctx manifests in the cache, so skip
1271 1272 # those since they don't implement the full api.
1272 1273 if (isinstance(cachemf, manifestctx) or
1273 1274 isinstance(cachemf, treemanifestctx)):
1274 1275 return cachemf
1275 1276
1276 1277 if self._treeinmem:
1277 1278 m = treemanifestctx(self._revlog, '', node)
1278 1279 else:
1279 1280 m = manifestctx(self._revlog, node)
1280 1281 if node != revlog.nullid:
1281 1282 self._mancache[node] = m
1282 1283 return m
1283 1284
1284 1285 def add(self, m, transaction, link, p1, p2, added, removed):
1285 1286 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1286 1287
1287 1288 class manifestctx(object):
1288 1289 """A class representing a single revision of a manifest, including its
1289 1290 contents, its parent revs, and its linkrev.
1290 1291 """
1291 1292 def __init__(self, revlog, node):
1292 1293 self._revlog = revlog
1293 1294 self._data = None
1294 1295
1295 1296 self._node = node
1296 1297
1297 1298 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1298 1299 # but let's add it later when something needs it and we can load it
1299 1300 # lazily.
1300 1301 #self.p1, self.p2 = revlog.parents(node)
1301 1302 #rev = revlog.rev(node)
1302 1303 #self.linkrev = revlog.linkrev(rev)
1303 1304
1304 1305 def node(self):
1305 1306 return self._node
1306 1307
1307 1308 def read(self):
1308 1309 if not self._data:
1309 1310 if self._node == revlog.nullid:
1310 1311 self._data = manifestdict()
1311 1312 else:
1312 1313 text = self._revlog.revision(self._node)
1313 1314 arraytext = array.array('c', text)
1314 1315 self._revlog._fulltextcache[self._node] = arraytext
1315 1316 self._data = manifestdict(text)
1316 1317 return self._data
1317 1318
1318 1319 def readfast(self):
1319 1320 rl = self._revlog
1320 1321 r = rl.rev(self._node)
1321 1322 deltaparent = rl.deltaparent(r)
1322 1323 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1323 1324 return self.readdelta()
1324 1325 return self.read()
1325 1326
1326 1327 def readdelta(self):
1327 1328 revlog = self._revlog
1328 1329 if revlog._usemanifestv2:
1329 1330 # Need to perform a slow delta
1330 1331 r0 = revlog.deltaparent(revlog.rev(self._node))
1331 1332 m0 = manifestctx(revlog, revlog.node(r0)).read()
1332 1333 m1 = self.read()
1333 1334 md = manifestdict()
1334 1335 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1335 1336 if n1:
1336 1337 md[f] = n1
1337 1338 if fl1:
1338 1339 md.setflag(f, fl1)
1339 1340 return md
1340 1341
1341 1342 r = revlog.rev(self._node)
1342 1343 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1343 1344 return manifestdict(d)
1344 1345
1345 1346 class treemanifestctx(object):
1346 1347 def __init__(self, revlog, dir, node):
1347 1348 revlog = revlog.dirlog(dir)
1348 1349 self._revlog = revlog
1349 1350 self._dir = dir
1350 1351 self._data = None
1351 1352
1352 1353 self._node = node
1353 1354
1354 1355 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1355 1356 # we can instantiate treemanifestctx objects for directories we don't
1356 1357 # have on disk.
1357 1358 #self.p1, self.p2 = revlog.parents(node)
1358 1359 #rev = revlog.rev(node)
1359 1360 #self.linkrev = revlog.linkrev(rev)
1360 1361
1361 1362 def read(self):
1362 1363 if not self._data:
1363 1364 if self._node == revlog.nullid:
1364 1365 self._data = treemanifest()
1365 1366 elif self._revlog._treeondisk:
1366 1367 m = treemanifest(dir=self._dir)
1367 1368 def gettext():
1368 1369 return self._revlog.revision(self._node)
1369 1370 def readsubtree(dir, subm):
1370 1371 return treemanifestctx(self._revlog, dir, subm).read()
1371 1372 m.read(gettext, readsubtree)
1372 1373 m.setnode(self._node)
1373 1374 self._data = m
1374 1375 else:
1375 1376 text = self._revlog.revision(self._node)
1376 1377 arraytext = array.array('c', text)
1377 1378 self._revlog.fulltextcache[self._node] = arraytext
1378 1379 self._data = treemanifest(dir=self._dir, text=text)
1379 1380
1380 1381 return self._data
1381 1382
1382 1383 def node(self):
1383 1384 return self._node
1384 1385
1385 1386 def readdelta(self):
1386 1387 # Need to perform a slow delta
1387 1388 revlog = self._revlog
1388 1389 r0 = revlog.deltaparent(revlog.rev(self._node))
1389 1390 m0 = treemanifestctx(revlog, self._dir, revlog.node(r0)).read()
1390 1391 m1 = self.read()
1391 1392 md = treemanifest(dir=self._dir)
1392 1393 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1393 1394 if n1:
1394 1395 md[f] = n1
1395 1396 if fl1:
1396 1397 md.setflag(f, fl1)
1397 1398 return md
1398 1399
1399 1400 def readfast(self):
1400 1401 rl = self._revlog
1401 1402 r = rl.rev(self._node)
1402 1403 deltaparent = rl.deltaparent(r)
1403 1404 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1404 1405 return self.readdelta()
1405 1406 return self.read()
1406 1407
1407 1408 class manifest(manifestrevlog):
1408 1409 def __init__(self, opener, dir='', dirlogcache=None):
1409 1410 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1410 1411 manifest.manifest only. External users should create a root manifest
1411 1412 log with manifest.manifest(opener) and call dirlog() on it.
1412 1413 '''
1413 1414 # During normal operations, we expect to deal with not more than four
1414 1415 # revs at a time (such as during commit --amend). When rebasing large
1415 1416 # stacks of commits, the number can go up, hence the config knob below.
1416 1417 cachesize = 4
1417 1418 usetreemanifest = False
1418 1419 opts = getattr(opener, 'options', None)
1419 1420 if opts is not None:
1420 1421 cachesize = opts.get('manifestcachesize', cachesize)
1421 1422 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1422 1423 self._mancache = util.lrucachedict(cachesize)
1423 1424 self._treeinmem = usetreemanifest
1424 1425 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1425 1426
1426 1427 def _newmanifest(self, data=''):
1427 1428 if self._treeinmem:
1428 1429 return treemanifest(self._dir, data)
1429 1430 return manifestdict(data)
1430 1431
1431 1432 def dirlog(self, dir):
1432 1433 """This overrides the base revlog implementation to allow construction
1433 1434 'manifest' types instead of manifestrevlog types. This is only needed
1434 1435 until we migrate off the 'manifest' type."""
1435 1436 if dir:
1436 1437 assert self._treeondisk
1437 1438 if dir not in self._dirlogcache:
1438 1439 self._dirlogcache[dir] = manifest(self.opener, dir,
1439 1440 self._dirlogcache)
1440 1441 return self._dirlogcache[dir]
1441 1442
1442 1443 def _slowreaddelta(self, node):
1443 1444 r0 = self.deltaparent(self.rev(node))
1444 1445 m0 = self.read(self.node(r0))
1445 1446 m1 = self.read(node)
1446 1447 md = self._newmanifest()
1447 1448 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1448 1449 if n1:
1449 1450 md[f] = n1
1450 1451 if fl1:
1451 1452 md.setflag(f, fl1)
1452 1453 return md
1453 1454
1454 1455 def readdelta(self, node):
1455 1456 if self._usemanifestv2 or self._treeondisk:
1456 1457 return self._slowreaddelta(node)
1457 1458 r = self.rev(node)
1458 1459 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1459 1460 return self._newmanifest(d)
1460 1461
1461 1462 def readshallowdelta(self, node):
1462 1463 '''For flat manifests, this is the same as readdelta(). For
1463 1464 treemanifests, this will read the delta for this revlog's directory,
1464 1465 without recursively reading subdirectory manifests. Instead, any
1465 1466 subdirectory entry will be reported as it appears in the manifests, i.e.
1466 1467 the subdirectory will be reported among files and distinguished only by
1467 1468 its 't' flag.'''
1468 1469 if not self._treeondisk:
1469 1470 return self.readdelta(node)
1470 1471 if self._usemanifestv2:
1471 1472 raise error.Abort(
1472 1473 _("readshallowdelta() not implemented for manifestv2"))
1473 1474 r = self.rev(node)
1474 1475 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1475 1476 return manifestdict(d)
1476 1477
1477 1478 def readshallowfast(self, node):
1478 1479 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1479 1480 '''
1480 1481 r = self.rev(node)
1481 1482 deltaparent = self.deltaparent(r)
1482 1483 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1483 1484 return self.readshallowdelta(node)
1484 1485 return self.readshallow(node)
1485 1486
1486 1487 def read(self, node):
1487 1488 if node == revlog.nullid:
1488 1489 return self._newmanifest() # don't upset local cache
1489 1490 if node in self._mancache:
1490 1491 cached = self._mancache[node]
1491 1492 if (isinstance(cached, manifestctx) or
1492 1493 isinstance(cached, treemanifestctx)):
1493 1494 cached = cached.read()
1494 1495 return cached
1495 1496 if self._treeondisk:
1496 1497 def gettext():
1497 1498 return self.revision(node)
1498 1499 def readsubtree(dir, subm):
1499 1500 return self.dirlog(dir).read(subm)
1500 1501 m = self._newmanifest()
1501 1502 m.read(gettext, readsubtree)
1502 1503 m.setnode(node)
1503 1504 arraytext = None
1504 1505 else:
1505 1506 text = self.revision(node)
1506 1507 m = self._newmanifest(text)
1507 1508 arraytext = array.array('c', text)
1508 1509 self._mancache[node] = m
1509 self.fulltextcache[node] = arraytext
1510 if arraytext is not None:
1511 self.fulltextcache[node] = arraytext
1510 1512 return m
1511 1513
1512 1514 def readshallow(self, node):
1513 1515 '''Reads the manifest in this directory. When using flat manifests,
1514 1516 this manifest will generally have files in subdirectories in it. Does
1515 1517 not cache the manifest as the callers generally do not read the same
1516 1518 version twice.'''
1517 1519 return manifestdict(self.revision(node))
1518 1520
1519 1521 def find(self, node, f):
1520 1522 '''look up entry for a single file efficiently.
1521 1523 return (node, flags) pair if found, (None, None) if not.'''
1522 1524 m = self.read(node)
1523 1525 try:
1524 1526 return m.find(f)
1525 1527 except KeyError:
1526 1528 return None, None
1527 1529
1528 1530 def clearcaches(self):
1529 1531 super(manifest, self).clearcaches()
1530 1532 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now