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