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