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