Mercurial > public > mercurial-scm > hg-stable
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 |