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