##// END OF EJS Templates
revlog: factor out _maxinline global....
Greg Ward -
r10900:cf016c98 default
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 descendant(self, start, end):
849 851 for i in self.descendants(start):
850 852 if i == end:
851 853 return True
852 854 elif i > end:
853 855 break
854 856 return False
855 857
856 858 def ancestor(self, a, b):
857 859 """calculate the least common ancestor of nodes a and b"""
858 860
859 861 # fast path, check if it is a descendant
860 862 a, b = self.rev(a), self.rev(b)
861 863 start, end = sorted((a, b))
862 864 if self.descendant(start, end):
863 865 return self.node(start)
864 866
865 867 def parents(rev):
866 868 return [p for p in self.parentrevs(rev) if p != nullrev]
867 869
868 870 c = ancestor.ancestor(a, b, parents)
869 871 if c is None:
870 872 return nullid
871 873
872 874 return self.node(c)
873 875
874 876 def _match(self, id):
875 877 if isinstance(id, (long, int)):
876 878 # rev
877 879 return self.node(id)
878 880 if len(id) == 20:
879 881 # possibly a binary node
880 882 # odds of a binary node being all hex in ASCII are 1 in 10**25
881 883 try:
882 884 node = id
883 885 self.rev(node) # quick search the index
884 886 return node
885 887 except LookupError:
886 888 pass # may be partial hex id
887 889 try:
888 890 # str(rev)
889 891 rev = int(id)
890 892 if str(rev) != id:
891 893 raise ValueError
892 894 if rev < 0:
893 895 rev = len(self) + rev
894 896 if rev < 0 or rev >= len(self):
895 897 raise ValueError
896 898 return self.node(rev)
897 899 except (ValueError, OverflowError):
898 900 pass
899 901 if len(id) == 40:
900 902 try:
901 903 # a full hex nodeid?
902 904 node = bin(id)
903 905 self.rev(node)
904 906 return node
905 907 except (TypeError, LookupError):
906 908 pass
907 909
908 910 def _partialmatch(self, id):
909 911 if len(id) < 40:
910 912 try:
911 913 # hex(node)[:...]
912 914 l = len(id) // 2 # grab an even number of digits
913 915 bin_id = bin(id[:l * 2])
914 916 nl = [n for n in self.nodemap if n[:l] == bin_id]
915 917 nl = [n for n in nl if hex(n).startswith(id)]
916 918 if len(nl) > 0:
917 919 if len(nl) == 1:
918 920 return nl[0]
919 921 raise LookupError(id, self.indexfile,
920 922 _('ambiguous identifier'))
921 923 return None
922 924 except TypeError:
923 925 pass
924 926
925 927 def lookup(self, id):
926 928 """locate a node based on:
927 929 - revision number or str(revision number)
928 930 - nodeid or subset of hex nodeid
929 931 """
930 932 n = self._match(id)
931 933 if n is not None:
932 934 return n
933 935 n = self._partialmatch(id)
934 936 if n:
935 937 return n
936 938
937 939 raise LookupError(id, self.indexfile, _('no match found'))
938 940
939 941 def cmp(self, node, text):
940 942 """compare text with a given file revision"""
941 943 p1, p2 = self.parents(node)
942 944 return hash(text, p1, p2) != node
943 945
944 946 def _addchunk(self, offset, data):
945 947 o, d = self._chunkcache
946 948 # try to add to existing cache
947 949 if o + len(d) == offset and len(d) + len(data) < _prereadsize:
948 950 self._chunkcache = o, d + data
949 951 else:
950 952 self._chunkcache = offset, data
951 953
952 954 def _loadchunk(self, offset, length):
953 955 if self._inline:
954 956 df = self.opener(self.indexfile)
955 957 else:
956 958 df = self.opener(self.datafile)
957 959
958 960 readahead = max(65536, length)
959 961 df.seek(offset)
960 962 d = df.read(readahead)
961 963 self._addchunk(offset, d)
962 964 if readahead > length:
963 965 return d[:length]
964 966 return d
965 967
966 968 def _getchunk(self, offset, length):
967 969 o, d = self._chunkcache
968 970 l = len(d)
969 971
970 972 # is it in the cache?
971 973 cachestart = offset - o
972 974 cacheend = cachestart + length
973 975 if cachestart >= 0 and cacheend <= l:
974 976 if cachestart == 0 and cacheend == l:
975 977 return d # avoid a copy
976 978 return d[cachestart:cacheend]
977 979
978 980 return self._loadchunk(offset, length)
979 981
980 982 def _chunkraw(self, startrev, endrev):
981 983 start = self.start(startrev)
982 984 length = self.end(endrev) - start
983 985 if self._inline:
984 986 start += (startrev + 1) * self._io.size
985 987 return self._getchunk(start, length)
986 988
987 989 def _chunk(self, rev):
988 990 return decompress(self._chunkraw(rev, rev))
989 991
990 992 def _chunkclear(self):
991 993 self._chunkcache = (0, '')
992 994
993 995 def revdiff(self, rev1, rev2):
994 996 """return or calculate a delta between two revisions"""
995 997 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
996 998 return self._chunk(rev2)
997 999
998 1000 return mdiff.textdiff(self.revision(self.node(rev1)),
999 1001 self.revision(self.node(rev2)))
1000 1002
1001 1003 def revision(self, node):
1002 1004 """return an uncompressed revision of a given node"""
1003 1005 if node == nullid:
1004 1006 return ""
1005 1007 if self._cache and self._cache[0] == node:
1006 1008 return self._cache[2]
1007 1009
1008 1010 # look up what we need to read
1009 1011 text = None
1010 1012 rev = self.rev(node)
1011 1013 base = self.base(rev)
1012 1014
1013 1015 # check rev flags
1014 1016 if self.index[rev][0] & 0xFFFF:
1015 1017 raise RevlogError(_('incompatible revision flag %x') %
1016 1018 (self.index[rev][0] & 0xFFFF))
1017 1019
1018 1020 # do we have useful data cached?
1019 1021 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
1020 1022 base = self._cache[1]
1021 1023 text = self._cache[2]
1022 1024
1023 1025 self._loadindex(base, rev + 1)
1024 1026 self._chunkraw(base, rev)
1025 1027 if text is None:
1026 1028 text = self._chunk(base)
1027 1029
1028 1030 bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
1029 1031 text = mdiff.patches(text, bins)
1030 1032 p1, p2 = self.parents(node)
1031 1033 if node != hash(text, p1, p2):
1032 1034 raise RevlogError(_("integrity check failed on %s:%d")
1033 1035 % (self.indexfile, rev))
1034 1036
1035 1037 self._cache = (node, rev, text)
1036 1038 return text
1037 1039
1038 1040 def checkinlinesize(self, tr, fp=None):
1039 if not self._inline or (self.start(-2) + self.length(-2)) < 131072:
1041 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1040 1042 return
1041 1043
1042 1044 trinfo = tr.find(self.indexfile)
1043 1045 if trinfo is None:
1044 1046 raise RevlogError(_("%s not found in the transaction")
1045 1047 % self.indexfile)
1046 1048
1047 1049 trindex = trinfo[2]
1048 1050 dataoff = self.start(trindex)
1049 1051
1050 1052 tr.add(self.datafile, dataoff)
1051 1053
1052 1054 if fp:
1053 1055 fp.flush()
1054 1056 fp.close()
1055 1057
1056 1058 df = self.opener(self.datafile, 'w')
1057 1059 try:
1058 1060 for r in self:
1059 1061 df.write(self._chunkraw(r, r))
1060 1062 finally:
1061 1063 df.close()
1062 1064
1063 1065 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1064 1066 self.version &= ~(REVLOGNGINLINEDATA)
1065 1067 self._inline = False
1066 1068 for i in self:
1067 1069 e = self._io.packentry(self.index[i], self.node, self.version, i)
1068 1070 fp.write(e)
1069 1071
1070 1072 # if we don't call rename, the temp file will never replace the
1071 1073 # real index
1072 1074 fp.rename()
1073 1075
1074 1076 tr.replace(self.indexfile, trindex * self._io.size)
1075 1077 self._chunkclear()
1076 1078
1077 1079 def addrevision(self, text, transaction, link, p1, p2, d=None):
1078 1080 """add a revision to the log
1079 1081
1080 1082 text - the revision data to add
1081 1083 transaction - the transaction object used for rollback
1082 1084 link - the linkrev data to add
1083 1085 p1, p2 - the parent nodeids of the revision
1084 1086 d - an optional precomputed delta
1085 1087 """
1086 1088 dfh = None
1087 1089 if not self._inline:
1088 1090 dfh = self.opener(self.datafile, "a")
1089 1091 ifh = self.opener(self.indexfile, "a+")
1090 1092 try:
1091 1093 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1092 1094 finally:
1093 1095 if dfh:
1094 1096 dfh.close()
1095 1097 ifh.close()
1096 1098
1097 1099 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1098 1100 node = hash(text, p1, p2)
1099 1101 if node in self.nodemap:
1100 1102 return node
1101 1103
1102 1104 curr = len(self)
1103 1105 prev = curr - 1
1104 1106 base = self.base(prev)
1105 1107 offset = self.end(prev)
1106 1108
1107 1109 if curr:
1108 1110 if not d:
1109 1111 ptext = self.revision(self.node(prev))
1110 1112 d = mdiff.textdiff(ptext, text)
1111 1113 data = compress(d)
1112 1114 l = len(data[1]) + len(data[0])
1113 1115 dist = l + offset - self.start(base)
1114 1116
1115 1117 # full versions are inserted when the needed deltas
1116 1118 # become comparable to the uncompressed text
1117 1119 if not curr or dist > len(text) * 2:
1118 1120 data = compress(text)
1119 1121 l = len(data[1]) + len(data[0])
1120 1122 base = curr
1121 1123
1122 1124 e = (offset_type(offset, 0), l, len(text),
1123 1125 base, link, self.rev(p1), self.rev(p2), node)
1124 1126 self.index.insert(-1, e)
1125 1127 self.nodemap[node] = curr
1126 1128
1127 1129 entry = self._io.packentry(e, self.node, self.version, curr)
1128 1130 if not self._inline:
1129 1131 transaction.add(self.datafile, offset)
1130 1132 transaction.add(self.indexfile, curr * len(entry))
1131 1133 if data[0]:
1132 1134 dfh.write(data[0])
1133 1135 dfh.write(data[1])
1134 1136 dfh.flush()
1135 1137 ifh.write(entry)
1136 1138 else:
1137 1139 offset += curr * self._io.size
1138 1140 transaction.add(self.indexfile, offset, curr)
1139 1141 ifh.write(entry)
1140 1142 ifh.write(data[0])
1141 1143 ifh.write(data[1])
1142 1144 self.checkinlinesize(transaction, ifh)
1143 1145
1144 1146 if type(text) == str: # only accept immutable objects
1145 1147 self._cache = (node, curr, text)
1146 1148 return node
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