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