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