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