##// END OF EJS Templates
use the new parseindex implementation C in parsers
Bernhard Leiner -
r7109:528b7fc1 default
parent child Browse files
Show More
@@ -1,1361 +1,1334 b''
1 1 """
2 2 revlog.py - storage back-end for mercurial
3 3
4 4 This provides efficient delta storage with O(1) retrieve and append
5 5 and O(changes) merge between branches
6 6
7 7 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
8 8
9 9 This software may be used and distributed according to the terms
10 10 of the GNU General Public License, incorporated herein by reference.
11 11 """
12 12
13 13 from node import bin, hex, nullid, nullrev, short
14 14 from i18n import _
15 import changegroup, errno, ancestor, mdiff
15 import changegroup, errno, ancestor, mdiff, parsers
16 16 import struct, util, zlib
17 17
18 18 _pack = struct.pack
19 19 _unpack = struct.unpack
20 20 _compress = zlib.compress
21 21 _decompress = zlib.decompress
22 22 _sha = util.sha1
23 23
24 24 # revlog flags
25 25 REVLOGV0 = 0
26 26 REVLOGNG = 1
27 27 REVLOGNGINLINEDATA = (1 << 16)
28 28 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
29 29 REVLOG_DEFAULT_FORMAT = REVLOGNG
30 30 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
31 31
32 32 class RevlogError(Exception):
33 33 pass
34 34
35 35 class LookupError(RevlogError, KeyError):
36 36 def __init__(self, name, index, message):
37 37 self.name = name
38 38 if isinstance(name, str) and len(name) == 20:
39 39 name = short(name)
40 40 RevlogError.__init__(self, _('%s@%s: %s') % (index, name, message))
41 41
42 42 def __str__(self):
43 43 return RevlogError.__str__(self)
44 44
45 45 def getoffset(q):
46 46 return int(q >> 16)
47 47
48 48 def gettype(q):
49 49 return int(q & 0xFFFF)
50 50
51 51 def offset_type(offset, type):
52 52 return long(long(offset) << 16 | type)
53 53
54 54 def hash(text, p1, p2):
55 55 """generate a hash from the given text and its parent hashes
56 56
57 57 This hash combines both the current file contents and its history
58 58 in a manner that makes it easy to distinguish nodes with the same
59 59 content in the revision graph.
60 60 """
61 61 l = [p1, p2]
62 62 l.sort()
63 63 s = _sha(l[0])
64 64 s.update(l[1])
65 65 s.update(text)
66 66 return s.digest()
67 67
68 68 def compress(text):
69 69 """ generate a possibly-compressed representation of text """
70 70 if not text:
71 71 return ("", text)
72 72 l = len(text)
73 73 bin = None
74 74 if l < 44:
75 75 pass
76 76 elif l > 1000000:
77 77 # zlib makes an internal copy, thus doubling memory usage for
78 78 # large files, so lets do this in pieces
79 79 z = zlib.compressobj()
80 80 p = []
81 81 pos = 0
82 82 while pos < l:
83 83 pos2 = pos + 2**20
84 84 p.append(z.compress(text[pos:pos2]))
85 85 pos = pos2
86 86 p.append(z.flush())
87 87 if sum(map(len, p)) < l:
88 88 bin = "".join(p)
89 89 else:
90 90 bin = _compress(text)
91 91 if bin is None or len(bin) > l:
92 92 if text[0] == '\0':
93 93 return ("", text)
94 94 return ('u', text)
95 95 return ("", bin)
96 96
97 97 def decompress(bin):
98 98 """ decompress the given input """
99 99 if not bin:
100 100 return bin
101 101 t = bin[0]
102 102 if t == '\0':
103 103 return bin
104 104 if t == 'x':
105 105 return _decompress(bin)
106 106 if t == 'u':
107 107 return bin[1:]
108 108 raise RevlogError(_("unknown compression type %r") % t)
109 109
110 110 class lazyparser(object):
111 111 """
112 112 this class avoids the need to parse the entirety of large indices
113 113 """
114 114
115 115 # lazyparser is not safe to use on windows if win32 extensions not
116 116 # available. it keeps file handle open, which make it not possible
117 117 # to break hardlinks on local cloned repos.
118 118
119 119 def __init__(self, dataf, size):
120 120 self.dataf = dataf
121 121 self.s = struct.calcsize(indexformatng)
122 122 self.datasize = size
123 123 self.l = size/self.s
124 124 self.index = [None] * self.l
125 125 self.map = {nullid: nullrev}
126 126 self.allmap = 0
127 127 self.all = 0
128 128 self.mapfind_count = 0
129 129
130 130 def loadmap(self):
131 131 """
132 132 during a commit, we need to make sure the rev being added is
133 133 not a duplicate. This requires loading the entire index,
134 134 which is fairly slow. loadmap can load up just the node map,
135 135 which takes much less time.
136 136 """
137 137 if self.allmap:
138 138 return
139 139 end = self.datasize
140 140 self.allmap = 1
141 141 cur = 0
142 142 count = 0
143 143 blocksize = self.s * 256
144 144 self.dataf.seek(0)
145 145 while cur < end:
146 146 data = self.dataf.read(blocksize)
147 147 off = 0
148 148 for x in xrange(256):
149 149 n = data[off + ngshaoffset:off + ngshaoffset + 20]
150 150 self.map[n] = count
151 151 count += 1
152 152 if count >= self.l:
153 153 break
154 154 off += self.s
155 155 cur += blocksize
156 156
157 157 def loadblock(self, blockstart, blocksize, data=None):
158 158 if self.all:
159 159 return
160 160 if data is None:
161 161 self.dataf.seek(blockstart)
162 162 if blockstart + blocksize > self.datasize:
163 163 # the revlog may have grown since we've started running,
164 164 # but we don't have space in self.index for more entries.
165 165 # limit blocksize so that we don't get too much data.
166 166 blocksize = max(self.datasize - blockstart, 0)
167 167 data = self.dataf.read(blocksize)
168 168 lend = len(data) / self.s
169 169 i = blockstart / self.s
170 170 off = 0
171 171 # lazyindex supports __delitem__
172 172 if lend > len(self.index) - i:
173 173 lend = len(self.index) - i
174 174 for x in xrange(lend):
175 175 if self.index[i + x] == None:
176 176 b = data[off : off + self.s]
177 177 self.index[i + x] = b
178 178 n = b[ngshaoffset:ngshaoffset + 20]
179 179 self.map[n] = i + x
180 180 off += self.s
181 181
182 182 def findnode(self, node):
183 183 """search backwards through the index file for a specific node"""
184 184 if self.allmap:
185 185 return None
186 186
187 187 # hg log will cause many many searches for the manifest
188 188 # nodes. After we get called a few times, just load the whole
189 189 # thing.
190 190 if self.mapfind_count > 8:
191 191 self.loadmap()
192 192 if node in self.map:
193 193 return node
194 194 return None
195 195 self.mapfind_count += 1
196 196 last = self.l - 1
197 197 while self.index[last] != None:
198 198 if last == 0:
199 199 self.all = 1
200 200 self.allmap = 1
201 201 return None
202 202 last -= 1
203 203 end = (last + 1) * self.s
204 204 blocksize = self.s * 256
205 205 while end >= 0:
206 206 start = max(end - blocksize, 0)
207 207 self.dataf.seek(start)
208 208 data = self.dataf.read(end - start)
209 209 findend = end - start
210 210 while True:
211 211 # we're searching backwards, so we have to make sure
212 212 # we don't find a changeset where this node is a parent
213 213 off = data.find(node, 0, findend)
214 214 findend = off
215 215 if off >= 0:
216 216 i = off / self.s
217 217 off = i * self.s
218 218 n = data[off + ngshaoffset:off + ngshaoffset + 20]
219 219 if n == node:
220 220 self.map[n] = i + start / self.s
221 221 return node
222 222 else:
223 223 break
224 224 end -= blocksize
225 225 return None
226 226
227 227 def loadindex(self, i=None, end=None):
228 228 if self.all:
229 229 return
230 230 all = False
231 231 if i == None:
232 232 blockstart = 0
233 233 blocksize = (65536 / self.s) * self.s
234 234 end = self.datasize
235 235 all = True
236 236 else:
237 237 if end:
238 238 blockstart = i * self.s
239 239 end = end * self.s
240 240 blocksize = end - blockstart
241 241 else:
242 242 blockstart = (i & ~1023) * self.s
243 243 blocksize = self.s * 1024
244 244 end = blockstart + blocksize
245 245 while blockstart < end:
246 246 self.loadblock(blockstart, blocksize)
247 247 blockstart += blocksize
248 248 if all:
249 249 self.all = True
250 250
251 251 class lazyindex(object):
252 252 """a lazy version of the index array"""
253 253 def __init__(self, parser):
254 254 self.p = parser
255 255 def __len__(self):
256 256 return len(self.p.index)
257 257 def load(self, pos):
258 258 if pos < 0:
259 259 pos += len(self.p.index)
260 260 self.p.loadindex(pos)
261 261 return self.p.index[pos]
262 262 def __getitem__(self, pos):
263 263 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
264 264 def __setitem__(self, pos, item):
265 265 self.p.index[pos] = _pack(indexformatng, *item)
266 266 def __delitem__(self, pos):
267 267 del self.p.index[pos]
268 268 def insert(self, pos, e):
269 269 self.p.index.insert(pos, _pack(indexformatng, *e))
270 270 def append(self, e):
271 271 self.p.index.append(_pack(indexformatng, *e))
272 272
273 273 class lazymap(object):
274 274 """a lazy version of the node map"""
275 275 def __init__(self, parser):
276 276 self.p = parser
277 277 def load(self, key):
278 278 n = self.p.findnode(key)
279 279 if n == None:
280 280 raise KeyError(key)
281 281 def __contains__(self, key):
282 282 if key in self.p.map:
283 283 return True
284 284 self.p.loadmap()
285 285 return key in self.p.map
286 286 def __iter__(self):
287 287 yield nullid
288 288 for i in xrange(self.p.l):
289 289 ret = self.p.index[i]
290 290 if not ret:
291 291 self.p.loadindex(i)
292 292 ret = self.p.index[i]
293 293 if isinstance(ret, str):
294 294 ret = _unpack(indexformatng, ret)
295 295 yield ret[7]
296 296 def __getitem__(self, key):
297 297 try:
298 298 return self.p.map[key]
299 299 except KeyError:
300 300 try:
301 301 self.load(key)
302 302 return self.p.map[key]
303 303 except KeyError:
304 304 raise KeyError("node " + hex(key))
305 305 def __setitem__(self, key, val):
306 306 self.p.map[key] = val
307 307 def __delitem__(self, key):
308 308 del self.p.map[key]
309 309
310 310 indexformatv0 = ">4l20s20s20s"
311 311 v0shaoffset = 56
312 312
313 313 class revlogoldio(object):
314 314 def __init__(self):
315 315 self.size = struct.calcsize(indexformatv0)
316 316
317 317 def parseindex(self, fp, inline):
318 318 s = self.size
319 319 index = []
320 320 nodemap = {nullid: nullrev}
321 321 n = off = 0
322 322 data = fp.read()
323 323 l = len(data)
324 324 while off + s <= l:
325 325 cur = data[off:off + s]
326 326 off += s
327 327 e = _unpack(indexformatv0, cur)
328 328 # transform to revlogv1 format
329 329 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
330 330 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
331 331 index.append(e2)
332 332 nodemap[e[6]] = n
333 333 n += 1
334 334
335 335 return index, nodemap, None
336 336
337 337 def packentry(self, entry, node, version, rev):
338 338 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
339 339 node(entry[5]), node(entry[6]), entry[7])
340 340 return _pack(indexformatv0, *e2)
341 341
342 342 # index ng:
343 343 # 6 bytes offset
344 344 # 2 bytes flags
345 345 # 4 bytes compressed length
346 346 # 4 bytes uncompressed length
347 347 # 4 bytes: base rev
348 348 # 4 bytes link rev
349 349 # 4 bytes parent 1 rev
350 350 # 4 bytes parent 2 rev
351 351 # 32 bytes: nodeid
352 352 indexformatng = ">Qiiiiii20s12x"
353 353 ngshaoffset = 32
354 354 versionformat = ">I"
355 355
356 356 class revlogio(object):
357 357 def __init__(self):
358 358 self.size = struct.calcsize(indexformatng)
359 359
360 360 def parseindex(self, fp, inline):
361 361 try:
362 362 size = util.fstat(fp).st_size
363 363 except AttributeError:
364 364 size = 0
365 365
366 366 if util.openhardlinks() and not inline and size > 1000000:
367 367 # big index, let's parse it on demand
368 368 parser = lazyparser(fp, size)
369 369 index = lazyindex(parser)
370 370 nodemap = lazymap(parser)
371 371 e = list(index[0])
372 372 type = gettype(e[0])
373 373 e[0] = offset_type(0, type)
374 374 index[0] = e
375 375 return index, nodemap, None
376 376
377 s = self.size
378 cache = None
379 index = []
380 nodemap = {nullid: nullrev}
381 n = off = 0
382 # if we're not using lazymap, always read the whole index
383 377 data = fp.read()
384 l = len(data) - s
385 append = index.append
386 if inline:
387 cache = (0, data)
388 while off <= l:
389 e = _unpack(indexformatng, data[off:off + s])
390 nodemap[e[7]] = n
391 append(e)
392 n += 1
393 if e[1] < 0:
394 break
395 off += e[1] + s
396 else:
397 while off <= l:
398 e = _unpack(indexformatng, data[off:off + s])
399 nodemap[e[7]] = n
400 append(e)
401 n += 1
402 off += s
403
404 e = list(index[0])
405 type = gettype(e[0])
406 e[0] = offset_type(0, type)
407 index[0] = e
408
378 # call the C implementation to parse the index data
379 index, nodemap, cache = parsers.parse_index(data, inline)
409 380 return index, nodemap, cache
410 381
411 382 def packentry(self, entry, node, version, rev):
412 383 p = _pack(indexformatng, *entry)
413 384 if rev == 0:
414 385 p = _pack(versionformat, version) + p[4:]
415 386 return p
416 387
417 388 class revlog(object):
418 389 """
419 390 the underlying revision storage object
420 391
421 392 A revlog consists of two parts, an index and the revision data.
422 393
423 394 The index is a file with a fixed record size containing
424 395 information on each revision, including its nodeid (hash), the
425 396 nodeids of its parents, the position and offset of its data within
426 397 the data file, and the revision it's based on. Finally, each entry
427 398 contains a linkrev entry that can serve as a pointer to external
428 399 data.
429 400
430 401 The revision data itself is a linear collection of data chunks.
431 402 Each chunk represents a revision and is usually represented as a
432 403 delta against the previous chunk. To bound lookup time, runs of
433 404 deltas are limited to about 2 times the length of the original
434 405 version data. This makes retrieval of a version proportional to
435 406 its size, or O(1) relative to the number of revisions.
436 407
437 408 Both pieces of the revlog are written to in an append-only
438 409 fashion, which means we never need to rewrite a file to insert or
439 410 remove data, and can use some simple techniques to avoid the need
440 411 for locking while reading.
441 412 """
442 413 def __init__(self, opener, indexfile):
443 414 """
444 415 create a revlog object
445 416
446 417 opener is a function that abstracts the file opening operation
447 418 and can be used to implement COW semantics or the like.
448 419 """
449 420 self.indexfile = indexfile
450 421 self.datafile = indexfile[:-2] + ".d"
451 422 self.opener = opener
452 423 self._cache = None
453 424 self._chunkcache = None
454 425 self.nodemap = {nullid: nullrev}
455 426 self.index = []
456 427
457 428 v = REVLOG_DEFAULT_VERSION
458 429 if hasattr(opener, "defversion"):
459 430 v = opener.defversion
460 431 if v & REVLOGNG:
461 432 v |= REVLOGNGINLINEDATA
462 433
463 434 i = ""
464 435 try:
465 436 f = self.opener(self.indexfile)
466 437 i = f.read(4)
467 438 f.seek(0)
468 439 if len(i) > 0:
469 440 v = struct.unpack(versionformat, i)[0]
470 441 except IOError, inst:
471 442 if inst.errno != errno.ENOENT:
472 443 raise
473 444
474 445 self.version = v
475 446 self._inline = v & REVLOGNGINLINEDATA
476 447 flags = v & ~0xFFFF
477 448 fmt = v & 0xFFFF
478 449 if fmt == REVLOGV0 and flags:
479 450 raise RevlogError(_("index %s unknown flags %#04x for format v0")
480 451 % (self.indexfile, flags >> 16))
481 452 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
482 453 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
483 454 % (self.indexfile, flags >> 16))
484 455 elif fmt > REVLOGNG:
485 456 raise RevlogError(_("index %s unknown format %d")
486 457 % (self.indexfile, fmt))
487 458
488 459 self._io = revlogio()
489 460 if self.version == REVLOGV0:
490 461 self._io = revlogoldio()
491 462 if i:
492 463 d = self._io.parseindex(f, self._inline)
493 464 self.index, self.nodemap, self._chunkcache = d
494 465
495 # add the magic null revision at -1
496 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
466 # add the magic null revision at -1 (if it hasn't been done already)
467 if (self.index == [] or isinstance(self.index, lazyindex) or
468 self.index[-1][7] != nullid) :
469 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
497 470
498 471 def _loadindex(self, start, end):
499 472 """load a block of indexes all at once from the lazy parser"""
500 473 if isinstance(self.index, lazyindex):
501 474 self.index.p.loadindex(start, end)
502 475
503 476 def _loadindexmap(self):
504 477 """loads both the map and the index from the lazy parser"""
505 478 if isinstance(self.index, lazyindex):
506 479 p = self.index.p
507 480 p.loadindex()
508 481 self.nodemap = p.map
509 482
510 483 def _loadmap(self):
511 484 """loads the map from the lazy parser"""
512 485 if isinstance(self.nodemap, lazymap):
513 486 self.nodemap.p.loadmap()
514 487 self.nodemap = self.nodemap.p.map
515 488
516 489 def tip(self):
517 490 return self.node(len(self.index) - 2)
518 491 def __len__(self):
519 492 return len(self.index) - 1
520 493 def __iter__(self):
521 494 for i in xrange(len(self)):
522 495 yield i
523 496 def rev(self, node):
524 497 try:
525 498 return self.nodemap[node]
526 499 except KeyError:
527 500 raise LookupError(node, self.indexfile, _('no node'))
528 501 def node(self, rev):
529 502 return self.index[rev][7]
530 503 def linkrev(self, node):
531 504 return self.index[self.rev(node)][4]
532 505 def parents(self, node):
533 506 d = self.index[self.rev(node)][5:7]
534 507 return (self.node(d[0]), self.node(d[1]))
535 508 def parentrevs(self, rev):
536 509 return self.index[rev][5:7]
537 510 def start(self, rev):
538 511 return int(self.index[rev][0] >> 16)
539 512 def end(self, rev):
540 513 return self.start(rev) + self.length(rev)
541 514 def length(self, rev):
542 515 return self.index[rev][1]
543 516 def base(self, rev):
544 517 return self.index[rev][3]
545 518
546 519 def size(self, rev):
547 520 """return the length of the uncompressed text for a given revision"""
548 521 l = self.index[rev][2]
549 522 if l >= 0:
550 523 return l
551 524
552 525 t = self.revision(self.node(rev))
553 526 return len(t)
554 527
555 528 # alternate implementation, The advantage to this code is it
556 529 # will be faster for a single revision. But, the results are not
557 530 # cached, so finding the size of every revision will be slower.
558 531 """
559 532 if self.cache and self.cache[1] == rev:
560 533 return len(self.cache[2])
561 534
562 535 base = self.base(rev)
563 536 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
564 537 base = self.cache[1]
565 538 text = self.cache[2]
566 539 else:
567 540 text = self.revision(self.node(base))
568 541
569 542 l = len(text)
570 543 for x in xrange(base + 1, rev + 1):
571 544 l = mdiff.patchedsize(l, self.chunk(x))
572 545 return l
573 546 """
574 547
575 548 def reachable(self, node, stop=None):
576 549 """return a hash of all nodes ancestral to a given node, including
577 550 the node itself, stopping when stop is matched"""
578 551 reachable = {}
579 552 visit = [node]
580 553 reachable[node] = 1
581 554 if stop:
582 555 stopn = self.rev(stop)
583 556 else:
584 557 stopn = 0
585 558 while visit:
586 559 n = visit.pop(0)
587 560 if n == stop:
588 561 continue
589 562 if n == nullid:
590 563 continue
591 564 for p in self.parents(n):
592 565 if self.rev(p) < stopn:
593 566 continue
594 567 if p not in reachable:
595 568 reachable[p] = 1
596 569 visit.append(p)
597 570 return reachable
598 571
599 572 def ancestors(self, *revs):
600 573 'Generate the ancestors of revs using a breadth-first visit'
601 574 visit = list(revs)
602 575 seen = util.set([nullrev])
603 576 while visit:
604 577 for parent in self.parentrevs(visit.pop(0)):
605 578 if parent not in seen:
606 579 visit.append(parent)
607 580 seen.add(parent)
608 581 yield parent
609 582
610 583 def descendants(self, *revs):
611 584 'Generate the descendants of revs in topological order'
612 585 seen = util.set(revs)
613 586 for i in xrange(min(revs) + 1, len(self)):
614 587 for x in self.parentrevs(i):
615 588 if x != nullrev and x in seen:
616 589 seen.add(i)
617 590 yield i
618 591 break
619 592
620 593 def nodesbetween(self, roots=None, heads=None):
621 594 """Return a tuple containing three elements. Elements 1 and 2 contain
622 595 a final list bases and heads after all the unreachable ones have been
623 596 pruned. Element 0 contains a topologically sorted list of all
624 597
625 598 nodes that satisfy these constraints:
626 599 1. All nodes must be descended from a node in roots (the nodes on
627 600 roots are considered descended from themselves).
628 601 2. All nodes must also be ancestors of a node in heads (the nodes in
629 602 heads are considered to be their own ancestors).
630 603
631 604 If roots is unspecified, nullid is assumed as the only root.
632 605 If heads is unspecified, it is taken to be the output of the
633 606 heads method (i.e. a list of all nodes in the repository that
634 607 have no children)."""
635 608 nonodes = ([], [], [])
636 609 if roots is not None:
637 610 roots = list(roots)
638 611 if not roots:
639 612 return nonodes
640 613 lowestrev = min([self.rev(n) for n in roots])
641 614 else:
642 615 roots = [nullid] # Everybody's a descendent of nullid
643 616 lowestrev = nullrev
644 617 if (lowestrev == nullrev) and (heads is None):
645 618 # We want _all_ the nodes!
646 619 return ([self.node(r) for r in self], [nullid], list(self.heads()))
647 620 if heads is None:
648 621 # All nodes are ancestors, so the latest ancestor is the last
649 622 # node.
650 623 highestrev = len(self) - 1
651 624 # Set ancestors to None to signal that every node is an ancestor.
652 625 ancestors = None
653 626 # Set heads to an empty dictionary for later discovery of heads
654 627 heads = {}
655 628 else:
656 629 heads = list(heads)
657 630 if not heads:
658 631 return nonodes
659 632 ancestors = {}
660 633 # Turn heads into a dictionary so we can remove 'fake' heads.
661 634 # Also, later we will be using it to filter out the heads we can't
662 635 # find from roots.
663 636 heads = dict.fromkeys(heads, 0)
664 637 # Start at the top and keep marking parents until we're done.
665 638 nodestotag = heads.keys()
666 639 # Remember where the top was so we can use it as a limit later.
667 640 highestrev = max([self.rev(n) for n in nodestotag])
668 641 while nodestotag:
669 642 # grab a node to tag
670 643 n = nodestotag.pop()
671 644 # Never tag nullid
672 645 if n == nullid:
673 646 continue
674 647 # A node's revision number represents its place in a
675 648 # topologically sorted list of nodes.
676 649 r = self.rev(n)
677 650 if r >= lowestrev:
678 651 if n not in ancestors:
679 652 # If we are possibly a descendent of one of the roots
680 653 # and we haven't already been marked as an ancestor
681 654 ancestors[n] = 1 # Mark as ancestor
682 655 # Add non-nullid parents to list of nodes to tag.
683 656 nodestotag.extend([p for p in self.parents(n) if
684 657 p != nullid])
685 658 elif n in heads: # We've seen it before, is it a fake head?
686 659 # So it is, real heads should not be the ancestors of
687 660 # any other heads.
688 661 heads.pop(n)
689 662 if not ancestors:
690 663 return nonodes
691 664 # Now that we have our set of ancestors, we want to remove any
692 665 # roots that are not ancestors.
693 666
694 667 # If one of the roots was nullid, everything is included anyway.
695 668 if lowestrev > nullrev:
696 669 # But, since we weren't, let's recompute the lowest rev to not
697 670 # include roots that aren't ancestors.
698 671
699 672 # Filter out roots that aren't ancestors of heads
700 673 roots = [n for n in roots if n in ancestors]
701 674 # Recompute the lowest revision
702 675 if roots:
703 676 lowestrev = min([self.rev(n) for n in roots])
704 677 else:
705 678 # No more roots? Return empty list
706 679 return nonodes
707 680 else:
708 681 # We are descending from nullid, and don't need to care about
709 682 # any other roots.
710 683 lowestrev = nullrev
711 684 roots = [nullid]
712 685 # Transform our roots list into a 'set' (i.e. a dictionary where the
713 686 # values don't matter.
714 687 descendents = dict.fromkeys(roots, 1)
715 688 # Also, keep the original roots so we can filter out roots that aren't
716 689 # 'real' roots (i.e. are descended from other roots).
717 690 roots = descendents.copy()
718 691 # Our topologically sorted list of output nodes.
719 692 orderedout = []
720 693 # Don't start at nullid since we don't want nullid in our output list,
721 694 # and if nullid shows up in descedents, empty parents will look like
722 695 # they're descendents.
723 696 for r in xrange(max(lowestrev, 0), highestrev + 1):
724 697 n = self.node(r)
725 698 isdescendent = False
726 699 if lowestrev == nullrev: # Everybody is a descendent of nullid
727 700 isdescendent = True
728 701 elif n in descendents:
729 702 # n is already a descendent
730 703 isdescendent = True
731 704 # This check only needs to be done here because all the roots
732 705 # will start being marked is descendents before the loop.
733 706 if n in roots:
734 707 # If n was a root, check if it's a 'real' root.
735 708 p = tuple(self.parents(n))
736 709 # If any of its parents are descendents, it's not a root.
737 710 if (p[0] in descendents) or (p[1] in descendents):
738 711 roots.pop(n)
739 712 else:
740 713 p = tuple(self.parents(n))
741 714 # A node is a descendent if either of its parents are
742 715 # descendents. (We seeded the dependents list with the roots
743 716 # up there, remember?)
744 717 if (p[0] in descendents) or (p[1] in descendents):
745 718 descendents[n] = 1
746 719 isdescendent = True
747 720 if isdescendent and ((ancestors is None) or (n in ancestors)):
748 721 # Only include nodes that are both descendents and ancestors.
749 722 orderedout.append(n)
750 723 if (ancestors is not None) and (n in heads):
751 724 # We're trying to figure out which heads are reachable
752 725 # from roots.
753 726 # Mark this head as having been reached
754 727 heads[n] = 1
755 728 elif ancestors is None:
756 729 # Otherwise, we're trying to discover the heads.
757 730 # Assume this is a head because if it isn't, the next step
758 731 # will eventually remove it.
759 732 heads[n] = 1
760 733 # But, obviously its parents aren't.
761 734 for p in self.parents(n):
762 735 heads.pop(p, None)
763 736 heads = [n for n in heads.iterkeys() if heads[n] != 0]
764 737 roots = roots.keys()
765 738 assert orderedout
766 739 assert roots
767 740 assert heads
768 741 return (orderedout, roots, heads)
769 742
770 743 def heads(self, start=None, stop=None):
771 744 """return the list of all nodes that have no children
772 745
773 746 if start is specified, only heads that are descendants of
774 747 start will be returned
775 748 if stop is specified, it will consider all the revs from stop
776 749 as if they had no children
777 750 """
778 751 if start is None and stop is None:
779 752 count = len(self)
780 753 if not count:
781 754 return [nullid]
782 755 ishead = [1] * (count + 1)
783 756 index = self.index
784 757 for r in xrange(count):
785 758 e = index[r]
786 759 ishead[e[5]] = ishead[e[6]] = 0
787 760 return [self.node(r) for r in xrange(count) if ishead[r]]
788 761
789 762 if start is None:
790 763 start = nullid
791 764 if stop is None:
792 765 stop = []
793 766 stoprevs = dict.fromkeys([self.rev(n) for n in stop])
794 767 startrev = self.rev(start)
795 768 reachable = {startrev: 1}
796 769 heads = {startrev: 1}
797 770
798 771 parentrevs = self.parentrevs
799 772 for r in xrange(startrev + 1, len(self)):
800 773 for p in parentrevs(r):
801 774 if p in reachable:
802 775 if r not in stoprevs:
803 776 reachable[r] = 1
804 777 heads[r] = 1
805 778 if p in heads and p not in stoprevs:
806 779 del heads[p]
807 780
808 781 return [self.node(r) for r in heads]
809 782
810 783 def children(self, node):
811 784 """find the children of a given node"""
812 785 c = []
813 786 p = self.rev(node)
814 787 for r in range(p + 1, len(self)):
815 788 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
816 789 if prevs:
817 790 for pr in prevs:
818 791 if pr == p:
819 792 c.append(self.node(r))
820 793 elif p == nullrev:
821 794 c.append(self.node(r))
822 795 return c
823 796
824 797 def _match(self, id):
825 798 if isinstance(id, (long, int)):
826 799 # rev
827 800 return self.node(id)
828 801 if len(id) == 20:
829 802 # possibly a binary node
830 803 # odds of a binary node being all hex in ASCII are 1 in 10**25
831 804 try:
832 805 node = id
833 806 r = self.rev(node) # quick search the index
834 807 return node
835 808 except LookupError:
836 809 pass # may be partial hex id
837 810 try:
838 811 # str(rev)
839 812 rev = int(id)
840 813 if str(rev) != id:
841 814 raise ValueError
842 815 if rev < 0:
843 816 rev = len(self) + rev
844 817 if rev < 0 or rev >= len(self):
845 818 raise ValueError
846 819 return self.node(rev)
847 820 except (ValueError, OverflowError):
848 821 pass
849 822 if len(id) == 40:
850 823 try:
851 824 # a full hex nodeid?
852 825 node = bin(id)
853 826 r = self.rev(node)
854 827 return node
855 828 except (TypeError, LookupError):
856 829 pass
857 830
858 831 def _partialmatch(self, id):
859 832 if len(id) < 40:
860 833 try:
861 834 # hex(node)[:...]
862 835 bin_id = bin(id[:len(id) & ~1]) # grab an even number of digits
863 836 node = None
864 837 for n in self.nodemap:
865 838 if n.startswith(bin_id) and hex(n).startswith(id):
866 839 if node is not None:
867 840 raise LookupError(id, self.indexfile,
868 841 _('ambiguous identifier'))
869 842 node = n
870 843 if node is not None:
871 844 return node
872 845 except TypeError:
873 846 pass
874 847
875 848 def lookup(self, id):
876 849 """locate a node based on:
877 850 - revision number or str(revision number)
878 851 - nodeid or subset of hex nodeid
879 852 """
880 853 n = self._match(id)
881 854 if n is not None:
882 855 return n
883 856 n = self._partialmatch(id)
884 857 if n:
885 858 return n
886 859
887 860 raise LookupError(id, self.indexfile, _('no match found'))
888 861
889 862 def cmp(self, node, text):
890 863 """compare text with a given file revision"""
891 864 p1, p2 = self.parents(node)
892 865 return hash(text, p1, p2) != node
893 866
894 867 def chunk(self, rev, df=None):
895 868 def loadcache(df):
896 869 if not df:
897 870 if self._inline:
898 871 df = self.opener(self.indexfile)
899 872 else:
900 873 df = self.opener(self.datafile)
901 874 df.seek(start)
902 875 self._chunkcache = (start, df.read(cache_length))
903 876
904 877 start, length = self.start(rev), self.length(rev)
905 878 if self._inline:
906 879 start += (rev + 1) * self._io.size
907 880 end = start + length
908 881
909 882 offset = 0
910 883 if not self._chunkcache:
911 884 cache_length = max(65536, length)
912 885 loadcache(df)
913 886 else:
914 887 cache_start = self._chunkcache[0]
915 888 cache_length = len(self._chunkcache[1])
916 889 cache_end = cache_start + cache_length
917 890 if start >= cache_start and end <= cache_end:
918 891 # it is cached
919 892 offset = start - cache_start
920 893 else:
921 894 cache_length = max(65536, length)
922 895 loadcache(df)
923 896
924 897 # avoid copying large chunks
925 898 c = self._chunkcache[1]
926 899 if cache_length != length:
927 900 c = c[offset:offset + length]
928 901
929 902 return decompress(c)
930 903
931 904 def delta(self, node):
932 905 """return or calculate a delta between a node and its predecessor"""
933 906 r = self.rev(node)
934 907 return self.revdiff(r - 1, r)
935 908
936 909 def revdiff(self, rev1, rev2):
937 910 """return or calculate a delta between two revisions"""
938 911 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
939 912 return self.chunk(rev2)
940 913
941 914 return mdiff.textdiff(self.revision(self.node(rev1)),
942 915 self.revision(self.node(rev2)))
943 916
944 917 def revision(self, node):
945 918 """return an uncompressed revision of a given node"""
946 919 if node == nullid:
947 920 return ""
948 921 if self._cache and self._cache[0] == node:
949 922 return str(self._cache[2])
950 923
951 924 # look up what we need to read
952 925 text = None
953 926 rev = self.rev(node)
954 927 base = self.base(rev)
955 928
956 929 # check rev flags
957 930 if self.index[rev][0] & 0xFFFF:
958 931 raise RevlogError(_('incompatible revision flag %x') %
959 932 (self.index[rev][0] & 0xFFFF))
960 933
961 934 df = None
962 935
963 936 # do we have useful data cached?
964 937 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
965 938 base = self._cache[1]
966 939 text = str(self._cache[2])
967 940 self._loadindex(base, rev + 1)
968 941 if not self._inline and rev > base + 1:
969 942 df = self.opener(self.datafile)
970 943 else:
971 944 self._loadindex(base, rev + 1)
972 945 if not self._inline and rev > base:
973 946 df = self.opener(self.datafile)
974 947 text = self.chunk(base, df=df)
975 948
976 949 bins = [self.chunk(r, df) for r in xrange(base + 1, rev + 1)]
977 950 text = mdiff.patches(text, bins)
978 951 p1, p2 = self.parents(node)
979 952 if node != hash(text, p1, p2):
980 953 raise RevlogError(_("integrity check failed on %s:%d")
981 954 % (self.datafile, rev))
982 955
983 956 self._cache = (node, rev, text)
984 957 return text
985 958
986 959 def checkinlinesize(self, tr, fp=None):
987 960 if not self._inline:
988 961 return
989 962 if not fp:
990 963 fp = self.opener(self.indexfile, 'r')
991 964 fp.seek(0, 2)
992 965 size = fp.tell()
993 966 if size < 131072:
994 967 return
995 968 trinfo = tr.find(self.indexfile)
996 969 if trinfo == None:
997 970 raise RevlogError(_("%s not found in the transaction")
998 971 % self.indexfile)
999 972
1000 973 trindex = trinfo[2]
1001 974 dataoff = self.start(trindex)
1002 975
1003 976 tr.add(self.datafile, dataoff)
1004 977 df = self.opener(self.datafile, 'w')
1005 978 try:
1006 979 calc = self._io.size
1007 980 for r in self:
1008 981 start = self.start(r) + (r + 1) * calc
1009 982 length = self.length(r)
1010 983 fp.seek(start)
1011 984 d = fp.read(length)
1012 985 df.write(d)
1013 986 finally:
1014 987 df.close()
1015 988
1016 989 fp.close()
1017 990 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1018 991 self.version &= ~(REVLOGNGINLINEDATA)
1019 992 self._inline = False
1020 993 for i in self:
1021 994 e = self._io.packentry(self.index[i], self.node, self.version, i)
1022 995 fp.write(e)
1023 996
1024 997 # if we don't call rename, the temp file will never replace the
1025 998 # real index
1026 999 fp.rename()
1027 1000
1028 1001 tr.replace(self.indexfile, trindex * calc)
1029 1002 self._chunkcache = None
1030 1003
1031 1004 def addrevision(self, text, transaction, link, p1, p2, d=None):
1032 1005 """add a revision to the log
1033 1006
1034 1007 text - the revision data to add
1035 1008 transaction - the transaction object used for rollback
1036 1009 link - the linkrev data to add
1037 1010 p1, p2 - the parent nodeids of the revision
1038 1011 d - an optional precomputed delta
1039 1012 """
1040 1013 dfh = None
1041 1014 if not self._inline:
1042 1015 dfh = self.opener(self.datafile, "a")
1043 1016 ifh = self.opener(self.indexfile, "a+")
1044 1017 try:
1045 1018 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1046 1019 finally:
1047 1020 if dfh:
1048 1021 dfh.close()
1049 1022 ifh.close()
1050 1023
1051 1024 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1052 1025 node = hash(text, p1, p2)
1053 1026 if node in self.nodemap:
1054 1027 return node
1055 1028
1056 1029 curr = len(self)
1057 1030 prev = curr - 1
1058 1031 base = self.base(prev)
1059 1032 offset = self.end(prev)
1060 1033
1061 1034 if curr:
1062 1035 if not d:
1063 1036 ptext = self.revision(self.node(prev))
1064 1037 d = mdiff.textdiff(ptext, text)
1065 1038 data = compress(d)
1066 1039 l = len(data[1]) + len(data[0])
1067 1040 dist = l + offset - self.start(base)
1068 1041
1069 1042 # full versions are inserted when the needed deltas
1070 1043 # become comparable to the uncompressed text
1071 1044 if not curr or dist > len(text) * 2:
1072 1045 data = compress(text)
1073 1046 l = len(data[1]) + len(data[0])
1074 1047 base = curr
1075 1048
1076 1049 e = (offset_type(offset, 0), l, len(text),
1077 1050 base, link, self.rev(p1), self.rev(p2), node)
1078 1051 self.index.insert(-1, e)
1079 1052 self.nodemap[node] = curr
1080 1053
1081 1054 entry = self._io.packentry(e, self.node, self.version, curr)
1082 1055 if not self._inline:
1083 1056 transaction.add(self.datafile, offset)
1084 1057 transaction.add(self.indexfile, curr * len(entry))
1085 1058 if data[0]:
1086 1059 dfh.write(data[0])
1087 1060 dfh.write(data[1])
1088 1061 dfh.flush()
1089 1062 ifh.write(entry)
1090 1063 else:
1091 1064 offset += curr * self._io.size
1092 1065 transaction.add(self.indexfile, offset, curr)
1093 1066 ifh.write(entry)
1094 1067 ifh.write(data[0])
1095 1068 ifh.write(data[1])
1096 1069 self.checkinlinesize(transaction, ifh)
1097 1070
1098 1071 self._cache = (node, curr, text)
1099 1072 return node
1100 1073
1101 1074 def ancestor(self, a, b):
1102 1075 """calculate the least common ancestor of nodes a and b"""
1103 1076
1104 1077 def parents(rev):
1105 1078 return [p for p in self.parentrevs(rev) if p != nullrev]
1106 1079
1107 1080 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1108 1081 if c is None:
1109 1082 return nullid
1110 1083
1111 1084 return self.node(c)
1112 1085
1113 1086 def group(self, nodelist, lookup, infocollect=None):
1114 1087 """calculate a delta group
1115 1088
1116 1089 Given a list of changeset revs, return a set of deltas and
1117 1090 metadata corresponding to nodes. the first delta is
1118 1091 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1119 1092 have this parent as it has all history before these
1120 1093 changesets. parent is parent[0]
1121 1094 """
1122 1095 revs = [self.rev(n) for n in nodelist]
1123 1096
1124 1097 # if we don't have any revisions touched by these changesets, bail
1125 1098 if not revs:
1126 1099 yield changegroup.closechunk()
1127 1100 return
1128 1101
1129 1102 # add the parent of the first rev
1130 1103 p = self.parents(self.node(revs[0]))[0]
1131 1104 revs.insert(0, self.rev(p))
1132 1105
1133 1106 # build deltas
1134 1107 for d in xrange(0, len(revs) - 1):
1135 1108 a, b = revs[d], revs[d + 1]
1136 1109 nb = self.node(b)
1137 1110
1138 1111 if infocollect is not None:
1139 1112 infocollect(nb)
1140 1113
1141 1114 p = self.parents(nb)
1142 1115 meta = nb + p[0] + p[1] + lookup(nb)
1143 1116 if a == -1:
1144 1117 d = self.revision(nb)
1145 1118 meta += mdiff.trivialdiffheader(len(d))
1146 1119 else:
1147 1120 d = self.revdiff(a, b)
1148 1121 yield changegroup.chunkheader(len(meta) + len(d))
1149 1122 yield meta
1150 1123 if len(d) > 2**20:
1151 1124 pos = 0
1152 1125 while pos < len(d):
1153 1126 pos2 = pos + 2 ** 18
1154 1127 yield d[pos:pos2]
1155 1128 pos = pos2
1156 1129 else:
1157 1130 yield d
1158 1131
1159 1132 yield changegroup.closechunk()
1160 1133
1161 1134 def addgroup(self, revs, linkmapper, transaction):
1162 1135 """
1163 1136 add a delta group
1164 1137
1165 1138 given a set of deltas, add them to the revision log. the
1166 1139 first delta is against its parent, which should be in our
1167 1140 log, the rest are against the previous delta.
1168 1141 """
1169 1142
1170 1143 #track the base of the current delta log
1171 1144 r = len(self)
1172 1145 t = r - 1
1173 1146 node = None
1174 1147
1175 1148 base = prev = nullrev
1176 1149 start = end = textlen = 0
1177 1150 if r:
1178 1151 end = self.end(t)
1179 1152
1180 1153 ifh = self.opener(self.indexfile, "a+")
1181 1154 isize = r * self._io.size
1182 1155 if self._inline:
1183 1156 transaction.add(self.indexfile, end + isize, r)
1184 1157 dfh = None
1185 1158 else:
1186 1159 transaction.add(self.indexfile, isize, r)
1187 1160 transaction.add(self.datafile, end)
1188 1161 dfh = self.opener(self.datafile, "a")
1189 1162
1190 1163 try:
1191 1164 # loop through our set of deltas
1192 1165 chain = None
1193 1166 for chunk in revs:
1194 1167 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1195 1168 link = linkmapper(cs)
1196 1169 if node in self.nodemap:
1197 1170 # this can happen if two branches make the same change
1198 1171 chain = node
1199 1172 continue
1200 1173 delta = buffer(chunk, 80)
1201 1174 del chunk
1202 1175
1203 1176 for p in (p1, p2):
1204 1177 if not p in self.nodemap:
1205 1178 raise LookupError(p, self.indexfile, _('unknown parent'))
1206 1179
1207 1180 if not chain:
1208 1181 # retrieve the parent revision of the delta chain
1209 1182 chain = p1
1210 1183 if not chain in self.nodemap:
1211 1184 raise LookupError(chain, self.indexfile, _('unknown base'))
1212 1185
1213 1186 # full versions are inserted when the needed deltas become
1214 1187 # comparable to the uncompressed text or when the previous
1215 1188 # version is not the one we have a delta against. We use
1216 1189 # the size of the previous full rev as a proxy for the
1217 1190 # current size.
1218 1191
1219 1192 if chain == prev:
1220 1193 cdelta = compress(delta)
1221 1194 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1222 1195 textlen = mdiff.patchedsize(textlen, delta)
1223 1196
1224 1197 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1225 1198 # flush our writes here so we can read it in revision
1226 1199 if dfh:
1227 1200 dfh.flush()
1228 1201 ifh.flush()
1229 1202 text = self.revision(chain)
1230 1203 if len(text) == 0:
1231 1204 # skip over trivial delta header
1232 1205 text = buffer(delta, 12)
1233 1206 else:
1234 1207 text = mdiff.patches(text, [delta])
1235 1208 del delta
1236 1209 chk = self._addrevision(text, transaction, link, p1, p2, None,
1237 1210 ifh, dfh)
1238 1211 if not dfh and not self._inline:
1239 1212 # addrevision switched from inline to conventional
1240 1213 # reopen the index
1241 1214 dfh = self.opener(self.datafile, "a")
1242 1215 ifh = self.opener(self.indexfile, "a")
1243 1216 if chk != node:
1244 1217 raise RevlogError(_("consistency error adding group"))
1245 1218 textlen = len(text)
1246 1219 else:
1247 1220 e = (offset_type(end, 0), cdeltalen, textlen, base,
1248 1221 link, self.rev(p1), self.rev(p2), node)
1249 1222 self.index.insert(-1, e)
1250 1223 self.nodemap[node] = r
1251 1224 entry = self._io.packentry(e, self.node, self.version, r)
1252 1225 if self._inline:
1253 1226 ifh.write(entry)
1254 1227 ifh.write(cdelta[0])
1255 1228 ifh.write(cdelta[1])
1256 1229 self.checkinlinesize(transaction, ifh)
1257 1230 if not self._inline:
1258 1231 dfh = self.opener(self.datafile, "a")
1259 1232 ifh = self.opener(self.indexfile, "a")
1260 1233 else:
1261 1234 dfh.write(cdelta[0])
1262 1235 dfh.write(cdelta[1])
1263 1236 ifh.write(entry)
1264 1237
1265 1238 t, r, chain, prev = r, r + 1, node, node
1266 1239 base = self.base(t)
1267 1240 start = self.start(base)
1268 1241 end = self.end(t)
1269 1242 finally:
1270 1243 if dfh:
1271 1244 dfh.close()
1272 1245 ifh.close()
1273 1246
1274 1247 return node
1275 1248
1276 1249 def strip(self, minlink):
1277 1250 """truncate the revlog on the first revision with a linkrev >= minlink
1278 1251
1279 1252 This function is called when we're stripping revision minlink and
1280 1253 its descendants from the repository.
1281 1254
1282 1255 We have to remove all revisions with linkrev >= minlink, because
1283 1256 the equivalent changelog revisions will be renumbered after the
1284 1257 strip.
1285 1258
1286 1259 So we truncate the revlog on the first of these revisions, and
1287 1260 trust that the caller has saved the revisions that shouldn't be
1288 1261 removed and that it'll readd them after this truncation.
1289 1262 """
1290 1263 if len(self) == 0:
1291 1264 return
1292 1265
1293 1266 if isinstance(self.index, lazyindex):
1294 1267 self._loadindexmap()
1295 1268
1296 1269 for rev in self:
1297 1270 if self.index[rev][4] >= minlink:
1298 1271 break
1299 1272 else:
1300 1273 return
1301 1274
1302 1275 # first truncate the files on disk
1303 1276 end = self.start(rev)
1304 1277 if not self._inline:
1305 1278 df = self.opener(self.datafile, "a")
1306 1279 df.truncate(end)
1307 1280 end = rev * self._io.size
1308 1281 else:
1309 1282 end += rev * self._io.size
1310 1283
1311 1284 indexf = self.opener(self.indexfile, "a")
1312 1285 indexf.truncate(end)
1313 1286
1314 1287 # then reset internal state in memory to forget those revisions
1315 1288 self._cache = None
1316 1289 self._chunkcache = None
1317 1290 for x in xrange(rev, len(self)):
1318 1291 del self.nodemap[self.node(x)]
1319 1292
1320 1293 del self.index[rev:-1]
1321 1294
1322 1295 def checksize(self):
1323 1296 expected = 0
1324 1297 if len(self):
1325 1298 expected = max(0, self.end(len(self) - 1))
1326 1299
1327 1300 try:
1328 1301 f = self.opener(self.datafile)
1329 1302 f.seek(0, 2)
1330 1303 actual = f.tell()
1331 1304 dd = actual - expected
1332 1305 except IOError, inst:
1333 1306 if inst.errno != errno.ENOENT:
1334 1307 raise
1335 1308 dd = 0
1336 1309
1337 1310 try:
1338 1311 f = self.opener(self.indexfile)
1339 1312 f.seek(0, 2)
1340 1313 actual = f.tell()
1341 1314 s = self._io.size
1342 1315 i = max(0, actual / s)
1343 1316 di = actual - (i * s)
1344 1317 if self._inline:
1345 1318 databytes = 0
1346 1319 for r in self:
1347 1320 databytes += max(0, self.length(r))
1348 1321 dd = 0
1349 1322 di = actual - len(self) * s - databytes
1350 1323 except IOError, inst:
1351 1324 if inst.errno != errno.ENOENT:
1352 1325 raise
1353 1326 di = 0
1354 1327
1355 1328 return (dd, di)
1356 1329
1357 1330 def files(self):
1358 1331 res = [ self.indexfile ]
1359 1332 if not self._inline:
1360 1333 res.append(self.datafile)
1361 1334 return res
General Comments 0
You need to be logged in to leave comments. Login now