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