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