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