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