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