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