##// END OF EJS Templates
revlog: raise an exception earlier if an entry is too large (issue4675)...
Jordi Gutiérrez Hermoso -
r25410:eee88912 default
parent child Browse files
Show More
@@ -1,1594 +1,1604 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 import collections
15 import collections
16 from node import bin, hex, nullid, nullrev
16 from node import bin, hex, nullid, nullrev
17 from i18n import _
17 from i18n import _
18 import ancestor, mdiff, parsers, error, util, templatefilters
18 import ancestor, mdiff, parsers, error, util, templatefilters
19 import struct, zlib, errno
19 import struct, zlib, errno
20
20
21 _pack = struct.pack
21 _pack = struct.pack
22 _unpack = struct.unpack
22 _unpack = struct.unpack
23 _compress = zlib.compress
23 _compress = zlib.compress
24 _decompress = zlib.decompress
24 _decompress = zlib.decompress
25 _sha = util.sha1
25 _sha = util.sha1
26
26
27 # revlog header flags
27 # revlog header flags
28 REVLOGV0 = 0
28 REVLOGV0 = 0
29 REVLOGNG = 1
29 REVLOGNG = 1
30 REVLOGNGINLINEDATA = (1 << 16)
30 REVLOGNGINLINEDATA = (1 << 16)
31 REVLOGGENERALDELTA = (1 << 17)
31 REVLOGGENERALDELTA = (1 << 17)
32 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
32 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
33 REVLOG_DEFAULT_FORMAT = REVLOGNG
33 REVLOG_DEFAULT_FORMAT = REVLOGNG
34 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
34 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
35 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
35 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
36
36
37 # revlog index flags
37 # revlog index flags
38 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
38 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
39 REVIDX_DEFAULT_FLAGS = 0
39 REVIDX_DEFAULT_FLAGS = 0
40 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
40 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
41
41
42 # max size of revlog with inline data
42 # max size of revlog with inline data
43 _maxinline = 131072
43 _maxinline = 131072
44 _chunksize = 1048576
44 _chunksize = 1048576
45
45
46 RevlogError = error.RevlogError
46 RevlogError = error.RevlogError
47 LookupError = error.LookupError
47 LookupError = error.LookupError
48 CensoredNodeError = error.CensoredNodeError
48 CensoredNodeError = error.CensoredNodeError
49
49
50 def getoffset(q):
50 def getoffset(q):
51 return int(q >> 16)
51 return int(q >> 16)
52
52
53 def gettype(q):
53 def gettype(q):
54 return int(q & 0xFFFF)
54 return int(q & 0xFFFF)
55
55
56 def offset_type(offset, type):
56 def offset_type(offset, type):
57 return long(long(offset) << 16 | type)
57 return long(long(offset) << 16 | type)
58
58
59 _nullhash = _sha(nullid)
59 _nullhash = _sha(nullid)
60
60
61 def hash(text, p1, p2):
61 def hash(text, p1, p2):
62 """generate a hash from the given text and its parent hashes
62 """generate a hash from the given text and its parent hashes
63
63
64 This hash combines both the current file contents and its history
64 This hash combines both the current file contents and its history
65 in a manner that makes it easy to distinguish nodes with the same
65 in a manner that makes it easy to distinguish nodes with the same
66 content in the revision graph.
66 content in the revision graph.
67 """
67 """
68 # As of now, if one of the parent node is null, p2 is null
68 # As of now, if one of the parent node is null, p2 is null
69 if p2 == nullid:
69 if p2 == nullid:
70 # deep copy of a hash is faster than creating one
70 # deep copy of a hash is faster than creating one
71 s = _nullhash.copy()
71 s = _nullhash.copy()
72 s.update(p1)
72 s.update(p1)
73 else:
73 else:
74 # none of the parent nodes are nullid
74 # none of the parent nodes are nullid
75 l = [p1, p2]
75 l = [p1, p2]
76 l.sort()
76 l.sort()
77 s = _sha(l[0])
77 s = _sha(l[0])
78 s.update(l[1])
78 s.update(l[1])
79 s.update(text)
79 s.update(text)
80 return s.digest()
80 return s.digest()
81
81
82 def decompress(bin):
82 def decompress(bin):
83 """ decompress the given input """
83 """ decompress the given input """
84 if not bin:
84 if not bin:
85 return bin
85 return bin
86 t = bin[0]
86 t = bin[0]
87 if t == '\0':
87 if t == '\0':
88 return bin
88 return bin
89 if t == 'x':
89 if t == 'x':
90 try:
90 try:
91 return _decompress(bin)
91 return _decompress(bin)
92 except zlib.error, e:
92 except zlib.error, e:
93 raise RevlogError(_("revlog decompress error: %s") % str(e))
93 raise RevlogError(_("revlog decompress error: %s") % str(e))
94 if t == 'u':
94 if t == 'u':
95 return bin[1:]
95 return bin[1:]
96 raise RevlogError(_("unknown compression type %r") % t)
96 raise RevlogError(_("unknown compression type %r") % t)
97
97
98 # index v0:
98 # index v0:
99 # 4 bytes: offset
99 # 4 bytes: offset
100 # 4 bytes: compressed length
100 # 4 bytes: compressed length
101 # 4 bytes: base rev
101 # 4 bytes: base rev
102 # 4 bytes: link rev
102 # 4 bytes: link rev
103 # 32 bytes: parent 1 nodeid
103 # 32 bytes: parent 1 nodeid
104 # 32 bytes: parent 2 nodeid
104 # 32 bytes: parent 2 nodeid
105 # 32 bytes: nodeid
105 # 32 bytes: nodeid
106 indexformatv0 = ">4l20s20s20s"
106 indexformatv0 = ">4l20s20s20s"
107 v0shaoffset = 56
107 v0shaoffset = 56
108
108
109 class revlogoldio(object):
109 class revlogoldio(object):
110 def __init__(self):
110 def __init__(self):
111 self.size = struct.calcsize(indexformatv0)
111 self.size = struct.calcsize(indexformatv0)
112
112
113 def parseindex(self, data, inline):
113 def parseindex(self, data, inline):
114 s = self.size
114 s = self.size
115 index = []
115 index = []
116 nodemap = {nullid: nullrev}
116 nodemap = {nullid: nullrev}
117 n = off = 0
117 n = off = 0
118 l = len(data)
118 l = len(data)
119 while off + s <= l:
119 while off + s <= l:
120 cur = data[off:off + s]
120 cur = data[off:off + s]
121 off += s
121 off += s
122 e = _unpack(indexformatv0, cur)
122 e = _unpack(indexformatv0, cur)
123 # transform to revlogv1 format
123 # transform to revlogv1 format
124 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
124 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
125 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
125 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
126 index.append(e2)
126 index.append(e2)
127 nodemap[e[6]] = n
127 nodemap[e[6]] = n
128 n += 1
128 n += 1
129
129
130 # add the magic null revision at -1
130 # add the magic null revision at -1
131 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
131 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
132
132
133 return index, nodemap, None
133 return index, nodemap, None
134
134
135 def packentry(self, entry, node, version, rev):
135 def packentry(self, entry, node, version, rev):
136 if gettype(entry[0]):
136 if gettype(entry[0]):
137 raise RevlogError(_("index entry flags need RevlogNG"))
137 raise RevlogError(_("index entry flags need RevlogNG"))
138 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
138 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
139 node(entry[5]), node(entry[6]), entry[7])
139 node(entry[5]), node(entry[6]), entry[7])
140 return _pack(indexformatv0, *e2)
140 return _pack(indexformatv0, *e2)
141
141
142 # index ng:
142 # index ng:
143 # 6 bytes: offset
143 # 6 bytes: offset
144 # 2 bytes: flags
144 # 2 bytes: flags
145 # 4 bytes: compressed length
145 # 4 bytes: compressed length
146 # 4 bytes: uncompressed length
146 # 4 bytes: uncompressed length
147 # 4 bytes: base rev
147 # 4 bytes: base rev
148 # 4 bytes: link rev
148 # 4 bytes: link rev
149 # 4 bytes: parent 1 rev
149 # 4 bytes: parent 1 rev
150 # 4 bytes: parent 2 rev
150 # 4 bytes: parent 2 rev
151 # 32 bytes: nodeid
151 # 32 bytes: nodeid
152 indexformatng = ">Qiiiiii20s12x"
152 indexformatng = ">Qiiiiii20s12x"
153 ngshaoffset = 32
153 ngshaoffset = 32
154 versionformat = ">I"
154 versionformat = ">I"
155
155
156 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
157 # signed integer)
158 _maxentrysize = 0x7fffffff
159
156 class revlogio(object):
160 class revlogio(object):
157 def __init__(self):
161 def __init__(self):
158 self.size = struct.calcsize(indexformatng)
162 self.size = struct.calcsize(indexformatng)
159
163
160 def parseindex(self, data, inline):
164 def parseindex(self, data, inline):
161 # call the C implementation to parse the index data
165 # call the C implementation to parse the index data
162 index, cache = parsers.parse_index2(data, inline)
166 index, cache = parsers.parse_index2(data, inline)
163 return index, getattr(index, 'nodemap', None), cache
167 return index, getattr(index, 'nodemap', None), cache
164
168
165 def packentry(self, entry, node, version, rev):
169 def packentry(self, entry, node, version, rev):
170 uncompressedlength = entry[2]
171 if uncompressedlength > _maxentrysize:
172 raise RevlogError(
173 _("size of %d bytes exceeds maximum revlog storage of 2GiB")
174 % uncompressedlength)
175
166 p = _pack(indexformatng, *entry)
176 p = _pack(indexformatng, *entry)
167 if rev == 0:
177 if rev == 0:
168 p = _pack(versionformat, version) + p[4:]
178 p = _pack(versionformat, version) + p[4:]
169 return p
179 return p
170
180
171 class revlog(object):
181 class revlog(object):
172 """
182 """
173 the underlying revision storage object
183 the underlying revision storage object
174
184
175 A revlog consists of two parts, an index and the revision data.
185 A revlog consists of two parts, an index and the revision data.
176
186
177 The index is a file with a fixed record size containing
187 The index is a file with a fixed record size containing
178 information on each revision, including its nodeid (hash), the
188 information on each revision, including its nodeid (hash), the
179 nodeids of its parents, the position and offset of its data within
189 nodeids of its parents, the position and offset of its data within
180 the data file, and the revision it's based on. Finally, each entry
190 the data file, and the revision it's based on. Finally, each entry
181 contains a linkrev entry that can serve as a pointer to external
191 contains a linkrev entry that can serve as a pointer to external
182 data.
192 data.
183
193
184 The revision data itself is a linear collection of data chunks.
194 The revision data itself is a linear collection of data chunks.
185 Each chunk represents a revision and is usually represented as a
195 Each chunk represents a revision and is usually represented as a
186 delta against the previous chunk. To bound lookup time, runs of
196 delta against the previous chunk. To bound lookup time, runs of
187 deltas are limited to about 2 times the length of the original
197 deltas are limited to about 2 times the length of the original
188 version data. This makes retrieval of a version proportional to
198 version data. This makes retrieval of a version proportional to
189 its size, or O(1) relative to the number of revisions.
199 its size, or O(1) relative to the number of revisions.
190
200
191 Both pieces of the revlog are written to in an append-only
201 Both pieces of the revlog are written to in an append-only
192 fashion, which means we never need to rewrite a file to insert or
202 fashion, which means we never need to rewrite a file to insert or
193 remove data, and can use some simple techniques to avoid the need
203 remove data, and can use some simple techniques to avoid the need
194 for locking while reading.
204 for locking while reading.
195 """
205 """
196 def __init__(self, opener, indexfile):
206 def __init__(self, opener, indexfile):
197 """
207 """
198 create a revlog object
208 create a revlog object
199
209
200 opener is a function that abstracts the file opening operation
210 opener is a function that abstracts the file opening operation
201 and can be used to implement COW semantics or the like.
211 and can be used to implement COW semantics or the like.
202 """
212 """
203 self.indexfile = indexfile
213 self.indexfile = indexfile
204 self.datafile = indexfile[:-2] + ".d"
214 self.datafile = indexfile[:-2] + ".d"
205 self.opener = opener
215 self.opener = opener
206 self._cache = None
216 self._cache = None
207 self._basecache = None
217 self._basecache = None
208 self._chunkcache = (0, '')
218 self._chunkcache = (0, '')
209 self._chunkcachesize = 65536
219 self._chunkcachesize = 65536
210 self._maxchainlen = None
220 self._maxchainlen = None
211 self.index = []
221 self.index = []
212 self._pcache = {}
222 self._pcache = {}
213 self._nodecache = {nullid: nullrev}
223 self._nodecache = {nullid: nullrev}
214 self._nodepos = None
224 self._nodepos = None
215
225
216 v = REVLOG_DEFAULT_VERSION
226 v = REVLOG_DEFAULT_VERSION
217 opts = getattr(opener, 'options', None)
227 opts = getattr(opener, 'options', None)
218 if opts is not None:
228 if opts is not None:
219 if 'revlogv1' in opts:
229 if 'revlogv1' in opts:
220 if 'generaldelta' in opts:
230 if 'generaldelta' in opts:
221 v |= REVLOGGENERALDELTA
231 v |= REVLOGGENERALDELTA
222 else:
232 else:
223 v = 0
233 v = 0
224 if 'chunkcachesize' in opts:
234 if 'chunkcachesize' in opts:
225 self._chunkcachesize = opts['chunkcachesize']
235 self._chunkcachesize = opts['chunkcachesize']
226 if 'maxchainlen' in opts:
236 if 'maxchainlen' in opts:
227 self._maxchainlen = opts['maxchainlen']
237 self._maxchainlen = opts['maxchainlen']
228
238
229 if self._chunkcachesize <= 0:
239 if self._chunkcachesize <= 0:
230 raise RevlogError(_('revlog chunk cache size %r is not greater '
240 raise RevlogError(_('revlog chunk cache size %r is not greater '
231 'than 0') % self._chunkcachesize)
241 'than 0') % self._chunkcachesize)
232 elif self._chunkcachesize & (self._chunkcachesize - 1):
242 elif self._chunkcachesize & (self._chunkcachesize - 1):
233 raise RevlogError(_('revlog chunk cache size %r is not a power '
243 raise RevlogError(_('revlog chunk cache size %r is not a power '
234 'of 2') % self._chunkcachesize)
244 'of 2') % self._chunkcachesize)
235
245
236 i = ''
246 i = ''
237 self._initempty = True
247 self._initempty = True
238 try:
248 try:
239 f = self.opener(self.indexfile)
249 f = self.opener(self.indexfile)
240 i = f.read()
250 i = f.read()
241 f.close()
251 f.close()
242 if len(i) > 0:
252 if len(i) > 0:
243 v = struct.unpack(versionformat, i[:4])[0]
253 v = struct.unpack(versionformat, i[:4])[0]
244 self._initempty = False
254 self._initempty = False
245 except IOError, inst:
255 except IOError, inst:
246 if inst.errno != errno.ENOENT:
256 if inst.errno != errno.ENOENT:
247 raise
257 raise
248
258
249 self.version = v
259 self.version = v
250 self._inline = v & REVLOGNGINLINEDATA
260 self._inline = v & REVLOGNGINLINEDATA
251 self._generaldelta = v & REVLOGGENERALDELTA
261 self._generaldelta = v & REVLOGGENERALDELTA
252 flags = v & ~0xFFFF
262 flags = v & ~0xFFFF
253 fmt = v & 0xFFFF
263 fmt = v & 0xFFFF
254 if fmt == REVLOGV0 and flags:
264 if fmt == REVLOGV0 and flags:
255 raise RevlogError(_("index %s unknown flags %#04x for format v0")
265 raise RevlogError(_("index %s unknown flags %#04x for format v0")
256 % (self.indexfile, flags >> 16))
266 % (self.indexfile, flags >> 16))
257 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
267 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
258 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
268 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
259 % (self.indexfile, flags >> 16))
269 % (self.indexfile, flags >> 16))
260 elif fmt > REVLOGNG:
270 elif fmt > REVLOGNG:
261 raise RevlogError(_("index %s unknown format %d")
271 raise RevlogError(_("index %s unknown format %d")
262 % (self.indexfile, fmt))
272 % (self.indexfile, fmt))
263
273
264 self._io = revlogio()
274 self._io = revlogio()
265 if self.version == REVLOGV0:
275 if self.version == REVLOGV0:
266 self._io = revlogoldio()
276 self._io = revlogoldio()
267 try:
277 try:
268 d = self._io.parseindex(i, self._inline)
278 d = self._io.parseindex(i, self._inline)
269 except (ValueError, IndexError):
279 except (ValueError, IndexError):
270 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
280 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
271 self.index, nodemap, self._chunkcache = d
281 self.index, nodemap, self._chunkcache = d
272 if nodemap is not None:
282 if nodemap is not None:
273 self.nodemap = self._nodecache = nodemap
283 self.nodemap = self._nodecache = nodemap
274 if not self._chunkcache:
284 if not self._chunkcache:
275 self._chunkclear()
285 self._chunkclear()
276 # revnum -> (chain-length, sum-delta-length)
286 # revnum -> (chain-length, sum-delta-length)
277 self._chaininfocache = {}
287 self._chaininfocache = {}
278
288
279 def tip(self):
289 def tip(self):
280 return self.node(len(self.index) - 2)
290 return self.node(len(self.index) - 2)
281 def __contains__(self, rev):
291 def __contains__(self, rev):
282 return 0 <= rev < len(self)
292 return 0 <= rev < len(self)
283 def __len__(self):
293 def __len__(self):
284 return len(self.index) - 1
294 return len(self.index) - 1
285 def __iter__(self):
295 def __iter__(self):
286 return iter(xrange(len(self)))
296 return iter(xrange(len(self)))
287 def revs(self, start=0, stop=None):
297 def revs(self, start=0, stop=None):
288 """iterate over all rev in this revlog (from start to stop)"""
298 """iterate over all rev in this revlog (from start to stop)"""
289 step = 1
299 step = 1
290 if stop is not None:
300 if stop is not None:
291 if start > stop:
301 if start > stop:
292 step = -1
302 step = -1
293 stop += step
303 stop += step
294 else:
304 else:
295 stop = len(self)
305 stop = len(self)
296 return xrange(start, stop, step)
306 return xrange(start, stop, step)
297
307
298 @util.propertycache
308 @util.propertycache
299 def nodemap(self):
309 def nodemap(self):
300 self.rev(self.node(0))
310 self.rev(self.node(0))
301 return self._nodecache
311 return self._nodecache
302
312
303 def hasnode(self, node):
313 def hasnode(self, node):
304 try:
314 try:
305 self.rev(node)
315 self.rev(node)
306 return True
316 return True
307 except KeyError:
317 except KeyError:
308 return False
318 return False
309
319
310 def clearcaches(self):
320 def clearcaches(self):
311 try:
321 try:
312 self._nodecache.clearcaches()
322 self._nodecache.clearcaches()
313 except AttributeError:
323 except AttributeError:
314 self._nodecache = {nullid: nullrev}
324 self._nodecache = {nullid: nullrev}
315 self._nodepos = None
325 self._nodepos = None
316
326
317 def rev(self, node):
327 def rev(self, node):
318 try:
328 try:
319 return self._nodecache[node]
329 return self._nodecache[node]
320 except TypeError:
330 except TypeError:
321 raise
331 raise
322 except RevlogError:
332 except RevlogError:
323 # parsers.c radix tree lookup failed
333 # parsers.c radix tree lookup failed
324 raise LookupError(node, self.indexfile, _('no node'))
334 raise LookupError(node, self.indexfile, _('no node'))
325 except KeyError:
335 except KeyError:
326 # pure python cache lookup failed
336 # pure python cache lookup failed
327 n = self._nodecache
337 n = self._nodecache
328 i = self.index
338 i = self.index
329 p = self._nodepos
339 p = self._nodepos
330 if p is None:
340 if p is None:
331 p = len(i) - 2
341 p = len(i) - 2
332 for r in xrange(p, -1, -1):
342 for r in xrange(p, -1, -1):
333 v = i[r][7]
343 v = i[r][7]
334 n[v] = r
344 n[v] = r
335 if v == node:
345 if v == node:
336 self._nodepos = r - 1
346 self._nodepos = r - 1
337 return r
347 return r
338 raise LookupError(node, self.indexfile, _('no node'))
348 raise LookupError(node, self.indexfile, _('no node'))
339
349
340 def node(self, rev):
350 def node(self, rev):
341 return self.index[rev][7]
351 return self.index[rev][7]
342 def linkrev(self, rev):
352 def linkrev(self, rev):
343 return self.index[rev][4]
353 return self.index[rev][4]
344 def parents(self, node):
354 def parents(self, node):
345 i = self.index
355 i = self.index
346 d = i[self.rev(node)]
356 d = i[self.rev(node)]
347 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
357 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
348 def parentrevs(self, rev):
358 def parentrevs(self, rev):
349 return self.index[rev][5:7]
359 return self.index[rev][5:7]
350 def start(self, rev):
360 def start(self, rev):
351 return int(self.index[rev][0] >> 16)
361 return int(self.index[rev][0] >> 16)
352 def end(self, rev):
362 def end(self, rev):
353 return self.start(rev) + self.length(rev)
363 return self.start(rev) + self.length(rev)
354 def length(self, rev):
364 def length(self, rev):
355 return self.index[rev][1]
365 return self.index[rev][1]
356 def chainbase(self, rev):
366 def chainbase(self, rev):
357 index = self.index
367 index = self.index
358 base = index[rev][3]
368 base = index[rev][3]
359 while base != rev:
369 while base != rev:
360 rev = base
370 rev = base
361 base = index[rev][3]
371 base = index[rev][3]
362 return base
372 return base
363 def chainlen(self, rev):
373 def chainlen(self, rev):
364 return self._chaininfo(rev)[0]
374 return self._chaininfo(rev)[0]
365
375
366 def _chaininfo(self, rev):
376 def _chaininfo(self, rev):
367 chaininfocache = self._chaininfocache
377 chaininfocache = self._chaininfocache
368 if rev in chaininfocache:
378 if rev in chaininfocache:
369 return chaininfocache[rev]
379 return chaininfocache[rev]
370 index = self.index
380 index = self.index
371 generaldelta = self._generaldelta
381 generaldelta = self._generaldelta
372 iterrev = rev
382 iterrev = rev
373 e = index[iterrev]
383 e = index[iterrev]
374 clen = 0
384 clen = 0
375 compresseddeltalen = 0
385 compresseddeltalen = 0
376 while iterrev != e[3]:
386 while iterrev != e[3]:
377 clen += 1
387 clen += 1
378 compresseddeltalen += e[1]
388 compresseddeltalen += e[1]
379 if generaldelta:
389 if generaldelta:
380 iterrev = e[3]
390 iterrev = e[3]
381 else:
391 else:
382 iterrev -= 1
392 iterrev -= 1
383 if iterrev in chaininfocache:
393 if iterrev in chaininfocache:
384 t = chaininfocache[iterrev]
394 t = chaininfocache[iterrev]
385 clen += t[0]
395 clen += t[0]
386 compresseddeltalen += t[1]
396 compresseddeltalen += t[1]
387 break
397 break
388 e = index[iterrev]
398 e = index[iterrev]
389 else:
399 else:
390 # Add text length of base since decompressing that also takes
400 # Add text length of base since decompressing that also takes
391 # work. For cache hits the length is already included.
401 # work. For cache hits the length is already included.
392 compresseddeltalen += e[1]
402 compresseddeltalen += e[1]
393 r = (clen, compresseddeltalen)
403 r = (clen, compresseddeltalen)
394 chaininfocache[rev] = r
404 chaininfocache[rev] = r
395 return r
405 return r
396
406
397 def flags(self, rev):
407 def flags(self, rev):
398 return self.index[rev][0] & 0xFFFF
408 return self.index[rev][0] & 0xFFFF
399 def rawsize(self, rev):
409 def rawsize(self, rev):
400 """return the length of the uncompressed text for a given revision"""
410 """return the length of the uncompressed text for a given revision"""
401 l = self.index[rev][2]
411 l = self.index[rev][2]
402 if l >= 0:
412 if l >= 0:
403 return l
413 return l
404
414
405 t = self.revision(self.node(rev))
415 t = self.revision(self.node(rev))
406 return len(t)
416 return len(t)
407 size = rawsize
417 size = rawsize
408
418
409 def ancestors(self, revs, stoprev=0, inclusive=False):
419 def ancestors(self, revs, stoprev=0, inclusive=False):
410 """Generate the ancestors of 'revs' in reverse topological order.
420 """Generate the ancestors of 'revs' in reverse topological order.
411 Does not generate revs lower than stoprev.
421 Does not generate revs lower than stoprev.
412
422
413 See the documentation for ancestor.lazyancestors for more details."""
423 See the documentation for ancestor.lazyancestors for more details."""
414
424
415 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
425 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
416 inclusive=inclusive)
426 inclusive=inclusive)
417
427
418 def descendants(self, revs):
428 def descendants(self, revs):
419 """Generate the descendants of 'revs' in revision order.
429 """Generate the descendants of 'revs' in revision order.
420
430
421 Yield a sequence of revision numbers starting with a child of
431 Yield a sequence of revision numbers starting with a child of
422 some rev in revs, i.e., each revision is *not* considered a
432 some rev in revs, i.e., each revision is *not* considered a
423 descendant of itself. Results are ordered by revision number (a
433 descendant of itself. Results are ordered by revision number (a
424 topological sort)."""
434 topological sort)."""
425 first = min(revs)
435 first = min(revs)
426 if first == nullrev:
436 if first == nullrev:
427 for i in self:
437 for i in self:
428 yield i
438 yield i
429 return
439 return
430
440
431 seen = set(revs)
441 seen = set(revs)
432 for i in self.revs(start=first + 1):
442 for i in self.revs(start=first + 1):
433 for x in self.parentrevs(i):
443 for x in self.parentrevs(i):
434 if x != nullrev and x in seen:
444 if x != nullrev and x in seen:
435 seen.add(i)
445 seen.add(i)
436 yield i
446 yield i
437 break
447 break
438
448
439 def findcommonmissing(self, common=None, heads=None):
449 def findcommonmissing(self, common=None, heads=None):
440 """Return a tuple of the ancestors of common and the ancestors of heads
450 """Return a tuple of the ancestors of common and the ancestors of heads
441 that are not ancestors of common. In revset terminology, we return the
451 that are not ancestors of common. In revset terminology, we return the
442 tuple:
452 tuple:
443
453
444 ::common, (::heads) - (::common)
454 ::common, (::heads) - (::common)
445
455
446 The list is sorted by revision number, meaning it is
456 The list is sorted by revision number, meaning it is
447 topologically sorted.
457 topologically sorted.
448
458
449 'heads' and 'common' are both lists of node IDs. If heads is
459 'heads' and 'common' are both lists of node IDs. If heads is
450 not supplied, uses all of the revlog's heads. If common is not
460 not supplied, uses all of the revlog's heads. If common is not
451 supplied, uses nullid."""
461 supplied, uses nullid."""
452 if common is None:
462 if common is None:
453 common = [nullid]
463 common = [nullid]
454 if heads is None:
464 if heads is None:
455 heads = self.heads()
465 heads = self.heads()
456
466
457 common = [self.rev(n) for n in common]
467 common = [self.rev(n) for n in common]
458 heads = [self.rev(n) for n in heads]
468 heads = [self.rev(n) for n in heads]
459
469
460 # we want the ancestors, but inclusive
470 # we want the ancestors, but inclusive
461 class lazyset(object):
471 class lazyset(object):
462 def __init__(self, lazyvalues):
472 def __init__(self, lazyvalues):
463 self.addedvalues = set()
473 self.addedvalues = set()
464 self.lazyvalues = lazyvalues
474 self.lazyvalues = lazyvalues
465
475
466 def __contains__(self, value):
476 def __contains__(self, value):
467 return value in self.addedvalues or value in self.lazyvalues
477 return value in self.addedvalues or value in self.lazyvalues
468
478
469 def __iter__(self):
479 def __iter__(self):
470 added = self.addedvalues
480 added = self.addedvalues
471 for r in added:
481 for r in added:
472 yield r
482 yield r
473 for r in self.lazyvalues:
483 for r in self.lazyvalues:
474 if not r in added:
484 if not r in added:
475 yield r
485 yield r
476
486
477 def add(self, value):
487 def add(self, value):
478 self.addedvalues.add(value)
488 self.addedvalues.add(value)
479
489
480 def update(self, values):
490 def update(self, values):
481 self.addedvalues.update(values)
491 self.addedvalues.update(values)
482
492
483 has = lazyset(self.ancestors(common))
493 has = lazyset(self.ancestors(common))
484 has.add(nullrev)
494 has.add(nullrev)
485 has.update(common)
495 has.update(common)
486
496
487 # take all ancestors from heads that aren't in has
497 # take all ancestors from heads that aren't in has
488 missing = set()
498 missing = set()
489 visit = collections.deque(r for r in heads if r not in has)
499 visit = collections.deque(r for r in heads if r not in has)
490 while visit:
500 while visit:
491 r = visit.popleft()
501 r = visit.popleft()
492 if r in missing:
502 if r in missing:
493 continue
503 continue
494 else:
504 else:
495 missing.add(r)
505 missing.add(r)
496 for p in self.parentrevs(r):
506 for p in self.parentrevs(r):
497 if p not in has:
507 if p not in has:
498 visit.append(p)
508 visit.append(p)
499 missing = list(missing)
509 missing = list(missing)
500 missing.sort()
510 missing.sort()
501 return has, [self.node(r) for r in missing]
511 return has, [self.node(r) for r in missing]
502
512
503 def incrementalmissingrevs(self, common=None):
513 def incrementalmissingrevs(self, common=None):
504 """Return an object that can be used to incrementally compute the
514 """Return an object that can be used to incrementally compute the
505 revision numbers of the ancestors of arbitrary sets that are not
515 revision numbers of the ancestors of arbitrary sets that are not
506 ancestors of common. This is an ancestor.incrementalmissingancestors
516 ancestors of common. This is an ancestor.incrementalmissingancestors
507 object.
517 object.
508
518
509 'common' is a list of revision numbers. If common is not supplied, uses
519 'common' is a list of revision numbers. If common is not supplied, uses
510 nullrev.
520 nullrev.
511 """
521 """
512 if common is None:
522 if common is None:
513 common = [nullrev]
523 common = [nullrev]
514
524
515 return ancestor.incrementalmissingancestors(self.parentrevs, common)
525 return ancestor.incrementalmissingancestors(self.parentrevs, common)
516
526
517 def findmissingrevs(self, common=None, heads=None):
527 def findmissingrevs(self, common=None, heads=None):
518 """Return the revision numbers of the ancestors of heads that
528 """Return the revision numbers of the ancestors of heads that
519 are not ancestors of common.
529 are not ancestors of common.
520
530
521 More specifically, return a list of revision numbers corresponding to
531 More specifically, return a list of revision numbers corresponding to
522 nodes N such that every N satisfies the following constraints:
532 nodes N such that every N satisfies the following constraints:
523
533
524 1. N is an ancestor of some node in 'heads'
534 1. N is an ancestor of some node in 'heads'
525 2. N is not an ancestor of any node in 'common'
535 2. N is not an ancestor of any node in 'common'
526
536
527 The list is sorted by revision number, meaning it is
537 The list is sorted by revision number, meaning it is
528 topologically sorted.
538 topologically sorted.
529
539
530 'heads' and 'common' are both lists of revision numbers. If heads is
540 'heads' and 'common' are both lists of revision numbers. If heads is
531 not supplied, uses all of the revlog's heads. If common is not
541 not supplied, uses all of the revlog's heads. If common is not
532 supplied, uses nullid."""
542 supplied, uses nullid."""
533 if common is None:
543 if common is None:
534 common = [nullrev]
544 common = [nullrev]
535 if heads is None:
545 if heads is None:
536 heads = self.headrevs()
546 heads = self.headrevs()
537
547
538 inc = self.incrementalmissingrevs(common=common)
548 inc = self.incrementalmissingrevs(common=common)
539 return inc.missingancestors(heads)
549 return inc.missingancestors(heads)
540
550
541 def findmissing(self, common=None, heads=None):
551 def findmissing(self, common=None, heads=None):
542 """Return the ancestors of heads that are not ancestors of common.
552 """Return the ancestors of heads that are not ancestors of common.
543
553
544 More specifically, return a list of nodes N such that every N
554 More specifically, return a list of nodes N such that every N
545 satisfies the following constraints:
555 satisfies the following constraints:
546
556
547 1. N is an ancestor of some node in 'heads'
557 1. N is an ancestor of some node in 'heads'
548 2. N is not an ancestor of any node in 'common'
558 2. N is not an ancestor of any node in 'common'
549
559
550 The list is sorted by revision number, meaning it is
560 The list is sorted by revision number, meaning it is
551 topologically sorted.
561 topologically sorted.
552
562
553 'heads' and 'common' are both lists of node IDs. If heads is
563 'heads' and 'common' are both lists of node IDs. If heads is
554 not supplied, uses all of the revlog's heads. If common is not
564 not supplied, uses all of the revlog's heads. If common is not
555 supplied, uses nullid."""
565 supplied, uses nullid."""
556 if common is None:
566 if common is None:
557 common = [nullid]
567 common = [nullid]
558 if heads is None:
568 if heads is None:
559 heads = self.heads()
569 heads = self.heads()
560
570
561 common = [self.rev(n) for n in common]
571 common = [self.rev(n) for n in common]
562 heads = [self.rev(n) for n in heads]
572 heads = [self.rev(n) for n in heads]
563
573
564 inc = self.incrementalmissingrevs(common=common)
574 inc = self.incrementalmissingrevs(common=common)
565 return [self.node(r) for r in inc.missingancestors(heads)]
575 return [self.node(r) for r in inc.missingancestors(heads)]
566
576
567 def nodesbetween(self, roots=None, heads=None):
577 def nodesbetween(self, roots=None, heads=None):
568 """Return a topological path from 'roots' to 'heads'.
578 """Return a topological path from 'roots' to 'heads'.
569
579
570 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
580 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
571 topologically sorted list of all nodes N that satisfy both of
581 topologically sorted list of all nodes N that satisfy both of
572 these constraints:
582 these constraints:
573
583
574 1. N is a descendant of some node in 'roots'
584 1. N is a descendant of some node in 'roots'
575 2. N is an ancestor of some node in 'heads'
585 2. N is an ancestor of some node in 'heads'
576
586
577 Every node is considered to be both a descendant and an ancestor
587 Every node is considered to be both a descendant and an ancestor
578 of itself, so every reachable node in 'roots' and 'heads' will be
588 of itself, so every reachable node in 'roots' and 'heads' will be
579 included in 'nodes'.
589 included in 'nodes'.
580
590
581 'outroots' is the list of reachable nodes in 'roots', i.e., the
591 'outroots' is the list of reachable nodes in 'roots', i.e., the
582 subset of 'roots' that is returned in 'nodes'. Likewise,
592 subset of 'roots' that is returned in 'nodes'. Likewise,
583 'outheads' is the subset of 'heads' that is also in 'nodes'.
593 'outheads' is the subset of 'heads' that is also in 'nodes'.
584
594
585 'roots' and 'heads' are both lists of node IDs. If 'roots' is
595 'roots' and 'heads' are both lists of node IDs. If 'roots' is
586 unspecified, uses nullid as the only root. If 'heads' is
596 unspecified, uses nullid as the only root. If 'heads' is
587 unspecified, uses list of all of the revlog's heads."""
597 unspecified, uses list of all of the revlog's heads."""
588 nonodes = ([], [], [])
598 nonodes = ([], [], [])
589 if roots is not None:
599 if roots is not None:
590 roots = list(roots)
600 roots = list(roots)
591 if not roots:
601 if not roots:
592 return nonodes
602 return nonodes
593 lowestrev = min([self.rev(n) for n in roots])
603 lowestrev = min([self.rev(n) for n in roots])
594 else:
604 else:
595 roots = [nullid] # Everybody's a descendant of nullid
605 roots = [nullid] # Everybody's a descendant of nullid
596 lowestrev = nullrev
606 lowestrev = nullrev
597 if (lowestrev == nullrev) and (heads is None):
607 if (lowestrev == nullrev) and (heads is None):
598 # We want _all_ the nodes!
608 # We want _all_ the nodes!
599 return ([self.node(r) for r in self], [nullid], list(self.heads()))
609 return ([self.node(r) for r in self], [nullid], list(self.heads()))
600 if heads is None:
610 if heads is None:
601 # All nodes are ancestors, so the latest ancestor is the last
611 # All nodes are ancestors, so the latest ancestor is the last
602 # node.
612 # node.
603 highestrev = len(self) - 1
613 highestrev = len(self) - 1
604 # Set ancestors to None to signal that every node is an ancestor.
614 # Set ancestors to None to signal that every node is an ancestor.
605 ancestors = None
615 ancestors = None
606 # Set heads to an empty dictionary for later discovery of heads
616 # Set heads to an empty dictionary for later discovery of heads
607 heads = {}
617 heads = {}
608 else:
618 else:
609 heads = list(heads)
619 heads = list(heads)
610 if not heads:
620 if not heads:
611 return nonodes
621 return nonodes
612 ancestors = set()
622 ancestors = set()
613 # Turn heads into a dictionary so we can remove 'fake' heads.
623 # Turn heads into a dictionary so we can remove 'fake' heads.
614 # Also, later we will be using it to filter out the heads we can't
624 # Also, later we will be using it to filter out the heads we can't
615 # find from roots.
625 # find from roots.
616 heads = dict.fromkeys(heads, False)
626 heads = dict.fromkeys(heads, False)
617 # Start at the top and keep marking parents until we're done.
627 # Start at the top and keep marking parents until we're done.
618 nodestotag = set(heads)
628 nodestotag = set(heads)
619 # Remember where the top was so we can use it as a limit later.
629 # Remember where the top was so we can use it as a limit later.
620 highestrev = max([self.rev(n) for n in nodestotag])
630 highestrev = max([self.rev(n) for n in nodestotag])
621 while nodestotag:
631 while nodestotag:
622 # grab a node to tag
632 # grab a node to tag
623 n = nodestotag.pop()
633 n = nodestotag.pop()
624 # Never tag nullid
634 # Never tag nullid
625 if n == nullid:
635 if n == nullid:
626 continue
636 continue
627 # A node's revision number represents its place in a
637 # A node's revision number represents its place in a
628 # topologically sorted list of nodes.
638 # topologically sorted list of nodes.
629 r = self.rev(n)
639 r = self.rev(n)
630 if r >= lowestrev:
640 if r >= lowestrev:
631 if n not in ancestors:
641 if n not in ancestors:
632 # If we are possibly a descendant of one of the roots
642 # If we are possibly a descendant of one of the roots
633 # and we haven't already been marked as an ancestor
643 # and we haven't already been marked as an ancestor
634 ancestors.add(n) # Mark as ancestor
644 ancestors.add(n) # Mark as ancestor
635 # Add non-nullid parents to list of nodes to tag.
645 # Add non-nullid parents to list of nodes to tag.
636 nodestotag.update([p for p in self.parents(n) if
646 nodestotag.update([p for p in self.parents(n) if
637 p != nullid])
647 p != nullid])
638 elif n in heads: # We've seen it before, is it a fake head?
648 elif n in heads: # We've seen it before, is it a fake head?
639 # So it is, real heads should not be the ancestors of
649 # So it is, real heads should not be the ancestors of
640 # any other heads.
650 # any other heads.
641 heads.pop(n)
651 heads.pop(n)
642 if not ancestors:
652 if not ancestors:
643 return nonodes
653 return nonodes
644 # Now that we have our set of ancestors, we want to remove any
654 # Now that we have our set of ancestors, we want to remove any
645 # roots that are not ancestors.
655 # roots that are not ancestors.
646
656
647 # If one of the roots was nullid, everything is included anyway.
657 # If one of the roots was nullid, everything is included anyway.
648 if lowestrev > nullrev:
658 if lowestrev > nullrev:
649 # But, since we weren't, let's recompute the lowest rev to not
659 # But, since we weren't, let's recompute the lowest rev to not
650 # include roots that aren't ancestors.
660 # include roots that aren't ancestors.
651
661
652 # Filter out roots that aren't ancestors of heads
662 # Filter out roots that aren't ancestors of heads
653 roots = [n for n in roots if n in ancestors]
663 roots = [n for n in roots if n in ancestors]
654 # Recompute the lowest revision
664 # Recompute the lowest revision
655 if roots:
665 if roots:
656 lowestrev = min([self.rev(n) for n in roots])
666 lowestrev = min([self.rev(n) for n in roots])
657 else:
667 else:
658 # No more roots? Return empty list
668 # No more roots? Return empty list
659 return nonodes
669 return nonodes
660 else:
670 else:
661 # We are descending from nullid, and don't need to care about
671 # We are descending from nullid, and don't need to care about
662 # any other roots.
672 # any other roots.
663 lowestrev = nullrev
673 lowestrev = nullrev
664 roots = [nullid]
674 roots = [nullid]
665 # Transform our roots list into a set.
675 # Transform our roots list into a set.
666 descendants = set(roots)
676 descendants = set(roots)
667 # Also, keep the original roots so we can filter out roots that aren't
677 # Also, keep the original roots so we can filter out roots that aren't
668 # 'real' roots (i.e. are descended from other roots).
678 # 'real' roots (i.e. are descended from other roots).
669 roots = descendants.copy()
679 roots = descendants.copy()
670 # Our topologically sorted list of output nodes.
680 # Our topologically sorted list of output nodes.
671 orderedout = []
681 orderedout = []
672 # Don't start at nullid since we don't want nullid in our output list,
682 # Don't start at nullid since we don't want nullid in our output list,
673 # and if nullid shows up in descendants, empty parents will look like
683 # and if nullid shows up in descendants, empty parents will look like
674 # they're descendants.
684 # they're descendants.
675 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
685 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
676 n = self.node(r)
686 n = self.node(r)
677 isdescendant = False
687 isdescendant = False
678 if lowestrev == nullrev: # Everybody is a descendant of nullid
688 if lowestrev == nullrev: # Everybody is a descendant of nullid
679 isdescendant = True
689 isdescendant = True
680 elif n in descendants:
690 elif n in descendants:
681 # n is already a descendant
691 # n is already a descendant
682 isdescendant = True
692 isdescendant = True
683 # This check only needs to be done here because all the roots
693 # This check only needs to be done here because all the roots
684 # will start being marked is descendants before the loop.
694 # will start being marked is descendants before the loop.
685 if n in roots:
695 if n in roots:
686 # If n was a root, check if it's a 'real' root.
696 # If n was a root, check if it's a 'real' root.
687 p = tuple(self.parents(n))
697 p = tuple(self.parents(n))
688 # If any of its parents are descendants, it's not a root.
698 # If any of its parents are descendants, it's not a root.
689 if (p[0] in descendants) or (p[1] in descendants):
699 if (p[0] in descendants) or (p[1] in descendants):
690 roots.remove(n)
700 roots.remove(n)
691 else:
701 else:
692 p = tuple(self.parents(n))
702 p = tuple(self.parents(n))
693 # A node is a descendant if either of its parents are
703 # A node is a descendant if either of its parents are
694 # descendants. (We seeded the dependents list with the roots
704 # descendants. (We seeded the dependents list with the roots
695 # up there, remember?)
705 # up there, remember?)
696 if (p[0] in descendants) or (p[1] in descendants):
706 if (p[0] in descendants) or (p[1] in descendants):
697 descendants.add(n)
707 descendants.add(n)
698 isdescendant = True
708 isdescendant = True
699 if isdescendant and ((ancestors is None) or (n in ancestors)):
709 if isdescendant and ((ancestors is None) or (n in ancestors)):
700 # Only include nodes that are both descendants and ancestors.
710 # Only include nodes that are both descendants and ancestors.
701 orderedout.append(n)
711 orderedout.append(n)
702 if (ancestors is not None) and (n in heads):
712 if (ancestors is not None) and (n in heads):
703 # We're trying to figure out which heads are reachable
713 # We're trying to figure out which heads are reachable
704 # from roots.
714 # from roots.
705 # Mark this head as having been reached
715 # Mark this head as having been reached
706 heads[n] = True
716 heads[n] = True
707 elif ancestors is None:
717 elif ancestors is None:
708 # Otherwise, we're trying to discover the heads.
718 # Otherwise, we're trying to discover the heads.
709 # Assume this is a head because if it isn't, the next step
719 # Assume this is a head because if it isn't, the next step
710 # will eventually remove it.
720 # will eventually remove it.
711 heads[n] = True
721 heads[n] = True
712 # But, obviously its parents aren't.
722 # But, obviously its parents aren't.
713 for p in self.parents(n):
723 for p in self.parents(n):
714 heads.pop(p, None)
724 heads.pop(p, None)
715 heads = [n for n, flag in heads.iteritems() if flag]
725 heads = [n for n, flag in heads.iteritems() if flag]
716 roots = list(roots)
726 roots = list(roots)
717 assert orderedout
727 assert orderedout
718 assert roots
728 assert roots
719 assert heads
729 assert heads
720 return (orderedout, roots, heads)
730 return (orderedout, roots, heads)
721
731
722 def headrevs(self):
732 def headrevs(self):
723 try:
733 try:
724 return self.index.headrevs()
734 return self.index.headrevs()
725 except AttributeError:
735 except AttributeError:
726 return self._headrevs()
736 return self._headrevs()
727
737
728 def computephases(self, roots):
738 def computephases(self, roots):
729 return self.index.computephasesmapsets(roots)
739 return self.index.computephasesmapsets(roots)
730
740
731 def _headrevs(self):
741 def _headrevs(self):
732 count = len(self)
742 count = len(self)
733 if not count:
743 if not count:
734 return [nullrev]
744 return [nullrev]
735 # we won't iter over filtered rev so nobody is a head at start
745 # we won't iter over filtered rev so nobody is a head at start
736 ishead = [0] * (count + 1)
746 ishead = [0] * (count + 1)
737 index = self.index
747 index = self.index
738 for r in self:
748 for r in self:
739 ishead[r] = 1 # I may be an head
749 ishead[r] = 1 # I may be an head
740 e = index[r]
750 e = index[r]
741 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
751 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
742 return [r for r, val in enumerate(ishead) if val]
752 return [r for r, val in enumerate(ishead) if val]
743
753
744 def heads(self, start=None, stop=None):
754 def heads(self, start=None, stop=None):
745 """return the list of all nodes that have no children
755 """return the list of all nodes that have no children
746
756
747 if start is specified, only heads that are descendants of
757 if start is specified, only heads that are descendants of
748 start will be returned
758 start will be returned
749 if stop is specified, it will consider all the revs from stop
759 if stop is specified, it will consider all the revs from stop
750 as if they had no children
760 as if they had no children
751 """
761 """
752 if start is None and stop is None:
762 if start is None and stop is None:
753 if not len(self):
763 if not len(self):
754 return [nullid]
764 return [nullid]
755 return [self.node(r) for r in self.headrevs()]
765 return [self.node(r) for r in self.headrevs()]
756
766
757 if start is None:
767 if start is None:
758 start = nullid
768 start = nullid
759 if stop is None:
769 if stop is None:
760 stop = []
770 stop = []
761 stoprevs = set([self.rev(n) for n in stop])
771 stoprevs = set([self.rev(n) for n in stop])
762 startrev = self.rev(start)
772 startrev = self.rev(start)
763 reachable = set((startrev,))
773 reachable = set((startrev,))
764 heads = set((startrev,))
774 heads = set((startrev,))
765
775
766 parentrevs = self.parentrevs
776 parentrevs = self.parentrevs
767 for r in self.revs(start=startrev + 1):
777 for r in self.revs(start=startrev + 1):
768 for p in parentrevs(r):
778 for p in parentrevs(r):
769 if p in reachable:
779 if p in reachable:
770 if r not in stoprevs:
780 if r not in stoprevs:
771 reachable.add(r)
781 reachable.add(r)
772 heads.add(r)
782 heads.add(r)
773 if p in heads and p not in stoprevs:
783 if p in heads and p not in stoprevs:
774 heads.remove(p)
784 heads.remove(p)
775
785
776 return [self.node(r) for r in heads]
786 return [self.node(r) for r in heads]
777
787
778 def children(self, node):
788 def children(self, node):
779 """find the children of a given node"""
789 """find the children of a given node"""
780 c = []
790 c = []
781 p = self.rev(node)
791 p = self.rev(node)
782 for r in self.revs(start=p + 1):
792 for r in self.revs(start=p + 1):
783 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
793 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
784 if prevs:
794 if prevs:
785 for pr in prevs:
795 for pr in prevs:
786 if pr == p:
796 if pr == p:
787 c.append(self.node(r))
797 c.append(self.node(r))
788 elif p == nullrev:
798 elif p == nullrev:
789 c.append(self.node(r))
799 c.append(self.node(r))
790 return c
800 return c
791
801
792 def descendant(self, start, end):
802 def descendant(self, start, end):
793 if start == nullrev:
803 if start == nullrev:
794 return True
804 return True
795 for i in self.descendants([start]):
805 for i in self.descendants([start]):
796 if i == end:
806 if i == end:
797 return True
807 return True
798 elif i > end:
808 elif i > end:
799 break
809 break
800 return False
810 return False
801
811
802 def commonancestorsheads(self, a, b):
812 def commonancestorsheads(self, a, b):
803 """calculate all the heads of the common ancestors of nodes a and b"""
813 """calculate all the heads of the common ancestors of nodes a and b"""
804 a, b = self.rev(a), self.rev(b)
814 a, b = self.rev(a), self.rev(b)
805 try:
815 try:
806 ancs = self.index.commonancestorsheads(a, b)
816 ancs = self.index.commonancestorsheads(a, b)
807 except (AttributeError, OverflowError): # C implementation failed
817 except (AttributeError, OverflowError): # C implementation failed
808 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
818 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
809 return map(self.node, ancs)
819 return map(self.node, ancs)
810
820
811 def isancestor(self, a, b):
821 def isancestor(self, a, b):
812 """return True if node a is an ancestor of node b
822 """return True if node a is an ancestor of node b
813
823
814 The implementation of this is trivial but the use of
824 The implementation of this is trivial but the use of
815 commonancestorsheads is not."""
825 commonancestorsheads is not."""
816 return a in self.commonancestorsheads(a, b)
826 return a in self.commonancestorsheads(a, b)
817
827
818 def ancestor(self, a, b):
828 def ancestor(self, a, b):
819 """calculate the "best" common ancestor of nodes a and b"""
829 """calculate the "best" common ancestor of nodes a and b"""
820
830
821 a, b = self.rev(a), self.rev(b)
831 a, b = self.rev(a), self.rev(b)
822 try:
832 try:
823 ancs = self.index.ancestors(a, b)
833 ancs = self.index.ancestors(a, b)
824 except (AttributeError, OverflowError):
834 except (AttributeError, OverflowError):
825 ancs = ancestor.ancestors(self.parentrevs, a, b)
835 ancs = ancestor.ancestors(self.parentrevs, a, b)
826 if ancs:
836 if ancs:
827 # choose a consistent winner when there's a tie
837 # choose a consistent winner when there's a tie
828 return min(map(self.node, ancs))
838 return min(map(self.node, ancs))
829 return nullid
839 return nullid
830
840
831 def _match(self, id):
841 def _match(self, id):
832 if isinstance(id, int):
842 if isinstance(id, int):
833 # rev
843 # rev
834 return self.node(id)
844 return self.node(id)
835 if len(id) == 20:
845 if len(id) == 20:
836 # possibly a binary node
846 # possibly a binary node
837 # odds of a binary node being all hex in ASCII are 1 in 10**25
847 # odds of a binary node being all hex in ASCII are 1 in 10**25
838 try:
848 try:
839 node = id
849 node = id
840 self.rev(node) # quick search the index
850 self.rev(node) # quick search the index
841 return node
851 return node
842 except LookupError:
852 except LookupError:
843 pass # may be partial hex id
853 pass # may be partial hex id
844 try:
854 try:
845 # str(rev)
855 # str(rev)
846 rev = int(id)
856 rev = int(id)
847 if str(rev) != id:
857 if str(rev) != id:
848 raise ValueError
858 raise ValueError
849 if rev < 0:
859 if rev < 0:
850 rev = len(self) + rev
860 rev = len(self) + rev
851 if rev < 0 or rev >= len(self):
861 if rev < 0 or rev >= len(self):
852 raise ValueError
862 raise ValueError
853 return self.node(rev)
863 return self.node(rev)
854 except (ValueError, OverflowError):
864 except (ValueError, OverflowError):
855 pass
865 pass
856 if len(id) == 40:
866 if len(id) == 40:
857 try:
867 try:
858 # a full hex nodeid?
868 # a full hex nodeid?
859 node = bin(id)
869 node = bin(id)
860 self.rev(node)
870 self.rev(node)
861 return node
871 return node
862 except (TypeError, LookupError):
872 except (TypeError, LookupError):
863 pass
873 pass
864
874
865 def _partialmatch(self, id):
875 def _partialmatch(self, id):
866 try:
876 try:
867 n = self.index.partialmatch(id)
877 n = self.index.partialmatch(id)
868 if n and self.hasnode(n):
878 if n and self.hasnode(n):
869 return n
879 return n
870 return None
880 return None
871 except RevlogError:
881 except RevlogError:
872 # parsers.c radix tree lookup gave multiple matches
882 # parsers.c radix tree lookup gave multiple matches
873 # fall through to slow path that filters hidden revisions
883 # fall through to slow path that filters hidden revisions
874 pass
884 pass
875 except (AttributeError, ValueError):
885 except (AttributeError, ValueError):
876 # we are pure python, or key was too short to search radix tree
886 # we are pure python, or key was too short to search radix tree
877 pass
887 pass
878
888
879 if id in self._pcache:
889 if id in self._pcache:
880 return self._pcache[id]
890 return self._pcache[id]
881
891
882 if len(id) < 40:
892 if len(id) < 40:
883 try:
893 try:
884 # hex(node)[:...]
894 # hex(node)[:...]
885 l = len(id) // 2 # grab an even number of digits
895 l = len(id) // 2 # grab an even number of digits
886 prefix = bin(id[:l * 2])
896 prefix = bin(id[:l * 2])
887 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
897 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
888 nl = [n for n in nl if hex(n).startswith(id) and
898 nl = [n for n in nl if hex(n).startswith(id) and
889 self.hasnode(n)]
899 self.hasnode(n)]
890 if len(nl) > 0:
900 if len(nl) > 0:
891 if len(nl) == 1:
901 if len(nl) == 1:
892 self._pcache[id] = nl[0]
902 self._pcache[id] = nl[0]
893 return nl[0]
903 return nl[0]
894 raise LookupError(id, self.indexfile,
904 raise LookupError(id, self.indexfile,
895 _('ambiguous identifier'))
905 _('ambiguous identifier'))
896 return None
906 return None
897 except TypeError:
907 except TypeError:
898 pass
908 pass
899
909
900 def lookup(self, id):
910 def lookup(self, id):
901 """locate a node based on:
911 """locate a node based on:
902 - revision number or str(revision number)
912 - revision number or str(revision number)
903 - nodeid or subset of hex nodeid
913 - nodeid or subset of hex nodeid
904 """
914 """
905 n = self._match(id)
915 n = self._match(id)
906 if n is not None:
916 if n is not None:
907 return n
917 return n
908 n = self._partialmatch(id)
918 n = self._partialmatch(id)
909 if n:
919 if n:
910 return n
920 return n
911
921
912 raise LookupError(id, self.indexfile, _('no match found'))
922 raise LookupError(id, self.indexfile, _('no match found'))
913
923
914 def cmp(self, node, text):
924 def cmp(self, node, text):
915 """compare text with a given file revision
925 """compare text with a given file revision
916
926
917 returns True if text is different than what is stored.
927 returns True if text is different than what is stored.
918 """
928 """
919 p1, p2 = self.parents(node)
929 p1, p2 = self.parents(node)
920 return hash(text, p1, p2) != node
930 return hash(text, p1, p2) != node
921
931
922 def _addchunk(self, offset, data):
932 def _addchunk(self, offset, data):
923 o, d = self._chunkcache
933 o, d = self._chunkcache
924 # try to add to existing cache
934 # try to add to existing cache
925 if o + len(d) == offset and len(d) + len(data) < _chunksize:
935 if o + len(d) == offset and len(d) + len(data) < _chunksize:
926 self._chunkcache = o, d + data
936 self._chunkcache = o, d + data
927 else:
937 else:
928 self._chunkcache = offset, data
938 self._chunkcache = offset, data
929
939
930 def _loadchunk(self, offset, length):
940 def _loadchunk(self, offset, length):
931 if self._inline:
941 if self._inline:
932 df = self.opener(self.indexfile)
942 df = self.opener(self.indexfile)
933 else:
943 else:
934 df = self.opener(self.datafile)
944 df = self.opener(self.datafile)
935
945
936 # Cache data both forward and backward around the requested
946 # Cache data both forward and backward around the requested
937 # data, in a fixed size window. This helps speed up operations
947 # data, in a fixed size window. This helps speed up operations
938 # involving reading the revlog backwards.
948 # involving reading the revlog backwards.
939 cachesize = self._chunkcachesize
949 cachesize = self._chunkcachesize
940 realoffset = offset & ~(cachesize - 1)
950 realoffset = offset & ~(cachesize - 1)
941 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
951 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
942 - realoffset)
952 - realoffset)
943 df.seek(realoffset)
953 df.seek(realoffset)
944 d = df.read(reallength)
954 d = df.read(reallength)
945 df.close()
955 df.close()
946 self._addchunk(realoffset, d)
956 self._addchunk(realoffset, d)
947 if offset != realoffset or reallength != length:
957 if offset != realoffset or reallength != length:
948 return util.buffer(d, offset - realoffset, length)
958 return util.buffer(d, offset - realoffset, length)
949 return d
959 return d
950
960
951 def _getchunk(self, offset, length):
961 def _getchunk(self, offset, length):
952 o, d = self._chunkcache
962 o, d = self._chunkcache
953 l = len(d)
963 l = len(d)
954
964
955 # is it in the cache?
965 # is it in the cache?
956 cachestart = offset - o
966 cachestart = offset - o
957 cacheend = cachestart + length
967 cacheend = cachestart + length
958 if cachestart >= 0 and cacheend <= l:
968 if cachestart >= 0 and cacheend <= l:
959 if cachestart == 0 and cacheend == l:
969 if cachestart == 0 and cacheend == l:
960 return d # avoid a copy
970 return d # avoid a copy
961 return util.buffer(d, cachestart, cacheend - cachestart)
971 return util.buffer(d, cachestart, cacheend - cachestart)
962
972
963 return self._loadchunk(offset, length)
973 return self._loadchunk(offset, length)
964
974
965 def _chunkraw(self, startrev, endrev):
975 def _chunkraw(self, startrev, endrev):
966 start = self.start(startrev)
976 start = self.start(startrev)
967 end = self.end(endrev)
977 end = self.end(endrev)
968 if self._inline:
978 if self._inline:
969 start += (startrev + 1) * self._io.size
979 start += (startrev + 1) * self._io.size
970 end += (endrev + 1) * self._io.size
980 end += (endrev + 1) * self._io.size
971 length = end - start
981 length = end - start
972 return self._getchunk(start, length)
982 return self._getchunk(start, length)
973
983
974 def _chunk(self, rev):
984 def _chunk(self, rev):
975 return decompress(self._chunkraw(rev, rev))
985 return decompress(self._chunkraw(rev, rev))
976
986
977 def _chunks(self, revs):
987 def _chunks(self, revs):
978 '''faster version of [self._chunk(rev) for rev in revs]
988 '''faster version of [self._chunk(rev) for rev in revs]
979
989
980 Assumes that revs is in ascending order.'''
990 Assumes that revs is in ascending order.'''
981 if not revs:
991 if not revs:
982 return []
992 return []
983 start = self.start
993 start = self.start
984 length = self.length
994 length = self.length
985 inline = self._inline
995 inline = self._inline
986 iosize = self._io.size
996 iosize = self._io.size
987 buffer = util.buffer
997 buffer = util.buffer
988
998
989 l = []
999 l = []
990 ladd = l.append
1000 ladd = l.append
991
1001
992 # preload the cache
1002 # preload the cache
993 try:
1003 try:
994 while True:
1004 while True:
995 # ensure that the cache doesn't change out from under us
1005 # ensure that the cache doesn't change out from under us
996 _cache = self._chunkcache
1006 _cache = self._chunkcache
997 self._chunkraw(revs[0], revs[-1])
1007 self._chunkraw(revs[0], revs[-1])
998 if _cache == self._chunkcache:
1008 if _cache == self._chunkcache:
999 break
1009 break
1000 offset, data = _cache
1010 offset, data = _cache
1001 except OverflowError:
1011 except OverflowError:
1002 # issue4215 - we can't cache a run of chunks greater than
1012 # issue4215 - we can't cache a run of chunks greater than
1003 # 2G on Windows
1013 # 2G on Windows
1004 return [self._chunk(rev) for rev in revs]
1014 return [self._chunk(rev) for rev in revs]
1005
1015
1006 for rev in revs:
1016 for rev in revs:
1007 chunkstart = start(rev)
1017 chunkstart = start(rev)
1008 if inline:
1018 if inline:
1009 chunkstart += (rev + 1) * iosize
1019 chunkstart += (rev + 1) * iosize
1010 chunklength = length(rev)
1020 chunklength = length(rev)
1011 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1021 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1012
1022
1013 return l
1023 return l
1014
1024
1015 def _chunkclear(self):
1025 def _chunkclear(self):
1016 self._chunkcache = (0, '')
1026 self._chunkcache = (0, '')
1017
1027
1018 def deltaparent(self, rev):
1028 def deltaparent(self, rev):
1019 """return deltaparent of the given revision"""
1029 """return deltaparent of the given revision"""
1020 base = self.index[rev][3]
1030 base = self.index[rev][3]
1021 if base == rev:
1031 if base == rev:
1022 return nullrev
1032 return nullrev
1023 elif self._generaldelta:
1033 elif self._generaldelta:
1024 return base
1034 return base
1025 else:
1035 else:
1026 return rev - 1
1036 return rev - 1
1027
1037
1028 def revdiff(self, rev1, rev2):
1038 def revdiff(self, rev1, rev2):
1029 """return or calculate a delta between two revisions"""
1039 """return or calculate a delta between two revisions"""
1030 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1040 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1031 return str(self._chunk(rev2))
1041 return str(self._chunk(rev2))
1032
1042
1033 return mdiff.textdiff(self.revision(rev1),
1043 return mdiff.textdiff(self.revision(rev1),
1034 self.revision(rev2))
1044 self.revision(rev2))
1035
1045
1036 def revision(self, nodeorrev):
1046 def revision(self, nodeorrev):
1037 """return an uncompressed revision of a given node or revision
1047 """return an uncompressed revision of a given node or revision
1038 number.
1048 number.
1039 """
1049 """
1040 if isinstance(nodeorrev, int):
1050 if isinstance(nodeorrev, int):
1041 rev = nodeorrev
1051 rev = nodeorrev
1042 node = self.node(rev)
1052 node = self.node(rev)
1043 else:
1053 else:
1044 node = nodeorrev
1054 node = nodeorrev
1045 rev = None
1055 rev = None
1046
1056
1047 _cache = self._cache # grab local copy of cache to avoid thread race
1057 _cache = self._cache # grab local copy of cache to avoid thread race
1048 cachedrev = None
1058 cachedrev = None
1049 if node == nullid:
1059 if node == nullid:
1050 return ""
1060 return ""
1051 if _cache:
1061 if _cache:
1052 if _cache[0] == node:
1062 if _cache[0] == node:
1053 return _cache[2]
1063 return _cache[2]
1054 cachedrev = _cache[1]
1064 cachedrev = _cache[1]
1055
1065
1056 # look up what we need to read
1066 # look up what we need to read
1057 text = None
1067 text = None
1058 if rev is None:
1068 if rev is None:
1059 rev = self.rev(node)
1069 rev = self.rev(node)
1060
1070
1061 # check rev flags
1071 # check rev flags
1062 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1072 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1063 raise RevlogError(_('incompatible revision flag %x') %
1073 raise RevlogError(_('incompatible revision flag %x') %
1064 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1074 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1065
1075
1066 # build delta chain
1076 # build delta chain
1067 chain = []
1077 chain = []
1068 index = self.index # for performance
1078 index = self.index # for performance
1069 generaldelta = self._generaldelta
1079 generaldelta = self._generaldelta
1070 iterrev = rev
1080 iterrev = rev
1071 e = index[iterrev]
1081 e = index[iterrev]
1072 while iterrev != e[3] and iterrev != cachedrev:
1082 while iterrev != e[3] and iterrev != cachedrev:
1073 chain.append(iterrev)
1083 chain.append(iterrev)
1074 if generaldelta:
1084 if generaldelta:
1075 iterrev = e[3]
1085 iterrev = e[3]
1076 else:
1086 else:
1077 iterrev -= 1
1087 iterrev -= 1
1078 e = index[iterrev]
1088 e = index[iterrev]
1079
1089
1080 if iterrev == cachedrev:
1090 if iterrev == cachedrev:
1081 # cache hit
1091 # cache hit
1082 text = _cache[2]
1092 text = _cache[2]
1083 else:
1093 else:
1084 chain.append(iterrev)
1094 chain.append(iterrev)
1085 chain.reverse()
1095 chain.reverse()
1086
1096
1087 # drop cache to save memory
1097 # drop cache to save memory
1088 self._cache = None
1098 self._cache = None
1089
1099
1090 bins = self._chunks(chain)
1100 bins = self._chunks(chain)
1091 if text is None:
1101 if text is None:
1092 text = str(bins[0])
1102 text = str(bins[0])
1093 bins = bins[1:]
1103 bins = bins[1:]
1094
1104
1095 text = mdiff.patches(text, bins)
1105 text = mdiff.patches(text, bins)
1096
1106
1097 text = self._checkhash(text, node, rev)
1107 text = self._checkhash(text, node, rev)
1098
1108
1099 self._cache = (node, rev, text)
1109 self._cache = (node, rev, text)
1100 return text
1110 return text
1101
1111
1102 def hash(self, text, p1, p2):
1112 def hash(self, text, p1, p2):
1103 """Compute a node hash.
1113 """Compute a node hash.
1104
1114
1105 Available as a function so that subclasses can replace the hash
1115 Available as a function so that subclasses can replace the hash
1106 as needed.
1116 as needed.
1107 """
1117 """
1108 return hash(text, p1, p2)
1118 return hash(text, p1, p2)
1109
1119
1110 def _checkhash(self, text, node, rev):
1120 def _checkhash(self, text, node, rev):
1111 p1, p2 = self.parents(node)
1121 p1, p2 = self.parents(node)
1112 self.checkhash(text, p1, p2, node, rev)
1122 self.checkhash(text, p1, p2, node, rev)
1113 return text
1123 return text
1114
1124
1115 def checkhash(self, text, p1, p2, node, rev=None):
1125 def checkhash(self, text, p1, p2, node, rev=None):
1116 if node != self.hash(text, p1, p2):
1126 if node != self.hash(text, p1, p2):
1117 revornode = rev
1127 revornode = rev
1118 if revornode is None:
1128 if revornode is None:
1119 revornode = templatefilters.short(hex(node))
1129 revornode = templatefilters.short(hex(node))
1120 raise RevlogError(_("integrity check failed on %s:%s")
1130 raise RevlogError(_("integrity check failed on %s:%s")
1121 % (self.indexfile, revornode))
1131 % (self.indexfile, revornode))
1122
1132
1123 def checkinlinesize(self, tr, fp=None):
1133 def checkinlinesize(self, tr, fp=None):
1124 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1134 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1125 return
1135 return
1126
1136
1127 trinfo = tr.find(self.indexfile)
1137 trinfo = tr.find(self.indexfile)
1128 if trinfo is None:
1138 if trinfo is None:
1129 raise RevlogError(_("%s not found in the transaction")
1139 raise RevlogError(_("%s not found in the transaction")
1130 % self.indexfile)
1140 % self.indexfile)
1131
1141
1132 trindex = trinfo[2]
1142 trindex = trinfo[2]
1133 if trindex is not None:
1143 if trindex is not None:
1134 dataoff = self.start(trindex)
1144 dataoff = self.start(trindex)
1135 else:
1145 else:
1136 # revlog was stripped at start of transaction, use all leftover data
1146 # revlog was stripped at start of transaction, use all leftover data
1137 trindex = len(self) - 1
1147 trindex = len(self) - 1
1138 dataoff = self.end(-2)
1148 dataoff = self.end(-2)
1139
1149
1140 tr.add(self.datafile, dataoff)
1150 tr.add(self.datafile, dataoff)
1141
1151
1142 if fp:
1152 if fp:
1143 fp.flush()
1153 fp.flush()
1144 fp.close()
1154 fp.close()
1145
1155
1146 df = self.opener(self.datafile, 'w')
1156 df = self.opener(self.datafile, 'w')
1147 try:
1157 try:
1148 for r in self:
1158 for r in self:
1149 df.write(self._chunkraw(r, r))
1159 df.write(self._chunkraw(r, r))
1150 finally:
1160 finally:
1151 df.close()
1161 df.close()
1152
1162
1153 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1163 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1154 self.version &= ~(REVLOGNGINLINEDATA)
1164 self.version &= ~(REVLOGNGINLINEDATA)
1155 self._inline = False
1165 self._inline = False
1156 for i in self:
1166 for i in self:
1157 e = self._io.packentry(self.index[i], self.node, self.version, i)
1167 e = self._io.packentry(self.index[i], self.node, self.version, i)
1158 fp.write(e)
1168 fp.write(e)
1159
1169
1160 # if we don't call close, the temp file will never replace the
1170 # if we don't call close, the temp file will never replace the
1161 # real index
1171 # real index
1162 fp.close()
1172 fp.close()
1163
1173
1164 tr.replace(self.indexfile, trindex * self._io.size)
1174 tr.replace(self.indexfile, trindex * self._io.size)
1165 self._chunkclear()
1175 self._chunkclear()
1166
1176
1167 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1177 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1168 node=None):
1178 node=None):
1169 """add a revision to the log
1179 """add a revision to the log
1170
1180
1171 text - the revision data to add
1181 text - the revision data to add
1172 transaction - the transaction object used for rollback
1182 transaction - the transaction object used for rollback
1173 link - the linkrev data to add
1183 link - the linkrev data to add
1174 p1, p2 - the parent nodeids of the revision
1184 p1, p2 - the parent nodeids of the revision
1175 cachedelta - an optional precomputed delta
1185 cachedelta - an optional precomputed delta
1176 node - nodeid of revision; typically node is not specified, and it is
1186 node - nodeid of revision; typically node is not specified, and it is
1177 computed by default as hash(text, p1, p2), however subclasses might
1187 computed by default as hash(text, p1, p2), however subclasses might
1178 use different hashing method (and override checkhash() in such case)
1188 use different hashing method (and override checkhash() in such case)
1179 """
1189 """
1180 if link == nullrev:
1190 if link == nullrev:
1181 raise RevlogError(_("attempted to add linkrev -1 to %s")
1191 raise RevlogError(_("attempted to add linkrev -1 to %s")
1182 % self.indexfile)
1192 % self.indexfile)
1183 node = node or self.hash(text, p1, p2)
1193 node = node or self.hash(text, p1, p2)
1184 if node in self.nodemap:
1194 if node in self.nodemap:
1185 return node
1195 return node
1186
1196
1187 dfh = None
1197 dfh = None
1188 if not self._inline:
1198 if not self._inline:
1189 dfh = self.opener(self.datafile, "a")
1199 dfh = self.opener(self.datafile, "a")
1190 ifh = self.opener(self.indexfile, "a+")
1200 ifh = self.opener(self.indexfile, "a+")
1191 try:
1201 try:
1192 return self._addrevision(node, text, transaction, link, p1, p2,
1202 return self._addrevision(node, text, transaction, link, p1, p2,
1193 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1203 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1194 finally:
1204 finally:
1195 if dfh:
1205 if dfh:
1196 dfh.close()
1206 dfh.close()
1197 ifh.close()
1207 ifh.close()
1198
1208
1199 def compress(self, text):
1209 def compress(self, text):
1200 """ generate a possibly-compressed representation of text """
1210 """ generate a possibly-compressed representation of text """
1201 if not text:
1211 if not text:
1202 return ("", text)
1212 return ("", text)
1203 l = len(text)
1213 l = len(text)
1204 bin = None
1214 bin = None
1205 if l < 44:
1215 if l < 44:
1206 pass
1216 pass
1207 elif l > 1000000:
1217 elif l > 1000000:
1208 # zlib makes an internal copy, thus doubling memory usage for
1218 # zlib makes an internal copy, thus doubling memory usage for
1209 # large files, so lets do this in pieces
1219 # large files, so lets do this in pieces
1210 z = zlib.compressobj()
1220 z = zlib.compressobj()
1211 p = []
1221 p = []
1212 pos = 0
1222 pos = 0
1213 while pos < l:
1223 while pos < l:
1214 pos2 = pos + 2**20
1224 pos2 = pos + 2**20
1215 p.append(z.compress(text[pos:pos2]))
1225 p.append(z.compress(text[pos:pos2]))
1216 pos = pos2
1226 pos = pos2
1217 p.append(z.flush())
1227 p.append(z.flush())
1218 if sum(map(len, p)) < l:
1228 if sum(map(len, p)) < l:
1219 bin = "".join(p)
1229 bin = "".join(p)
1220 else:
1230 else:
1221 bin = _compress(text)
1231 bin = _compress(text)
1222 if bin is None or len(bin) > l:
1232 if bin is None or len(bin) > l:
1223 if text[0] == '\0':
1233 if text[0] == '\0':
1224 return ("", text)
1234 return ("", text)
1225 return ('u', text)
1235 return ('u', text)
1226 return ("", bin)
1236 return ("", bin)
1227
1237
1228 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1238 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1229 cachedelta, ifh, dfh):
1239 cachedelta, ifh, dfh):
1230 """internal function to add revisions to the log
1240 """internal function to add revisions to the log
1231
1241
1232 see addrevision for argument descriptions.
1242 see addrevision for argument descriptions.
1233 invariants:
1243 invariants:
1234 - text is optional (can be None); if not set, cachedelta must be set.
1244 - text is optional (can be None); if not set, cachedelta must be set.
1235 if both are set, they must correspond to each other.
1245 if both are set, they must correspond to each other.
1236 """
1246 """
1237 btext = [text]
1247 btext = [text]
1238 def buildtext():
1248 def buildtext():
1239 if btext[0] is not None:
1249 if btext[0] is not None:
1240 return btext[0]
1250 return btext[0]
1241 # flush any pending writes here so we can read it in revision
1251 # flush any pending writes here so we can read it in revision
1242 if dfh:
1252 if dfh:
1243 dfh.flush()
1253 dfh.flush()
1244 ifh.flush()
1254 ifh.flush()
1245 baserev = cachedelta[0]
1255 baserev = cachedelta[0]
1246 delta = cachedelta[1]
1256 delta = cachedelta[1]
1247 # special case deltas which replace entire base; no need to decode
1257 # special case deltas which replace entire base; no need to decode
1248 # base revision. this neatly avoids censored bases, which throw when
1258 # base revision. this neatly avoids censored bases, which throw when
1249 # they're decoded.
1259 # they're decoded.
1250 hlen = struct.calcsize(">lll")
1260 hlen = struct.calcsize(">lll")
1251 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1261 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1252 len(delta) - hlen):
1262 len(delta) - hlen):
1253 btext[0] = delta[hlen:]
1263 btext[0] = delta[hlen:]
1254 else:
1264 else:
1255 basetext = self.revision(self.node(baserev))
1265 basetext = self.revision(self.node(baserev))
1256 btext[0] = mdiff.patch(basetext, delta)
1266 btext[0] = mdiff.patch(basetext, delta)
1257 try:
1267 try:
1258 self.checkhash(btext[0], p1, p2, node)
1268 self.checkhash(btext[0], p1, p2, node)
1259 if flags & REVIDX_ISCENSORED:
1269 if flags & REVIDX_ISCENSORED:
1260 raise RevlogError(_('node %s is not censored') % node)
1270 raise RevlogError(_('node %s is not censored') % node)
1261 except CensoredNodeError:
1271 except CensoredNodeError:
1262 # must pass the censored index flag to add censored revisions
1272 # must pass the censored index flag to add censored revisions
1263 if not flags & REVIDX_ISCENSORED:
1273 if not flags & REVIDX_ISCENSORED:
1264 raise
1274 raise
1265 return btext[0]
1275 return btext[0]
1266
1276
1267 def builddelta(rev):
1277 def builddelta(rev):
1268 # can we use the cached delta?
1278 # can we use the cached delta?
1269 if cachedelta and cachedelta[0] == rev:
1279 if cachedelta and cachedelta[0] == rev:
1270 delta = cachedelta[1]
1280 delta = cachedelta[1]
1271 else:
1281 else:
1272 t = buildtext()
1282 t = buildtext()
1273 if self.iscensored(rev):
1283 if self.iscensored(rev):
1274 # deltas based on a censored revision must replace the
1284 # deltas based on a censored revision must replace the
1275 # full content in one patch, so delta works everywhere
1285 # full content in one patch, so delta works everywhere
1276 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1286 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1277 delta = header + t
1287 delta = header + t
1278 else:
1288 else:
1279 ptext = self.revision(self.node(rev))
1289 ptext = self.revision(self.node(rev))
1280 delta = mdiff.textdiff(ptext, t)
1290 delta = mdiff.textdiff(ptext, t)
1281 data = self.compress(delta)
1291 data = self.compress(delta)
1282 l = len(data[1]) + len(data[0])
1292 l = len(data[1]) + len(data[0])
1283 if basecache[0] == rev:
1293 if basecache[0] == rev:
1284 chainbase = basecache[1]
1294 chainbase = basecache[1]
1285 else:
1295 else:
1286 chainbase = self.chainbase(rev)
1296 chainbase = self.chainbase(rev)
1287 dist = l + offset - self.start(chainbase)
1297 dist = l + offset - self.start(chainbase)
1288 if self._generaldelta:
1298 if self._generaldelta:
1289 base = rev
1299 base = rev
1290 else:
1300 else:
1291 base = chainbase
1301 base = chainbase
1292 chainlen, compresseddeltalen = self._chaininfo(rev)
1302 chainlen, compresseddeltalen = self._chaininfo(rev)
1293 chainlen += 1
1303 chainlen += 1
1294 compresseddeltalen += l
1304 compresseddeltalen += l
1295 return dist, l, data, base, chainbase, chainlen, compresseddeltalen
1305 return dist, l, data, base, chainbase, chainlen, compresseddeltalen
1296
1306
1297 curr = len(self)
1307 curr = len(self)
1298 prev = curr - 1
1308 prev = curr - 1
1299 base = chainbase = curr
1309 base = chainbase = curr
1300 chainlen = None
1310 chainlen = None
1301 offset = self.end(prev)
1311 offset = self.end(prev)
1302 d = None
1312 d = None
1303 if self._basecache is None:
1313 if self._basecache is None:
1304 self._basecache = (prev, self.chainbase(prev))
1314 self._basecache = (prev, self.chainbase(prev))
1305 basecache = self._basecache
1315 basecache = self._basecache
1306 p1r, p2r = self.rev(p1), self.rev(p2)
1316 p1r, p2r = self.rev(p1), self.rev(p2)
1307
1317
1308 # should we try to build a delta?
1318 # should we try to build a delta?
1309 if prev != nullrev:
1319 if prev != nullrev:
1310 if self._generaldelta:
1320 if self._generaldelta:
1311 if p1r >= basecache[1]:
1321 if p1r >= basecache[1]:
1312 d = builddelta(p1r)
1322 d = builddelta(p1r)
1313 elif p2r >= basecache[1]:
1323 elif p2r >= basecache[1]:
1314 d = builddelta(p2r)
1324 d = builddelta(p2r)
1315 else:
1325 else:
1316 d = builddelta(prev)
1326 d = builddelta(prev)
1317 else:
1327 else:
1318 d = builddelta(prev)
1328 d = builddelta(prev)
1319 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1329 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1320
1330
1321 # full versions are inserted when the needed deltas
1331 # full versions are inserted when the needed deltas
1322 # become comparable to the uncompressed text
1332 # become comparable to the uncompressed text
1323 if text is None:
1333 if text is None:
1324 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1334 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1325 cachedelta[1])
1335 cachedelta[1])
1326 else:
1336 else:
1327 textlen = len(text)
1337 textlen = len(text)
1328
1338
1329 # - 'dist' is the distance from the base revision -- bounding it limits
1339 # - 'dist' is the distance from the base revision -- bounding it limits
1330 # the amount of I/O we need to do.
1340 # the amount of I/O we need to do.
1331 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1341 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1332 # to apply -- bounding it limits the amount of CPU we consume.
1342 # to apply -- bounding it limits the amount of CPU we consume.
1333 if (d is None or dist > textlen * 4 or l > textlen or
1343 if (d is None or dist > textlen * 4 or l > textlen or
1334 compresseddeltalen > textlen * 2 or
1344 compresseddeltalen > textlen * 2 or
1335 (self._maxchainlen and chainlen > self._maxchainlen)):
1345 (self._maxchainlen and chainlen > self._maxchainlen)):
1336 text = buildtext()
1346 text = buildtext()
1337 data = self.compress(text)
1347 data = self.compress(text)
1338 l = len(data[1]) + len(data[0])
1348 l = len(data[1]) + len(data[0])
1339 base = chainbase = curr
1349 base = chainbase = curr
1340
1350
1341 e = (offset_type(offset, flags), l, textlen,
1351 e = (offset_type(offset, flags), l, textlen,
1342 base, link, p1r, p2r, node)
1352 base, link, p1r, p2r, node)
1343 self.index.insert(-1, e)
1353 self.index.insert(-1, e)
1344 self.nodemap[node] = curr
1354 self.nodemap[node] = curr
1345
1355
1346 entry = self._io.packentry(e, self.node, self.version, curr)
1356 entry = self._io.packentry(e, self.node, self.version, curr)
1347 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1357 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1348
1358
1349 if type(text) == str: # only accept immutable objects
1359 if type(text) == str: # only accept immutable objects
1350 self._cache = (node, curr, text)
1360 self._cache = (node, curr, text)
1351 self._basecache = (curr, chainbase)
1361 self._basecache = (curr, chainbase)
1352 return node
1362 return node
1353
1363
1354 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1364 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1355 curr = len(self) - 1
1365 curr = len(self) - 1
1356 if not self._inline:
1366 if not self._inline:
1357 transaction.add(self.datafile, offset)
1367 transaction.add(self.datafile, offset)
1358 transaction.add(self.indexfile, curr * len(entry))
1368 transaction.add(self.indexfile, curr * len(entry))
1359 if data[0]:
1369 if data[0]:
1360 dfh.write(data[0])
1370 dfh.write(data[0])
1361 dfh.write(data[1])
1371 dfh.write(data[1])
1362 dfh.flush()
1372 dfh.flush()
1363 ifh.write(entry)
1373 ifh.write(entry)
1364 else:
1374 else:
1365 offset += curr * self._io.size
1375 offset += curr * self._io.size
1366 transaction.add(self.indexfile, offset, curr)
1376 transaction.add(self.indexfile, offset, curr)
1367 ifh.write(entry)
1377 ifh.write(entry)
1368 ifh.write(data[0])
1378 ifh.write(data[0])
1369 ifh.write(data[1])
1379 ifh.write(data[1])
1370 self.checkinlinesize(transaction, ifh)
1380 self.checkinlinesize(transaction, ifh)
1371
1381
1372 def addgroup(self, bundle, linkmapper, transaction):
1382 def addgroup(self, bundle, linkmapper, transaction):
1373 """
1383 """
1374 add a delta group
1384 add a delta group
1375
1385
1376 given a set of deltas, add them to the revision log. the
1386 given a set of deltas, add them to the revision log. the
1377 first delta is against its parent, which should be in our
1387 first delta is against its parent, which should be in our
1378 log, the rest are against the previous delta.
1388 log, the rest are against the previous delta.
1379 """
1389 """
1380
1390
1381 # track the base of the current delta log
1391 # track the base of the current delta log
1382 content = []
1392 content = []
1383 node = None
1393 node = None
1384
1394
1385 r = len(self)
1395 r = len(self)
1386 end = 0
1396 end = 0
1387 if r:
1397 if r:
1388 end = self.end(r - 1)
1398 end = self.end(r - 1)
1389 ifh = self.opener(self.indexfile, "a+")
1399 ifh = self.opener(self.indexfile, "a+")
1390 isize = r * self._io.size
1400 isize = r * self._io.size
1391 if self._inline:
1401 if self._inline:
1392 transaction.add(self.indexfile, end + isize, r)
1402 transaction.add(self.indexfile, end + isize, r)
1393 dfh = None
1403 dfh = None
1394 else:
1404 else:
1395 transaction.add(self.indexfile, isize, r)
1405 transaction.add(self.indexfile, isize, r)
1396 transaction.add(self.datafile, end)
1406 transaction.add(self.datafile, end)
1397 dfh = self.opener(self.datafile, "a")
1407 dfh = self.opener(self.datafile, "a")
1398 def flush():
1408 def flush():
1399 if dfh:
1409 if dfh:
1400 dfh.flush()
1410 dfh.flush()
1401 ifh.flush()
1411 ifh.flush()
1402 try:
1412 try:
1403 # loop through our set of deltas
1413 # loop through our set of deltas
1404 chain = None
1414 chain = None
1405 while True:
1415 while True:
1406 chunkdata = bundle.deltachunk(chain)
1416 chunkdata = bundle.deltachunk(chain)
1407 if not chunkdata:
1417 if not chunkdata:
1408 break
1418 break
1409 node = chunkdata['node']
1419 node = chunkdata['node']
1410 p1 = chunkdata['p1']
1420 p1 = chunkdata['p1']
1411 p2 = chunkdata['p2']
1421 p2 = chunkdata['p2']
1412 cs = chunkdata['cs']
1422 cs = chunkdata['cs']
1413 deltabase = chunkdata['deltabase']
1423 deltabase = chunkdata['deltabase']
1414 delta = chunkdata['delta']
1424 delta = chunkdata['delta']
1415
1425
1416 content.append(node)
1426 content.append(node)
1417
1427
1418 link = linkmapper(cs)
1428 link = linkmapper(cs)
1419 if node in self.nodemap:
1429 if node in self.nodemap:
1420 # this can happen if two branches make the same change
1430 # this can happen if two branches make the same change
1421 chain = node
1431 chain = node
1422 continue
1432 continue
1423
1433
1424 for p in (p1, p2):
1434 for p in (p1, p2):
1425 if p not in self.nodemap:
1435 if p not in self.nodemap:
1426 raise LookupError(p, self.indexfile,
1436 raise LookupError(p, self.indexfile,
1427 _('unknown parent'))
1437 _('unknown parent'))
1428
1438
1429 if deltabase not in self.nodemap:
1439 if deltabase not in self.nodemap:
1430 raise LookupError(deltabase, self.indexfile,
1440 raise LookupError(deltabase, self.indexfile,
1431 _('unknown delta base'))
1441 _('unknown delta base'))
1432
1442
1433 baserev = self.rev(deltabase)
1443 baserev = self.rev(deltabase)
1434
1444
1435 if baserev != nullrev and self.iscensored(baserev):
1445 if baserev != nullrev and self.iscensored(baserev):
1436 # if base is censored, delta must be full replacement in a
1446 # if base is censored, delta must be full replacement in a
1437 # single patch operation
1447 # single patch operation
1438 hlen = struct.calcsize(">lll")
1448 hlen = struct.calcsize(">lll")
1439 oldlen = self.rawsize(baserev)
1449 oldlen = self.rawsize(baserev)
1440 newlen = len(delta) - hlen
1450 newlen = len(delta) - hlen
1441 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1451 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1442 raise error.CensoredBaseError(self.indexfile,
1452 raise error.CensoredBaseError(self.indexfile,
1443 self.node(baserev))
1453 self.node(baserev))
1444
1454
1445 flags = REVIDX_DEFAULT_FLAGS
1455 flags = REVIDX_DEFAULT_FLAGS
1446 if self._peek_iscensored(baserev, delta, flush):
1456 if self._peek_iscensored(baserev, delta, flush):
1447 flags |= REVIDX_ISCENSORED
1457 flags |= REVIDX_ISCENSORED
1448
1458
1449 chain = self._addrevision(node, None, transaction, link,
1459 chain = self._addrevision(node, None, transaction, link,
1450 p1, p2, flags, (baserev, delta),
1460 p1, p2, flags, (baserev, delta),
1451 ifh, dfh)
1461 ifh, dfh)
1452 if not dfh and not self._inline:
1462 if not dfh and not self._inline:
1453 # addrevision switched from inline to conventional
1463 # addrevision switched from inline to conventional
1454 # reopen the index
1464 # reopen the index
1455 ifh.close()
1465 ifh.close()
1456 dfh = self.opener(self.datafile, "a")
1466 dfh = self.opener(self.datafile, "a")
1457 ifh = self.opener(self.indexfile, "a")
1467 ifh = self.opener(self.indexfile, "a")
1458 finally:
1468 finally:
1459 if dfh:
1469 if dfh:
1460 dfh.close()
1470 dfh.close()
1461 ifh.close()
1471 ifh.close()
1462
1472
1463 return content
1473 return content
1464
1474
1465 def iscensored(self, rev):
1475 def iscensored(self, rev):
1466 """Check if a file revision is censored."""
1476 """Check if a file revision is censored."""
1467 return False
1477 return False
1468
1478
1469 def _peek_iscensored(self, baserev, delta, flush):
1479 def _peek_iscensored(self, baserev, delta, flush):
1470 """Quickly check if a delta produces a censored revision."""
1480 """Quickly check if a delta produces a censored revision."""
1471 return False
1481 return False
1472
1482
1473 def getstrippoint(self, minlink):
1483 def getstrippoint(self, minlink):
1474 """find the minimum rev that must be stripped to strip the linkrev
1484 """find the minimum rev that must be stripped to strip the linkrev
1475
1485
1476 Returns a tuple containing the minimum rev and a set of all revs that
1486 Returns a tuple containing the minimum rev and a set of all revs that
1477 have linkrevs that will be broken by this strip.
1487 have linkrevs that will be broken by this strip.
1478 """
1488 """
1479 brokenrevs = set()
1489 brokenrevs = set()
1480 strippoint = len(self)
1490 strippoint = len(self)
1481
1491
1482 heads = {}
1492 heads = {}
1483 futurelargelinkrevs = set()
1493 futurelargelinkrevs = set()
1484 for head in self.headrevs():
1494 for head in self.headrevs():
1485 headlinkrev = self.linkrev(head)
1495 headlinkrev = self.linkrev(head)
1486 heads[head] = headlinkrev
1496 heads[head] = headlinkrev
1487 if headlinkrev >= minlink:
1497 if headlinkrev >= minlink:
1488 futurelargelinkrevs.add(headlinkrev)
1498 futurelargelinkrevs.add(headlinkrev)
1489
1499
1490 # This algorithm involves walking down the rev graph, starting at the
1500 # This algorithm involves walking down the rev graph, starting at the
1491 # heads. Since the revs are topologically sorted according to linkrev,
1501 # heads. Since the revs are topologically sorted according to linkrev,
1492 # once all head linkrevs are below the minlink, we know there are
1502 # once all head linkrevs are below the minlink, we know there are
1493 # no more revs that could have a linkrev greater than minlink.
1503 # no more revs that could have a linkrev greater than minlink.
1494 # So we can stop walking.
1504 # So we can stop walking.
1495 while futurelargelinkrevs:
1505 while futurelargelinkrevs:
1496 strippoint -= 1
1506 strippoint -= 1
1497 linkrev = heads.pop(strippoint)
1507 linkrev = heads.pop(strippoint)
1498
1508
1499 if linkrev < minlink:
1509 if linkrev < minlink:
1500 brokenrevs.add(strippoint)
1510 brokenrevs.add(strippoint)
1501 else:
1511 else:
1502 futurelargelinkrevs.remove(linkrev)
1512 futurelargelinkrevs.remove(linkrev)
1503
1513
1504 for p in self.parentrevs(strippoint):
1514 for p in self.parentrevs(strippoint):
1505 if p != nullrev:
1515 if p != nullrev:
1506 plinkrev = self.linkrev(p)
1516 plinkrev = self.linkrev(p)
1507 heads[p] = plinkrev
1517 heads[p] = plinkrev
1508 if plinkrev >= minlink:
1518 if plinkrev >= minlink:
1509 futurelargelinkrevs.add(plinkrev)
1519 futurelargelinkrevs.add(plinkrev)
1510
1520
1511 return strippoint, brokenrevs
1521 return strippoint, brokenrevs
1512
1522
1513 def strip(self, minlink, transaction):
1523 def strip(self, minlink, transaction):
1514 """truncate the revlog on the first revision with a linkrev >= minlink
1524 """truncate the revlog on the first revision with a linkrev >= minlink
1515
1525
1516 This function is called when we're stripping revision minlink and
1526 This function is called when we're stripping revision minlink and
1517 its descendants from the repository.
1527 its descendants from the repository.
1518
1528
1519 We have to remove all revisions with linkrev >= minlink, because
1529 We have to remove all revisions with linkrev >= minlink, because
1520 the equivalent changelog revisions will be renumbered after the
1530 the equivalent changelog revisions will be renumbered after the
1521 strip.
1531 strip.
1522
1532
1523 So we truncate the revlog on the first of these revisions, and
1533 So we truncate the revlog on the first of these revisions, and
1524 trust that the caller has saved the revisions that shouldn't be
1534 trust that the caller has saved the revisions that shouldn't be
1525 removed and that it'll re-add them after this truncation.
1535 removed and that it'll re-add them after this truncation.
1526 """
1536 """
1527 if len(self) == 0:
1537 if len(self) == 0:
1528 return
1538 return
1529
1539
1530 rev, _ = self.getstrippoint(minlink)
1540 rev, _ = self.getstrippoint(minlink)
1531 if rev == len(self):
1541 if rev == len(self):
1532 return
1542 return
1533
1543
1534 # first truncate the files on disk
1544 # first truncate the files on disk
1535 end = self.start(rev)
1545 end = self.start(rev)
1536 if not self._inline:
1546 if not self._inline:
1537 transaction.add(self.datafile, end)
1547 transaction.add(self.datafile, end)
1538 end = rev * self._io.size
1548 end = rev * self._io.size
1539 else:
1549 else:
1540 end += rev * self._io.size
1550 end += rev * self._io.size
1541
1551
1542 transaction.add(self.indexfile, end)
1552 transaction.add(self.indexfile, end)
1543
1553
1544 # then reset internal state in memory to forget those revisions
1554 # then reset internal state in memory to forget those revisions
1545 self._cache = None
1555 self._cache = None
1546 self._chaininfocache = {}
1556 self._chaininfocache = {}
1547 self._chunkclear()
1557 self._chunkclear()
1548 for x in xrange(rev, len(self)):
1558 for x in xrange(rev, len(self)):
1549 del self.nodemap[self.node(x)]
1559 del self.nodemap[self.node(x)]
1550
1560
1551 del self.index[rev:-1]
1561 del self.index[rev:-1]
1552
1562
1553 def checksize(self):
1563 def checksize(self):
1554 expected = 0
1564 expected = 0
1555 if len(self):
1565 if len(self):
1556 expected = max(0, self.end(len(self) - 1))
1566 expected = max(0, self.end(len(self) - 1))
1557
1567
1558 try:
1568 try:
1559 f = self.opener(self.datafile)
1569 f = self.opener(self.datafile)
1560 f.seek(0, 2)
1570 f.seek(0, 2)
1561 actual = f.tell()
1571 actual = f.tell()
1562 f.close()
1572 f.close()
1563 dd = actual - expected
1573 dd = actual - expected
1564 except IOError, inst:
1574 except IOError, inst:
1565 if inst.errno != errno.ENOENT:
1575 if inst.errno != errno.ENOENT:
1566 raise
1576 raise
1567 dd = 0
1577 dd = 0
1568
1578
1569 try:
1579 try:
1570 f = self.opener(self.indexfile)
1580 f = self.opener(self.indexfile)
1571 f.seek(0, 2)
1581 f.seek(0, 2)
1572 actual = f.tell()
1582 actual = f.tell()
1573 f.close()
1583 f.close()
1574 s = self._io.size
1584 s = self._io.size
1575 i = max(0, actual // s)
1585 i = max(0, actual // s)
1576 di = actual - (i * s)
1586 di = actual - (i * s)
1577 if self._inline:
1587 if self._inline:
1578 databytes = 0
1588 databytes = 0
1579 for r in self:
1589 for r in self:
1580 databytes += max(0, self.length(r))
1590 databytes += max(0, self.length(r))
1581 dd = 0
1591 dd = 0
1582 di = actual - len(self) * s - databytes
1592 di = actual - len(self) * s - databytes
1583 except IOError, inst:
1593 except IOError, inst:
1584 if inst.errno != errno.ENOENT:
1594 if inst.errno != errno.ENOENT:
1585 raise
1595 raise
1586 di = 0
1596 di = 0
1587
1597
1588 return (dd, di)
1598 return (dd, di)
1589
1599
1590 def files(self):
1600 def files(self):
1591 res = [self.indexfile]
1601 res = [self.indexfile]
1592 if not self._inline:
1602 if not self._inline:
1593 res.append(self.datafile)
1603 res.append(self.datafile)
1594 return res
1604 return res
General Comments 0
You need to be logged in to leave comments. Login now