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