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