##// END OF EJS Templates
manifest: add __nonzero__ method...
Durham Goode -
r30331:b19291e5 default
parent child Browse files
Show More
@@ -1,1557 +1,1562 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 def __nonzero__(self):
426 # nonzero is covered by the __len__ function, but implementing it here
427 # makes it easier for extensions to override.
428 return len(self._lm) != 0
429
425 430 def __setitem__(self, key, node):
426 431 self._lm[key] = node, self.flags(key, '')
427 432
428 433 def __contains__(self, key):
429 434 return key in self._lm
430 435
431 436 def __delitem__(self, key):
432 437 del self._lm[key]
433 438
434 439 def __iter__(self):
435 440 return self._lm.__iter__()
436 441
437 442 def iterkeys(self):
438 443 return self._lm.iterkeys()
439 444
440 445 def keys(self):
441 446 return list(self.iterkeys())
442 447
443 448 def filesnotin(self, m2):
444 449 '''Set of files in this manifest that are not in the other'''
445 450 diff = self.diff(m2)
446 451 files = set(filepath
447 452 for filepath, hashflags in diff.iteritems()
448 453 if hashflags[1][0] is None)
449 454 return files
450 455
451 456 @propertycache
452 457 def _dirs(self):
453 458 return util.dirs(self)
454 459
455 460 def dirs(self):
456 461 return self._dirs
457 462
458 463 def hasdir(self, dir):
459 464 return dir in self._dirs
460 465
461 466 def _filesfastpath(self, match):
462 467 '''Checks whether we can correctly and quickly iterate over matcher
463 468 files instead of over manifest files.'''
464 469 files = match.files()
465 470 return (len(files) < 100 and (match.isexact() or
466 471 (match.prefix() and all(fn in self for fn in files))))
467 472
468 473 def walk(self, match):
469 474 '''Generates matching file names.
470 475
471 476 Equivalent to manifest.matches(match).iterkeys(), but without creating
472 477 an entirely new manifest.
473 478
474 479 It also reports nonexistent files by marking them bad with match.bad().
475 480 '''
476 481 if match.always():
477 482 for f in iter(self):
478 483 yield f
479 484 return
480 485
481 486 fset = set(match.files())
482 487
483 488 # avoid the entire walk if we're only looking for specific files
484 489 if self._filesfastpath(match):
485 490 for fn in sorted(fset):
486 491 yield fn
487 492 return
488 493
489 494 for fn in self:
490 495 if fn in fset:
491 496 # specified pattern is the exact name
492 497 fset.remove(fn)
493 498 if match(fn):
494 499 yield fn
495 500
496 501 # for dirstate.walk, files=['.'] means "walk the whole tree".
497 502 # follow that here, too
498 503 fset.discard('.')
499 504
500 505 for fn in sorted(fset):
501 506 if not self.hasdir(fn):
502 507 match.bad(fn, None)
503 508
504 509 def matches(self, match):
505 510 '''generate a new manifest filtered by the match argument'''
506 511 if match.always():
507 512 return self.copy()
508 513
509 514 if self._filesfastpath(match):
510 515 m = manifestdict()
511 516 lm = self._lm
512 517 for fn in match.files():
513 518 if fn in lm:
514 519 m._lm[fn] = lm[fn]
515 520 return m
516 521
517 522 m = manifestdict()
518 523 m._lm = self._lm.filtercopy(match)
519 524 return m
520 525
521 526 def diff(self, m2, clean=False):
522 527 '''Finds changes between the current manifest and m2.
523 528
524 529 Args:
525 530 m2: the manifest to which this manifest should be compared.
526 531 clean: if true, include files unchanged between these manifests
527 532 with a None value in the returned dictionary.
528 533
529 534 The result is returned as a dict with filename as key and
530 535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
531 536 nodeid in the current/other manifest and fl1/fl2 is the flag
532 537 in the current/other manifest. Where the file does not exist,
533 538 the nodeid will be None and the flags will be the empty
534 539 string.
535 540 '''
536 541 return self._lm.diff(m2._lm, clean)
537 542
538 543 def setflag(self, key, flag):
539 544 self._lm[key] = self[key], flag
540 545
541 546 def get(self, key, default=None):
542 547 try:
543 548 return self._lm[key][0]
544 549 except KeyError:
545 550 return default
546 551
547 552 def flags(self, key, default=''):
548 553 try:
549 554 return self._lm[key][1]
550 555 except KeyError:
551 556 return default
552 557
553 558 def copy(self):
554 559 c = manifestdict()
555 560 c._lm = self._lm.copy()
556 561 return c
557 562
558 563 def iteritems(self):
559 564 return (x[:2] for x in self._lm.iterentries())
560 565
561 566 def iterentries(self):
562 567 return self._lm.iterentries()
563 568
564 569 def text(self, usemanifestv2=False):
565 570 if usemanifestv2:
566 571 return _textv2(self._lm.iterentries())
567 572 else:
568 573 # use (probably) native version for v1
569 574 return self._lm.text()
570 575
571 576 def fastdelta(self, base, changes):
572 577 """Given a base manifest text as an array.array and a list of changes
573 578 relative to that text, compute a delta that can be used by revlog.
574 579 """
575 580 delta = []
576 581 dstart = None
577 582 dend = None
578 583 dline = [""]
579 584 start = 0
580 585 # zero copy representation of base as a buffer
581 586 addbuf = util.buffer(base)
582 587
583 588 changes = list(changes)
584 589 if len(changes) < 1000:
585 590 # start with a readonly loop that finds the offset of
586 591 # each line and creates the deltas
587 592 for f, todelete in changes:
588 593 # bs will either be the index of the item or the insert point
589 594 start, end = _msearch(addbuf, f, start)
590 595 if not todelete:
591 596 h, fl = self._lm[f]
592 597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
593 598 else:
594 599 if start == end:
595 600 # item we want to delete was not found, error out
596 601 raise AssertionError(
597 602 _("failed to remove %s from manifest") % f)
598 603 l = ""
599 604 if dstart is not None and dstart <= start and dend >= start:
600 605 if dend < end:
601 606 dend = end
602 607 if l:
603 608 dline.append(l)
604 609 else:
605 610 if dstart is not None:
606 611 delta.append([dstart, dend, "".join(dline)])
607 612 dstart = start
608 613 dend = end
609 614 dline = [l]
610 615
611 616 if dstart is not None:
612 617 delta.append([dstart, dend, "".join(dline)])
613 618 # apply the delta to the base, and get a delta for addrevision
614 619 deltatext, arraytext = _addlistdelta(base, delta)
615 620 else:
616 621 # For large changes, it's much cheaper to just build the text and
617 622 # diff it.
618 623 arraytext = array.array('c', self.text())
619 624 deltatext = mdiff.textdiff(base, arraytext)
620 625
621 626 return arraytext, deltatext
622 627
623 628 def _msearch(m, s, lo=0, hi=None):
624 629 '''return a tuple (start, end) that says where to find s within m.
625 630
626 631 If the string is found m[start:end] are the line containing
627 632 that string. If start == end the string was not found and
628 633 they indicate the proper sorted insertion point.
629 634
630 635 m should be a buffer or a string
631 636 s is a string'''
632 637 def advance(i, c):
633 638 while i < lenm and m[i] != c:
634 639 i += 1
635 640 return i
636 641 if not s:
637 642 return (lo, lo)
638 643 lenm = len(m)
639 644 if not hi:
640 645 hi = lenm
641 646 while lo < hi:
642 647 mid = (lo + hi) // 2
643 648 start = mid
644 649 while start > 0 and m[start - 1] != '\n':
645 650 start -= 1
646 651 end = advance(start, '\0')
647 652 if m[start:end] < s:
648 653 # we know that after the null there are 40 bytes of sha1
649 654 # this translates to the bisect lo = mid + 1
650 655 lo = advance(end + 40, '\n') + 1
651 656 else:
652 657 # this translates to the bisect hi = mid
653 658 hi = start
654 659 end = advance(lo, '\0')
655 660 found = m[lo:end]
656 661 if s == found:
657 662 # we know that after the null there are 40 bytes of sha1
658 663 end = advance(end + 40, '\n')
659 664 return (lo, end + 1)
660 665 else:
661 666 return (lo, lo)
662 667
663 668 def _checkforbidden(l):
664 669 """Check filenames for illegal characters."""
665 670 for f in l:
666 671 if '\n' in f or '\r' in f:
667 672 raise error.RevlogError(
668 673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
669 674
670 675
671 676 # apply the changes collected during the bisect loop to our addlist
672 677 # return a delta suitable for addrevision
673 678 def _addlistdelta(addlist, x):
674 679 # for large addlist arrays, building a new array is cheaper
675 680 # than repeatedly modifying the existing one
676 681 currentposition = 0
677 682 newaddlist = array.array('c')
678 683
679 684 for start, end, content in x:
680 685 newaddlist += addlist[currentposition:start]
681 686 if content:
682 687 newaddlist += array.array('c', content)
683 688
684 689 currentposition = end
685 690
686 691 newaddlist += addlist[currentposition:]
687 692
688 693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
689 694 + content for start, end, content in x)
690 695 return deltatext, newaddlist
691 696
692 697 def _splittopdir(f):
693 698 if '/' in f:
694 699 dir, subpath = f.split('/', 1)
695 700 return dir + '/', subpath
696 701 else:
697 702 return '', f
698 703
699 704 _noop = lambda s: None
700 705
701 706 class treemanifest(object):
702 707 def __init__(self, dir='', text=''):
703 708 self._dir = dir
704 709 self._node = revlog.nullid
705 710 self._loadfunc = _noop
706 711 self._copyfunc = _noop
707 712 self._dirty = False
708 713 self._dirs = {}
709 714 # Using _lazymanifest here is a little slower than plain old dicts
710 715 self._files = {}
711 716 self._flags = {}
712 717 if text:
713 718 def readsubtree(subdir, subm):
714 719 raise AssertionError('treemanifest constructor only accepts '
715 720 'flat manifests')
716 721 self.parse(text, readsubtree)
717 722 self._dirty = True # Mark flat manifest dirty after parsing
718 723
719 724 def _subpath(self, path):
720 725 return self._dir + path
721 726
722 727 def __len__(self):
723 728 self._load()
724 729 size = len(self._files)
725 730 for m in self._dirs.values():
726 731 size += m.__len__()
727 732 return size
728 733
729 734 def _isempty(self):
730 735 self._load() # for consistency; already loaded by all callers
731 736 return (not self._files and (not self._dirs or
732 737 all(m._isempty() for m in self._dirs.values())))
733 738
734 739 def __repr__(self):
735 740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
736 741 (self._dir, revlog.hex(self._node),
737 742 bool(self._loadfunc is _noop),
738 743 self._dirty, id(self)))
739 744
740 745 def dir(self):
741 746 '''The directory that this tree manifest represents, including a
742 747 trailing '/'. Empty string for the repo root directory.'''
743 748 return self._dir
744 749
745 750 def node(self):
746 751 '''This node of this instance. nullid for unsaved instances. Should
747 752 be updated when the instance is read or written from a revlog.
748 753 '''
749 754 assert not self._dirty
750 755 return self._node
751 756
752 757 def setnode(self, node):
753 758 self._node = node
754 759 self._dirty = False
755 760
756 761 def iterentries(self):
757 762 self._load()
758 763 for p, n in sorted(self._dirs.items() + self._files.items()):
759 764 if p in self._files:
760 765 yield self._subpath(p), n, self._flags.get(p, '')
761 766 else:
762 767 for x in n.iterentries():
763 768 yield x
764 769
765 770 def iteritems(self):
766 771 self._load()
767 772 for p, n in sorted(self._dirs.items() + self._files.items()):
768 773 if p in self._files:
769 774 yield self._subpath(p), n
770 775 else:
771 776 for f, sn in n.iteritems():
772 777 yield f, sn
773 778
774 779 def iterkeys(self):
775 780 self._load()
776 781 for p in sorted(self._dirs.keys() + self._files.keys()):
777 782 if p in self._files:
778 783 yield self._subpath(p)
779 784 else:
780 785 for f in self._dirs[p].iterkeys():
781 786 yield f
782 787
783 788 def keys(self):
784 789 return list(self.iterkeys())
785 790
786 791 def __iter__(self):
787 792 return self.iterkeys()
788 793
789 794 def __contains__(self, f):
790 795 if f is None:
791 796 return False
792 797 self._load()
793 798 dir, subpath = _splittopdir(f)
794 799 if dir:
795 800 if dir not in self._dirs:
796 801 return False
797 802 return self._dirs[dir].__contains__(subpath)
798 803 else:
799 804 return f in self._files
800 805
801 806 def get(self, f, default=None):
802 807 self._load()
803 808 dir, subpath = _splittopdir(f)
804 809 if dir:
805 810 if dir not in self._dirs:
806 811 return default
807 812 return self._dirs[dir].get(subpath, default)
808 813 else:
809 814 return self._files.get(f, default)
810 815
811 816 def __getitem__(self, f):
812 817 self._load()
813 818 dir, subpath = _splittopdir(f)
814 819 if dir:
815 820 return self._dirs[dir].__getitem__(subpath)
816 821 else:
817 822 return self._files[f]
818 823
819 824 def flags(self, f):
820 825 self._load()
821 826 dir, subpath = _splittopdir(f)
822 827 if dir:
823 828 if dir not in self._dirs:
824 829 return ''
825 830 return self._dirs[dir].flags(subpath)
826 831 else:
827 832 if f in self._dirs:
828 833 return ''
829 834 return self._flags.get(f, '')
830 835
831 836 def find(self, f):
832 837 self._load()
833 838 dir, subpath = _splittopdir(f)
834 839 if dir:
835 840 return self._dirs[dir].find(subpath)
836 841 else:
837 842 return self._files[f], self._flags.get(f, '')
838 843
839 844 def __delitem__(self, f):
840 845 self._load()
841 846 dir, subpath = _splittopdir(f)
842 847 if dir:
843 848 self._dirs[dir].__delitem__(subpath)
844 849 # If the directory is now empty, remove it
845 850 if self._dirs[dir]._isempty():
846 851 del self._dirs[dir]
847 852 else:
848 853 del self._files[f]
849 854 if f in self._flags:
850 855 del self._flags[f]
851 856 self._dirty = True
852 857
853 858 def __setitem__(self, f, n):
854 859 assert n is not None
855 860 self._load()
856 861 dir, subpath = _splittopdir(f)
857 862 if dir:
858 863 if dir not in self._dirs:
859 864 self._dirs[dir] = treemanifest(self._subpath(dir))
860 865 self._dirs[dir].__setitem__(subpath, n)
861 866 else:
862 867 self._files[f] = n[:21] # to match manifestdict's behavior
863 868 self._dirty = True
864 869
865 870 def _load(self):
866 871 if self._loadfunc is not _noop:
867 872 lf, self._loadfunc = self._loadfunc, _noop
868 873 lf(self)
869 874 elif self._copyfunc is not _noop:
870 875 cf, self._copyfunc = self._copyfunc, _noop
871 876 cf(self)
872 877
873 878 def setflag(self, f, flags):
874 879 """Set the flags (symlink, executable) for path f."""
875 880 self._load()
876 881 dir, subpath = _splittopdir(f)
877 882 if dir:
878 883 if dir not in self._dirs:
879 884 self._dirs[dir] = treemanifest(self._subpath(dir))
880 885 self._dirs[dir].setflag(subpath, flags)
881 886 else:
882 887 self._flags[f] = flags
883 888 self._dirty = True
884 889
885 890 def copy(self):
886 891 copy = treemanifest(self._dir)
887 892 copy._node = self._node
888 893 copy._dirty = self._dirty
889 894 if self._copyfunc is _noop:
890 895 def _copyfunc(s):
891 896 self._load()
892 897 for d in self._dirs:
893 898 s._dirs[d] = self._dirs[d].copy()
894 899 s._files = dict.copy(self._files)
895 900 s._flags = dict.copy(self._flags)
896 901 if self._loadfunc is _noop:
897 902 _copyfunc(copy)
898 903 else:
899 904 copy._copyfunc = _copyfunc
900 905 else:
901 906 copy._copyfunc = self._copyfunc
902 907 return copy
903 908
904 909 def filesnotin(self, m2):
905 910 '''Set of files in this manifest that are not in the other'''
906 911 files = set()
907 912 def _filesnotin(t1, t2):
908 913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
909 914 return
910 915 t1._load()
911 916 t2._load()
912 917 for d, m1 in t1._dirs.iteritems():
913 918 if d in t2._dirs:
914 919 m2 = t2._dirs[d]
915 920 _filesnotin(m1, m2)
916 921 else:
917 922 files.update(m1.iterkeys())
918 923
919 924 for fn in t1._files.iterkeys():
920 925 if fn not in t2._files:
921 926 files.add(t1._subpath(fn))
922 927
923 928 _filesnotin(self, m2)
924 929 return files
925 930
926 931 @propertycache
927 932 def _alldirs(self):
928 933 return util.dirs(self)
929 934
930 935 def dirs(self):
931 936 return self._alldirs
932 937
933 938 def hasdir(self, dir):
934 939 self._load()
935 940 topdir, subdir = _splittopdir(dir)
936 941 if topdir:
937 942 if topdir in self._dirs:
938 943 return self._dirs[topdir].hasdir(subdir)
939 944 return False
940 945 return (dir + '/') in self._dirs
941 946
942 947 def walk(self, match):
943 948 '''Generates matching file names.
944 949
945 950 Equivalent to manifest.matches(match).iterkeys(), but without creating
946 951 an entirely new manifest.
947 952
948 953 It also reports nonexistent files by marking them bad with match.bad().
949 954 '''
950 955 if match.always():
951 956 for f in iter(self):
952 957 yield f
953 958 return
954 959
955 960 fset = set(match.files())
956 961
957 962 for fn in self._walk(match):
958 963 if fn in fset:
959 964 # specified pattern is the exact name
960 965 fset.remove(fn)
961 966 yield fn
962 967
963 968 # for dirstate.walk, files=['.'] means "walk the whole tree".
964 969 # follow that here, too
965 970 fset.discard('.')
966 971
967 972 for fn in sorted(fset):
968 973 if not self.hasdir(fn):
969 974 match.bad(fn, None)
970 975
971 976 def _walk(self, match):
972 977 '''Recursively generates matching file names for walk().'''
973 978 if not match.visitdir(self._dir[:-1] or '.'):
974 979 return
975 980
976 981 # yield this dir's files and walk its submanifests
977 982 self._load()
978 983 for p in sorted(self._dirs.keys() + self._files.keys()):
979 984 if p in self._files:
980 985 fullp = self._subpath(p)
981 986 if match(fullp):
982 987 yield fullp
983 988 else:
984 989 for f in self._dirs[p]._walk(match):
985 990 yield f
986 991
987 992 def matches(self, match):
988 993 '''generate a new manifest filtered by the match argument'''
989 994 if match.always():
990 995 return self.copy()
991 996
992 997 return self._matches(match)
993 998
994 999 def _matches(self, match):
995 1000 '''recursively generate a new manifest filtered by the match argument.
996 1001 '''
997 1002
998 1003 visit = match.visitdir(self._dir[:-1] or '.')
999 1004 if visit == 'all':
1000 1005 return self.copy()
1001 1006 ret = treemanifest(self._dir)
1002 1007 if not visit:
1003 1008 return ret
1004 1009
1005 1010 self._load()
1006 1011 for fn in self._files:
1007 1012 fullp = self._subpath(fn)
1008 1013 if not match(fullp):
1009 1014 continue
1010 1015 ret._files[fn] = self._files[fn]
1011 1016 if fn in self._flags:
1012 1017 ret._flags[fn] = self._flags[fn]
1013 1018
1014 1019 for dir, subm in self._dirs.iteritems():
1015 1020 m = subm._matches(match)
1016 1021 if not m._isempty():
1017 1022 ret._dirs[dir] = m
1018 1023
1019 1024 if not ret._isempty():
1020 1025 ret._dirty = True
1021 1026 return ret
1022 1027
1023 1028 def diff(self, m2, clean=False):
1024 1029 '''Finds changes between the current manifest and m2.
1025 1030
1026 1031 Args:
1027 1032 m2: the manifest to which this manifest should be compared.
1028 1033 clean: if true, include files unchanged between these manifests
1029 1034 with a None value in the returned dictionary.
1030 1035
1031 1036 The result is returned as a dict with filename as key and
1032 1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1033 1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1034 1039 in the current/other manifest. Where the file does not exist,
1035 1040 the nodeid will be None and the flags will be the empty
1036 1041 string.
1037 1042 '''
1038 1043 result = {}
1039 1044 emptytree = treemanifest()
1040 1045 def _diff(t1, t2):
1041 1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1042 1047 return
1043 1048 t1._load()
1044 1049 t2._load()
1045 1050 for d, m1 in t1._dirs.iteritems():
1046 1051 m2 = t2._dirs.get(d, emptytree)
1047 1052 _diff(m1, m2)
1048 1053
1049 1054 for d, m2 in t2._dirs.iteritems():
1050 1055 if d not in t1._dirs:
1051 1056 _diff(emptytree, m2)
1052 1057
1053 1058 for fn, n1 in t1._files.iteritems():
1054 1059 fl1 = t1._flags.get(fn, '')
1055 1060 n2 = t2._files.get(fn, None)
1056 1061 fl2 = t2._flags.get(fn, '')
1057 1062 if n1 != n2 or fl1 != fl2:
1058 1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1059 1064 elif clean:
1060 1065 result[t1._subpath(fn)] = None
1061 1066
1062 1067 for fn, n2 in t2._files.iteritems():
1063 1068 if fn not in t1._files:
1064 1069 fl2 = t2._flags.get(fn, '')
1065 1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1066 1071
1067 1072 _diff(self, m2)
1068 1073 return result
1069 1074
1070 1075 def unmodifiedsince(self, m2):
1071 1076 return not self._dirty and not m2._dirty and self._node == m2._node
1072 1077
1073 1078 def parse(self, text, readsubtree):
1074 1079 for f, n, fl in _parse(text):
1075 1080 if fl == 't':
1076 1081 f = f + '/'
1077 1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1078 1083 elif '/' in f:
1079 1084 # This is a flat manifest, so use __setitem__ and setflag rather
1080 1085 # than assigning directly to _files and _flags, so we can
1081 1086 # assign a path in a subdirectory, and to mark dirty (compared
1082 1087 # to nullid).
1083 1088 self[f] = n
1084 1089 if fl:
1085 1090 self.setflag(f, fl)
1086 1091 else:
1087 1092 # Assigning to _files and _flags avoids marking as dirty,
1088 1093 # and should be a little faster.
1089 1094 self._files[f] = n
1090 1095 if fl:
1091 1096 self._flags[f] = fl
1092 1097
1093 1098 def text(self, usemanifestv2=False):
1094 1099 """Get the full data of this manifest as a bytestring."""
1095 1100 self._load()
1096 1101 return _text(self.iterentries(), usemanifestv2)
1097 1102
1098 1103 def dirtext(self, usemanifestv2=False):
1099 1104 """Get the full data of this directory as a bytestring. Make sure that
1100 1105 any submanifests have been written first, so their nodeids are correct.
1101 1106 """
1102 1107 self._load()
1103 1108 flags = self.flags
1104 1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1105 1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1106 1111 return _text(sorted(dirs + files), usemanifestv2)
1107 1112
1108 1113 def read(self, gettext, readsubtree):
1109 1114 def _load_for_read(s):
1110 1115 s.parse(gettext(), readsubtree)
1111 1116 s._dirty = False
1112 1117 self._loadfunc = _load_for_read
1113 1118
1114 1119 def writesubtrees(self, m1, m2, writesubtree):
1115 1120 self._load() # for consistency; should never have any effect here
1116 1121 m1._load()
1117 1122 m2._load()
1118 1123 emptytree = treemanifest()
1119 1124 for d, subm in self._dirs.iteritems():
1120 1125 subp1 = m1._dirs.get(d, emptytree)._node
1121 1126 subp2 = m2._dirs.get(d, emptytree)._node
1122 1127 if subp1 == revlog.nullid:
1123 1128 subp1, subp2 = subp2, subp1
1124 1129 writesubtree(subm, subp1, subp2)
1125 1130
1126 1131 class manifestrevlog(revlog.revlog):
1127 1132 '''A revlog that stores manifest texts. This is responsible for caching the
1128 1133 full-text manifest contents.
1129 1134 '''
1130 1135 def __init__(self, opener, dir='', dirlogcache=None):
1131 1136 # During normal operations, we expect to deal with not more than four
1132 1137 # revs at a time (such as during commit --amend). When rebasing large
1133 1138 # stacks of commits, the number can go up, hence the config knob below.
1134 1139 cachesize = 4
1135 1140 usetreemanifest = False
1136 1141 usemanifestv2 = False
1137 1142 opts = getattr(opener, 'options', None)
1138 1143 if opts is not None:
1139 1144 cachesize = opts.get('manifestcachesize', cachesize)
1140 1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1141 1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1142 1147
1143 1148 self._treeondisk = usetreemanifest
1144 1149 self._usemanifestv2 = usemanifestv2
1145 1150
1146 1151 self._fulltextcache = util.lrucachedict(cachesize)
1147 1152
1148 1153 indexfile = "00manifest.i"
1149 1154 if dir:
1150 1155 assert self._treeondisk, 'opts is %r' % opts
1151 1156 if not dir.endswith('/'):
1152 1157 dir = dir + '/'
1153 1158 indexfile = "meta/" + dir + "00manifest.i"
1154 1159 self._dir = dir
1155 1160 # The dirlogcache is kept on the root manifest log
1156 1161 if dir:
1157 1162 self._dirlogcache = dirlogcache
1158 1163 else:
1159 1164 self._dirlogcache = {'': self}
1160 1165
1161 1166 super(manifestrevlog, self).__init__(opener, indexfile,
1162 1167 checkambig=bool(dir))
1163 1168
1164 1169 @property
1165 1170 def fulltextcache(self):
1166 1171 return self._fulltextcache
1167 1172
1168 1173 def clearcaches(self):
1169 1174 super(manifestrevlog, self).clearcaches()
1170 1175 self._fulltextcache.clear()
1171 1176 self._dirlogcache = {'': self}
1172 1177
1173 1178 def dirlog(self, dir):
1174 1179 if dir:
1175 1180 assert self._treeondisk
1176 1181 if dir not in self._dirlogcache:
1177 1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1178 1183 self._dirlogcache)
1179 1184 return self._dirlogcache[dir]
1180 1185
1181 1186 def add(self, m, transaction, link, p1, p2, added, removed):
1182 1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1183 1188 and not self._usemanifestv2):
1184 1189 # If our first parent is in the manifest cache, we can
1185 1190 # compute a delta here using properties we know about the
1186 1191 # manifest up-front, which may save time later for the
1187 1192 # revlog layer.
1188 1193
1189 1194 _checkforbidden(added)
1190 1195 # combine the changed lists into one sorted iterator
1191 1196 work = heapq.merge([(x, False) for x in added],
1192 1197 [(x, True) for x in removed])
1193 1198
1194 1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1195 1200 cachedelta = self.rev(p1), deltatext
1196 1201 text = util.buffer(arraytext)
1197 1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1198 1203 else:
1199 1204 # The first parent manifest isn't already loaded, so we'll
1200 1205 # just encode a fulltext of the manifest and pass that
1201 1206 # through to the revlog layer, and let it handle the delta
1202 1207 # process.
1203 1208 if self._treeondisk:
1204 1209 m1 = self.read(p1)
1205 1210 m2 = self.read(p2)
1206 1211 n = self._addtree(m, transaction, link, m1, m2)
1207 1212 arraytext = None
1208 1213 else:
1209 1214 text = m.text(self._usemanifestv2)
1210 1215 n = self.addrevision(text, transaction, link, p1, p2)
1211 1216 arraytext = array.array('c', text)
1212 1217
1213 1218 if arraytext is not None:
1214 1219 self.fulltextcache[n] = arraytext
1215 1220
1216 1221 return n
1217 1222
1218 1223 def _addtree(self, m, transaction, link, m1, m2):
1219 1224 # If the manifest is unchanged compared to one parent,
1220 1225 # don't write a new revision
1221 1226 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1222 1227 return m.node()
1223 1228 def writesubtree(subm, subp1, subp2):
1224 1229 sublog = self.dirlog(subm.dir())
1225 1230 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1226 1231 m.writesubtrees(m1, m2, writesubtree)
1227 1232 text = m.dirtext(self._usemanifestv2)
1228 1233 # Double-check whether contents are unchanged to one parent
1229 1234 if text == m1.dirtext(self._usemanifestv2):
1230 1235 n = m1.node()
1231 1236 elif text == m2.dirtext(self._usemanifestv2):
1232 1237 n = m2.node()
1233 1238 else:
1234 1239 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1235 1240 # Save nodeid so parent manifest can calculate its nodeid
1236 1241 m.setnode(n)
1237 1242 return n
1238 1243
1239 1244 class manifestlog(object):
1240 1245 """A collection class representing the collection of manifest snapshots
1241 1246 referenced by commits in the repository.
1242 1247
1243 1248 In this situation, 'manifest' refers to the abstract concept of a snapshot
1244 1249 of the list of files in the given commit. Consumers of the output of this
1245 1250 class do not care about the implementation details of the actual manifests
1246 1251 they receive (i.e. tree or flat or lazily loaded, etc)."""
1247 1252 def __init__(self, opener, repo):
1248 1253 self._repo = repo
1249 1254
1250 1255 usetreemanifest = False
1251 1256
1252 1257 opts = getattr(opener, 'options', None)
1253 1258 if opts is not None:
1254 1259 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1255 1260 self._treeinmem = usetreemanifest
1256 1261
1257 1262 self._oldmanifest = repo._constructmanifest()
1258 1263 self._revlog = self._oldmanifest
1259 1264
1260 1265 # A cache of the manifestctx or treemanifestctx for each directory
1261 1266 self._dirmancache = {}
1262 1267
1263 1268 # We'll separate this into it's own cache once oldmanifest is no longer
1264 1269 # used
1265 1270 self._mancache = self._oldmanifest._mancache
1266 1271 self._dirmancache[''] = self._mancache
1267 1272
1268 1273 # A future patch makes this use the same config value as the existing
1269 1274 # mancache
1270 1275 self.cachesize = 4
1271 1276
1272 1277 def __getitem__(self, node):
1273 1278 """Retrieves the manifest instance for the given node. Throws a
1274 1279 LookupError if not found.
1275 1280 """
1276 1281 return self.get('', node)
1277 1282
1278 1283 def get(self, dir, node):
1279 1284 """Retrieves the manifest instance for the given node. Throws a
1280 1285 LookupError if not found.
1281 1286 """
1282 1287 if node in self._dirmancache.get(dir, ()):
1283 1288 cachemf = self._dirmancache[dir][node]
1284 1289 # The old manifest may put non-ctx manifests in the cache, so
1285 1290 # skip those since they don't implement the full api.
1286 1291 if (isinstance(cachemf, manifestctx) or
1287 1292 isinstance(cachemf, treemanifestctx)):
1288 1293 return cachemf
1289 1294
1290 1295 if dir:
1291 1296 if self._revlog._treeondisk:
1292 1297 dirlog = self._revlog.dirlog(dir)
1293 1298 if node not in dirlog.nodemap:
1294 1299 raise LookupError(node, dirlog.indexfile,
1295 1300 _('no node'))
1296 1301 m = treemanifestctx(self._repo, dir, node)
1297 1302 else:
1298 1303 raise error.Abort(
1299 1304 _("cannot ask for manifest directory '%s' in a flat "
1300 1305 "manifest") % dir)
1301 1306 else:
1302 1307 if node not in self._revlog.nodemap:
1303 1308 raise LookupError(node, self._revlog.indexfile,
1304 1309 _('no node'))
1305 1310 if self._treeinmem:
1306 1311 m = treemanifestctx(self._repo, '', node)
1307 1312 else:
1308 1313 m = manifestctx(self._repo, node)
1309 1314
1310 1315 if node != revlog.nullid:
1311 1316 mancache = self._dirmancache.get(dir)
1312 1317 if not mancache:
1313 1318 mancache = util.lrucachedict(self.cachesize)
1314 1319 self._dirmancache[dir] = mancache
1315 1320 mancache[node] = m
1316 1321 return m
1317 1322
1318 1323 def add(self, m, transaction, link, p1, p2, added, removed):
1319 1324 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1320 1325
1321 1326 class manifestctx(object):
1322 1327 """A class representing a single revision of a manifest, including its
1323 1328 contents, its parent revs, and its linkrev.
1324 1329 """
1325 1330 def __init__(self, repo, node):
1326 1331 self._repo = repo
1327 1332 self._data = None
1328 1333
1329 1334 self._node = node
1330 1335
1331 1336 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1332 1337 # but let's add it later when something needs it and we can load it
1333 1338 # lazily.
1334 1339 #self.p1, self.p2 = revlog.parents(node)
1335 1340 #rev = revlog.rev(node)
1336 1341 #self.linkrev = revlog.linkrev(rev)
1337 1342
1338 1343 def node(self):
1339 1344 return self._node
1340 1345
1341 1346 def read(self):
1342 1347 if not self._data:
1343 1348 if self._node == revlog.nullid:
1344 1349 self._data = manifestdict()
1345 1350 else:
1346 1351 rl = self._repo.manifestlog._revlog
1347 1352 text = rl.revision(self._node)
1348 1353 arraytext = array.array('c', text)
1349 1354 rl._fulltextcache[self._node] = arraytext
1350 1355 self._data = manifestdict(text)
1351 1356 return self._data
1352 1357
1353 1358 def readfast(self, shallow=False):
1354 1359 '''Calls either readdelta or read, based on which would be less work.
1355 1360 readdelta is called if the delta is against the p1, and therefore can be
1356 1361 read quickly.
1357 1362
1358 1363 If `shallow` is True, nothing changes since this is a flat manifest.
1359 1364 '''
1360 1365 rl = self._repo.manifestlog._revlog
1361 1366 r = rl.rev(self._node)
1362 1367 deltaparent = rl.deltaparent(r)
1363 1368 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1364 1369 return self.readdelta()
1365 1370 return self.read()
1366 1371
1367 1372 def readdelta(self, shallow=False):
1368 1373 '''Returns a manifest containing just the entries that are present
1369 1374 in this manifest, but not in its p1 manifest. This is efficient to read
1370 1375 if the revlog delta is already p1.
1371 1376
1372 1377 Changing the value of `shallow` has no effect on flat manifests.
1373 1378 '''
1374 1379 revlog = self._repo.manifestlog._revlog
1375 1380 if revlog._usemanifestv2:
1376 1381 # Need to perform a slow delta
1377 1382 r0 = revlog.deltaparent(revlog.rev(self._node))
1378 1383 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1379 1384 m1 = self.read()
1380 1385 md = manifestdict()
1381 1386 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1382 1387 if n1:
1383 1388 md[f] = n1
1384 1389 if fl1:
1385 1390 md.setflag(f, fl1)
1386 1391 return md
1387 1392
1388 1393 r = revlog.rev(self._node)
1389 1394 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1390 1395 return manifestdict(d)
1391 1396
1392 1397 class treemanifestctx(object):
1393 1398 def __init__(self, repo, dir, node):
1394 1399 self._repo = repo
1395 1400 self._dir = dir
1396 1401 self._data = None
1397 1402
1398 1403 self._node = node
1399 1404
1400 1405 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1401 1406 # we can instantiate treemanifestctx objects for directories we don't
1402 1407 # have on disk.
1403 1408 #self.p1, self.p2 = revlog.parents(node)
1404 1409 #rev = revlog.rev(node)
1405 1410 #self.linkrev = revlog.linkrev(rev)
1406 1411
1407 1412 def _revlog(self):
1408 1413 return self._repo.manifestlog._revlog.dirlog(self._dir)
1409 1414
1410 1415 def read(self):
1411 1416 if not self._data:
1412 1417 rl = self._revlog()
1413 1418 if self._node == revlog.nullid:
1414 1419 self._data = treemanifest()
1415 1420 elif rl._treeondisk:
1416 1421 m = treemanifest(dir=self._dir)
1417 1422 def gettext():
1418 1423 return rl.revision(self._node)
1419 1424 def readsubtree(dir, subm):
1420 1425 return treemanifestctx(self._repo, dir, subm).read()
1421 1426 m.read(gettext, readsubtree)
1422 1427 m.setnode(self._node)
1423 1428 self._data = m
1424 1429 else:
1425 1430 text = revlog.revision(self._node)
1426 1431 arraytext = array.array('c', text)
1427 1432 rl.fulltextcache[self._node] = arraytext
1428 1433 self._data = treemanifest(dir=self._dir, text=text)
1429 1434
1430 1435 return self._data
1431 1436
1432 1437 def node(self):
1433 1438 return self._node
1434 1439
1435 1440 def readdelta(self, shallow=False):
1436 1441 '''Returns a manifest containing just the entries that are present
1437 1442 in this manifest, but not in its p1 manifest. This is efficient to read
1438 1443 if the revlog delta is already p1.
1439 1444
1440 1445 If `shallow` is True, this will read the delta for this directory,
1441 1446 without recursively reading subdirectory manifests. Instead, any
1442 1447 subdirectory entry will be reported as it appears in the manifest, i.e.
1443 1448 the subdirectory will be reported among files and distinguished only by
1444 1449 its 't' flag.
1445 1450 '''
1446 1451 revlog = self._revlog()
1447 1452 if shallow and not revlog._usemanifestv2:
1448 1453 r = revlog.rev(self._node)
1449 1454 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1450 1455 return manifestdict(d)
1451 1456 else:
1452 1457 # Need to perform a slow delta
1453 1458 r0 = revlog.deltaparent(revlog.rev(self._node))
1454 1459 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1455 1460 m1 = self.read()
1456 1461 md = treemanifest(dir=self._dir)
1457 1462 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1458 1463 if n1:
1459 1464 md[f] = n1
1460 1465 if fl1:
1461 1466 md.setflag(f, fl1)
1462 1467 return md
1463 1468
1464 1469 def readfast(self, shallow=False):
1465 1470 '''Calls either readdelta or read, based on which would be less work.
1466 1471 readdelta is called if the delta is against the p1, and therefore can be
1467 1472 read quickly.
1468 1473
1469 1474 If `shallow` is True, it only returns the entries from this manifest,
1470 1475 and not any submanifests.
1471 1476 '''
1472 1477 rl = self._revlog()
1473 1478 r = rl.rev(self._node)
1474 1479 deltaparent = rl.deltaparent(r)
1475 1480 if (deltaparent != revlog.nullrev and
1476 1481 deltaparent in rl.parentrevs(r)):
1477 1482 return self.readdelta(shallow=shallow)
1478 1483
1479 1484 if shallow:
1480 1485 return manifestdict(rl.revision(self._node))
1481 1486 else:
1482 1487 return self.read()
1483 1488
1484 1489 class manifest(manifestrevlog):
1485 1490 def __init__(self, opener, dir='', dirlogcache=None):
1486 1491 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1487 1492 manifest.manifest only. External users should create a root manifest
1488 1493 log with manifest.manifest(opener) and call dirlog() on it.
1489 1494 '''
1490 1495 # During normal operations, we expect to deal with not more than four
1491 1496 # revs at a time (such as during commit --amend). When rebasing large
1492 1497 # stacks of commits, the number can go up, hence the config knob below.
1493 1498 cachesize = 4
1494 1499 usetreemanifest = False
1495 1500 opts = getattr(opener, 'options', None)
1496 1501 if opts is not None:
1497 1502 cachesize = opts.get('manifestcachesize', cachesize)
1498 1503 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1499 1504 self._mancache = util.lrucachedict(cachesize)
1500 1505 self._treeinmem = usetreemanifest
1501 1506 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1502 1507
1503 1508 def _newmanifest(self, data=''):
1504 1509 if self._treeinmem:
1505 1510 return treemanifest(self._dir, data)
1506 1511 return manifestdict(data)
1507 1512
1508 1513 def dirlog(self, dir):
1509 1514 """This overrides the base revlog implementation to allow construction
1510 1515 'manifest' types instead of manifestrevlog types. This is only needed
1511 1516 until we migrate off the 'manifest' type."""
1512 1517 if dir:
1513 1518 assert self._treeondisk
1514 1519 if dir not in self._dirlogcache:
1515 1520 self._dirlogcache[dir] = manifest(self.opener, dir,
1516 1521 self._dirlogcache)
1517 1522 return self._dirlogcache[dir]
1518 1523
1519 1524 def read(self, node):
1520 1525 if node == revlog.nullid:
1521 1526 return self._newmanifest() # don't upset local cache
1522 1527 if node in self._mancache:
1523 1528 cached = self._mancache[node]
1524 1529 if (isinstance(cached, manifestctx) or
1525 1530 isinstance(cached, treemanifestctx)):
1526 1531 cached = cached.read()
1527 1532 return cached
1528 1533 if self._treeondisk:
1529 1534 def gettext():
1530 1535 return self.revision(node)
1531 1536 def readsubtree(dir, subm):
1532 1537 return self.dirlog(dir).read(subm)
1533 1538 m = self._newmanifest()
1534 1539 m.read(gettext, readsubtree)
1535 1540 m.setnode(node)
1536 1541 arraytext = None
1537 1542 else:
1538 1543 text = self.revision(node)
1539 1544 m = self._newmanifest(text)
1540 1545 arraytext = array.array('c', text)
1541 1546 self._mancache[node] = m
1542 1547 if arraytext is not None:
1543 1548 self.fulltextcache[node] = arraytext
1544 1549 return m
1545 1550
1546 1551 def find(self, node, f):
1547 1552 '''look up entry for a single file efficiently.
1548 1553 return (node, flags) pair if found, (None, None) if not.'''
1549 1554 m = self.read(node)
1550 1555 try:
1551 1556 return m.find(f)
1552 1557 except KeyError:
1553 1558 return None, None
1554 1559
1555 1560 def clearcaches(self):
1556 1561 super(manifest, self).clearcaches()
1557 1562 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now