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