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