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