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