##// END OF EJS Templates
manifestdict: inline _intersectfiles()...
Martin von Zweigbergk -
r24666:3092885b default
parent child Browse files
Show More
@@ -1,839 +1,833
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 def _intersectfiles(self, files):
202 '''make a new lazymanifest with the intersection of self with files
203
204 The algorithm assumes that files is much smaller than self.'''
205 ret = manifestdict()
206 lm = self._lm
207 for fn in files:
208 if fn in lm:
209 ret._lm[fn] = lm[fn]
210 return ret
211
212 201 def filesnotin(self, m2):
213 202 '''Set of files in this manifest that are not in the other'''
214 203 files = set(self)
215 204 files.difference_update(m2)
216 205 return files
217 206
218 207 @propertycache
219 208 def _dirs(self):
220 209 return util.dirs(self)
221 210
222 211 def dirs(self):
223 212 return self._dirs
224 213
225 214 def hasdir(self, dir):
226 215 return dir in self._dirs
227 216
228 217 def walk(self, match):
229 218 '''Generates matching file names.
230 219
231 220 Equivalent to manifest.matches(match).iterkeys(), but without creating
232 221 an entirely new manifest.
233 222
234 223 It also reports nonexistent files by marking them bad with match.bad().
235 224 '''
236 225 fset = set(match.files())
237 226
238 227 # avoid the entire walk if we're only looking for specific files
239 228 if fset and not match.anypats():
240 229 if util.all(fn in self for fn in fset):
241 230 for fn in sorted(fset):
242 231 yield fn
243 232 raise StopIteration
244 233
245 234 for fn in self:
246 235 if fn in fset:
247 236 # specified pattern is the exact name
248 237 fset.remove(fn)
249 238 if match(fn):
250 239 yield fn
251 240
252 241 # for dirstate.walk, files=['.'] means "walk the whole tree".
253 242 # follow that here, too
254 243 fset.discard('.')
255 244
256 245 for fn in sorted(fset):
257 246 if not self.hasdir(fn):
258 247 match.bad(fn, None)
259 248
260 249 def matches(self, match):
261 250 '''generate a new manifest filtered by the match argument'''
262 251 if match.always():
263 252 return self.copy()
264 253
265 254 files = match.files()
266 255 if (len(files) < 100 and (match.isexact() or
267 256 (not match.anypats() and util.all(fn in self for fn in files)))):
268 return self._intersectfiles(files)
257 m = manifestdict()
258 lm = self._lm
259 for fn in match.files():
260 if fn in lm:
261 m._lm[fn] = lm[fn]
262 return m
269 263
270 264 m = manifestdict('')
271 265 m._lm = self._lm.filtercopy(match)
272 266 return m
273 267
274 268 def diff(self, m2, clean=False):
275 269 '''Finds changes between the current manifest and m2.
276 270
277 271 Args:
278 272 m2: the manifest to which this manifest should be compared.
279 273 clean: if true, include files unchanged between these manifests
280 274 with a None value in the returned dictionary.
281 275
282 276 The result is returned as a dict with filename as key and
283 277 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
284 278 nodeid in the current/other manifest and fl1/fl2 is the flag
285 279 in the current/other manifest. Where the file does not exist,
286 280 the nodeid will be None and the flags will be the empty
287 281 string.
288 282 '''
289 283 return self._lm.diff(m2._lm, clean)
290 284
291 285 def setflag(self, key, flag):
292 286 self._lm[key] = self[key], flag
293 287
294 288 def get(self, key, default=None):
295 289 try:
296 290 return self._lm[key][0]
297 291 except KeyError:
298 292 return default
299 293
300 294 def flags(self, key, default=''):
301 295 try:
302 296 return self._lm[key][1]
303 297 except KeyError:
304 298 return default
305 299
306 300 def copy(self):
307 301 c = manifestdict('')
308 302 c._lm = self._lm.copy()
309 303 return c
310 304
311 305 def iteritems(self):
312 306 return (x[:2] for x in self._lm.iterentries())
313 307
314 308 def text(self, usemanifestv2=False):
315 309 if usemanifestv2:
316 310 return _textv2(self._lm.iterentries())
317 311 else:
318 312 # use (probably) native version for v1
319 313 return self._lm.text()
320 314
321 315 def fastdelta(self, base, changes):
322 316 """Given a base manifest text as an array.array and a list of changes
323 317 relative to that text, compute a delta that can be used by revlog.
324 318 """
325 319 delta = []
326 320 dstart = None
327 321 dend = None
328 322 dline = [""]
329 323 start = 0
330 324 # zero copy representation of base as a buffer
331 325 addbuf = util.buffer(base)
332 326
333 327 # start with a readonly loop that finds the offset of
334 328 # each line and creates the deltas
335 329 for f, todelete in changes:
336 330 # bs will either be the index of the item or the insert point
337 331 start, end = _msearch(addbuf, f, start)
338 332 if not todelete:
339 333 h, fl = self._lm[f]
340 334 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
341 335 else:
342 336 if start == end:
343 337 # item we want to delete was not found, error out
344 338 raise AssertionError(
345 339 _("failed to remove %s from manifest") % f)
346 340 l = ""
347 341 if dstart is not None and dstart <= start and dend >= start:
348 342 if dend < end:
349 343 dend = end
350 344 if l:
351 345 dline.append(l)
352 346 else:
353 347 if dstart is not None:
354 348 delta.append([dstart, dend, "".join(dline)])
355 349 dstart = start
356 350 dend = end
357 351 dline = [l]
358 352
359 353 if dstart is not None:
360 354 delta.append([dstart, dend, "".join(dline)])
361 355 # apply the delta to the base, and get a delta for addrevision
362 356 deltatext, arraytext = _addlistdelta(base, delta)
363 357 return arraytext, deltatext
364 358
365 359 def _msearch(m, s, lo=0, hi=None):
366 360 '''return a tuple (start, end) that says where to find s within m.
367 361
368 362 If the string is found m[start:end] are the line containing
369 363 that string. If start == end the string was not found and
370 364 they indicate the proper sorted insertion point.
371 365
372 366 m should be a buffer or a string
373 367 s is a string'''
374 368 def advance(i, c):
375 369 while i < lenm and m[i] != c:
376 370 i += 1
377 371 return i
378 372 if not s:
379 373 return (lo, lo)
380 374 lenm = len(m)
381 375 if not hi:
382 376 hi = lenm
383 377 while lo < hi:
384 378 mid = (lo + hi) // 2
385 379 start = mid
386 380 while start > 0 and m[start - 1] != '\n':
387 381 start -= 1
388 382 end = advance(start, '\0')
389 383 if m[start:end] < s:
390 384 # we know that after the null there are 40 bytes of sha1
391 385 # this translates to the bisect lo = mid + 1
392 386 lo = advance(end + 40, '\n') + 1
393 387 else:
394 388 # this translates to the bisect hi = mid
395 389 hi = start
396 390 end = advance(lo, '\0')
397 391 found = m[lo:end]
398 392 if s == found:
399 393 # we know that after the null there are 40 bytes of sha1
400 394 end = advance(end + 40, '\n')
401 395 return (lo, end + 1)
402 396 else:
403 397 return (lo, lo)
404 398
405 399 def _checkforbidden(l):
406 400 """Check filenames for illegal characters."""
407 401 for f in l:
408 402 if '\n' in f or '\r' in f:
409 403 raise error.RevlogError(
410 404 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
411 405
412 406
413 407 # apply the changes collected during the bisect loop to our addlist
414 408 # return a delta suitable for addrevision
415 409 def _addlistdelta(addlist, x):
416 410 # for large addlist arrays, building a new array is cheaper
417 411 # than repeatedly modifying the existing one
418 412 currentposition = 0
419 413 newaddlist = array.array('c')
420 414
421 415 for start, end, content in x:
422 416 newaddlist += addlist[currentposition:start]
423 417 if content:
424 418 newaddlist += array.array('c', content)
425 419
426 420 currentposition = end
427 421
428 422 newaddlist += addlist[currentposition:]
429 423
430 424 deltatext = "".join(struct.pack(">lll", start, end, len(content))
431 425 + content for start, end, content in x)
432 426 return deltatext, newaddlist
433 427
434 428 def _splittopdir(f):
435 429 if '/' in f:
436 430 dir, subpath = f.split('/', 1)
437 431 return dir + '/', subpath
438 432 else:
439 433 return '', f
440 434
441 435 class treemanifest(object):
442 436 def __init__(self, dir='', text=''):
443 437 self._dir = dir
444 438 self._dirs = {}
445 439 # Using _lazymanifest here is a little slower than plain old dicts
446 440 self._files = {}
447 441 self._flags = {}
448 442 for f, n, fl in _parse(text):
449 443 self[f] = n
450 444 if fl:
451 445 self.setflag(f, fl)
452 446
453 447 def _subpath(self, path):
454 448 return self._dir + path
455 449
456 450 def __len__(self):
457 451 size = len(self._files)
458 452 for m in self._dirs.values():
459 453 size += m.__len__()
460 454 return size
461 455
462 456 def _isempty(self):
463 457 return (not self._files and (not self._dirs or
464 458 util.all(m._isempty() for m in self._dirs.values())))
465 459
466 460 def __str__(self):
467 461 return '<treemanifest dir=%s>' % self._dir
468 462
469 463 def iteritems(self):
470 464 for p, n in sorted(self._dirs.items() + self._files.items()):
471 465 if p in self._files:
472 466 yield self._subpath(p), n
473 467 else:
474 468 for f, sn in n.iteritems():
475 469 yield f, sn
476 470
477 471 def iterkeys(self):
478 472 for p in sorted(self._dirs.keys() + self._files.keys()):
479 473 if p in self._files:
480 474 yield self._subpath(p)
481 475 else:
482 476 for f in self._dirs[p].iterkeys():
483 477 yield f
484 478
485 479 def keys(self):
486 480 return list(self.iterkeys())
487 481
488 482 def __iter__(self):
489 483 return self.iterkeys()
490 484
491 485 def __contains__(self, f):
492 486 if f is None:
493 487 return False
494 488 dir, subpath = _splittopdir(f)
495 489 if dir:
496 490 if dir not in self._dirs:
497 491 return False
498 492 return self._dirs[dir].__contains__(subpath)
499 493 else:
500 494 return f in self._files
501 495
502 496 def get(self, f, default=None):
503 497 dir, subpath = _splittopdir(f)
504 498 if dir:
505 499 if dir not in self._dirs:
506 500 return default
507 501 return self._dirs[dir].get(subpath, default)
508 502 else:
509 503 return self._files.get(f, default)
510 504
511 505 def __getitem__(self, f):
512 506 dir, subpath = _splittopdir(f)
513 507 if dir:
514 508 return self._dirs[dir].__getitem__(subpath)
515 509 else:
516 510 return self._files[f]
517 511
518 512 def flags(self, f):
519 513 dir, subpath = _splittopdir(f)
520 514 if dir:
521 515 if dir not in self._dirs:
522 516 return ''
523 517 return self._dirs[dir].flags(subpath)
524 518 else:
525 519 if f in self._dirs:
526 520 return ''
527 521 return self._flags.get(f, '')
528 522
529 523 def find(self, f):
530 524 dir, subpath = _splittopdir(f)
531 525 if dir:
532 526 return self._dirs[dir].find(subpath)
533 527 else:
534 528 return self._files[f], self._flags.get(f, '')
535 529
536 530 def __delitem__(self, f):
537 531 dir, subpath = _splittopdir(f)
538 532 if dir:
539 533 self._dirs[dir].__delitem__(subpath)
540 534 # If the directory is now empty, remove it
541 535 if self._dirs[dir]._isempty():
542 536 del self._dirs[dir]
543 537 else:
544 538 del self._files[f]
545 539 if f in self._flags:
546 540 del self._flags[f]
547 541
548 542 def __setitem__(self, f, n):
549 543 assert n is not None
550 544 dir, subpath = _splittopdir(f)
551 545 if dir:
552 546 if dir not in self._dirs:
553 547 self._dirs[dir] = treemanifest(self._subpath(dir))
554 548 self._dirs[dir].__setitem__(subpath, n)
555 549 else:
556 550 self._files[f] = n[:21] # to match manifestdict's behavior
557 551
558 552 def setflag(self, f, flags):
559 553 """Set the flags (symlink, executable) for path f."""
560 554 dir, subpath = _splittopdir(f)
561 555 if dir:
562 556 if dir not in self._dirs:
563 557 self._dirs[dir] = treemanifest(self._subpath(dir))
564 558 self._dirs[dir].setflag(subpath, flags)
565 559 else:
566 560 self._flags[f] = flags
567 561
568 562 def copy(self):
569 563 copy = treemanifest(self._dir)
570 564 for d in self._dirs:
571 565 copy._dirs[d] = self._dirs[d].copy()
572 566 copy._files = dict.copy(self._files)
573 567 copy._flags = dict.copy(self._flags)
574 568 return copy
575 569
576 570 def filesnotin(self, m2):
577 571 '''Set of files in this manifest that are not in the other'''
578 572 files = set()
579 573 def _filesnotin(t1, t2):
580 574 for d, m1 in t1._dirs.iteritems():
581 575 if d in t2._dirs:
582 576 m2 = t2._dirs[d]
583 577 _filesnotin(m1, m2)
584 578 else:
585 579 files.update(m1.iterkeys())
586 580
587 581 for fn in t1._files.iterkeys():
588 582 if fn not in t2._files:
589 583 files.add(t1._subpath(fn))
590 584
591 585 _filesnotin(self, m2)
592 586 return files
593 587
594 588 @propertycache
595 589 def _alldirs(self):
596 590 return util.dirs(self)
597 591
598 592 def dirs(self):
599 593 return self._alldirs
600 594
601 595 def hasdir(self, dir):
602 596 topdir, subdir = _splittopdir(dir)
603 597 if topdir:
604 598 if topdir in self._dirs:
605 599 return self._dirs[topdir].hasdir(subdir)
606 600 return False
607 601 return (dir + '/') in self._dirs
608 602
609 603 def walk(self, match):
610 604 '''Generates matching file names.
611 605
612 606 Equivalent to manifest.matches(match).iterkeys(), but without creating
613 607 an entirely new manifest.
614 608
615 609 It also reports nonexistent files by marking them bad with match.bad().
616 610 '''
617 611 fset = set(match.files())
618 612
619 613 # avoid the entire walk if we're only looking for specific files
620 614 if fset and not match.anypats():
621 615 if util.all(fn in self for fn in fset):
622 616 for fn in sorted(fset):
623 617 yield fn
624 618 raise StopIteration
625 619
626 620 for fn in self._walk(match):
627 621 if fn in fset:
628 622 # specified pattern is the exact name
629 623 fset.remove(fn)
630 624 yield fn
631 625
632 626 # for dirstate.walk, files=['.'] means "walk the whole tree".
633 627 # follow that here, too
634 628 fset.discard('.')
635 629
636 630 for fn in sorted(fset):
637 631 if not self.hasdir(fn):
638 632 match.bad(fn, None)
639 633
640 634 def _walk(self, match):
641 635 '''Recursively generates matching file names for walk().'''
642 636
643 637 # yield this dir's files and walk its submanifests
644 638 for p in sorted(self._dirs.keys() + self._files.keys()):
645 639 if p in self._files:
646 640 fullp = self._subpath(p)
647 641 if match(fullp):
648 642 yield fullp
649 643 else:
650 644 for f in self._dirs[p]._walk(match):
651 645 yield f
652 646
653 647 def matches(self, match):
654 648 '''generate a new manifest filtered by the match argument'''
655 649 if match.always():
656 650 return self.copy()
657 651
658 652 return self._matches(match)
659 653
660 654 def _matches(self, match, alldirs=False):
661 655 '''recursively generate a new manifest filtered by the match argument.
662 656
663 657 Will visit all subdirectories if alldirs is True, otherwise it will
664 658 only visit subdirectories for which match.visitdir is True.'''
665 659
666 660 ret = treemanifest(self._dir)
667 661 if not alldirs:
668 662 # substring to strip trailing slash
669 663 visit = match.visitdir(self._dir[:-1] or '.')
670 664 if not visit:
671 665 return ret
672 666 alldirs = (visit == 'all')
673 667
674 668 for fn in self._files:
675 669 fullp = self._subpath(fn)
676 670 if not match(fullp):
677 671 continue
678 672 ret._files[fn] = self._files[fn]
679 673 if fn in self._flags:
680 674 ret._flags[fn] = self._flags[fn]
681 675
682 676 for dir, subm in self._dirs.iteritems():
683 677 m = subm._matches(match, alldirs)
684 678 if not m._isempty():
685 679 ret._dirs[dir] = m
686 680
687 681 return ret
688 682
689 683 def diff(self, m2, clean=False):
690 684 '''Finds changes between the current manifest and m2.
691 685
692 686 Args:
693 687 m2: the manifest to which this manifest should be compared.
694 688 clean: if true, include files unchanged between these manifests
695 689 with a None value in the returned dictionary.
696 690
697 691 The result is returned as a dict with filename as key and
698 692 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
699 693 nodeid in the current/other manifest and fl1/fl2 is the flag
700 694 in the current/other manifest. Where the file does not exist,
701 695 the nodeid will be None and the flags will be the empty
702 696 string.
703 697 '''
704 698 result = {}
705 699 emptytree = treemanifest()
706 700 def _diff(t1, t2):
707 701 for d, m1 in t1._dirs.iteritems():
708 702 m2 = t2._dirs.get(d, emptytree)
709 703 _diff(m1, m2)
710 704
711 705 for d, m2 in t2._dirs.iteritems():
712 706 if d not in t1._dirs:
713 707 _diff(emptytree, m2)
714 708
715 709 for fn, n1 in t1._files.iteritems():
716 710 fl1 = t1._flags.get(fn, '')
717 711 n2 = t2._files.get(fn, None)
718 712 fl2 = t2._flags.get(fn, '')
719 713 if n1 != n2 or fl1 != fl2:
720 714 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
721 715 elif clean:
722 716 result[t1._subpath(fn)] = None
723 717
724 718 for fn, n2 in t2._files.iteritems():
725 719 if fn not in t1._files:
726 720 fl2 = t2._flags.get(fn, '')
727 721 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
728 722
729 723 _diff(self, m2)
730 724 return result
731 725
732 726 def text(self, usemanifestv2=False):
733 727 """Get the full data of this manifest as a bytestring."""
734 728 flags = self.flags
735 729 return _text(((f, self[f], flags(f)) for f in self.keys()),
736 730 usemanifestv2)
737 731
738 732 class manifest(revlog.revlog):
739 733 def __init__(self, opener):
740 734 # During normal operations, we expect to deal with not more than four
741 735 # revs at a time (such as during commit --amend). When rebasing large
742 736 # stacks of commits, the number can go up, hence the config knob below.
743 737 cachesize = 4
744 738 usetreemanifest = False
745 739 usemanifestv2 = False
746 740 opts = getattr(opener, 'options', None)
747 741 if opts is not None:
748 742 cachesize = opts.get('manifestcachesize', cachesize)
749 743 usetreemanifest = opts.get('usetreemanifest', usetreemanifest)
750 744 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
751 745 self._mancache = util.lrucachedict(cachesize)
752 746 revlog.revlog.__init__(self, opener, "00manifest.i")
753 747 self._usetreemanifest = usetreemanifest
754 748 self._usemanifestv2 = usemanifestv2
755 749
756 750 def _newmanifest(self, data=''):
757 751 if self._usetreemanifest:
758 752 return treemanifest('', data)
759 753 return manifestdict(data)
760 754
761 755 def _slowreaddelta(self, node):
762 756 r0 = self.deltaparent(self.rev(node))
763 757 m0 = self.read(self.node(r0))
764 758 m1 = self.read(node)
765 759 md = self._newmanifest()
766 760 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
767 761 if n1:
768 762 md[f] = n1
769 763 if fl1:
770 764 md.setflag(f, fl1)
771 765 return md
772 766
773 767 def readdelta(self, node):
774 768 if self._usemanifestv2 or self._usetreemanifest:
775 769 return self._slowreaddelta(node)
776 770 r = self.rev(node)
777 771 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
778 772 return self._newmanifest(d)
779 773
780 774 def readfast(self, node):
781 775 '''use the faster of readdelta or read'''
782 776 r = self.rev(node)
783 777 deltaparent = self.deltaparent(r)
784 778 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
785 779 return self.readdelta(node)
786 780 return self.read(node)
787 781
788 782 def read(self, node):
789 783 if node == revlog.nullid:
790 784 return self._newmanifest() # don't upset local cache
791 785 if node in self._mancache:
792 786 return self._mancache[node][0]
793 787 text = self.revision(node)
794 788 arraytext = array.array('c', text)
795 789 m = self._newmanifest(text)
796 790 self._mancache[node] = (m, arraytext)
797 791 return m
798 792
799 793 def find(self, node, f):
800 794 '''look up entry for a single file efficiently.
801 795 return (node, flags) pair if found, (None, None) if not.'''
802 796 m = self.read(node)
803 797 try:
804 798 return m.find(f)
805 799 except KeyError:
806 800 return None, None
807 801
808 802 def add(self, m, transaction, link, p1, p2, added, removed):
809 803 if (p1 in self._mancache and not self._usetreemanifest
810 804 and not self._usemanifestv2):
811 805 # If our first parent is in the manifest cache, we can
812 806 # compute a delta here using properties we know about the
813 807 # manifest up-front, which may save time later for the
814 808 # revlog layer.
815 809
816 810 _checkforbidden(added)
817 811 # combine the changed lists into one list for sorting
818 812 work = [(x, False) for x in added]
819 813 work.extend((x, True) for x in removed)
820 814 # this could use heapq.merge() (from Python 2.6+) or equivalent
821 815 # since the lists are already sorted
822 816 work.sort()
823 817
824 818 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
825 819 cachedelta = self.rev(p1), deltatext
826 820 text = util.buffer(arraytext)
827 821 else:
828 822 # The first parent manifest isn't already loaded, so we'll
829 823 # just encode a fulltext of the manifest and pass that
830 824 # through to the revlog layer, and let it handle the delta
831 825 # process.
832 826 text = m.text(self._usemanifestv2)
833 827 arraytext = array.array('c', text)
834 828 cachedelta = None
835 829
836 830 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
837 831 self._mancache[n] = (m, arraytext)
838 832
839 833 return n
General Comments 0
You need to be logged in to leave comments. Login now