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