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