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