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