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