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