comparison mercurial/manifest.py @ 24803:e89f909edffa stable 3.4-rc

merge default into stable for 3.4 freeze
author Matt Mackall <mpm@selenic.com>
date Thu, 16 Apr 2015 20:57:51 -0500
parents 055b3cbe6c57
children d9832a12a06e
comparison
equal deleted inserted replaced
24753:612ed41ae359 24803:e89f909edffa
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from i18n import _ 8 from i18n import _
9 import mdiff, parsers, error, revlog, util 9 import mdiff, parsers, error, revlog, util
10 import array, struct 10 import array, struct
11 11 import os
12 class manifestdict(dict): 12
13 def __init__(self, mapping=None, flags=None): 13 propertycache = util.propertycache
14 if mapping is None: 14
15 mapping = {} 15 def _parsev1(data):
16 if flags is None: 16 # This method does a little bit of excessive-looking
17 flags = {} 17 # precondition checking. This is so that the behavior of this
18 dict.__init__(self, mapping) 18 # class exactly matches its C counterpart to try and help
19 self._flags = flags 19 # prevent surprise breakage for anyone that develops against
20 # the pure version.
21 if data and data[-1] != '\n':
22 raise ValueError('Manifest did not end in a newline.')
23 prev = None
24 for l in data.splitlines():
25 if prev is not None and prev > l:
26 raise ValueError('Manifest lines not in sorted order.')
27 prev = l
28 f, n = l.split('\0')
29 if len(n) > 40:
30 yield f, revlog.bin(n[:40]), n[40:]
31 else:
32 yield f, revlog.bin(n), ''
33
34 def _parsev2(data):
35 metadataend = data.find('\n')
36 # Just ignore metadata for now
37 pos = metadataend + 1
38 prevf = ''
39 while pos < len(data):
40 end = data.find('\n', pos + 1) # +1 to skip stem length byte
41 if end == -1:
42 raise ValueError('Manifest ended with incomplete file entry.')
43 stemlen = ord(data[pos])
44 items = data[pos + 1:end].split('\0')
45 f = prevf[:stemlen] + items[0]
46 if prevf > f:
47 raise ValueError('Manifest entries not in sorted order.')
48 fl = items[1]
49 # Just ignore metadata (items[2:] for now)
50 n = data[end + 1:end + 21]
51 yield f, n, fl
52 pos = end + 22
53 prevf = f
54
55 def _parse(data):
56 """Generates (path, node, flags) tuples from a manifest text"""
57 if data.startswith('\0'):
58 return iter(_parsev2(data))
59 else:
60 return iter(_parsev1(data))
61
62 def _text(it, usemanifestv2):
63 """Given an iterator over (path, node, flags) tuples, returns a manifest
64 text"""
65 if usemanifestv2:
66 return _textv2(it)
67 else:
68 return _textv1(it)
69
70 def _textv1(it):
71 files = []
72 lines = []
73 _hex = revlog.hex
74 for f, n, fl in it:
75 files.append(f)
76 # if this is changed to support newlines in filenames,
77 # be sure to check the templates/ dir again (especially *-raw.tmpl)
78 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
79
80 _checkforbidden(files)
81 return ''.join(lines)
82
83 def _textv2(it):
84 files = []
85 lines = ['\0\n']
86 prevf = ''
87 for f, n, fl in it:
88 files.append(f)
89 stem = os.path.commonprefix([prevf, f])
90 stemlen = min(len(stem), 255)
91 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
92 prevf = f
93 _checkforbidden(files)
94 return ''.join(lines)
95
96 class _lazymanifest(dict):
97 """This is the pure implementation of lazymanifest.
98
99 It has not been optimized *at all* and is not lazy.
100 """
101
102 def __init__(self, data):
103 dict.__init__(self)
104 for f, n, fl in _parse(data):
105 self[f] = n, fl
106
20 def __setitem__(self, k, v): 107 def __setitem__(self, k, v):
21 assert v is not None 108 node, flag = v
22 dict.__setitem__(self, k, v) 109 assert node is not None
23 def flags(self, f): 110 if len(node) > 21:
24 return self._flags.get(f, "") 111 node = node[:21] # match c implementation behavior
25 def setflag(self, f, flags): 112 dict.__setitem__(self, k, (node, flag))
26 """Set the flags (symlink, executable) for path f.""" 113
27 self._flags[f] = flags 114 def __iter__(self):
115 return iter(sorted(dict.keys(self)))
116
117 def iterkeys(self):
118 return iter(sorted(dict.keys(self)))
119
120 def iterentries(self):
121 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
122
28 def copy(self): 123 def copy(self):
29 return manifestdict(self, dict.copy(self._flags)) 124 c = _lazymanifest('')
30 def intersectfiles(self, files): 125 c.update(self)
31 '''make a new manifestdict with the intersection of self with files 126 return c
32 127
33 The algorithm assumes that files is much smaller than self.''' 128 def diff(self, m2, clean=False):
34 ret = manifestdict() 129 '''Finds changes between the current manifest and m2.'''
35 for fn in files: 130 diff = {}
36 if fn in self: 131
37 ret[fn] = self[fn] 132 for fn, e1 in self.iteritems():
38 flags = self._flags.get(fn, None) 133 if fn not in m2:
39 if flags: 134 diff[fn] = e1, (None, '')
40 ret._flags[fn] = flags 135 else:
41 return ret 136 e2 = m2[fn]
137 if e1 != e2:
138 diff[fn] = e1, e2
139 elif clean:
140 diff[fn] = None
141
142 for fn, e2 in m2.iteritems():
143 if fn not in self:
144 diff[fn] = (None, ''), e2
145
146 return diff
147
148 def filtercopy(self, filterfn):
149 c = _lazymanifest('')
150 for f, n, fl in self.iterentries():
151 if filterfn(f):
152 c[f] = n, fl
153 return c
154
155 def text(self):
156 """Get the full data of this manifest as a bytestring."""
157 return _textv1(self.iterentries())
158
159 try:
160 _lazymanifest = parsers.lazymanifest
161 except AttributeError:
162 pass
163
164 class manifestdict(object):
165 def __init__(self, data=''):
166 if data.startswith('\0'):
167 #_lazymanifest can not parse v2
168 self._lm = _lazymanifest('')
169 for f, n, fl in _parsev2(data):
170 self._lm[f] = n, fl
171 else:
172 self._lm = _lazymanifest(data)
173
174 def __getitem__(self, key):
175 return self._lm[key][0]
176
177 def find(self, key):
178 return self._lm[key]
179
180 def __len__(self):
181 return len(self._lm)
182
183 def __setitem__(self, key, node):
184 self._lm[key] = node, self.flags(key, '')
185
186 def __contains__(self, key):
187 return key in self._lm
188
189 def __delitem__(self, key):
190 del self._lm[key]
191
192 def __iter__(self):
193 return self._lm.__iter__()
194
195 def iterkeys(self):
196 return self._lm.iterkeys()
197
198 def keys(self):
199 return list(self.iterkeys())
200
201 def filesnotin(self, m2):
202 '''Set of files in this manifest that are not in the other'''
203 files = set(self)
204 files.difference_update(m2)
205 return files
206
207 @propertycache
208 def _dirs(self):
209 return util.dirs(self)
210
211 def dirs(self):
212 return self._dirs
213
214 def hasdir(self, dir):
215 return dir in self._dirs
216
217 def _filesfastpath(self, match):
218 '''Checks whether we can correctly and quickly iterate over matcher
219 files instead of over manifest files.'''
220 files = match.files()
221 return (len(files) < 100 and (match.isexact() or
222 (not match.anypats() and util.all(fn in self for fn in files))))
223
224 def walk(self, match):
225 '''Generates matching file names.
226
227 Equivalent to manifest.matches(match).iterkeys(), but without creating
228 an entirely new manifest.
229
230 It also reports nonexistent files by marking them bad with match.bad().
231 '''
232 if match.always():
233 for f in iter(self):
234 yield f
235 return
236
237 fset = set(match.files())
238
239 # avoid the entire walk if we're only looking for specific files
240 if self._filesfastpath(match):
241 for fn in sorted(fset):
242 yield fn
243 return
244
245 for fn in self:
246 if fn in fset:
247 # specified pattern is the exact name
248 fset.remove(fn)
249 if match(fn):
250 yield fn
251
252 # for dirstate.walk, files=['.'] means "walk the whole tree".
253 # follow that here, too
254 fset.discard('.')
255
256 for fn in sorted(fset):
257 if not self.hasdir(fn):
258 match.bad(fn, None)
42 259
43 def matches(self, match): 260 def matches(self, match):
44 '''generate a new manifest filtered by the match argument''' 261 '''generate a new manifest filtered by the match argument'''
45 if match.always(): 262 if match.always():
46 return self.copy() 263 return self.copy()
47 264
48 files = match.files() 265 if self._filesfastpath(match):
49 if (match.matchfn == match.exact or 266 m = manifestdict()
50 (not match.anypats() and util.all(fn in self for fn in files))): 267 lm = self._lm
51 return self.intersectfiles(files) 268 for fn in match.files():
52 269 if fn in lm:
53 mf = self.copy() 270 m._lm[fn] = lm[fn]
54 for fn in mf.keys(): 271 return m
55 if not match(fn): 272
56 del mf[fn] 273 m = manifestdict()
57 return mf 274 m._lm = self._lm.filtercopy(match)
275 return m
58 276
59 def diff(self, m2, clean=False): 277 def diff(self, m2, clean=False):
60 '''Finds changes between the current manifest and m2. 278 '''Finds changes between the current manifest and m2.
61 279
62 Args: 280 Args:
69 nodeid in the current/other manifest and fl1/fl2 is the flag 287 nodeid in the current/other manifest and fl1/fl2 is the flag
70 in the current/other manifest. Where the file does not exist, 288 in the current/other manifest. Where the file does not exist,
71 the nodeid will be None and the flags will be the empty 289 the nodeid will be None and the flags will be the empty
72 string. 290 string.
73 ''' 291 '''
74 diff = {} 292 return self._lm.diff(m2._lm, clean)
75 293
76 for fn, n1 in self.iteritems(): 294 def setflag(self, key, flag):
77 fl1 = self._flags.get(fn, '') 295 self._lm[key] = self[key], flag
78 n2 = m2.get(fn, None) 296
79 fl2 = m2._flags.get(fn, '') 297 def get(self, key, default=None):
80 if n2 is None: 298 try:
81 fl2 = '' 299 return self._lm[key][0]
82 if n1 != n2 or fl1 != fl2: 300 except KeyError:
83 diff[fn] = ((n1, fl1), (n2, fl2)) 301 return default
84 elif clean: 302
85 diff[fn] = None 303 def flags(self, key, default=''):
86 304 try:
87 for fn, n2 in m2.iteritems(): 305 return self._lm[key][1]
88 if fn not in self: 306 except KeyError:
89 fl2 = m2._flags.get(fn, '') 307 return default
90 diff[fn] = ((None, ''), (n2, fl2)) 308
91 309 def copy(self):
92 return diff 310 c = manifestdict()
93 311 c._lm = self._lm.copy()
94 def text(self): 312 return c
95 """Get the full data of this manifest as a bytestring.""" 313
96 fl = sorted(self) 314 def iteritems(self):
97 _checkforbidden(fl) 315 return (x[:2] for x in self._lm.iterentries())
98 316
99 hex, flags = revlog.hex, self.flags 317 def text(self, usemanifestv2=False):
100 # if this is changed to support newlines in filenames, 318 if usemanifestv2:
101 # be sure to check the templates/ dir again (especially *-raw.tmpl) 319 return _textv2(self._lm.iterentries())
102 return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl) 320 else:
321 # use (probably) native version for v1
322 return self._lm.text()
103 323
104 def fastdelta(self, base, changes): 324 def fastdelta(self, base, changes):
105 """Given a base manifest text as an array.array and a list of changes 325 """Given a base manifest text as an array.array and a list of changes
106 relative to that text, compute a delta that can be used by revlog. 326 relative to that text, compute a delta that can be used by revlog.
107 """ 327 """
117 # each line and creates the deltas 337 # each line and creates the deltas
118 for f, todelete in changes: 338 for f, todelete in changes:
119 # bs will either be the index of the item or the insert point 339 # bs will either be the index of the item or the insert point
120 start, end = _msearch(addbuf, f, start) 340 start, end = _msearch(addbuf, f, start)
121 if not todelete: 341 if not todelete:
122 l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f)) 342 h, fl = self._lm[f]
343 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
123 else: 344 else:
124 if start == end: 345 if start == end:
125 # item we want to delete was not found, error out 346 # item we want to delete was not found, error out
126 raise AssertionError( 347 raise AssertionError(
127 _("failed to remove %s from manifest") % f) 348 _("failed to remove %s from manifest") % f)
211 432
212 deltatext = "".join(struct.pack(">lll", start, end, len(content)) 433 deltatext = "".join(struct.pack(">lll", start, end, len(content))
213 + content for start, end, content in x) 434 + content for start, end, content in x)
214 return deltatext, newaddlist 435 return deltatext, newaddlist
215 436
216 def _parse(lines): 437 def _splittopdir(f):
217 mfdict = manifestdict() 438 if '/' in f:
218 parsers.parse_manifest(mfdict, mfdict._flags, lines) 439 dir, subpath = f.split('/', 1)
219 return mfdict 440 return dir + '/', subpath
441 else:
442 return '', f
443
444 class treemanifest(object):
445 def __init__(self, dir='', text=''):
446 self._dir = dir
447 self._dirs = {}
448 # Using _lazymanifest here is a little slower than plain old dicts
449 self._files = {}
450 self._flags = {}
451 self.parse(text)
452
453 def _subpath(self, path):
454 return self._dir + path
455
456 def __len__(self):
457 size = len(self._files)
458 for m in self._dirs.values():
459 size += m.__len__()
460 return size
461
462 def _isempty(self):
463 return (not self._files and (not self._dirs or
464 util.all(m._isempty() for m in self._dirs.values())))
465
466 def __str__(self):
467 return '<treemanifest dir=%s>' % self._dir
468
469 def iteritems(self):
470 for p, n in sorted(self._dirs.items() + self._files.items()):
471 if p in self._files:
472 yield self._subpath(p), n
473 else:
474 for f, sn in n.iteritems():
475 yield f, sn
476
477 def iterkeys(self):
478 for p in sorted(self._dirs.keys() + self._files.keys()):
479 if p in self._files:
480 yield self._subpath(p)
481 else:
482 for f in self._dirs[p].iterkeys():
483 yield f
484
485 def keys(self):
486 return list(self.iterkeys())
487
488 def __iter__(self):
489 return self.iterkeys()
490
491 def __contains__(self, f):
492 if f is None:
493 return False
494 dir, subpath = _splittopdir(f)
495 if dir:
496 if dir not in self._dirs:
497 return False
498 return self._dirs[dir].__contains__(subpath)
499 else:
500 return f in self._files
501
502 def get(self, f, default=None):
503 dir, subpath = _splittopdir(f)
504 if dir:
505 if dir not in self._dirs:
506 return default
507 return self._dirs[dir].get(subpath, default)
508 else:
509 return self._files.get(f, default)
510
511 def __getitem__(self, f):
512 dir, subpath = _splittopdir(f)
513 if dir:
514 return self._dirs[dir].__getitem__(subpath)
515 else:
516 return self._files[f]
517
518 def flags(self, f):
519 dir, subpath = _splittopdir(f)
520 if dir:
521 if dir not in self._dirs:
522 return ''
523 return self._dirs[dir].flags(subpath)
524 else:
525 if f in self._dirs:
526 return ''
527 return self._flags.get(f, '')
528
529 def find(self, f):
530 dir, subpath = _splittopdir(f)
531 if dir:
532 return self._dirs[dir].find(subpath)
533 else:
534 return self._files[f], self._flags.get(f, '')
535
536 def __delitem__(self, f):
537 dir, subpath = _splittopdir(f)
538 if dir:
539 self._dirs[dir].__delitem__(subpath)
540 # If the directory is now empty, remove it
541 if self._dirs[dir]._isempty():
542 del self._dirs[dir]
543 else:
544 del self._files[f]
545 if f in self._flags:
546 del self._flags[f]
547
548 def __setitem__(self, f, n):
549 assert n is not None
550 dir, subpath = _splittopdir(f)
551 if dir:
552 if dir not in self._dirs:
553 self._dirs[dir] = treemanifest(self._subpath(dir))
554 self._dirs[dir].__setitem__(subpath, n)
555 else:
556 self._files[f] = n[:21] # to match manifestdict's behavior
557
558 def setflag(self, f, flags):
559 """Set the flags (symlink, executable) for path f."""
560 dir, subpath = _splittopdir(f)
561 if dir:
562 if dir not in self._dirs:
563 self._dirs[dir] = treemanifest(self._subpath(dir))
564 self._dirs[dir].setflag(subpath, flags)
565 else:
566 self._flags[f] = flags
567
568 def copy(self):
569 copy = treemanifest(self._dir)
570 for d in self._dirs:
571 copy._dirs[d] = self._dirs[d].copy()
572 copy._files = dict.copy(self._files)
573 copy._flags = dict.copy(self._flags)
574 return copy
575
576 def filesnotin(self, m2):
577 '''Set of files in this manifest that are not in the other'''
578 files = set()
579 def _filesnotin(t1, t2):
580 for d, m1 in t1._dirs.iteritems():
581 if d in t2._dirs:
582 m2 = t2._dirs[d]
583 _filesnotin(m1, m2)
584 else:
585 files.update(m1.iterkeys())
586
587 for fn in t1._files.iterkeys():
588 if fn not in t2._files:
589 files.add(t1._subpath(fn))
590
591 _filesnotin(self, m2)
592 return files
593
594 @propertycache
595 def _alldirs(self):
596 return util.dirs(self)
597
598 def dirs(self):
599 return self._alldirs
600
601 def hasdir(self, dir):
602 topdir, subdir = _splittopdir(dir)
603 if topdir:
604 if topdir in self._dirs:
605 return self._dirs[topdir].hasdir(subdir)
606 return False
607 return (dir + '/') in self._dirs
608
609 def walk(self, match):
610 '''Generates matching file names.
611
612 Equivalent to manifest.matches(match).iterkeys(), but without creating
613 an entirely new manifest.
614
615 It also reports nonexistent files by marking them bad with match.bad().
616 '''
617 if match.always():
618 for f in iter(self):
619 yield f
620 return
621
622 fset = set(match.files())
623
624 for fn in self._walk(match):
625 if fn in fset:
626 # specified pattern is the exact name
627 fset.remove(fn)
628 yield fn
629
630 # for dirstate.walk, files=['.'] means "walk the whole tree".
631 # follow that here, too
632 fset.discard('.')
633
634 for fn in sorted(fset):
635 if not self.hasdir(fn):
636 match.bad(fn, None)
637
638 def _walk(self, match, alldirs=False):
639 '''Recursively generates matching file names for walk().
640
641 Will visit all subdirectories if alldirs is True, otherwise it will
642 only visit subdirectories for which match.visitdir is True.'''
643
644 if not alldirs:
645 # substring to strip trailing slash
646 visit = match.visitdir(self._dir[:-1] or '.')
647 if not visit:
648 return
649 alldirs = (visit == 'all')
650
651 # yield this dir's files and walk its submanifests
652 for p in sorted(self._dirs.keys() + self._files.keys()):
653 if p in self._files:
654 fullp = self._subpath(p)
655 if match(fullp):
656 yield fullp
657 else:
658 for f in self._dirs[p]._walk(match, alldirs):
659 yield f
660
661 def matches(self, match):
662 '''generate a new manifest filtered by the match argument'''
663 if match.always():
664 return self.copy()
665
666 return self._matches(match)
667
668 def _matches(self, match, alldirs=False):
669 '''recursively generate a new manifest filtered by the match argument.
670
671 Will visit all subdirectories if alldirs is True, otherwise it will
672 only visit subdirectories for which match.visitdir is True.'''
673
674 ret = treemanifest(self._dir)
675 if not alldirs:
676 # substring to strip trailing slash
677 visit = match.visitdir(self._dir[:-1] or '.')
678 if not visit:
679 return ret
680 alldirs = (visit == 'all')
681
682 for fn in self._files:
683 fullp = self._subpath(fn)
684 if not match(fullp):
685 continue
686 ret._files[fn] = self._files[fn]
687 if fn in self._flags:
688 ret._flags[fn] = self._flags[fn]
689
690 for dir, subm in self._dirs.iteritems():
691 m = subm._matches(match, alldirs)
692 if not m._isempty():
693 ret._dirs[dir] = m
694
695 return ret
696
697 def diff(self, m2, clean=False):
698 '''Finds changes between the current manifest and m2.
699
700 Args:
701 m2: the manifest to which this manifest should be compared.
702 clean: if true, include files unchanged between these manifests
703 with a None value in the returned dictionary.
704
705 The result is returned as a dict with filename as key and
706 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
707 nodeid in the current/other manifest and fl1/fl2 is the flag
708 in the current/other manifest. Where the file does not exist,
709 the nodeid will be None and the flags will be the empty
710 string.
711 '''
712 result = {}
713 emptytree = treemanifest()
714 def _diff(t1, t2):
715 for d, m1 in t1._dirs.iteritems():
716 m2 = t2._dirs.get(d, emptytree)
717 _diff(m1, m2)
718
719 for d, m2 in t2._dirs.iteritems():
720 if d not in t1._dirs:
721 _diff(emptytree, m2)
722
723 for fn, n1 in t1._files.iteritems():
724 fl1 = t1._flags.get(fn, '')
725 n2 = t2._files.get(fn, None)
726 fl2 = t2._flags.get(fn, '')
727 if n1 != n2 or fl1 != fl2:
728 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
729 elif clean:
730 result[t1._subpath(fn)] = None
731
732 for fn, n2 in t2._files.iteritems():
733 if fn not in t1._files:
734 fl2 = t2._flags.get(fn, '')
735 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
736
737 _diff(self, m2)
738 return result
739
740 def parse(self, text):
741 for f, n, fl in _parse(text):
742 self[f] = n
743 if fl:
744 self.setflag(f, fl)
745
746 def text(self, usemanifestv2=False):
747 """Get the full data of this manifest as a bytestring."""
748 flags = self.flags
749 return _text(((f, self[f], flags(f)) for f in self.keys()),
750 usemanifestv2)
220 751
221 class manifest(revlog.revlog): 752 class manifest(revlog.revlog):
222 def __init__(self, opener): 753 def __init__(self, opener):
223 # we expect to deal with not more than four revs at a time, 754 # During normal operations, we expect to deal with not more than four
224 # during a commit --amend 755 # revs at a time (such as during commit --amend). When rebasing large
225 self._mancache = util.lrucachedict(4) 756 # stacks of commits, the number can go up, hence the config knob below.
757 cachesize = 4
758 usetreemanifest = False
759 usemanifestv2 = False
760 opts = getattr(opener, 'options', None)
761 if opts is not None:
762 cachesize = opts.get('manifestcachesize', cachesize)
763 usetreemanifest = opts.get('usetreemanifest', usetreemanifest)
764 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
765 self._mancache = util.lrucachedict(cachesize)
226 revlog.revlog.__init__(self, opener, "00manifest.i") 766 revlog.revlog.__init__(self, opener, "00manifest.i")
767 self._treeinmem = usetreemanifest
768 self._treeondisk = usetreemanifest
769 self._usemanifestv2 = usemanifestv2
770
771 def _newmanifest(self, data=''):
772 if self._treeinmem:
773 return treemanifest('', data)
774 return manifestdict(data)
775
776 def _slowreaddelta(self, node):
777 r0 = self.deltaparent(self.rev(node))
778 m0 = self.read(self.node(r0))
779 m1 = self.read(node)
780 md = self._newmanifest()
781 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
782 if n1:
783 md[f] = n1
784 if fl1:
785 md.setflag(f, fl1)
786 return md
227 787
228 def readdelta(self, node): 788 def readdelta(self, node):
789 if self._usemanifestv2 or self._treeondisk:
790 return self._slowreaddelta(node)
229 r = self.rev(node) 791 r = self.rev(node)
230 return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r))) 792 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
793 return self._newmanifest(d)
231 794
232 def readfast(self, node): 795 def readfast(self, node):
233 '''use the faster of readdelta or read''' 796 '''use the faster of readdelta or read'''
234 r = self.rev(node) 797 r = self.rev(node)
235 deltaparent = self.deltaparent(r) 798 deltaparent = self.deltaparent(r)
237 return self.readdelta(node) 800 return self.readdelta(node)
238 return self.read(node) 801 return self.read(node)
239 802
240 def read(self, node): 803 def read(self, node):
241 if node == revlog.nullid: 804 if node == revlog.nullid:
242 return manifestdict() # don't upset local cache 805 return self._newmanifest() # don't upset local cache
243 if node in self._mancache: 806 if node in self._mancache:
244 return self._mancache[node][0] 807 return self._mancache[node][0]
245 text = self.revision(node) 808 text = self.revision(node)
246 arraytext = array.array('c', text) 809 arraytext = array.array('c', text)
247 mapping = _parse(text) 810 m = self._newmanifest(text)
248 self._mancache[node] = (mapping, arraytext) 811 self._mancache[node] = (m, arraytext)
249 return mapping 812 return m
250 813
251 def find(self, node, f): 814 def find(self, node, f):
252 '''look up entry for a single file efficiently. 815 '''look up entry for a single file efficiently.
253 return (node, flags) pair if found, (None, None) if not.''' 816 return (node, flags) pair if found, (None, None) if not.'''
254 if node in self._mancache: 817 m = self.read(node)
255 mapping = self._mancache[node][0] 818 try:
256 return mapping.get(f), mapping.flags(f) 819 return m.find(f)
257 text = self.revision(node) 820 except KeyError:
258 start, end = _msearch(text, f)
259 if start == end:
260 return None, None 821 return None, None
261 l = text[start:end] 822
262 f, n = l.split('\0') 823 def add(self, m, transaction, link, p1, p2, added, removed):
263 return revlog.bin(n[:40]), n[40:-1] 824 if (p1 in self._mancache and not self._treeinmem
264 825 and not self._usemanifestv2):
265 def add(self, map, transaction, link, p1, p2, added, removed):
266 if p1 in self._mancache:
267 # If our first parent is in the manifest cache, we can 826 # If our first parent is in the manifest cache, we can
268 # compute a delta here using properties we know about the 827 # compute a delta here using properties we know about the
269 # manifest up-front, which may save time later for the 828 # manifest up-front, which may save time later for the
270 # revlog layer. 829 # revlog layer.
271 830
275 work.extend((x, True) for x in removed) 834 work.extend((x, True) for x in removed)
276 # this could use heapq.merge() (from Python 2.6+) or equivalent 835 # this could use heapq.merge() (from Python 2.6+) or equivalent
277 # since the lists are already sorted 836 # since the lists are already sorted
278 work.sort() 837 work.sort()
279 838
280 arraytext, deltatext = map.fastdelta(self._mancache[p1][1], work) 839 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
281 cachedelta = self.rev(p1), deltatext 840 cachedelta = self.rev(p1), deltatext
282 text = util.buffer(arraytext) 841 text = util.buffer(arraytext)
842 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
283 else: 843 else:
284 # The first parent manifest isn't already loaded, so we'll 844 # The first parent manifest isn't already loaded, so we'll
285 # just encode a fulltext of the manifest and pass that 845 # just encode a fulltext of the manifest and pass that
286 # through to the revlog layer, and let it handle the delta 846 # through to the revlog layer, and let it handle the delta
287 # process. 847 # process.
288 text = map.text() 848 text = m.text(self._usemanifestv2)
289 arraytext = array.array('c', text) 849 arraytext = array.array('c', text)
290 cachedelta = None 850 n = self.addrevision(text, transaction, link, p1, p2)
291 851
292 n = self.addrevision(text, transaction, link, p1, p2, cachedelta) 852 self._mancache[n] = (m, arraytext)
293 self._mancache[n] = (map, arraytext)
294 853
295 return n 854 return n