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