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