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