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