##// END OF EJS Templates
revlog: introduce commonancestorsheads method...
Mads Kiilerich -
r21104:40ace21c default
parent child Browse files
Show More
@@ -1,1437 +1,1446 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 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):
738 """calculate all the heads of the common ancestors of nodes a and b"""
739 a, b = self.rev(a), self.rev(b)
740 try:
741 ancs = self.index.commonancestorsheads(a, b)
742 except (AttributeError, OverflowError): # C implementation failed
743 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
744 return map(self.node, ancs)
745
737 def commonancestors(self, a, b):
746 def commonancestors(self, a, b):
738 """calculate the least common ancestors of nodes a and b"""
747 """calculate the least common ancestors of nodes a and b"""
739 a, b = self.rev(a), self.rev(b)
748 a, b = self.rev(a), self.rev(b)
740 try:
749 try:
741 ancs = self.index.ancestors(a, b)
750 ancs = self.index.ancestors(a, b)
742 except (AttributeError, OverflowError): # C implementation failed
751 except (AttributeError, OverflowError): # C implementation failed
743 ancs = ancestor.ancestors(self.parentrevs, a, b)
752 ancs = ancestor.ancestors(self.parentrevs, a, b)
744 return map(self.node, ancs)
753 return map(self.node, ancs)
745
754
746 def ancestor(self, a, b):
755 def ancestor(self, a, b):
747 """calculate a least common ancestor of nodes a and b"""
756 """calculate a least common ancestor of nodes a and b"""
748 ancs = self.commonancestors(a, b)
757 ancs = self.commonancestors(a, b)
749 if ancs:
758 if ancs:
750 # choose a consistent winner when there's a tie
759 # choose a consistent winner when there's a tie
751 return min(ancs)
760 return min(ancs)
752 return nullid
761 return nullid
753
762
754 def _match(self, id):
763 def _match(self, id):
755 if isinstance(id, int):
764 if isinstance(id, int):
756 # rev
765 # rev
757 return self.node(id)
766 return self.node(id)
758 if len(id) == 20:
767 if len(id) == 20:
759 # possibly a binary node
768 # possibly a binary node
760 # odds of a binary node being all hex in ASCII are 1 in 10**25
769 # odds of a binary node being all hex in ASCII are 1 in 10**25
761 try:
770 try:
762 node = id
771 node = id
763 self.rev(node) # quick search the index
772 self.rev(node) # quick search the index
764 return node
773 return node
765 except LookupError:
774 except LookupError:
766 pass # may be partial hex id
775 pass # may be partial hex id
767 try:
776 try:
768 # str(rev)
777 # str(rev)
769 rev = int(id)
778 rev = int(id)
770 if str(rev) != id:
779 if str(rev) != id:
771 raise ValueError
780 raise ValueError
772 if rev < 0:
781 if rev < 0:
773 rev = len(self) + rev
782 rev = len(self) + rev
774 if rev < 0 or rev >= len(self):
783 if rev < 0 or rev >= len(self):
775 raise ValueError
784 raise ValueError
776 return self.node(rev)
785 return self.node(rev)
777 except (ValueError, OverflowError):
786 except (ValueError, OverflowError):
778 pass
787 pass
779 if len(id) == 40:
788 if len(id) == 40:
780 try:
789 try:
781 # a full hex nodeid?
790 # a full hex nodeid?
782 node = bin(id)
791 node = bin(id)
783 self.rev(node)
792 self.rev(node)
784 return node
793 return node
785 except (TypeError, LookupError):
794 except (TypeError, LookupError):
786 pass
795 pass
787
796
788 def _partialmatch(self, id):
797 def _partialmatch(self, id):
789 try:
798 try:
790 n = self.index.partialmatch(id)
799 n = self.index.partialmatch(id)
791 if n and self.hasnode(n):
800 if n and self.hasnode(n):
792 return n
801 return n
793 return None
802 return None
794 except RevlogError:
803 except RevlogError:
795 # parsers.c radix tree lookup gave multiple matches
804 # parsers.c radix tree lookup gave multiple matches
796 # fall through to slow path that filters hidden revisions
805 # fall through to slow path that filters hidden revisions
797 pass
806 pass
798 except (AttributeError, ValueError):
807 except (AttributeError, ValueError):
799 # we are pure python, or key was too short to search radix tree
808 # we are pure python, or key was too short to search radix tree
800 pass
809 pass
801
810
802 if id in self._pcache:
811 if id in self._pcache:
803 return self._pcache[id]
812 return self._pcache[id]
804
813
805 if len(id) < 40:
814 if len(id) < 40:
806 try:
815 try:
807 # hex(node)[:...]
816 # hex(node)[:...]
808 l = len(id) // 2 # grab an even number of digits
817 l = len(id) // 2 # grab an even number of digits
809 prefix = bin(id[:l * 2])
818 prefix = bin(id[:l * 2])
810 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
819 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
811 nl = [n for n in nl if hex(n).startswith(id) and
820 nl = [n for n in nl if hex(n).startswith(id) and
812 self.hasnode(n)]
821 self.hasnode(n)]
813 if len(nl) > 0:
822 if len(nl) > 0:
814 if len(nl) == 1:
823 if len(nl) == 1:
815 self._pcache[id] = nl[0]
824 self._pcache[id] = nl[0]
816 return nl[0]
825 return nl[0]
817 raise LookupError(id, self.indexfile,
826 raise LookupError(id, self.indexfile,
818 _('ambiguous identifier'))
827 _('ambiguous identifier'))
819 return None
828 return None
820 except TypeError:
829 except TypeError:
821 pass
830 pass
822
831
823 def lookup(self, id):
832 def lookup(self, id):
824 """locate a node based on:
833 """locate a node based on:
825 - revision number or str(revision number)
834 - revision number or str(revision number)
826 - nodeid or subset of hex nodeid
835 - nodeid or subset of hex nodeid
827 """
836 """
828 n = self._match(id)
837 n = self._match(id)
829 if n is not None:
838 if n is not None:
830 return n
839 return n
831 n = self._partialmatch(id)
840 n = self._partialmatch(id)
832 if n:
841 if n:
833 return n
842 return n
834
843
835 raise LookupError(id, self.indexfile, _('no match found'))
844 raise LookupError(id, self.indexfile, _('no match found'))
836
845
837 def cmp(self, node, text):
846 def cmp(self, node, text):
838 """compare text with a given file revision
847 """compare text with a given file revision
839
848
840 returns True if text is different than what is stored.
849 returns True if text is different than what is stored.
841 """
850 """
842 p1, p2 = self.parents(node)
851 p1, p2 = self.parents(node)
843 return hash(text, p1, p2) != node
852 return hash(text, p1, p2) != node
844
853
845 def _addchunk(self, offset, data):
854 def _addchunk(self, offset, data):
846 o, d = self._chunkcache
855 o, d = self._chunkcache
847 # try to add to existing cache
856 # try to add to existing cache
848 if o + len(d) == offset and len(d) + len(data) < _chunksize:
857 if o + len(d) == offset and len(d) + len(data) < _chunksize:
849 self._chunkcache = o, d + data
858 self._chunkcache = o, d + data
850 else:
859 else:
851 self._chunkcache = offset, data
860 self._chunkcache = offset, data
852
861
853 def _loadchunk(self, offset, length):
862 def _loadchunk(self, offset, length):
854 if self._inline:
863 if self._inline:
855 df = self.opener(self.indexfile)
864 df = self.opener(self.indexfile)
856 else:
865 else:
857 df = self.opener(self.datafile)
866 df = self.opener(self.datafile)
858
867
859 # Cache data both forward and backward around the requested
868 # Cache data both forward and backward around the requested
860 # data, in a fixed size window. This helps speed up operations
869 # data, in a fixed size window. This helps speed up operations
861 # involving reading the revlog backwards.
870 # involving reading the revlog backwards.
862 cachesize = self._chunkcachesize
871 cachesize = self._chunkcachesize
863 realoffset = offset & ~(cachesize - 1)
872 realoffset = offset & ~(cachesize - 1)
864 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
873 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
865 - realoffset)
874 - realoffset)
866 df.seek(realoffset)
875 df.seek(realoffset)
867 d = df.read(reallength)
876 d = df.read(reallength)
868 df.close()
877 df.close()
869 self._addchunk(realoffset, d)
878 self._addchunk(realoffset, d)
870 if offset != realoffset or reallength != length:
879 if offset != realoffset or reallength != length:
871 return util.buffer(d, offset - realoffset, length)
880 return util.buffer(d, offset - realoffset, length)
872 return d
881 return d
873
882
874 def _getchunk(self, offset, length):
883 def _getchunk(self, offset, length):
875 o, d = self._chunkcache
884 o, d = self._chunkcache
876 l = len(d)
885 l = len(d)
877
886
878 # is it in the cache?
887 # is it in the cache?
879 cachestart = offset - o
888 cachestart = offset - o
880 cacheend = cachestart + length
889 cacheend = cachestart + length
881 if cachestart >= 0 and cacheend <= l:
890 if cachestart >= 0 and cacheend <= l:
882 if cachestart == 0 and cacheend == l:
891 if cachestart == 0 and cacheend == l:
883 return d # avoid a copy
892 return d # avoid a copy
884 return util.buffer(d, cachestart, cacheend - cachestart)
893 return util.buffer(d, cachestart, cacheend - cachestart)
885
894
886 return self._loadchunk(offset, length)
895 return self._loadchunk(offset, length)
887
896
888 def _chunkraw(self, startrev, endrev):
897 def _chunkraw(self, startrev, endrev):
889 start = self.start(startrev)
898 start = self.start(startrev)
890 end = self.end(endrev)
899 end = self.end(endrev)
891 if self._inline:
900 if self._inline:
892 start += (startrev + 1) * self._io.size
901 start += (startrev + 1) * self._io.size
893 end += (endrev + 1) * self._io.size
902 end += (endrev + 1) * self._io.size
894 length = end - start
903 length = end - start
895 return self._getchunk(start, length)
904 return self._getchunk(start, length)
896
905
897 def _chunk(self, rev):
906 def _chunk(self, rev):
898 return decompress(self._chunkraw(rev, rev))
907 return decompress(self._chunkraw(rev, rev))
899
908
900 def _chunks(self, revs):
909 def _chunks(self, revs):
901 '''faster version of [self._chunk(rev) for rev in revs]
910 '''faster version of [self._chunk(rev) for rev in revs]
902
911
903 Assumes that revs is in ascending order.'''
912 Assumes that revs is in ascending order.'''
904 if not revs:
913 if not revs:
905 return []
914 return []
906 start = self.start
915 start = self.start
907 length = self.length
916 length = self.length
908 inline = self._inline
917 inline = self._inline
909 iosize = self._io.size
918 iosize = self._io.size
910 buffer = util.buffer
919 buffer = util.buffer
911
920
912 l = []
921 l = []
913 ladd = l.append
922 ladd = l.append
914
923
915 # preload the cache
924 # preload the cache
916 try:
925 try:
917 self._chunkraw(revs[0], revs[-1])
926 self._chunkraw(revs[0], revs[-1])
918 offset, data = self._chunkcache
927 offset, data = self._chunkcache
919 except OverflowError:
928 except OverflowError:
920 # issue4215 - we can't cache a run of chunks greater than
929 # issue4215 - we can't cache a run of chunks greater than
921 # 2G on Windows
930 # 2G on Windows
922 return [self._chunk(rev) for rev in revs]
931 return [self._chunk(rev) for rev in revs]
923
932
924 for rev in revs:
933 for rev in revs:
925 chunkstart = start(rev)
934 chunkstart = start(rev)
926 if inline:
935 if inline:
927 chunkstart += (rev + 1) * iosize
936 chunkstart += (rev + 1) * iosize
928 chunklength = length(rev)
937 chunklength = length(rev)
929 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
938 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
930
939
931 return l
940 return l
932
941
933 def _chunkclear(self):
942 def _chunkclear(self):
934 self._chunkcache = (0, '')
943 self._chunkcache = (0, '')
935
944
936 def deltaparent(self, rev):
945 def deltaparent(self, rev):
937 """return deltaparent of the given revision"""
946 """return deltaparent of the given revision"""
938 base = self.index[rev][3]
947 base = self.index[rev][3]
939 if base == rev:
948 if base == rev:
940 return nullrev
949 return nullrev
941 elif self._generaldelta:
950 elif self._generaldelta:
942 return base
951 return base
943 else:
952 else:
944 return rev - 1
953 return rev - 1
945
954
946 def revdiff(self, rev1, rev2):
955 def revdiff(self, rev1, rev2):
947 """return or calculate a delta between two revisions"""
956 """return or calculate a delta between two revisions"""
948 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
957 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
949 return str(self._chunk(rev2))
958 return str(self._chunk(rev2))
950
959
951 return mdiff.textdiff(self.revision(rev1),
960 return mdiff.textdiff(self.revision(rev1),
952 self.revision(rev2))
961 self.revision(rev2))
953
962
954 def revision(self, nodeorrev):
963 def revision(self, nodeorrev):
955 """return an uncompressed revision of a given node or revision
964 """return an uncompressed revision of a given node or revision
956 number.
965 number.
957 """
966 """
958 if isinstance(nodeorrev, int):
967 if isinstance(nodeorrev, int):
959 rev = nodeorrev
968 rev = nodeorrev
960 node = self.node(rev)
969 node = self.node(rev)
961 else:
970 else:
962 node = nodeorrev
971 node = nodeorrev
963 rev = None
972 rev = None
964
973
965 cachedrev = None
974 cachedrev = None
966 if node == nullid:
975 if node == nullid:
967 return ""
976 return ""
968 if self._cache:
977 if self._cache:
969 if self._cache[0] == node:
978 if self._cache[0] == node:
970 return self._cache[2]
979 return self._cache[2]
971 cachedrev = self._cache[1]
980 cachedrev = self._cache[1]
972
981
973 # look up what we need to read
982 # look up what we need to read
974 text = None
983 text = None
975 if rev is None:
984 if rev is None:
976 rev = self.rev(node)
985 rev = self.rev(node)
977
986
978 # check rev flags
987 # check rev flags
979 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
988 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
980 raise RevlogError(_('incompatible revision flag %x') %
989 raise RevlogError(_('incompatible revision flag %x') %
981 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
990 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
982
991
983 # build delta chain
992 # build delta chain
984 chain = []
993 chain = []
985 index = self.index # for performance
994 index = self.index # for performance
986 generaldelta = self._generaldelta
995 generaldelta = self._generaldelta
987 iterrev = rev
996 iterrev = rev
988 e = index[iterrev]
997 e = index[iterrev]
989 while iterrev != e[3] and iterrev != cachedrev:
998 while iterrev != e[3] and iterrev != cachedrev:
990 chain.append(iterrev)
999 chain.append(iterrev)
991 if generaldelta:
1000 if generaldelta:
992 iterrev = e[3]
1001 iterrev = e[3]
993 else:
1002 else:
994 iterrev -= 1
1003 iterrev -= 1
995 e = index[iterrev]
1004 e = index[iterrev]
996
1005
997 if iterrev == cachedrev:
1006 if iterrev == cachedrev:
998 # cache hit
1007 # cache hit
999 text = self._cache[2]
1008 text = self._cache[2]
1000 else:
1009 else:
1001 chain.append(iterrev)
1010 chain.append(iterrev)
1002 chain.reverse()
1011 chain.reverse()
1003
1012
1004 # drop cache to save memory
1013 # drop cache to save memory
1005 self._cache = None
1014 self._cache = None
1006
1015
1007 bins = self._chunks(chain)
1016 bins = self._chunks(chain)
1008 if text is None:
1017 if text is None:
1009 text = str(bins[0])
1018 text = str(bins[0])
1010 bins = bins[1:]
1019 bins = bins[1:]
1011
1020
1012 text = mdiff.patches(text, bins)
1021 text = mdiff.patches(text, bins)
1013
1022
1014 text = self._checkhash(text, node, rev)
1023 text = self._checkhash(text, node, rev)
1015
1024
1016 self._cache = (node, rev, text)
1025 self._cache = (node, rev, text)
1017 return text
1026 return text
1018
1027
1019 def _checkhash(self, text, node, rev):
1028 def _checkhash(self, text, node, rev):
1020 p1, p2 = self.parents(node)
1029 p1, p2 = self.parents(node)
1021 self.checkhash(text, p1, p2, node, rev)
1030 self.checkhash(text, p1, p2, node, rev)
1022 return text
1031 return text
1023
1032
1024 def checkhash(self, text, p1, p2, node, rev=None):
1033 def checkhash(self, text, p1, p2, node, rev=None):
1025 if node != hash(text, p1, p2):
1034 if node != hash(text, p1, p2):
1026 revornode = rev
1035 revornode = rev
1027 if revornode is None:
1036 if revornode is None:
1028 revornode = templatefilters.short(hex(node))
1037 revornode = templatefilters.short(hex(node))
1029 raise RevlogError(_("integrity check failed on %s:%s")
1038 raise RevlogError(_("integrity check failed on %s:%s")
1030 % (self.indexfile, revornode))
1039 % (self.indexfile, revornode))
1031
1040
1032 def checkinlinesize(self, tr, fp=None):
1041 def checkinlinesize(self, tr, fp=None):
1033 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1042 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1034 return
1043 return
1035
1044
1036 trinfo = tr.find(self.indexfile)
1045 trinfo = tr.find(self.indexfile)
1037 if trinfo is None:
1046 if trinfo is None:
1038 raise RevlogError(_("%s not found in the transaction")
1047 raise RevlogError(_("%s not found in the transaction")
1039 % self.indexfile)
1048 % self.indexfile)
1040
1049
1041 trindex = trinfo[2]
1050 trindex = trinfo[2]
1042 dataoff = self.start(trindex)
1051 dataoff = self.start(trindex)
1043
1052
1044 tr.add(self.datafile, dataoff)
1053 tr.add(self.datafile, dataoff)
1045
1054
1046 if fp:
1055 if fp:
1047 fp.flush()
1056 fp.flush()
1048 fp.close()
1057 fp.close()
1049
1058
1050 df = self.opener(self.datafile, 'w')
1059 df = self.opener(self.datafile, 'w')
1051 try:
1060 try:
1052 for r in self:
1061 for r in self:
1053 df.write(self._chunkraw(r, r))
1062 df.write(self._chunkraw(r, r))
1054 finally:
1063 finally:
1055 df.close()
1064 df.close()
1056
1065
1057 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1066 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1058 self.version &= ~(REVLOGNGINLINEDATA)
1067 self.version &= ~(REVLOGNGINLINEDATA)
1059 self._inline = False
1068 self._inline = False
1060 for i in self:
1069 for i in self:
1061 e = self._io.packentry(self.index[i], self.node, self.version, i)
1070 e = self._io.packentry(self.index[i], self.node, self.version, i)
1062 fp.write(e)
1071 fp.write(e)
1063
1072
1064 # if we don't call close, the temp file will never replace the
1073 # if we don't call close, the temp file will never replace the
1065 # real index
1074 # real index
1066 fp.close()
1075 fp.close()
1067
1076
1068 tr.replace(self.indexfile, trindex * self._io.size)
1077 tr.replace(self.indexfile, trindex * self._io.size)
1069 self._chunkclear()
1078 self._chunkclear()
1070
1079
1071 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1080 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1072 node=None):
1081 node=None):
1073 """add a revision to the log
1082 """add a revision to the log
1074
1083
1075 text - the revision data to add
1084 text - the revision data to add
1076 transaction - the transaction object used for rollback
1085 transaction - the transaction object used for rollback
1077 link - the linkrev data to add
1086 link - the linkrev data to add
1078 p1, p2 - the parent nodeids of the revision
1087 p1, p2 - the parent nodeids of the revision
1079 cachedelta - an optional precomputed delta
1088 cachedelta - an optional precomputed delta
1080 node - nodeid of revision; typically node is not specified, and it is
1089 node - nodeid of revision; typically node is not specified, and it is
1081 computed by default as hash(text, p1, p2), however subclasses might
1090 computed by default as hash(text, p1, p2), however subclasses might
1082 use different hashing method (and override checkhash() in such case)
1091 use different hashing method (and override checkhash() in such case)
1083 """
1092 """
1084 if link == nullrev:
1093 if link == nullrev:
1085 raise RevlogError(_("attempted to add linkrev -1 to %s")
1094 raise RevlogError(_("attempted to add linkrev -1 to %s")
1086 % self.indexfile)
1095 % self.indexfile)
1087 node = node or hash(text, p1, p2)
1096 node = node or hash(text, p1, p2)
1088 if node in self.nodemap:
1097 if node in self.nodemap:
1089 return node
1098 return node
1090
1099
1091 dfh = None
1100 dfh = None
1092 if not self._inline:
1101 if not self._inline:
1093 dfh = self.opener(self.datafile, "a")
1102 dfh = self.opener(self.datafile, "a")
1094 ifh = self.opener(self.indexfile, "a+")
1103 ifh = self.opener(self.indexfile, "a+")
1095 try:
1104 try:
1096 return self._addrevision(node, text, transaction, link, p1, p2,
1105 return self._addrevision(node, text, transaction, link, p1, p2,
1097 cachedelta, ifh, dfh)
1106 cachedelta, ifh, dfh)
1098 finally:
1107 finally:
1099 if dfh:
1108 if dfh:
1100 dfh.close()
1109 dfh.close()
1101 ifh.close()
1110 ifh.close()
1102
1111
1103 def compress(self, text):
1112 def compress(self, text):
1104 """ generate a possibly-compressed representation of text """
1113 """ generate a possibly-compressed representation of text """
1105 if not text:
1114 if not text:
1106 return ("", text)
1115 return ("", text)
1107 l = len(text)
1116 l = len(text)
1108 bin = None
1117 bin = None
1109 if l < 44:
1118 if l < 44:
1110 pass
1119 pass
1111 elif l > 1000000:
1120 elif l > 1000000:
1112 # zlib makes an internal copy, thus doubling memory usage for
1121 # zlib makes an internal copy, thus doubling memory usage for
1113 # large files, so lets do this in pieces
1122 # large files, so lets do this in pieces
1114 z = zlib.compressobj()
1123 z = zlib.compressobj()
1115 p = []
1124 p = []
1116 pos = 0
1125 pos = 0
1117 while pos < l:
1126 while pos < l:
1118 pos2 = pos + 2**20
1127 pos2 = pos + 2**20
1119 p.append(z.compress(text[pos:pos2]))
1128 p.append(z.compress(text[pos:pos2]))
1120 pos = pos2
1129 pos = pos2
1121 p.append(z.flush())
1130 p.append(z.flush())
1122 if sum(map(len, p)) < l:
1131 if sum(map(len, p)) < l:
1123 bin = "".join(p)
1132 bin = "".join(p)
1124 else:
1133 else:
1125 bin = _compress(text)
1134 bin = _compress(text)
1126 if bin is None or len(bin) > l:
1135 if bin is None or len(bin) > l:
1127 if text[0] == '\0':
1136 if text[0] == '\0':
1128 return ("", text)
1137 return ("", text)
1129 return ('u', text)
1138 return ('u', text)
1130 return ("", bin)
1139 return ("", bin)
1131
1140
1132 def _addrevision(self, node, text, transaction, link, p1, p2,
1141 def _addrevision(self, node, text, transaction, link, p1, p2,
1133 cachedelta, ifh, dfh):
1142 cachedelta, ifh, dfh):
1134 """internal function to add revisions to the log
1143 """internal function to add revisions to the log
1135
1144
1136 see addrevision for argument descriptions.
1145 see addrevision for argument descriptions.
1137 invariants:
1146 invariants:
1138 - text is optional (can be None); if not set, cachedelta must be set.
1147 - text is optional (can be None); if not set, cachedelta must be set.
1139 if both are set, they must correspond to each other.
1148 if both are set, they must correspond to each other.
1140 """
1149 """
1141 btext = [text]
1150 btext = [text]
1142 def buildtext():
1151 def buildtext():
1143 if btext[0] is not None:
1152 if btext[0] is not None:
1144 return btext[0]
1153 return btext[0]
1145 # flush any pending writes here so we can read it in revision
1154 # flush any pending writes here so we can read it in revision
1146 if dfh:
1155 if dfh:
1147 dfh.flush()
1156 dfh.flush()
1148 ifh.flush()
1157 ifh.flush()
1149 basetext = self.revision(self.node(cachedelta[0]))
1158 basetext = self.revision(self.node(cachedelta[0]))
1150 btext[0] = mdiff.patch(basetext, cachedelta[1])
1159 btext[0] = mdiff.patch(basetext, cachedelta[1])
1151 self.checkhash(btext[0], p1, p2, node)
1160 self.checkhash(btext[0], p1, p2, node)
1152 return btext[0]
1161 return btext[0]
1153
1162
1154 def builddelta(rev):
1163 def builddelta(rev):
1155 # can we use the cached delta?
1164 # can we use the cached delta?
1156 if cachedelta and cachedelta[0] == rev:
1165 if cachedelta and cachedelta[0] == rev:
1157 delta = cachedelta[1]
1166 delta = cachedelta[1]
1158 else:
1167 else:
1159 t = buildtext()
1168 t = buildtext()
1160 ptext = self.revision(self.node(rev))
1169 ptext = self.revision(self.node(rev))
1161 delta = mdiff.textdiff(ptext, t)
1170 delta = mdiff.textdiff(ptext, t)
1162 data = self.compress(delta)
1171 data = self.compress(delta)
1163 l = len(data[1]) + len(data[0])
1172 l = len(data[1]) + len(data[0])
1164 if basecache[0] == rev:
1173 if basecache[0] == rev:
1165 chainbase = basecache[1]
1174 chainbase = basecache[1]
1166 else:
1175 else:
1167 chainbase = self.chainbase(rev)
1176 chainbase = self.chainbase(rev)
1168 dist = l + offset - self.start(chainbase)
1177 dist = l + offset - self.start(chainbase)
1169 if self._generaldelta:
1178 if self._generaldelta:
1170 base = rev
1179 base = rev
1171 else:
1180 else:
1172 base = chainbase
1181 base = chainbase
1173 return dist, l, data, base, chainbase
1182 return dist, l, data, base, chainbase
1174
1183
1175 curr = len(self)
1184 curr = len(self)
1176 prev = curr - 1
1185 prev = curr - 1
1177 base = chainbase = curr
1186 base = chainbase = curr
1178 offset = self.end(prev)
1187 offset = self.end(prev)
1179 flags = 0
1188 flags = 0
1180 d = None
1189 d = None
1181 if self._basecache is None:
1190 if self._basecache is None:
1182 self._basecache = (prev, self.chainbase(prev))
1191 self._basecache = (prev, self.chainbase(prev))
1183 basecache = self._basecache
1192 basecache = self._basecache
1184 p1r, p2r = self.rev(p1), self.rev(p2)
1193 p1r, p2r = self.rev(p1), self.rev(p2)
1185
1194
1186 # should we try to build a delta?
1195 # should we try to build a delta?
1187 if prev != nullrev:
1196 if prev != nullrev:
1188 if self._generaldelta:
1197 if self._generaldelta:
1189 if p1r >= basecache[1]:
1198 if p1r >= basecache[1]:
1190 d = builddelta(p1r)
1199 d = builddelta(p1r)
1191 elif p2r >= basecache[1]:
1200 elif p2r >= basecache[1]:
1192 d = builddelta(p2r)
1201 d = builddelta(p2r)
1193 else:
1202 else:
1194 d = builddelta(prev)
1203 d = builddelta(prev)
1195 else:
1204 else:
1196 d = builddelta(prev)
1205 d = builddelta(prev)
1197 dist, l, data, base, chainbase = d
1206 dist, l, data, base, chainbase = d
1198
1207
1199 # full versions are inserted when the needed deltas
1208 # full versions are inserted when the needed deltas
1200 # become comparable to the uncompressed text
1209 # become comparable to the uncompressed text
1201 if text is None:
1210 if text is None:
1202 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1211 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1203 cachedelta[1])
1212 cachedelta[1])
1204 else:
1213 else:
1205 textlen = len(text)
1214 textlen = len(text)
1206 if d is None or dist > textlen * 2:
1215 if d is None or dist > textlen * 2:
1207 text = buildtext()
1216 text = buildtext()
1208 data = self.compress(text)
1217 data = self.compress(text)
1209 l = len(data[1]) + len(data[0])
1218 l = len(data[1]) + len(data[0])
1210 base = chainbase = curr
1219 base = chainbase = curr
1211
1220
1212 e = (offset_type(offset, flags), l, textlen,
1221 e = (offset_type(offset, flags), l, textlen,
1213 base, link, p1r, p2r, node)
1222 base, link, p1r, p2r, node)
1214 self.index.insert(-1, e)
1223 self.index.insert(-1, e)
1215 self.nodemap[node] = curr
1224 self.nodemap[node] = curr
1216
1225
1217 entry = self._io.packentry(e, self.node, self.version, curr)
1226 entry = self._io.packentry(e, self.node, self.version, curr)
1218 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1227 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1219
1228
1220 if type(text) == str: # only accept immutable objects
1229 if type(text) == str: # only accept immutable objects
1221 self._cache = (node, curr, text)
1230 self._cache = (node, curr, text)
1222 self._basecache = (curr, chainbase)
1231 self._basecache = (curr, chainbase)
1223 return node
1232 return node
1224
1233
1225 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1234 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1226 curr = len(self) - 1
1235 curr = len(self) - 1
1227 if not self._inline:
1236 if not self._inline:
1228 transaction.add(self.datafile, offset)
1237 transaction.add(self.datafile, offset)
1229 transaction.add(self.indexfile, curr * len(entry))
1238 transaction.add(self.indexfile, curr * len(entry))
1230 if data[0]:
1239 if data[0]:
1231 dfh.write(data[0])
1240 dfh.write(data[0])
1232 dfh.write(data[1])
1241 dfh.write(data[1])
1233 dfh.flush()
1242 dfh.flush()
1234 ifh.write(entry)
1243 ifh.write(entry)
1235 else:
1244 else:
1236 offset += curr * self._io.size
1245 offset += curr * self._io.size
1237 transaction.add(self.indexfile, offset, curr)
1246 transaction.add(self.indexfile, offset, curr)
1238 ifh.write(entry)
1247 ifh.write(entry)
1239 ifh.write(data[0])
1248 ifh.write(data[0])
1240 ifh.write(data[1])
1249 ifh.write(data[1])
1241 self.checkinlinesize(transaction, ifh)
1250 self.checkinlinesize(transaction, ifh)
1242
1251
1243 def addgroup(self, bundle, linkmapper, transaction):
1252 def addgroup(self, bundle, linkmapper, transaction):
1244 """
1253 """
1245 add a delta group
1254 add a delta group
1246
1255
1247 given a set of deltas, add them to the revision log. the
1256 given a set of deltas, add them to the revision log. the
1248 first delta is against its parent, which should be in our
1257 first delta is against its parent, which should be in our
1249 log, the rest are against the previous delta.
1258 log, the rest are against the previous delta.
1250 """
1259 """
1251
1260
1252 # track the base of the current delta log
1261 # track the base of the current delta log
1253 content = []
1262 content = []
1254 node = None
1263 node = None
1255
1264
1256 r = len(self)
1265 r = len(self)
1257 end = 0
1266 end = 0
1258 if r:
1267 if r:
1259 end = self.end(r - 1)
1268 end = self.end(r - 1)
1260 ifh = self.opener(self.indexfile, "a+")
1269 ifh = self.opener(self.indexfile, "a+")
1261 isize = r * self._io.size
1270 isize = r * self._io.size
1262 if self._inline:
1271 if self._inline:
1263 transaction.add(self.indexfile, end + isize, r)
1272 transaction.add(self.indexfile, end + isize, r)
1264 dfh = None
1273 dfh = None
1265 else:
1274 else:
1266 transaction.add(self.indexfile, isize, r)
1275 transaction.add(self.indexfile, isize, r)
1267 transaction.add(self.datafile, end)
1276 transaction.add(self.datafile, end)
1268 dfh = self.opener(self.datafile, "a")
1277 dfh = self.opener(self.datafile, "a")
1269
1278
1270 try:
1279 try:
1271 # loop through our set of deltas
1280 # loop through our set of deltas
1272 chain = None
1281 chain = None
1273 while True:
1282 while True:
1274 chunkdata = bundle.deltachunk(chain)
1283 chunkdata = bundle.deltachunk(chain)
1275 if not chunkdata:
1284 if not chunkdata:
1276 break
1285 break
1277 node = chunkdata['node']
1286 node = chunkdata['node']
1278 p1 = chunkdata['p1']
1287 p1 = chunkdata['p1']
1279 p2 = chunkdata['p2']
1288 p2 = chunkdata['p2']
1280 cs = chunkdata['cs']
1289 cs = chunkdata['cs']
1281 deltabase = chunkdata['deltabase']
1290 deltabase = chunkdata['deltabase']
1282 delta = chunkdata['delta']
1291 delta = chunkdata['delta']
1283
1292
1284 content.append(node)
1293 content.append(node)
1285
1294
1286 link = linkmapper(cs)
1295 link = linkmapper(cs)
1287 if node in self.nodemap:
1296 if node in self.nodemap:
1288 # this can happen if two branches make the same change
1297 # this can happen if two branches make the same change
1289 chain = node
1298 chain = node
1290 continue
1299 continue
1291
1300
1292 for p in (p1, p2):
1301 for p in (p1, p2):
1293 if p not in self.nodemap:
1302 if p not in self.nodemap:
1294 raise LookupError(p, self.indexfile,
1303 raise LookupError(p, self.indexfile,
1295 _('unknown parent'))
1304 _('unknown parent'))
1296
1305
1297 if deltabase not in self.nodemap:
1306 if deltabase not in self.nodemap:
1298 raise LookupError(deltabase, self.indexfile,
1307 raise LookupError(deltabase, self.indexfile,
1299 _('unknown delta base'))
1308 _('unknown delta base'))
1300
1309
1301 baserev = self.rev(deltabase)
1310 baserev = self.rev(deltabase)
1302 chain = self._addrevision(node, None, transaction, link,
1311 chain = self._addrevision(node, None, transaction, link,
1303 p1, p2, (baserev, delta), ifh, dfh)
1312 p1, p2, (baserev, delta), ifh, dfh)
1304 if not dfh and not self._inline:
1313 if not dfh and not self._inline:
1305 # addrevision switched from inline to conventional
1314 # addrevision switched from inline to conventional
1306 # reopen the index
1315 # reopen the index
1307 ifh.close()
1316 ifh.close()
1308 dfh = self.opener(self.datafile, "a")
1317 dfh = self.opener(self.datafile, "a")
1309 ifh = self.opener(self.indexfile, "a")
1318 ifh = self.opener(self.indexfile, "a")
1310 finally:
1319 finally:
1311 if dfh:
1320 if dfh:
1312 dfh.close()
1321 dfh.close()
1313 ifh.close()
1322 ifh.close()
1314
1323
1315 return content
1324 return content
1316
1325
1317 def getstrippoint(self, minlink):
1326 def getstrippoint(self, minlink):
1318 """find the minimum rev that must be stripped to strip the linkrev
1327 """find the minimum rev that must be stripped to strip the linkrev
1319
1328
1320 Returns a tuple containing the minimum rev and a set of all revs that
1329 Returns a tuple containing the minimum rev and a set of all revs that
1321 have linkrevs that will be broken by this strip.
1330 have linkrevs that will be broken by this strip.
1322 """
1331 """
1323 brokenrevs = set()
1332 brokenrevs = set()
1324 strippoint = len(self)
1333 strippoint = len(self)
1325
1334
1326 heads = {}
1335 heads = {}
1327 futurelargelinkrevs = set()
1336 futurelargelinkrevs = set()
1328 for head in self.headrevs():
1337 for head in self.headrevs():
1329 headlinkrev = self.linkrev(head)
1338 headlinkrev = self.linkrev(head)
1330 heads[head] = headlinkrev
1339 heads[head] = headlinkrev
1331 if headlinkrev >= minlink:
1340 if headlinkrev >= minlink:
1332 futurelargelinkrevs.add(headlinkrev)
1341 futurelargelinkrevs.add(headlinkrev)
1333
1342
1334 # This algorithm involves walking down the rev graph, starting at the
1343 # This algorithm involves walking down the rev graph, starting at the
1335 # heads. Since the revs are topologically sorted according to linkrev,
1344 # heads. Since the revs are topologically sorted according to linkrev,
1336 # once all head linkrevs are below the minlink, we know there are
1345 # once all head linkrevs are below the minlink, we know there are
1337 # no more revs that could have a linkrev greater than minlink.
1346 # no more revs that could have a linkrev greater than minlink.
1338 # So we can stop walking.
1347 # So we can stop walking.
1339 while futurelargelinkrevs:
1348 while futurelargelinkrevs:
1340 strippoint -= 1
1349 strippoint -= 1
1341 linkrev = heads.pop(strippoint)
1350 linkrev = heads.pop(strippoint)
1342
1351
1343 if linkrev < minlink:
1352 if linkrev < minlink:
1344 brokenrevs.add(strippoint)
1353 brokenrevs.add(strippoint)
1345 else:
1354 else:
1346 futurelargelinkrevs.remove(linkrev)
1355 futurelargelinkrevs.remove(linkrev)
1347
1356
1348 for p in self.parentrevs(strippoint):
1357 for p in self.parentrevs(strippoint):
1349 if p != nullrev:
1358 if p != nullrev:
1350 plinkrev = self.linkrev(p)
1359 plinkrev = self.linkrev(p)
1351 heads[p] = plinkrev
1360 heads[p] = plinkrev
1352 if plinkrev >= minlink:
1361 if plinkrev >= minlink:
1353 futurelargelinkrevs.add(plinkrev)
1362 futurelargelinkrevs.add(plinkrev)
1354
1363
1355 return strippoint, brokenrevs
1364 return strippoint, brokenrevs
1356
1365
1357 def strip(self, minlink, transaction):
1366 def strip(self, minlink, transaction):
1358 """truncate the revlog on the first revision with a linkrev >= minlink
1367 """truncate the revlog on the first revision with a linkrev >= minlink
1359
1368
1360 This function is called when we're stripping revision minlink and
1369 This function is called when we're stripping revision minlink and
1361 its descendants from the repository.
1370 its descendants from the repository.
1362
1371
1363 We have to remove all revisions with linkrev >= minlink, because
1372 We have to remove all revisions with linkrev >= minlink, because
1364 the equivalent changelog revisions will be renumbered after the
1373 the equivalent changelog revisions will be renumbered after the
1365 strip.
1374 strip.
1366
1375
1367 So we truncate the revlog on the first of these revisions, and
1376 So we truncate the revlog on the first of these revisions, and
1368 trust that the caller has saved the revisions that shouldn't be
1377 trust that the caller has saved the revisions that shouldn't be
1369 removed and that it'll re-add them after this truncation.
1378 removed and that it'll re-add them after this truncation.
1370 """
1379 """
1371 if len(self) == 0:
1380 if len(self) == 0:
1372 return
1381 return
1373
1382
1374 rev, _ = self.getstrippoint(minlink)
1383 rev, _ = self.getstrippoint(minlink)
1375 if rev == len(self):
1384 if rev == len(self):
1376 return
1385 return
1377
1386
1378 # first truncate the files on disk
1387 # first truncate the files on disk
1379 end = self.start(rev)
1388 end = self.start(rev)
1380 if not self._inline:
1389 if not self._inline:
1381 transaction.add(self.datafile, end)
1390 transaction.add(self.datafile, end)
1382 end = rev * self._io.size
1391 end = rev * self._io.size
1383 else:
1392 else:
1384 end += rev * self._io.size
1393 end += rev * self._io.size
1385
1394
1386 transaction.add(self.indexfile, end)
1395 transaction.add(self.indexfile, end)
1387
1396
1388 # then reset internal state in memory to forget those revisions
1397 # then reset internal state in memory to forget those revisions
1389 self._cache = None
1398 self._cache = None
1390 self._chunkclear()
1399 self._chunkclear()
1391 for x in xrange(rev, len(self)):
1400 for x in xrange(rev, len(self)):
1392 del self.nodemap[self.node(x)]
1401 del self.nodemap[self.node(x)]
1393
1402
1394 del self.index[rev:-1]
1403 del self.index[rev:-1]
1395
1404
1396 def checksize(self):
1405 def checksize(self):
1397 expected = 0
1406 expected = 0
1398 if len(self):
1407 if len(self):
1399 expected = max(0, self.end(len(self) - 1))
1408 expected = max(0, self.end(len(self) - 1))
1400
1409
1401 try:
1410 try:
1402 f = self.opener(self.datafile)
1411 f = self.opener(self.datafile)
1403 f.seek(0, 2)
1412 f.seek(0, 2)
1404 actual = f.tell()
1413 actual = f.tell()
1405 f.close()
1414 f.close()
1406 dd = actual - expected
1415 dd = actual - expected
1407 except IOError, inst:
1416 except IOError, inst:
1408 if inst.errno != errno.ENOENT:
1417 if inst.errno != errno.ENOENT:
1409 raise
1418 raise
1410 dd = 0
1419 dd = 0
1411
1420
1412 try:
1421 try:
1413 f = self.opener(self.indexfile)
1422 f = self.opener(self.indexfile)
1414 f.seek(0, 2)
1423 f.seek(0, 2)
1415 actual = f.tell()
1424 actual = f.tell()
1416 f.close()
1425 f.close()
1417 s = self._io.size
1426 s = self._io.size
1418 i = max(0, actual // s)
1427 i = max(0, actual // s)
1419 di = actual - (i * s)
1428 di = actual - (i * s)
1420 if self._inline:
1429 if self._inline:
1421 databytes = 0
1430 databytes = 0
1422 for r in self:
1431 for r in self:
1423 databytes += max(0, self.length(r))
1432 databytes += max(0, self.length(r))
1424 dd = 0
1433 dd = 0
1425 di = actual - len(self) * s - databytes
1434 di = actual - len(self) * s - databytes
1426 except IOError, inst:
1435 except IOError, inst:
1427 if inst.errno != errno.ENOENT:
1436 if inst.errno != errno.ENOENT:
1428 raise
1437 raise
1429 di = 0
1438 di = 0
1430
1439
1431 return (dd, di)
1440 return (dd, di)
1432
1441
1433 def files(self):
1442 def files(self):
1434 res = [self.indexfile]
1443 res = [self.indexfile]
1435 if not self._inline:
1444 if not self._inline:
1436 res.append(self.datafile)
1445 res.append(self.datafile)
1437 return res
1446 return res
General Comments 0
You need to be logged in to leave comments. Login now