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