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