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