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