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