##// END OF EJS Templates
manifest: add copy to mfctx classes...
Durham Goode -
r30343:952e1916 default
parent child Browse files
Show More
@@ -1,1591 +1,1611 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 __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 lazymanifestiter(object):
108 108 def __init__(self, lm):
109 109 self.pos = 0
110 110 self.lm = lm
111 111
112 112 def __iter__(self):
113 113 return self
114 114
115 115 def next(self):
116 116 try:
117 117 data, pos = self.lm._get(self.pos)
118 118 except IndexError:
119 119 raise StopIteration
120 120 if pos == -1:
121 121 self.pos += 1
122 122 return data[0]
123 123 self.pos += 1
124 124 zeropos = data.find('\x00', pos)
125 125 return data[pos:zeropos]
126 126
127 127 class lazymanifestiterentries(object):
128 128 def __init__(self, lm):
129 129 self.lm = lm
130 130 self.pos = 0
131 131
132 132 def __iter__(self):
133 133 return self
134 134
135 135 def next(self):
136 136 try:
137 137 data, pos = self.lm._get(self.pos)
138 138 except IndexError:
139 139 raise StopIteration
140 140 if pos == -1:
141 141 self.pos += 1
142 142 return data
143 143 zeropos = data.find('\x00', pos)
144 144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 145 zeropos + 1, 40)
146 146 flags = self.lm._getflags(data, self.pos, zeropos)
147 147 self.pos += 1
148 148 return (data[pos:zeropos], hashval, flags)
149 149
150 150 def unhexlify(data, extra, pos, length):
151 151 s = data[pos:pos + length].decode('hex')
152 152 if extra:
153 153 s += chr(extra & 0xff)
154 154 return s
155 155
156 156 def _cmp(a, b):
157 157 return (a > b) - (a < b)
158 158
159 159 class _lazymanifest(object):
160 160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 161 if positions is None:
162 162 self.positions = self.findlines(data)
163 163 self.extrainfo = [0] * len(self.positions)
164 164 self.data = data
165 165 self.extradata = []
166 166 else:
167 167 self.positions = positions[:]
168 168 self.extrainfo = extrainfo[:]
169 169 self.extradata = extradata[:]
170 170 self.data = data
171 171
172 172 def findlines(self, data):
173 173 if not data:
174 174 return []
175 175 pos = data.find("\n")
176 176 if pos == -1 or data[-1] != '\n':
177 177 raise ValueError("Manifest did not end in a newline.")
178 178 positions = [0]
179 179 prev = data[:data.find('\x00')]
180 180 while pos < len(data) - 1 and pos != -1:
181 181 positions.append(pos + 1)
182 182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 183 if nexts < prev:
184 184 raise ValueError("Manifest lines not in sorted order.")
185 185 prev = nexts
186 186 pos = data.find("\n", pos + 1)
187 187 return positions
188 188
189 189 def _get(self, index):
190 190 # get the position encoded in pos:
191 191 # positive number is an index in 'data'
192 192 # negative number is in extrapieces
193 193 pos = self.positions[index]
194 194 if pos >= 0:
195 195 return self.data, pos
196 196 return self.extradata[-pos - 1], -1
197 197
198 198 def _getkey(self, pos):
199 199 if pos >= 0:
200 200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 201 return self.extradata[-pos - 1][0]
202 202
203 203 def bsearch(self, key):
204 204 first = 0
205 205 last = len(self.positions) - 1
206 206
207 207 while first <= last:
208 208 midpoint = (first + last)//2
209 209 nextpos = self.positions[midpoint]
210 210 candidate = self._getkey(nextpos)
211 211 r = _cmp(key, candidate)
212 212 if r == 0:
213 213 return midpoint
214 214 else:
215 215 if r < 0:
216 216 last = midpoint - 1
217 217 else:
218 218 first = midpoint + 1
219 219 return -1
220 220
221 221 def bsearch2(self, key):
222 222 # same as the above, but will always return the position
223 223 # done for performance reasons
224 224 first = 0
225 225 last = len(self.positions) - 1
226 226
227 227 while first <= last:
228 228 midpoint = (first + last)//2
229 229 nextpos = self.positions[midpoint]
230 230 candidate = self._getkey(nextpos)
231 231 r = _cmp(key, candidate)
232 232 if r == 0:
233 233 return (midpoint, True)
234 234 else:
235 235 if r < 0:
236 236 last = midpoint - 1
237 237 else:
238 238 first = midpoint + 1
239 239 return (first, False)
240 240
241 241 def __contains__(self, key):
242 242 return self.bsearch(key) != -1
243 243
244 244 def _getflags(self, data, needle, pos):
245 245 start = pos + 41
246 246 end = data.find("\n", start)
247 247 if end == -1:
248 248 end = len(data) - 1
249 249 if start == end:
250 250 return ''
251 251 return self.data[start:end]
252 252
253 253 def __getitem__(self, key):
254 254 if not isinstance(key, str):
255 255 raise TypeError("getitem: manifest keys must be a string.")
256 256 needle = self.bsearch(key)
257 257 if needle == -1:
258 258 raise KeyError
259 259 data, pos = self._get(needle)
260 260 if pos == -1:
261 261 return (data[1], data[2])
262 262 zeropos = data.find('\x00', pos)
263 263 assert 0 <= needle <= len(self.positions)
264 264 assert len(self.extrainfo) == len(self.positions)
265 265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 266 flags = self._getflags(data, needle, zeropos)
267 267 return (hashval, flags)
268 268
269 269 def __delitem__(self, key):
270 270 needle, found = self.bsearch2(key)
271 271 if not found:
272 272 raise KeyError
273 273 cur = self.positions[needle]
274 274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 276 if cur >= 0:
277 277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278 278
279 279 def __setitem__(self, key, value):
280 280 if not isinstance(key, str):
281 281 raise TypeError("setitem: manifest keys must be a string.")
282 282 if not isinstance(value, tuple) or len(value) != 2:
283 283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 284 hashval = value[0]
285 285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 286 raise TypeError("node must be a 20-byte string")
287 287 flags = value[1]
288 288 if len(hashval) == 22:
289 289 hashval = hashval[:-1]
290 290 if not isinstance(flags, str) or len(flags) > 1:
291 291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 292 needle, found = self.bsearch2(key)
293 293 if found:
294 294 # put the item
295 295 pos = self.positions[needle]
296 296 if pos < 0:
297 297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 298 else:
299 299 # just don't bother
300 300 self.extradata.append((key, hashval, value[1]))
301 301 self.positions[needle] = -len(self.extradata)
302 302 else:
303 303 # not found, put it in with extra positions
304 304 self.extradata.append((key, hashval, value[1]))
305 305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 306 + self.positions[needle:])
307 307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 308 self.extrainfo[needle:])
309 309
310 310 def copy(self):
311 311 # XXX call _compact like in C?
312 312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 313 self.extradata)
314 314
315 315 def _compact(self):
316 316 # hopefully not called TOO often
317 317 if len(self.extradata) == 0:
318 318 return
319 319 l = []
320 320 last_cut = 0
321 321 i = 0
322 322 offset = 0
323 323 self.extrainfo = [0] * len(self.positions)
324 324 while i < len(self.positions):
325 325 if self.positions[i] >= 0:
326 326 cur = self.positions[i]
327 327 last_cut = cur
328 328 while True:
329 329 self.positions[i] = offset
330 330 i += 1
331 331 if i == len(self.positions) or self.positions[i] < 0:
332 332 break
333 333 offset += self.positions[i] - cur
334 334 cur = self.positions[i]
335 335 end_cut = self.data.find('\n', cur)
336 336 if end_cut != -1:
337 337 end_cut += 1
338 338 offset += end_cut - cur
339 339 l.append(self.data[last_cut:end_cut])
340 340 else:
341 341 while i < len(self.positions) and self.positions[i] < 0:
342 342 cur = self.positions[i]
343 343 t = self.extradata[-cur - 1]
344 344 l.append(self._pack(t))
345 345 self.positions[i] = offset
346 346 if len(t[1]) > 20:
347 347 self.extrainfo[i] = ord(t[1][21])
348 348 offset += len(l[-1])
349 349 i += 1
350 350 self.data = ''.join(l)
351 351 self.extradata = []
352 352
353 353 def _pack(self, d):
354 354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355 355
356 356 def text(self):
357 357 self._compact()
358 358 return self.data
359 359
360 360 def diff(self, m2, clean=False):
361 361 '''Finds changes between the current manifest and m2.'''
362 362 # XXX think whether efficiency matters here
363 363 diff = {}
364 364
365 365 for fn, e1, flags in self.iterentries():
366 366 if fn not in m2:
367 367 diff[fn] = (e1, flags), (None, '')
368 368 else:
369 369 e2 = m2[fn]
370 370 if (e1, flags) != e2:
371 371 diff[fn] = (e1, flags), e2
372 372 elif clean:
373 373 diff[fn] = None
374 374
375 375 for fn, e2, flags in m2.iterentries():
376 376 if fn not in self:
377 377 diff[fn] = (None, ''), (e2, flags)
378 378
379 379 return diff
380 380
381 381 def iterentries(self):
382 382 return lazymanifestiterentries(self)
383 383
384 384 def iterkeys(self):
385 385 return lazymanifestiter(self)
386 386
387 387 def __iter__(self):
388 388 return lazymanifestiter(self)
389 389
390 390 def __len__(self):
391 391 return len(self.positions)
392 392
393 393 def filtercopy(self, filterfn):
394 394 # XXX should be optimized
395 395 c = _lazymanifest('')
396 396 for f, n, fl in self.iterentries():
397 397 if filterfn(f):
398 398 c[f] = n, fl
399 399 return c
400 400
401 401 try:
402 402 _lazymanifest = parsers.lazymanifest
403 403 except AttributeError:
404 404 pass
405 405
406 406 class manifestdict(object):
407 407 def __init__(self, data=''):
408 408 if data.startswith('\0'):
409 409 #_lazymanifest can not parse v2
410 410 self._lm = _lazymanifest('')
411 411 for f, n, fl in _parsev2(data):
412 412 self._lm[f] = n, fl
413 413 else:
414 414 self._lm = _lazymanifest(data)
415 415
416 416 def __getitem__(self, key):
417 417 return self._lm[key][0]
418 418
419 419 def find(self, key):
420 420 return self._lm[key]
421 421
422 422 def __len__(self):
423 423 return len(self._lm)
424 424
425 425 def __nonzero__(self):
426 426 # nonzero is covered by the __len__ function, but implementing it here
427 427 # makes it easier for extensions to override.
428 428 return len(self._lm) != 0
429 429
430 430 def __setitem__(self, key, node):
431 431 self._lm[key] = node, self.flags(key, '')
432 432
433 433 def __contains__(self, key):
434 434 return key in self._lm
435 435
436 436 def __delitem__(self, key):
437 437 del self._lm[key]
438 438
439 439 def __iter__(self):
440 440 return self._lm.__iter__()
441 441
442 442 def iterkeys(self):
443 443 return self._lm.iterkeys()
444 444
445 445 def keys(self):
446 446 return list(self.iterkeys())
447 447
448 448 def filesnotin(self, m2):
449 449 '''Set of files in this manifest that are not in the other'''
450 450 diff = self.diff(m2)
451 451 files = set(filepath
452 452 for filepath, hashflags in diff.iteritems()
453 453 if hashflags[1][0] is None)
454 454 return files
455 455
456 456 @propertycache
457 457 def _dirs(self):
458 458 return util.dirs(self)
459 459
460 460 def dirs(self):
461 461 return self._dirs
462 462
463 463 def hasdir(self, dir):
464 464 return dir in self._dirs
465 465
466 466 def _filesfastpath(self, match):
467 467 '''Checks whether we can correctly and quickly iterate over matcher
468 468 files instead of over manifest files.'''
469 469 files = match.files()
470 470 return (len(files) < 100 and (match.isexact() or
471 471 (match.prefix() and all(fn in self for fn in files))))
472 472
473 473 def walk(self, match):
474 474 '''Generates matching file names.
475 475
476 476 Equivalent to manifest.matches(match).iterkeys(), but without creating
477 477 an entirely new manifest.
478 478
479 479 It also reports nonexistent files by marking them bad with match.bad().
480 480 '''
481 481 if match.always():
482 482 for f in iter(self):
483 483 yield f
484 484 return
485 485
486 486 fset = set(match.files())
487 487
488 488 # avoid the entire walk if we're only looking for specific files
489 489 if self._filesfastpath(match):
490 490 for fn in sorted(fset):
491 491 yield fn
492 492 return
493 493
494 494 for fn in self:
495 495 if fn in fset:
496 496 # specified pattern is the exact name
497 497 fset.remove(fn)
498 498 if match(fn):
499 499 yield fn
500 500
501 501 # for dirstate.walk, files=['.'] means "walk the whole tree".
502 502 # follow that here, too
503 503 fset.discard('.')
504 504
505 505 for fn in sorted(fset):
506 506 if not self.hasdir(fn):
507 507 match.bad(fn, None)
508 508
509 509 def matches(self, match):
510 510 '''generate a new manifest filtered by the match argument'''
511 511 if match.always():
512 512 return self.copy()
513 513
514 514 if self._filesfastpath(match):
515 515 m = manifestdict()
516 516 lm = self._lm
517 517 for fn in match.files():
518 518 if fn in lm:
519 519 m._lm[fn] = lm[fn]
520 520 return m
521 521
522 522 m = manifestdict()
523 523 m._lm = self._lm.filtercopy(match)
524 524 return m
525 525
526 526 def diff(self, m2, clean=False):
527 527 '''Finds changes between the current manifest and m2.
528 528
529 529 Args:
530 530 m2: the manifest to which this manifest should be compared.
531 531 clean: if true, include files unchanged between these manifests
532 532 with a None value in the returned dictionary.
533 533
534 534 The result is returned as a dict with filename as key and
535 535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
536 536 nodeid in the current/other manifest and fl1/fl2 is the flag
537 537 in the current/other manifest. Where the file does not exist,
538 538 the nodeid will be None and the flags will be the empty
539 539 string.
540 540 '''
541 541 return self._lm.diff(m2._lm, clean)
542 542
543 543 def setflag(self, key, flag):
544 544 self._lm[key] = self[key], flag
545 545
546 546 def get(self, key, default=None):
547 547 try:
548 548 return self._lm[key][0]
549 549 except KeyError:
550 550 return default
551 551
552 552 def flags(self, key, default=''):
553 553 try:
554 554 return self._lm[key][1]
555 555 except KeyError:
556 556 return default
557 557
558 558 def copy(self):
559 559 c = manifestdict()
560 560 c._lm = self._lm.copy()
561 561 return c
562 562
563 563 def iteritems(self):
564 564 return (x[:2] for x in self._lm.iterentries())
565 565
566 566 def iterentries(self):
567 567 return self._lm.iterentries()
568 568
569 569 def text(self, usemanifestv2=False):
570 570 if usemanifestv2:
571 571 return _textv2(self._lm.iterentries())
572 572 else:
573 573 # use (probably) native version for v1
574 574 return self._lm.text()
575 575
576 576 def fastdelta(self, base, changes):
577 577 """Given a base manifest text as an array.array and a list of changes
578 578 relative to that text, compute a delta that can be used by revlog.
579 579 """
580 580 delta = []
581 581 dstart = None
582 582 dend = None
583 583 dline = [""]
584 584 start = 0
585 585 # zero copy representation of base as a buffer
586 586 addbuf = util.buffer(base)
587 587
588 588 changes = list(changes)
589 589 if len(changes) < 1000:
590 590 # start with a readonly loop that finds the offset of
591 591 # each line and creates the deltas
592 592 for f, todelete in changes:
593 593 # bs will either be the index of the item or the insert point
594 594 start, end = _msearch(addbuf, f, start)
595 595 if not todelete:
596 596 h, fl = self._lm[f]
597 597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
598 598 else:
599 599 if start == end:
600 600 # item we want to delete was not found, error out
601 601 raise AssertionError(
602 602 _("failed to remove %s from manifest") % f)
603 603 l = ""
604 604 if dstart is not None and dstart <= start and dend >= start:
605 605 if dend < end:
606 606 dend = end
607 607 if l:
608 608 dline.append(l)
609 609 else:
610 610 if dstart is not None:
611 611 delta.append([dstart, dend, "".join(dline)])
612 612 dstart = start
613 613 dend = end
614 614 dline = [l]
615 615
616 616 if dstart is not None:
617 617 delta.append([dstart, dend, "".join(dline)])
618 618 # apply the delta to the base, and get a delta for addrevision
619 619 deltatext, arraytext = _addlistdelta(base, delta)
620 620 else:
621 621 # For large changes, it's much cheaper to just build the text and
622 622 # diff it.
623 623 arraytext = array.array('c', self.text())
624 624 deltatext = mdiff.textdiff(base, arraytext)
625 625
626 626 return arraytext, deltatext
627 627
628 628 def _msearch(m, s, lo=0, hi=None):
629 629 '''return a tuple (start, end) that says where to find s within m.
630 630
631 631 If the string is found m[start:end] are the line containing
632 632 that string. If start == end the string was not found and
633 633 they indicate the proper sorted insertion point.
634 634
635 635 m should be a buffer or a string
636 636 s is a string'''
637 637 def advance(i, c):
638 638 while i < lenm and m[i] != c:
639 639 i += 1
640 640 return i
641 641 if not s:
642 642 return (lo, lo)
643 643 lenm = len(m)
644 644 if not hi:
645 645 hi = lenm
646 646 while lo < hi:
647 647 mid = (lo + hi) // 2
648 648 start = mid
649 649 while start > 0 and m[start - 1] != '\n':
650 650 start -= 1
651 651 end = advance(start, '\0')
652 652 if m[start:end] < s:
653 653 # we know that after the null there are 40 bytes of sha1
654 654 # this translates to the bisect lo = mid + 1
655 655 lo = advance(end + 40, '\n') + 1
656 656 else:
657 657 # this translates to the bisect hi = mid
658 658 hi = start
659 659 end = advance(lo, '\0')
660 660 found = m[lo:end]
661 661 if s == found:
662 662 # we know that after the null there are 40 bytes of sha1
663 663 end = advance(end + 40, '\n')
664 664 return (lo, end + 1)
665 665 else:
666 666 return (lo, lo)
667 667
668 668 def _checkforbidden(l):
669 669 """Check filenames for illegal characters."""
670 670 for f in l:
671 671 if '\n' in f or '\r' in f:
672 672 raise error.RevlogError(
673 673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
674 674
675 675
676 676 # apply the changes collected during the bisect loop to our addlist
677 677 # return a delta suitable for addrevision
678 678 def _addlistdelta(addlist, x):
679 679 # for large addlist arrays, building a new array is cheaper
680 680 # than repeatedly modifying the existing one
681 681 currentposition = 0
682 682 newaddlist = array.array('c')
683 683
684 684 for start, end, content in x:
685 685 newaddlist += addlist[currentposition:start]
686 686 if content:
687 687 newaddlist += array.array('c', content)
688 688
689 689 currentposition = end
690 690
691 691 newaddlist += addlist[currentposition:]
692 692
693 693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
694 694 + content for start, end, content in x)
695 695 return deltatext, newaddlist
696 696
697 697 def _splittopdir(f):
698 698 if '/' in f:
699 699 dir, subpath = f.split('/', 1)
700 700 return dir + '/', subpath
701 701 else:
702 702 return '', f
703 703
704 704 _noop = lambda s: None
705 705
706 706 class treemanifest(object):
707 707 def __init__(self, dir='', text=''):
708 708 self._dir = dir
709 709 self._node = revlog.nullid
710 710 self._loadfunc = _noop
711 711 self._copyfunc = _noop
712 712 self._dirty = False
713 713 self._dirs = {}
714 714 # Using _lazymanifest here is a little slower than plain old dicts
715 715 self._files = {}
716 716 self._flags = {}
717 717 if text:
718 718 def readsubtree(subdir, subm):
719 719 raise AssertionError('treemanifest constructor only accepts '
720 720 'flat manifests')
721 721 self.parse(text, readsubtree)
722 722 self._dirty = True # Mark flat manifest dirty after parsing
723 723
724 724 def _subpath(self, path):
725 725 return self._dir + path
726 726
727 727 def __len__(self):
728 728 self._load()
729 729 size = len(self._files)
730 730 for m in self._dirs.values():
731 731 size += m.__len__()
732 732 return size
733 733
734 734 def _isempty(self):
735 735 self._load() # for consistency; already loaded by all callers
736 736 return (not self._files and (not self._dirs or
737 737 all(m._isempty() for m in self._dirs.values())))
738 738
739 739 def __repr__(self):
740 740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
741 741 (self._dir, revlog.hex(self._node),
742 742 bool(self._loadfunc is _noop),
743 743 self._dirty, id(self)))
744 744
745 745 def dir(self):
746 746 '''The directory that this tree manifest represents, including a
747 747 trailing '/'. Empty string for the repo root directory.'''
748 748 return self._dir
749 749
750 750 def node(self):
751 751 '''This node of this instance. nullid for unsaved instances. Should
752 752 be updated when the instance is read or written from a revlog.
753 753 '''
754 754 assert not self._dirty
755 755 return self._node
756 756
757 757 def setnode(self, node):
758 758 self._node = node
759 759 self._dirty = False
760 760
761 761 def iterentries(self):
762 762 self._load()
763 763 for p, n in sorted(self._dirs.items() + self._files.items()):
764 764 if p in self._files:
765 765 yield self._subpath(p), n, self._flags.get(p, '')
766 766 else:
767 767 for x in n.iterentries():
768 768 yield x
769 769
770 770 def iteritems(self):
771 771 self._load()
772 772 for p, n in sorted(self._dirs.items() + self._files.items()):
773 773 if p in self._files:
774 774 yield self._subpath(p), n
775 775 else:
776 776 for f, sn in n.iteritems():
777 777 yield f, sn
778 778
779 779 def iterkeys(self):
780 780 self._load()
781 781 for p in sorted(self._dirs.keys() + self._files.keys()):
782 782 if p in self._files:
783 783 yield self._subpath(p)
784 784 else:
785 785 for f in self._dirs[p].iterkeys():
786 786 yield f
787 787
788 788 def keys(self):
789 789 return list(self.iterkeys())
790 790
791 791 def __iter__(self):
792 792 return self.iterkeys()
793 793
794 794 def __contains__(self, f):
795 795 if f is None:
796 796 return False
797 797 self._load()
798 798 dir, subpath = _splittopdir(f)
799 799 if dir:
800 800 if dir not in self._dirs:
801 801 return False
802 802 return self._dirs[dir].__contains__(subpath)
803 803 else:
804 804 return f in self._files
805 805
806 806 def get(self, f, default=None):
807 807 self._load()
808 808 dir, subpath = _splittopdir(f)
809 809 if dir:
810 810 if dir not in self._dirs:
811 811 return default
812 812 return self._dirs[dir].get(subpath, default)
813 813 else:
814 814 return self._files.get(f, default)
815 815
816 816 def __getitem__(self, f):
817 817 self._load()
818 818 dir, subpath = _splittopdir(f)
819 819 if dir:
820 820 return self._dirs[dir].__getitem__(subpath)
821 821 else:
822 822 return self._files[f]
823 823
824 824 def flags(self, f):
825 825 self._load()
826 826 dir, subpath = _splittopdir(f)
827 827 if dir:
828 828 if dir not in self._dirs:
829 829 return ''
830 830 return self._dirs[dir].flags(subpath)
831 831 else:
832 832 if f in self._dirs:
833 833 return ''
834 834 return self._flags.get(f, '')
835 835
836 836 def find(self, f):
837 837 self._load()
838 838 dir, subpath = _splittopdir(f)
839 839 if dir:
840 840 return self._dirs[dir].find(subpath)
841 841 else:
842 842 return self._files[f], self._flags.get(f, '')
843 843
844 844 def __delitem__(self, f):
845 845 self._load()
846 846 dir, subpath = _splittopdir(f)
847 847 if dir:
848 848 self._dirs[dir].__delitem__(subpath)
849 849 # If the directory is now empty, remove it
850 850 if self._dirs[dir]._isempty():
851 851 del self._dirs[dir]
852 852 else:
853 853 del self._files[f]
854 854 if f in self._flags:
855 855 del self._flags[f]
856 856 self._dirty = True
857 857
858 858 def __setitem__(self, f, n):
859 859 assert n is not None
860 860 self._load()
861 861 dir, subpath = _splittopdir(f)
862 862 if dir:
863 863 if dir not in self._dirs:
864 864 self._dirs[dir] = treemanifest(self._subpath(dir))
865 865 self._dirs[dir].__setitem__(subpath, n)
866 866 else:
867 867 self._files[f] = n[:21] # to match manifestdict's behavior
868 868 self._dirty = True
869 869
870 870 def _load(self):
871 871 if self._loadfunc is not _noop:
872 872 lf, self._loadfunc = self._loadfunc, _noop
873 873 lf(self)
874 874 elif self._copyfunc is not _noop:
875 875 cf, self._copyfunc = self._copyfunc, _noop
876 876 cf(self)
877 877
878 878 def setflag(self, f, flags):
879 879 """Set the flags (symlink, executable) for path f."""
880 880 self._load()
881 881 dir, subpath = _splittopdir(f)
882 882 if dir:
883 883 if dir not in self._dirs:
884 884 self._dirs[dir] = treemanifest(self._subpath(dir))
885 885 self._dirs[dir].setflag(subpath, flags)
886 886 else:
887 887 self._flags[f] = flags
888 888 self._dirty = True
889 889
890 890 def copy(self):
891 891 copy = treemanifest(self._dir)
892 892 copy._node = self._node
893 893 copy._dirty = self._dirty
894 894 if self._copyfunc is _noop:
895 895 def _copyfunc(s):
896 896 self._load()
897 897 for d in self._dirs:
898 898 s._dirs[d] = self._dirs[d].copy()
899 899 s._files = dict.copy(self._files)
900 900 s._flags = dict.copy(self._flags)
901 901 if self._loadfunc is _noop:
902 902 _copyfunc(copy)
903 903 else:
904 904 copy._copyfunc = _copyfunc
905 905 else:
906 906 copy._copyfunc = self._copyfunc
907 907 return copy
908 908
909 909 def filesnotin(self, m2):
910 910 '''Set of files in this manifest that are not in the other'''
911 911 files = set()
912 912 def _filesnotin(t1, t2):
913 913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
914 914 return
915 915 t1._load()
916 916 t2._load()
917 917 for d, m1 in t1._dirs.iteritems():
918 918 if d in t2._dirs:
919 919 m2 = t2._dirs[d]
920 920 _filesnotin(m1, m2)
921 921 else:
922 922 files.update(m1.iterkeys())
923 923
924 924 for fn in t1._files.iterkeys():
925 925 if fn not in t2._files:
926 926 files.add(t1._subpath(fn))
927 927
928 928 _filesnotin(self, m2)
929 929 return files
930 930
931 931 @propertycache
932 932 def _alldirs(self):
933 933 return util.dirs(self)
934 934
935 935 def dirs(self):
936 936 return self._alldirs
937 937
938 938 def hasdir(self, dir):
939 939 self._load()
940 940 topdir, subdir = _splittopdir(dir)
941 941 if topdir:
942 942 if topdir in self._dirs:
943 943 return self._dirs[topdir].hasdir(subdir)
944 944 return False
945 945 return (dir + '/') in self._dirs
946 946
947 947 def walk(self, match):
948 948 '''Generates matching file names.
949 949
950 950 Equivalent to manifest.matches(match).iterkeys(), but without creating
951 951 an entirely new manifest.
952 952
953 953 It also reports nonexistent files by marking them bad with match.bad().
954 954 '''
955 955 if match.always():
956 956 for f in iter(self):
957 957 yield f
958 958 return
959 959
960 960 fset = set(match.files())
961 961
962 962 for fn in self._walk(match):
963 963 if fn in fset:
964 964 # specified pattern is the exact name
965 965 fset.remove(fn)
966 966 yield fn
967 967
968 968 # for dirstate.walk, files=['.'] means "walk the whole tree".
969 969 # follow that here, too
970 970 fset.discard('.')
971 971
972 972 for fn in sorted(fset):
973 973 if not self.hasdir(fn):
974 974 match.bad(fn, None)
975 975
976 976 def _walk(self, match):
977 977 '''Recursively generates matching file names for walk().'''
978 978 if not match.visitdir(self._dir[:-1] or '.'):
979 979 return
980 980
981 981 # yield this dir's files and walk its submanifests
982 982 self._load()
983 983 for p in sorted(self._dirs.keys() + self._files.keys()):
984 984 if p in self._files:
985 985 fullp = self._subpath(p)
986 986 if match(fullp):
987 987 yield fullp
988 988 else:
989 989 for f in self._dirs[p]._walk(match):
990 990 yield f
991 991
992 992 def matches(self, match):
993 993 '''generate a new manifest filtered by the match argument'''
994 994 if match.always():
995 995 return self.copy()
996 996
997 997 return self._matches(match)
998 998
999 999 def _matches(self, match):
1000 1000 '''recursively generate a new manifest filtered by the match argument.
1001 1001 '''
1002 1002
1003 1003 visit = match.visitdir(self._dir[:-1] or '.')
1004 1004 if visit == 'all':
1005 1005 return self.copy()
1006 1006 ret = treemanifest(self._dir)
1007 1007 if not visit:
1008 1008 return ret
1009 1009
1010 1010 self._load()
1011 1011 for fn in self._files:
1012 1012 fullp = self._subpath(fn)
1013 1013 if not match(fullp):
1014 1014 continue
1015 1015 ret._files[fn] = self._files[fn]
1016 1016 if fn in self._flags:
1017 1017 ret._flags[fn] = self._flags[fn]
1018 1018
1019 1019 for dir, subm in self._dirs.iteritems():
1020 1020 m = subm._matches(match)
1021 1021 if not m._isempty():
1022 1022 ret._dirs[dir] = m
1023 1023
1024 1024 if not ret._isempty():
1025 1025 ret._dirty = True
1026 1026 return ret
1027 1027
1028 1028 def diff(self, m2, clean=False):
1029 1029 '''Finds changes between the current manifest and m2.
1030 1030
1031 1031 Args:
1032 1032 m2: the manifest to which this manifest should be compared.
1033 1033 clean: if true, include files unchanged between these manifests
1034 1034 with a None value in the returned dictionary.
1035 1035
1036 1036 The result is returned as a dict with filename as key and
1037 1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1038 1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1039 1039 in the current/other manifest. Where the file does not exist,
1040 1040 the nodeid will be None and the flags will be the empty
1041 1041 string.
1042 1042 '''
1043 1043 result = {}
1044 1044 emptytree = treemanifest()
1045 1045 def _diff(t1, t2):
1046 1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1047 1047 return
1048 1048 t1._load()
1049 1049 t2._load()
1050 1050 for d, m1 in t1._dirs.iteritems():
1051 1051 m2 = t2._dirs.get(d, emptytree)
1052 1052 _diff(m1, m2)
1053 1053
1054 1054 for d, m2 in t2._dirs.iteritems():
1055 1055 if d not in t1._dirs:
1056 1056 _diff(emptytree, m2)
1057 1057
1058 1058 for fn, n1 in t1._files.iteritems():
1059 1059 fl1 = t1._flags.get(fn, '')
1060 1060 n2 = t2._files.get(fn, None)
1061 1061 fl2 = t2._flags.get(fn, '')
1062 1062 if n1 != n2 or fl1 != fl2:
1063 1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1064 1064 elif clean:
1065 1065 result[t1._subpath(fn)] = None
1066 1066
1067 1067 for fn, n2 in t2._files.iteritems():
1068 1068 if fn not in t1._files:
1069 1069 fl2 = t2._flags.get(fn, '')
1070 1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1071 1071
1072 1072 _diff(self, m2)
1073 1073 return result
1074 1074
1075 1075 def unmodifiedsince(self, m2):
1076 1076 return not self._dirty and not m2._dirty and self._node == m2._node
1077 1077
1078 1078 def parse(self, text, readsubtree):
1079 1079 for f, n, fl in _parse(text):
1080 1080 if fl == 't':
1081 1081 f = f + '/'
1082 1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1083 1083 elif '/' in f:
1084 1084 # This is a flat manifest, so use __setitem__ and setflag rather
1085 1085 # than assigning directly to _files and _flags, so we can
1086 1086 # assign a path in a subdirectory, and to mark dirty (compared
1087 1087 # to nullid).
1088 1088 self[f] = n
1089 1089 if fl:
1090 1090 self.setflag(f, fl)
1091 1091 else:
1092 1092 # Assigning to _files and _flags avoids marking as dirty,
1093 1093 # and should be a little faster.
1094 1094 self._files[f] = n
1095 1095 if fl:
1096 1096 self._flags[f] = fl
1097 1097
1098 1098 def text(self, usemanifestv2=False):
1099 1099 """Get the full data of this manifest as a bytestring."""
1100 1100 self._load()
1101 1101 return _text(self.iterentries(), usemanifestv2)
1102 1102
1103 1103 def dirtext(self, usemanifestv2=False):
1104 1104 """Get the full data of this directory as a bytestring. Make sure that
1105 1105 any submanifests have been written first, so their nodeids are correct.
1106 1106 """
1107 1107 self._load()
1108 1108 flags = self.flags
1109 1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1110 1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1111 1111 return _text(sorted(dirs + files), usemanifestv2)
1112 1112
1113 1113 def read(self, gettext, readsubtree):
1114 1114 def _load_for_read(s):
1115 1115 s.parse(gettext(), readsubtree)
1116 1116 s._dirty = False
1117 1117 self._loadfunc = _load_for_read
1118 1118
1119 1119 def writesubtrees(self, m1, m2, writesubtree):
1120 1120 self._load() # for consistency; should never have any effect here
1121 1121 m1._load()
1122 1122 m2._load()
1123 1123 emptytree = treemanifest()
1124 1124 for d, subm in self._dirs.iteritems():
1125 1125 subp1 = m1._dirs.get(d, emptytree)._node
1126 1126 subp2 = m2._dirs.get(d, emptytree)._node
1127 1127 if subp1 == revlog.nullid:
1128 1128 subp1, subp2 = subp2, subp1
1129 1129 writesubtree(subm, subp1, subp2)
1130 1130
1131 1131 class manifestrevlog(revlog.revlog):
1132 1132 '''A revlog that stores manifest texts. This is responsible for caching the
1133 1133 full-text manifest contents.
1134 1134 '''
1135 1135 def __init__(self, opener, dir='', dirlogcache=None):
1136 1136 # During normal operations, we expect to deal with not more than four
1137 1137 # revs at a time (such as during commit --amend). When rebasing large
1138 1138 # stacks of commits, the number can go up, hence the config knob below.
1139 1139 cachesize = 4
1140 1140 usetreemanifest = False
1141 1141 usemanifestv2 = False
1142 1142 opts = getattr(opener, 'options', None)
1143 1143 if opts is not None:
1144 1144 cachesize = opts.get('manifestcachesize', cachesize)
1145 1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1146 1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1147 1147
1148 1148 self._treeondisk = usetreemanifest
1149 1149 self._usemanifestv2 = usemanifestv2
1150 1150
1151 1151 self._fulltextcache = util.lrucachedict(cachesize)
1152 1152
1153 1153 indexfile = "00manifest.i"
1154 1154 if dir:
1155 1155 assert self._treeondisk, 'opts is %r' % opts
1156 1156 if not dir.endswith('/'):
1157 1157 dir = dir + '/'
1158 1158 indexfile = "meta/" + dir + "00manifest.i"
1159 1159 self._dir = dir
1160 1160 # The dirlogcache is kept on the root manifest log
1161 1161 if dir:
1162 1162 self._dirlogcache = dirlogcache
1163 1163 else:
1164 1164 self._dirlogcache = {'': self}
1165 1165
1166 1166 super(manifestrevlog, self).__init__(opener, indexfile,
1167 1167 checkambig=bool(dir))
1168 1168
1169 1169 @property
1170 1170 def fulltextcache(self):
1171 1171 return self._fulltextcache
1172 1172
1173 1173 def clearcaches(self):
1174 1174 super(manifestrevlog, self).clearcaches()
1175 1175 self._fulltextcache.clear()
1176 1176 self._dirlogcache = {'': self}
1177 1177
1178 1178 def dirlog(self, dir):
1179 1179 if dir:
1180 1180 assert self._treeondisk
1181 1181 if dir not in self._dirlogcache:
1182 1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1183 1183 self._dirlogcache)
1184 1184 return self._dirlogcache[dir]
1185 1185
1186 1186 def add(self, m, transaction, link, p1, p2, added, removed):
1187 1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1188 1188 and not self._usemanifestv2):
1189 1189 # If our first parent is in the manifest cache, we can
1190 1190 # compute a delta here using properties we know about the
1191 1191 # manifest up-front, which may save time later for the
1192 1192 # revlog layer.
1193 1193
1194 1194 _checkforbidden(added)
1195 1195 # combine the changed lists into one sorted iterator
1196 1196 work = heapq.merge([(x, False) for x in added],
1197 1197 [(x, True) for x in removed])
1198 1198
1199 1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1200 1200 cachedelta = self.rev(p1), deltatext
1201 1201 text = util.buffer(arraytext)
1202 1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1203 1203 else:
1204 1204 # The first parent manifest isn't already loaded, so we'll
1205 1205 # just encode a fulltext of the manifest and pass that
1206 1206 # through to the revlog layer, and let it handle the delta
1207 1207 # process.
1208 1208 if self._treeondisk:
1209 1209 m1 = self.read(p1)
1210 1210 m2 = self.read(p2)
1211 1211 n = self._addtree(m, transaction, link, m1, m2)
1212 1212 arraytext = None
1213 1213 else:
1214 1214 text = m.text(self._usemanifestv2)
1215 1215 n = self.addrevision(text, transaction, link, p1, p2)
1216 1216 arraytext = array.array('c', text)
1217 1217
1218 1218 if arraytext is not None:
1219 1219 self.fulltextcache[n] = arraytext
1220 1220
1221 1221 return n
1222 1222
1223 1223 def _addtree(self, m, transaction, link, m1, m2):
1224 1224 # If the manifest is unchanged compared to one parent,
1225 1225 # don't write a new revision
1226 1226 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1227 1227 return m.node()
1228 1228 def writesubtree(subm, subp1, subp2):
1229 1229 sublog = self.dirlog(subm.dir())
1230 1230 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1231 1231 m.writesubtrees(m1, m2, writesubtree)
1232 1232 text = m.dirtext(self._usemanifestv2)
1233 1233 # Double-check whether contents are unchanged to one parent
1234 1234 if text == m1.dirtext(self._usemanifestv2):
1235 1235 n = m1.node()
1236 1236 elif text == m2.dirtext(self._usemanifestv2):
1237 1237 n = m2.node()
1238 1238 else:
1239 1239 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1240 1240 # Save nodeid so parent manifest can calculate its nodeid
1241 1241 m.setnode(n)
1242 1242 return n
1243 1243
1244 1244 class manifestlog(object):
1245 1245 """A collection class representing the collection of manifest snapshots
1246 1246 referenced by commits in the repository.
1247 1247
1248 1248 In this situation, 'manifest' refers to the abstract concept of a snapshot
1249 1249 of the list of files in the given commit. Consumers of the output of this
1250 1250 class do not care about the implementation details of the actual manifests
1251 1251 they receive (i.e. tree or flat or lazily loaded, etc)."""
1252 1252 def __init__(self, opener, repo):
1253 1253 self._repo = repo
1254 1254
1255 1255 usetreemanifest = False
1256 1256
1257 1257 opts = getattr(opener, 'options', None)
1258 1258 if opts is not None:
1259 1259 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1260 1260 self._treeinmem = usetreemanifest
1261 1261
1262 1262 self._oldmanifest = repo._constructmanifest()
1263 1263 self._revlog = self._oldmanifest
1264 1264
1265 1265 # A cache of the manifestctx or treemanifestctx for each directory
1266 1266 self._dirmancache = {}
1267 1267
1268 1268 # We'll separate this into it's own cache once oldmanifest is no longer
1269 1269 # used
1270 1270 self._mancache = self._oldmanifest._mancache
1271 1271 self._dirmancache[''] = self._mancache
1272 1272
1273 1273 # A future patch makes this use the same config value as the existing
1274 1274 # mancache
1275 1275 self.cachesize = 4
1276 1276
1277 1277 def __getitem__(self, node):
1278 1278 """Retrieves the manifest instance for the given node. Throws a
1279 1279 LookupError if not found.
1280 1280 """
1281 1281 return self.get('', node)
1282 1282
1283 1283 def get(self, dir, node):
1284 1284 """Retrieves the manifest instance for the given node. Throws a
1285 1285 LookupError if not found.
1286 1286 """
1287 1287 if node in self._dirmancache.get(dir, ()):
1288 1288 cachemf = self._dirmancache[dir][node]
1289 1289 # The old manifest may put non-ctx manifests in the cache, so
1290 1290 # skip those since they don't implement the full api.
1291 1291 if (isinstance(cachemf, manifestctx) or
1292 1292 isinstance(cachemf, treemanifestctx)):
1293 1293 return cachemf
1294 1294
1295 1295 if dir:
1296 1296 if self._revlog._treeondisk:
1297 1297 dirlog = self._revlog.dirlog(dir)
1298 1298 if node not in dirlog.nodemap:
1299 1299 raise LookupError(node, dirlog.indexfile,
1300 1300 _('no node'))
1301 1301 m = treemanifestctx(self._repo, dir, node)
1302 1302 else:
1303 1303 raise error.Abort(
1304 1304 _("cannot ask for manifest directory '%s' in a flat "
1305 1305 "manifest") % dir)
1306 1306 else:
1307 1307 if node not in self._revlog.nodemap:
1308 1308 raise LookupError(node, self._revlog.indexfile,
1309 1309 _('no node'))
1310 1310 if self._treeinmem:
1311 1311 m = treemanifestctx(self._repo, '', node)
1312 1312 else:
1313 1313 m = manifestctx(self._repo, node)
1314 1314
1315 1315 if node != revlog.nullid:
1316 1316 mancache = self._dirmancache.get(dir)
1317 1317 if not mancache:
1318 1318 mancache = util.lrucachedict(self.cachesize)
1319 1319 self._dirmancache[dir] = mancache
1320 1320 mancache[node] = m
1321 1321 return m
1322 1322
1323 1323 def add(self, m, transaction, link, p1, p2, added, removed):
1324 1324 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1325 1325
1326 1326 class memmanifestctx(object):
1327 1327 def __init__(self, repo):
1328 1328 self._repo = repo
1329 1329 self._manifestdict = manifestdict()
1330 1330
1331 1331 def new(self):
1332 1332 return memmanifestctx(self._repo)
1333 1333
1334 def copy(self):
1335 memmf = memmanifestctx(self._repo)
1336 memmf._manifestdict = self.read().copy()
1337 return memmf
1338
1334 1339 def read(self):
1335 1340 return self._manifestdict
1336 1341
1337 1342 class manifestctx(object):
1338 1343 """A class representing a single revision of a manifest, including its
1339 1344 contents, its parent revs, and its linkrev.
1340 1345 """
1341 1346 def __init__(self, repo, node):
1342 1347 self._repo = repo
1343 1348 self._data = None
1344 1349
1345 1350 self._node = node
1346 1351
1347 1352 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1348 1353 # but let's add it later when something needs it and we can load it
1349 1354 # lazily.
1350 1355 #self.p1, self.p2 = revlog.parents(node)
1351 1356 #rev = revlog.rev(node)
1352 1357 #self.linkrev = revlog.linkrev(rev)
1353 1358
1354 1359 def _revlog(self):
1355 1360 return self._repo.manifestlog._revlog
1356 1361
1357 1362 def node(self):
1358 1363 return self._node
1359 1364
1360 1365 def new(self):
1361 1366 return memmanifestctx(self._repo)
1362 1367
1368 def copy(self):
1369 memmf = memmanifestctx(self._repo)
1370 memmf._manifestdict = self.read().copy()
1371 return memmf
1372
1363 1373 def read(self):
1364 1374 if not self._data:
1365 1375 if self._node == revlog.nullid:
1366 1376 self._data = manifestdict()
1367 1377 else:
1368 1378 rl = self._revlog()
1369 1379 text = rl.revision(self._node)
1370 1380 arraytext = array.array('c', text)
1371 1381 rl._fulltextcache[self._node] = arraytext
1372 1382 self._data = manifestdict(text)
1373 1383 return self._data
1374 1384
1375 1385 def readfast(self, shallow=False):
1376 1386 '''Calls either readdelta or read, based on which would be less work.
1377 1387 readdelta is called if the delta is against the p1, and therefore can be
1378 1388 read quickly.
1379 1389
1380 1390 If `shallow` is True, nothing changes since this is a flat manifest.
1381 1391 '''
1382 1392 rl = self._revlog()
1383 1393 r = rl.rev(self._node)
1384 1394 deltaparent = rl.deltaparent(r)
1385 1395 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1386 1396 return self.readdelta()
1387 1397 return self.read()
1388 1398
1389 1399 def readdelta(self, shallow=False):
1390 1400 '''Returns a manifest containing just the entries that are present
1391 1401 in this manifest, but not in its p1 manifest. This is efficient to read
1392 1402 if the revlog delta is already p1.
1393 1403
1394 1404 Changing the value of `shallow` has no effect on flat manifests.
1395 1405 '''
1396 1406 revlog = self._revlog()
1397 1407 if revlog._usemanifestv2:
1398 1408 # Need to perform a slow delta
1399 1409 r0 = revlog.deltaparent(revlog.rev(self._node))
1400 1410 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1401 1411 m1 = self.read()
1402 1412 md = manifestdict()
1403 1413 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1404 1414 if n1:
1405 1415 md[f] = n1
1406 1416 if fl1:
1407 1417 md.setflag(f, fl1)
1408 1418 return md
1409 1419
1410 1420 r = revlog.rev(self._node)
1411 1421 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1412 1422 return manifestdict(d)
1413 1423
1414 1424 def find(self, key):
1415 1425 return self.read().find(key)
1416 1426
1417 1427 class memtreemanifestctx(object):
1418 1428 def __init__(self, repo, dir=''):
1419 1429 self._repo = repo
1420 1430 self._dir = dir
1421 1431 self._treemanifest = treemanifest()
1422 1432
1423 1433 def new(self, dir=''):
1424 1434 return memtreemanifestctx(self._repo, dir=dir)
1425 1435
1436 def copy(self):
1437 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1438 memmf._treemanifest = self._treemanifest.copy()
1439 return memmf
1440
1426 1441 def read(self):
1427 1442 return self._treemanifest
1428 1443
1429 1444 class treemanifestctx(object):
1430 1445 def __init__(self, repo, dir, node):
1431 1446 self._repo = repo
1432 1447 self._dir = dir
1433 1448 self._data = None
1434 1449
1435 1450 self._node = node
1436 1451
1437 1452 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1438 1453 # we can instantiate treemanifestctx objects for directories we don't
1439 1454 # have on disk.
1440 1455 #self.p1, self.p2 = revlog.parents(node)
1441 1456 #rev = revlog.rev(node)
1442 1457 #self.linkrev = revlog.linkrev(rev)
1443 1458
1444 1459 def _revlog(self):
1445 1460 return self._repo.manifestlog._revlog.dirlog(self._dir)
1446 1461
1447 1462 def read(self):
1448 1463 if not self._data:
1449 1464 rl = self._revlog()
1450 1465 if self._node == revlog.nullid:
1451 1466 self._data = treemanifest()
1452 1467 elif rl._treeondisk:
1453 1468 m = treemanifest(dir=self._dir)
1454 1469 def gettext():
1455 1470 return rl.revision(self._node)
1456 1471 def readsubtree(dir, subm):
1457 1472 return treemanifestctx(self._repo, dir, subm).read()
1458 1473 m.read(gettext, readsubtree)
1459 1474 m.setnode(self._node)
1460 1475 self._data = m
1461 1476 else:
1462 1477 text = rl.revision(self._node)
1463 1478 arraytext = array.array('c', text)
1464 1479 rl.fulltextcache[self._node] = arraytext
1465 1480 self._data = treemanifest(dir=self._dir, text=text)
1466 1481
1467 1482 return self._data
1468 1483
1469 1484 def node(self):
1470 1485 return self._node
1471 1486
1472 1487 def new(self, dir=''):
1473 1488 return memtreemanifestctx(self._repo, dir=dir)
1474 1489
1490 def copy(self):
1491 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1492 memmf._treemanifest = self.read().copy()
1493 return memmf
1494
1475 1495 def readdelta(self, shallow=False):
1476 1496 '''Returns a manifest containing just the entries that are present
1477 1497 in this manifest, but not in its p1 manifest. This is efficient to read
1478 1498 if the revlog delta is already p1.
1479 1499
1480 1500 If `shallow` is True, this will read the delta for this directory,
1481 1501 without recursively reading subdirectory manifests. Instead, any
1482 1502 subdirectory entry will be reported as it appears in the manifest, i.e.
1483 1503 the subdirectory will be reported among files and distinguished only by
1484 1504 its 't' flag.
1485 1505 '''
1486 1506 revlog = self._revlog()
1487 1507 if shallow and not revlog._usemanifestv2:
1488 1508 r = revlog.rev(self._node)
1489 1509 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1490 1510 return manifestdict(d)
1491 1511 else:
1492 1512 # Need to perform a slow delta
1493 1513 r0 = revlog.deltaparent(revlog.rev(self._node))
1494 1514 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1495 1515 m1 = self.read()
1496 1516 md = treemanifest(dir=self._dir)
1497 1517 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1498 1518 if n1:
1499 1519 md[f] = n1
1500 1520 if fl1:
1501 1521 md.setflag(f, fl1)
1502 1522 return md
1503 1523
1504 1524 def readfast(self, shallow=False):
1505 1525 '''Calls either readdelta or read, based on which would be less work.
1506 1526 readdelta is called if the delta is against the p1, and therefore can be
1507 1527 read quickly.
1508 1528
1509 1529 If `shallow` is True, it only returns the entries from this manifest,
1510 1530 and not any submanifests.
1511 1531 '''
1512 1532 rl = self._revlog()
1513 1533 r = rl.rev(self._node)
1514 1534 deltaparent = rl.deltaparent(r)
1515 1535 if (deltaparent != revlog.nullrev and
1516 1536 deltaparent in rl.parentrevs(r)):
1517 1537 return self.readdelta(shallow=shallow)
1518 1538
1519 1539 if shallow:
1520 1540 return manifestdict(rl.revision(self._node))
1521 1541 else:
1522 1542 return self.read()
1523 1543
1524 1544 def find(self, key):
1525 1545 return self.read().find(key)
1526 1546
1527 1547 class manifest(manifestrevlog):
1528 1548 def __init__(self, opener, dir='', dirlogcache=None):
1529 1549 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1530 1550 manifest.manifest only. External users should create a root manifest
1531 1551 log with manifest.manifest(opener) and call dirlog() on it.
1532 1552 '''
1533 1553 # During normal operations, we expect to deal with not more than four
1534 1554 # revs at a time (such as during commit --amend). When rebasing large
1535 1555 # stacks of commits, the number can go up, hence the config knob below.
1536 1556 cachesize = 4
1537 1557 usetreemanifest = False
1538 1558 opts = getattr(opener, 'options', None)
1539 1559 if opts is not None:
1540 1560 cachesize = opts.get('manifestcachesize', cachesize)
1541 1561 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1542 1562 self._mancache = util.lrucachedict(cachesize)
1543 1563 self._treeinmem = usetreemanifest
1544 1564 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1545 1565
1546 1566 def _newmanifest(self, data=''):
1547 1567 if self._treeinmem:
1548 1568 return treemanifest(self._dir, data)
1549 1569 return manifestdict(data)
1550 1570
1551 1571 def dirlog(self, dir):
1552 1572 """This overrides the base revlog implementation to allow construction
1553 1573 'manifest' types instead of manifestrevlog types. This is only needed
1554 1574 until we migrate off the 'manifest' type."""
1555 1575 if dir:
1556 1576 assert self._treeondisk
1557 1577 if dir not in self._dirlogcache:
1558 1578 self._dirlogcache[dir] = manifest(self.opener, dir,
1559 1579 self._dirlogcache)
1560 1580 return self._dirlogcache[dir]
1561 1581
1562 1582 def read(self, node):
1563 1583 if node == revlog.nullid:
1564 1584 return self._newmanifest() # don't upset local cache
1565 1585 if node in self._mancache:
1566 1586 cached = self._mancache[node]
1567 1587 if (isinstance(cached, manifestctx) or
1568 1588 isinstance(cached, treemanifestctx)):
1569 1589 cached = cached.read()
1570 1590 return cached
1571 1591 if self._treeondisk:
1572 1592 def gettext():
1573 1593 return self.revision(node)
1574 1594 def readsubtree(dir, subm):
1575 1595 return self.dirlog(dir).read(subm)
1576 1596 m = self._newmanifest()
1577 1597 m.read(gettext, readsubtree)
1578 1598 m.setnode(node)
1579 1599 arraytext = None
1580 1600 else:
1581 1601 text = self.revision(node)
1582 1602 m = self._newmanifest(text)
1583 1603 arraytext = array.array('c', text)
1584 1604 self._mancache[node] = m
1585 1605 if arraytext is not None:
1586 1606 self.fulltextcache[node] = arraytext
1587 1607 return m
1588 1608
1589 1609 def clearcaches(self):
1590 1610 super(manifest, self).clearcaches()
1591 1611 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now