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