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