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