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