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