##// END OF EJS Templates
revlog: don't cross-check ancestor result against Python version
Bryan O'Sullivan -
r18989:12a3474c default
parent child Browse files
Show More
@@ -1,1343 +1,1340 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, dagutil
17 import ancestor, mdiff, parsers, error, util, dagutil
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 = (0, 0)
203 self._basecache = (0, 0)
204 self._chunkcache = (0, '')
204 self._chunkcache = (0, '')
205 self.index = []
205 self.index = []
206 self._pcache = {}
206 self._pcache = {}
207 self._nodecache = {nullid: nullrev}
207 self._nodecache = {nullid: nullrev}
208 self._nodepos = None
208 self._nodepos = None
209
209
210 v = REVLOG_DEFAULT_VERSION
210 v = REVLOG_DEFAULT_VERSION
211 opts = getattr(opener, 'options', None)
211 opts = getattr(opener, 'options', None)
212 if opts is not None:
212 if opts is not None:
213 if 'revlogv1' in opts:
213 if 'revlogv1' in opts:
214 if 'generaldelta' in opts:
214 if 'generaldelta' in opts:
215 v |= REVLOGGENERALDELTA
215 v |= REVLOGGENERALDELTA
216 else:
216 else:
217 v = 0
217 v = 0
218
218
219 i = ''
219 i = ''
220 self._initempty = True
220 self._initempty = True
221 try:
221 try:
222 f = self.opener(self.indexfile)
222 f = self.opener(self.indexfile)
223 i = f.read()
223 i = f.read()
224 f.close()
224 f.close()
225 if len(i) > 0:
225 if len(i) > 0:
226 v = struct.unpack(versionformat, i[:4])[0]
226 v = struct.unpack(versionformat, i[:4])[0]
227 self._initempty = False
227 self._initempty = False
228 except IOError, inst:
228 except IOError, inst:
229 if inst.errno != errno.ENOENT:
229 if inst.errno != errno.ENOENT:
230 raise
230 raise
231
231
232 self.version = v
232 self.version = v
233 self._inline = v & REVLOGNGINLINEDATA
233 self._inline = v & REVLOGNGINLINEDATA
234 self._generaldelta = v & REVLOGGENERALDELTA
234 self._generaldelta = v & REVLOGGENERALDELTA
235 flags = v & ~0xFFFF
235 flags = v & ~0xFFFF
236 fmt = v & 0xFFFF
236 fmt = v & 0xFFFF
237 if fmt == REVLOGV0 and flags:
237 if fmt == REVLOGV0 and flags:
238 raise RevlogError(_("index %s unknown flags %#04x for format v0")
238 raise RevlogError(_("index %s unknown flags %#04x for format v0")
239 % (self.indexfile, flags >> 16))
239 % (self.indexfile, flags >> 16))
240 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
240 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
241 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
241 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
242 % (self.indexfile, flags >> 16))
242 % (self.indexfile, flags >> 16))
243 elif fmt > REVLOGNG:
243 elif fmt > REVLOGNG:
244 raise RevlogError(_("index %s unknown format %d")
244 raise RevlogError(_("index %s unknown format %d")
245 % (self.indexfile, fmt))
245 % (self.indexfile, fmt))
246
246
247 self._io = revlogio()
247 self._io = revlogio()
248 if self.version == REVLOGV0:
248 if self.version == REVLOGV0:
249 self._io = revlogoldio()
249 self._io = revlogoldio()
250 try:
250 try:
251 d = self._io.parseindex(i, self._inline)
251 d = self._io.parseindex(i, self._inline)
252 except (ValueError, IndexError):
252 except (ValueError, IndexError):
253 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
253 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
254 self.index, nodemap, self._chunkcache = d
254 self.index, nodemap, self._chunkcache = d
255 if nodemap is not None:
255 if nodemap is not None:
256 self.nodemap = self._nodecache = nodemap
256 self.nodemap = self._nodecache = nodemap
257 if not self._chunkcache:
257 if not self._chunkcache:
258 self._chunkclear()
258 self._chunkclear()
259
259
260 def tip(self):
260 def tip(self):
261 return self.node(len(self.index) - 2)
261 return self.node(len(self.index) - 2)
262 def __len__(self):
262 def __len__(self):
263 return len(self.index) - 1
263 return len(self.index) - 1
264 def __iter__(self):
264 def __iter__(self):
265 return iter(xrange(len(self)))
265 return iter(xrange(len(self)))
266 def revs(self, start=0, stop=None):
266 def revs(self, start=0, stop=None):
267 """iterate over all rev in this revlog (from start to stop)"""
267 """iterate over all rev in this revlog (from start to stop)"""
268 step = 1
268 step = 1
269 if stop is not None:
269 if stop is not None:
270 if start > stop:
270 if start > stop:
271 step = -1
271 step = -1
272 stop += step
272 stop += step
273 else:
273 else:
274 stop = len(self)
274 stop = len(self)
275 return xrange(start, stop, step)
275 return xrange(start, stop, step)
276
276
277 @util.propertycache
277 @util.propertycache
278 def nodemap(self):
278 def nodemap(self):
279 self.rev(self.node(0))
279 self.rev(self.node(0))
280 return self._nodecache
280 return self._nodecache
281
281
282 def hasnode(self, node):
282 def hasnode(self, node):
283 try:
283 try:
284 self.rev(node)
284 self.rev(node)
285 return True
285 return True
286 except KeyError:
286 except KeyError:
287 return False
287 return False
288
288
289 def clearcaches(self):
289 def clearcaches(self):
290 try:
290 try:
291 self._nodecache.clearcaches()
291 self._nodecache.clearcaches()
292 except AttributeError:
292 except AttributeError:
293 self._nodecache = {nullid: nullrev}
293 self._nodecache = {nullid: nullrev}
294 self._nodepos = None
294 self._nodepos = None
295
295
296 def rev(self, node):
296 def rev(self, node):
297 try:
297 try:
298 return self._nodecache[node]
298 return self._nodecache[node]
299 except RevlogError:
299 except RevlogError:
300 # parsers.c radix tree lookup failed
300 # parsers.c radix tree lookup failed
301 raise LookupError(node, self.indexfile, _('no node'))
301 raise LookupError(node, self.indexfile, _('no node'))
302 except KeyError:
302 except KeyError:
303 # pure python cache lookup failed
303 # pure python cache lookup failed
304 n = self._nodecache
304 n = self._nodecache
305 i = self.index
305 i = self.index
306 p = self._nodepos
306 p = self._nodepos
307 if p is None:
307 if p is None:
308 p = len(i) - 2
308 p = len(i) - 2
309 for r in xrange(p, -1, -1):
309 for r in xrange(p, -1, -1):
310 v = i[r][7]
310 v = i[r][7]
311 n[v] = r
311 n[v] = r
312 if v == node:
312 if v == node:
313 self._nodepos = r - 1
313 self._nodepos = r - 1
314 return r
314 return r
315 raise LookupError(node, self.indexfile, _('no node'))
315 raise LookupError(node, self.indexfile, _('no node'))
316
316
317 def node(self, rev):
317 def node(self, rev):
318 return self.index[rev][7]
318 return self.index[rev][7]
319 def linkrev(self, rev):
319 def linkrev(self, rev):
320 return self.index[rev][4]
320 return self.index[rev][4]
321 def parents(self, node):
321 def parents(self, node):
322 i = self.index
322 i = self.index
323 d = i[self.rev(node)]
323 d = i[self.rev(node)]
324 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
324 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
325 def parentrevs(self, rev):
325 def parentrevs(self, rev):
326 return self.index[rev][5:7]
326 return self.index[rev][5:7]
327 def start(self, rev):
327 def start(self, rev):
328 return int(self.index[rev][0] >> 16)
328 return int(self.index[rev][0] >> 16)
329 def end(self, rev):
329 def end(self, rev):
330 return self.start(rev) + self.length(rev)
330 return self.start(rev) + self.length(rev)
331 def length(self, rev):
331 def length(self, rev):
332 return self.index[rev][1]
332 return self.index[rev][1]
333 def chainbase(self, rev):
333 def chainbase(self, rev):
334 index = self.index
334 index = self.index
335 base = index[rev][3]
335 base = index[rev][3]
336 while base != rev:
336 while base != rev:
337 rev = base
337 rev = base
338 base = index[rev][3]
338 base = index[rev][3]
339 return base
339 return base
340 def flags(self, rev):
340 def flags(self, rev):
341 return self.index[rev][0] & 0xFFFF
341 return self.index[rev][0] & 0xFFFF
342 def rawsize(self, rev):
342 def rawsize(self, rev):
343 """return the length of the uncompressed text for a given revision"""
343 """return the length of the uncompressed text for a given revision"""
344 l = self.index[rev][2]
344 l = self.index[rev][2]
345 if l >= 0:
345 if l >= 0:
346 return l
346 return l
347
347
348 t = self.revision(self.node(rev))
348 t = self.revision(self.node(rev))
349 return len(t)
349 return len(t)
350 size = rawsize
350 size = rawsize
351
351
352 def ancestors(self, revs, stoprev=0, inclusive=False):
352 def ancestors(self, revs, stoprev=0, inclusive=False):
353 """Generate the ancestors of 'revs' in reverse topological order.
353 """Generate the ancestors of 'revs' in reverse topological order.
354 Does not generate revs lower than stoprev.
354 Does not generate revs lower than stoprev.
355
355
356 See the documentation for ancestor.lazyancestors for more details."""
356 See the documentation for ancestor.lazyancestors for more details."""
357
357
358 return ancestor.lazyancestors(self, revs, stoprev=stoprev,
358 return ancestor.lazyancestors(self, revs, stoprev=stoprev,
359 inclusive=inclusive)
359 inclusive=inclusive)
360
360
361 def descendants(self, revs):
361 def descendants(self, revs):
362 """Generate the descendants of 'revs' in revision order.
362 """Generate the descendants of 'revs' in revision order.
363
363
364 Yield a sequence of revision numbers starting with a child of
364 Yield a sequence of revision numbers starting with a child of
365 some rev in revs, i.e., each revision is *not* considered a
365 some rev in revs, i.e., each revision is *not* considered a
366 descendant of itself. Results are ordered by revision number (a
366 descendant of itself. Results are ordered by revision number (a
367 topological sort)."""
367 topological sort)."""
368 first = min(revs)
368 first = min(revs)
369 if first == nullrev:
369 if first == nullrev:
370 for i in self:
370 for i in self:
371 yield i
371 yield i
372 return
372 return
373
373
374 seen = set(revs)
374 seen = set(revs)
375 for i in self.revs(start=first + 1):
375 for i in self.revs(start=first + 1):
376 for x in self.parentrevs(i):
376 for x in self.parentrevs(i):
377 if x != nullrev and x in seen:
377 if x != nullrev and x in seen:
378 seen.add(i)
378 seen.add(i)
379 yield i
379 yield i
380 break
380 break
381
381
382 def findcommonmissing(self, common=None, heads=None):
382 def findcommonmissing(self, common=None, heads=None):
383 """Return a tuple of the ancestors of common and the ancestors of heads
383 """Return a tuple of the ancestors of common and the ancestors of heads
384 that are not ancestors of common. In revset terminology, we return the
384 that are not ancestors of common. In revset terminology, we return the
385 tuple:
385 tuple:
386
386
387 ::common, (::heads) - (::common)
387 ::common, (::heads) - (::common)
388
388
389 The list is sorted by revision number, meaning it is
389 The list is sorted by revision number, meaning it is
390 topologically sorted.
390 topologically sorted.
391
391
392 'heads' and 'common' are both lists of node IDs. If heads is
392 'heads' and 'common' are both lists of node IDs. If heads is
393 not supplied, uses all of the revlog's heads. If common is not
393 not supplied, uses all of the revlog's heads. If common is not
394 supplied, uses nullid."""
394 supplied, uses nullid."""
395 if common is None:
395 if common is None:
396 common = [nullid]
396 common = [nullid]
397 if heads is None:
397 if heads is None:
398 heads = self.heads()
398 heads = self.heads()
399
399
400 common = [self.rev(n) for n in common]
400 common = [self.rev(n) for n in common]
401 heads = [self.rev(n) for n in heads]
401 heads = [self.rev(n) for n in heads]
402
402
403 # we want the ancestors, but inclusive
403 # we want the ancestors, but inclusive
404 has = set(self.ancestors(common))
404 has = set(self.ancestors(common))
405 has.add(nullrev)
405 has.add(nullrev)
406 has.update(common)
406 has.update(common)
407
407
408 # take all ancestors from heads that aren't in has
408 # take all ancestors from heads that aren't in has
409 missing = set()
409 missing = set()
410 visit = util.deque(r for r in heads if r not in has)
410 visit = util.deque(r for r in heads if r not in has)
411 while visit:
411 while visit:
412 r = visit.popleft()
412 r = visit.popleft()
413 if r in missing:
413 if r in missing:
414 continue
414 continue
415 else:
415 else:
416 missing.add(r)
416 missing.add(r)
417 for p in self.parentrevs(r):
417 for p in self.parentrevs(r):
418 if p not in has:
418 if p not in has:
419 visit.append(p)
419 visit.append(p)
420 missing = list(missing)
420 missing = list(missing)
421 missing.sort()
421 missing.sort()
422 return has, [self.node(r) for r in missing]
422 return has, [self.node(r) for r in missing]
423
423
424 def findmissingrevs(self, common=None, heads=None):
424 def findmissingrevs(self, common=None, heads=None):
425 """Return the revision numbers of the ancestors of heads that
425 """Return the revision numbers of the ancestors of heads that
426 are not ancestors of common.
426 are not ancestors of common.
427
427
428 More specifically, return a list of revision numbers corresponding to
428 More specifically, return a list of revision numbers corresponding to
429 nodes N such that every N satisfies the following constraints:
429 nodes N such that every N satisfies the following constraints:
430
430
431 1. N is an ancestor of some node in 'heads'
431 1. N is an ancestor of some node in 'heads'
432 2. N is not an ancestor of any node in 'common'
432 2. N is not an ancestor of any node in 'common'
433
433
434 The list is sorted by revision number, meaning it is
434 The list is sorted by revision number, meaning it is
435 topologically sorted.
435 topologically sorted.
436
436
437 'heads' and 'common' are both lists of revision numbers. If heads is
437 'heads' and 'common' are both lists of revision numbers. If heads is
438 not supplied, uses all of the revlog's heads. If common is not
438 not supplied, uses all of the revlog's heads. If common is not
439 supplied, uses nullid."""
439 supplied, uses nullid."""
440 if common is None:
440 if common is None:
441 common = [nullrev]
441 common = [nullrev]
442 if heads is None:
442 if heads is None:
443 heads = self.headrevs()
443 heads = self.headrevs()
444
444
445 return ancestor.missingancestors(heads, common, self.parentrevs)
445 return ancestor.missingancestors(heads, common, self.parentrevs)
446
446
447 def findmissing(self, common=None, heads=None):
447 def findmissing(self, common=None, heads=None):
448 """Return the ancestors of heads that are not ancestors of common.
448 """Return the ancestors of heads that are not ancestors of common.
449
449
450 More specifically, return a list of nodes N such that every N
450 More specifically, return a list of nodes N such that every N
451 satisfies the following constraints:
451 satisfies the following constraints:
452
452
453 1. N is an ancestor of some node in 'heads'
453 1. N is an ancestor of some node in 'heads'
454 2. N is not an ancestor of any node in 'common'
454 2. N is not an ancestor of any node in 'common'
455
455
456 The list is sorted by revision number, meaning it is
456 The list is sorted by revision number, meaning it is
457 topologically sorted.
457 topologically sorted.
458
458
459 'heads' and 'common' are both lists of node IDs. If heads is
459 'heads' and 'common' are both lists of node IDs. If heads is
460 not supplied, uses all of the revlog's heads. If common is not
460 not supplied, uses all of the revlog's heads. If common is not
461 supplied, uses nullid."""
461 supplied, uses nullid."""
462 if common is None:
462 if common is None:
463 common = [nullid]
463 common = [nullid]
464 if heads is None:
464 if heads is None:
465 heads = self.heads()
465 heads = self.heads()
466
466
467 common = [self.rev(n) for n in common]
467 common = [self.rev(n) for n in common]
468 heads = [self.rev(n) for n in heads]
468 heads = [self.rev(n) for n in heads]
469
469
470 return [self.node(r) for r in
470 return [self.node(r) for r in
471 ancestor.missingancestors(heads, common, self.parentrevs)]
471 ancestor.missingancestors(heads, common, self.parentrevs)]
472
472
473 def nodesbetween(self, roots=None, heads=None):
473 def nodesbetween(self, roots=None, heads=None):
474 """Return a topological path from 'roots' to 'heads'.
474 """Return a topological path from 'roots' to 'heads'.
475
475
476 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
476 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
477 topologically sorted list of all nodes N that satisfy both of
477 topologically sorted list of all nodes N that satisfy both of
478 these constraints:
478 these constraints:
479
479
480 1. N is a descendant of some node in 'roots'
480 1. N is a descendant of some node in 'roots'
481 2. N is an ancestor of some node in 'heads'
481 2. N is an ancestor of some node in 'heads'
482
482
483 Every node is considered to be both a descendant and an ancestor
483 Every node is considered to be both a descendant and an ancestor
484 of itself, so every reachable node in 'roots' and 'heads' will be
484 of itself, so every reachable node in 'roots' and 'heads' will be
485 included in 'nodes'.
485 included in 'nodes'.
486
486
487 'outroots' is the list of reachable nodes in 'roots', i.e., the
487 'outroots' is the list of reachable nodes in 'roots', i.e., the
488 subset of 'roots' that is returned in 'nodes'. Likewise,
488 subset of 'roots' that is returned in 'nodes'. Likewise,
489 'outheads' is the subset of 'heads' that is also in 'nodes'.
489 'outheads' is the subset of 'heads' that is also in 'nodes'.
490
490
491 'roots' and 'heads' are both lists of node IDs. If 'roots' is
491 'roots' and 'heads' are both lists of node IDs. If 'roots' is
492 unspecified, uses nullid as the only root. If 'heads' is
492 unspecified, uses nullid as the only root. If 'heads' is
493 unspecified, uses list of all of the revlog's heads."""
493 unspecified, uses list of all of the revlog's heads."""
494 nonodes = ([], [], [])
494 nonodes = ([], [], [])
495 if roots is not None:
495 if roots is not None:
496 roots = list(roots)
496 roots = list(roots)
497 if not roots:
497 if not roots:
498 return nonodes
498 return nonodes
499 lowestrev = min([self.rev(n) for n in roots])
499 lowestrev = min([self.rev(n) for n in roots])
500 else:
500 else:
501 roots = [nullid] # Everybody's a descendant of nullid
501 roots = [nullid] # Everybody's a descendant of nullid
502 lowestrev = nullrev
502 lowestrev = nullrev
503 if (lowestrev == nullrev) and (heads is None):
503 if (lowestrev == nullrev) and (heads is None):
504 # We want _all_ the nodes!
504 # We want _all_ the nodes!
505 return ([self.node(r) for r in self], [nullid], list(self.heads()))
505 return ([self.node(r) for r in self], [nullid], list(self.heads()))
506 if heads is None:
506 if heads is None:
507 # All nodes are ancestors, so the latest ancestor is the last
507 # All nodes are ancestors, so the latest ancestor is the last
508 # node.
508 # node.
509 highestrev = len(self) - 1
509 highestrev = len(self) - 1
510 # Set ancestors to None to signal that every node is an ancestor.
510 # Set ancestors to None to signal that every node is an ancestor.
511 ancestors = None
511 ancestors = None
512 # Set heads to an empty dictionary for later discovery of heads
512 # Set heads to an empty dictionary for later discovery of heads
513 heads = {}
513 heads = {}
514 else:
514 else:
515 heads = list(heads)
515 heads = list(heads)
516 if not heads:
516 if not heads:
517 return nonodes
517 return nonodes
518 ancestors = set()
518 ancestors = set()
519 # Turn heads into a dictionary so we can remove 'fake' heads.
519 # Turn heads into a dictionary so we can remove 'fake' heads.
520 # Also, later we will be using it to filter out the heads we can't
520 # Also, later we will be using it to filter out the heads we can't
521 # find from roots.
521 # find from roots.
522 heads = dict.fromkeys(heads, False)
522 heads = dict.fromkeys(heads, False)
523 # Start at the top and keep marking parents until we're done.
523 # Start at the top and keep marking parents until we're done.
524 nodestotag = set(heads)
524 nodestotag = set(heads)
525 # Remember where the top was so we can use it as a limit later.
525 # Remember where the top was so we can use it as a limit later.
526 highestrev = max([self.rev(n) for n in nodestotag])
526 highestrev = max([self.rev(n) for n in nodestotag])
527 while nodestotag:
527 while nodestotag:
528 # grab a node to tag
528 # grab a node to tag
529 n = nodestotag.pop()
529 n = nodestotag.pop()
530 # Never tag nullid
530 # Never tag nullid
531 if n == nullid:
531 if n == nullid:
532 continue
532 continue
533 # A node's revision number represents its place in a
533 # A node's revision number represents its place in a
534 # topologically sorted list of nodes.
534 # topologically sorted list of nodes.
535 r = self.rev(n)
535 r = self.rev(n)
536 if r >= lowestrev:
536 if r >= lowestrev:
537 if n not in ancestors:
537 if n not in ancestors:
538 # If we are possibly a descendant of one of the roots
538 # If we are possibly a descendant of one of the roots
539 # and we haven't already been marked as an ancestor
539 # and we haven't already been marked as an ancestor
540 ancestors.add(n) # Mark as ancestor
540 ancestors.add(n) # Mark as ancestor
541 # Add non-nullid parents to list of nodes to tag.
541 # Add non-nullid parents to list of nodes to tag.
542 nodestotag.update([p for p in self.parents(n) if
542 nodestotag.update([p for p in self.parents(n) if
543 p != nullid])
543 p != nullid])
544 elif n in heads: # We've seen it before, is it a fake head?
544 elif n in heads: # We've seen it before, is it a fake head?
545 # So it is, real heads should not be the ancestors of
545 # So it is, real heads should not be the ancestors of
546 # any other heads.
546 # any other heads.
547 heads.pop(n)
547 heads.pop(n)
548 if not ancestors:
548 if not ancestors:
549 return nonodes
549 return nonodes
550 # Now that we have our set of ancestors, we want to remove any
550 # Now that we have our set of ancestors, we want to remove any
551 # roots that are not ancestors.
551 # roots that are not ancestors.
552
552
553 # If one of the roots was nullid, everything is included anyway.
553 # If one of the roots was nullid, everything is included anyway.
554 if lowestrev > nullrev:
554 if lowestrev > nullrev:
555 # But, since we weren't, let's recompute the lowest rev to not
555 # But, since we weren't, let's recompute the lowest rev to not
556 # include roots that aren't ancestors.
556 # include roots that aren't ancestors.
557
557
558 # Filter out roots that aren't ancestors of heads
558 # Filter out roots that aren't ancestors of heads
559 roots = [n for n in roots if n in ancestors]
559 roots = [n for n in roots if n in ancestors]
560 # Recompute the lowest revision
560 # Recompute the lowest revision
561 if roots:
561 if roots:
562 lowestrev = min([self.rev(n) for n in roots])
562 lowestrev = min([self.rev(n) for n in roots])
563 else:
563 else:
564 # No more roots? Return empty list
564 # No more roots? Return empty list
565 return nonodes
565 return nonodes
566 else:
566 else:
567 # We are descending from nullid, and don't need to care about
567 # We are descending from nullid, and don't need to care about
568 # any other roots.
568 # any other roots.
569 lowestrev = nullrev
569 lowestrev = nullrev
570 roots = [nullid]
570 roots = [nullid]
571 # Transform our roots list into a set.
571 # Transform our roots list into a set.
572 descendants = set(roots)
572 descendants = set(roots)
573 # Also, keep the original roots so we can filter out roots that aren't
573 # Also, keep the original roots so we can filter out roots that aren't
574 # 'real' roots (i.e. are descended from other roots).
574 # 'real' roots (i.e. are descended from other roots).
575 roots = descendants.copy()
575 roots = descendants.copy()
576 # Our topologically sorted list of output nodes.
576 # Our topologically sorted list of output nodes.
577 orderedout = []
577 orderedout = []
578 # Don't start at nullid since we don't want nullid in our output list,
578 # Don't start at nullid since we don't want nullid in our output list,
579 # and if nullid shows up in descendants, empty parents will look like
579 # and if nullid shows up in descendants, empty parents will look like
580 # they're descendants.
580 # they're descendants.
581 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
581 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
582 n = self.node(r)
582 n = self.node(r)
583 isdescendant = False
583 isdescendant = False
584 if lowestrev == nullrev: # Everybody is a descendant of nullid
584 if lowestrev == nullrev: # Everybody is a descendant of nullid
585 isdescendant = True
585 isdescendant = True
586 elif n in descendants:
586 elif n in descendants:
587 # n is already a descendant
587 # n is already a descendant
588 isdescendant = True
588 isdescendant = True
589 # This check only needs to be done here because all the roots
589 # This check only needs to be done here because all the roots
590 # will start being marked is descendants before the loop.
590 # will start being marked is descendants before the loop.
591 if n in roots:
591 if n in roots:
592 # If n was a root, check if it's a 'real' root.
592 # If n was a root, check if it's a 'real' root.
593 p = tuple(self.parents(n))
593 p = tuple(self.parents(n))
594 # If any of its parents are descendants, it's not a root.
594 # If any of its parents are descendants, it's not a root.
595 if (p[0] in descendants) or (p[1] in descendants):
595 if (p[0] in descendants) or (p[1] in descendants):
596 roots.remove(n)
596 roots.remove(n)
597 else:
597 else:
598 p = tuple(self.parents(n))
598 p = tuple(self.parents(n))
599 # A node is a descendant if either of its parents are
599 # A node is a descendant if either of its parents are
600 # descendants. (We seeded the dependents list with the roots
600 # descendants. (We seeded the dependents list with the roots
601 # up there, remember?)
601 # up there, remember?)
602 if (p[0] in descendants) or (p[1] in descendants):
602 if (p[0] in descendants) or (p[1] in descendants):
603 descendants.add(n)
603 descendants.add(n)
604 isdescendant = True
604 isdescendant = True
605 if isdescendant and ((ancestors is None) or (n in ancestors)):
605 if isdescendant and ((ancestors is None) or (n in ancestors)):
606 # Only include nodes that are both descendants and ancestors.
606 # Only include nodes that are both descendants and ancestors.
607 orderedout.append(n)
607 orderedout.append(n)
608 if (ancestors is not None) and (n in heads):
608 if (ancestors is not None) and (n in heads):
609 # We're trying to figure out which heads are reachable
609 # We're trying to figure out which heads are reachable
610 # from roots.
610 # from roots.
611 # Mark this head as having been reached
611 # Mark this head as having been reached
612 heads[n] = True
612 heads[n] = True
613 elif ancestors is None:
613 elif ancestors is None:
614 # Otherwise, we're trying to discover the heads.
614 # Otherwise, we're trying to discover the heads.
615 # Assume this is a head because if it isn't, the next step
615 # Assume this is a head because if it isn't, the next step
616 # will eventually remove it.
616 # will eventually remove it.
617 heads[n] = True
617 heads[n] = True
618 # But, obviously its parents aren't.
618 # But, obviously its parents aren't.
619 for p in self.parents(n):
619 for p in self.parents(n):
620 heads.pop(p, None)
620 heads.pop(p, None)
621 heads = [n for n, flag in heads.iteritems() if flag]
621 heads = [n for n, flag in heads.iteritems() if flag]
622 roots = list(roots)
622 roots = list(roots)
623 assert orderedout
623 assert orderedout
624 assert roots
624 assert roots
625 assert heads
625 assert heads
626 return (orderedout, roots, heads)
626 return (orderedout, roots, heads)
627
627
628 def headrevs(self):
628 def headrevs(self):
629 try:
629 try:
630 return self.index.headrevs()
630 return self.index.headrevs()
631 except AttributeError:
631 except AttributeError:
632 return self._headrevs()
632 return self._headrevs()
633
633
634 def _headrevs(self):
634 def _headrevs(self):
635 count = len(self)
635 count = len(self)
636 if not count:
636 if not count:
637 return [nullrev]
637 return [nullrev]
638 # we won't iter over filtered rev so nobody is a head at start
638 # we won't iter over filtered rev so nobody is a head at start
639 ishead = [0] * (count + 1)
639 ishead = [0] * (count + 1)
640 index = self.index
640 index = self.index
641 for r in self:
641 for r in self:
642 ishead[r] = 1 # I may be an head
642 ishead[r] = 1 # I may be an head
643 e = index[r]
643 e = index[r]
644 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
644 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
645 return [r for r, val in enumerate(ishead) if val]
645 return [r for r, val in enumerate(ishead) if val]
646
646
647 def heads(self, start=None, stop=None):
647 def heads(self, start=None, stop=None):
648 """return the list of all nodes that have no children
648 """return the list of all nodes that have no children
649
649
650 if start is specified, only heads that are descendants of
650 if start is specified, only heads that are descendants of
651 start will be returned
651 start will be returned
652 if stop is specified, it will consider all the revs from stop
652 if stop is specified, it will consider all the revs from stop
653 as if they had no children
653 as if they had no children
654 """
654 """
655 if start is None and stop is None:
655 if start is None and stop is None:
656 if not len(self):
656 if not len(self):
657 return [nullid]
657 return [nullid]
658 return [self.node(r) for r in self.headrevs()]
658 return [self.node(r) for r in self.headrevs()]
659
659
660 if start is None:
660 if start is None:
661 start = nullid
661 start = nullid
662 if stop is None:
662 if stop is None:
663 stop = []
663 stop = []
664 stoprevs = set([self.rev(n) for n in stop])
664 stoprevs = set([self.rev(n) for n in stop])
665 startrev = self.rev(start)
665 startrev = self.rev(start)
666 reachable = set((startrev,))
666 reachable = set((startrev,))
667 heads = set((startrev,))
667 heads = set((startrev,))
668
668
669 parentrevs = self.parentrevs
669 parentrevs = self.parentrevs
670 for r in self.revs(start=startrev + 1):
670 for r in self.revs(start=startrev + 1):
671 for p in parentrevs(r):
671 for p in parentrevs(r):
672 if p in reachable:
672 if p in reachable:
673 if r not in stoprevs:
673 if r not in stoprevs:
674 reachable.add(r)
674 reachable.add(r)
675 heads.add(r)
675 heads.add(r)
676 if p in heads and p not in stoprevs:
676 if p in heads and p not in stoprevs:
677 heads.remove(p)
677 heads.remove(p)
678
678
679 return [self.node(r) for r in heads]
679 return [self.node(r) for r in heads]
680
680
681 def children(self, node):
681 def children(self, node):
682 """find the children of a given node"""
682 """find the children of a given node"""
683 c = []
683 c = []
684 p = self.rev(node)
684 p = self.rev(node)
685 for r in self.revs(start=p + 1):
685 for r in self.revs(start=p + 1):
686 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
686 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
687 if prevs:
687 if prevs:
688 for pr in prevs:
688 for pr in prevs:
689 if pr == p:
689 if pr == p:
690 c.append(self.node(r))
690 c.append(self.node(r))
691 elif p == nullrev:
691 elif p == nullrev:
692 c.append(self.node(r))
692 c.append(self.node(r))
693 return c
693 return c
694
694
695 def descendant(self, start, end):
695 def descendant(self, start, end):
696 if start == nullrev:
696 if start == nullrev:
697 return True
697 return True
698 for i in self.descendants([start]):
698 for i in self.descendants([start]):
699 if i == end:
699 if i == end:
700 return True
700 return True
701 elif i > end:
701 elif i > end:
702 break
702 break
703 return False
703 return False
704
704
705 def ancestor(self, a, b):
705 def ancestor(self, a, b):
706 """calculate the least common ancestor of nodes a and b"""
706 """calculate the least common ancestor of nodes a and b"""
707
707
708 a, b = self.rev(a), self.rev(b)
708 a, b = self.rev(a), self.rev(b)
709 try:
709 try:
710 ancs = self.index.ancestors(a, b)
710 ancs = self.index.ancestors(a, b)
711 old = ancestor.ancestors(self.parentrevs, a, b)
712 assert set(ancs) == old, ('opinions differ over ancestor(%d, %d)' %
713 (a, b))
714 except (AttributeError, OverflowError):
711 except (AttributeError, OverflowError):
715 ancs = ancestor.ancestors(self.parentrevs, a, b)
712 ancs = ancestor.ancestors(self.parentrevs, a, b)
716 if ancs:
713 if ancs:
717 # choose a consistent winner when there's a tie
714 # choose a consistent winner when there's a tie
718 return min(map(self.node, ancs))
715 return min(map(self.node, ancs))
719 return nullid
716 return nullid
720
717
721 def _match(self, id):
718 def _match(self, id):
722 if isinstance(id, int):
719 if isinstance(id, int):
723 # rev
720 # rev
724 return self.node(id)
721 return self.node(id)
725 if len(id) == 20:
722 if len(id) == 20:
726 # possibly a binary node
723 # possibly a binary node
727 # odds of a binary node being all hex in ASCII are 1 in 10**25
724 # odds of a binary node being all hex in ASCII are 1 in 10**25
728 try:
725 try:
729 node = id
726 node = id
730 self.rev(node) # quick search the index
727 self.rev(node) # quick search the index
731 return node
728 return node
732 except LookupError:
729 except LookupError:
733 pass # may be partial hex id
730 pass # may be partial hex id
734 try:
731 try:
735 # str(rev)
732 # str(rev)
736 rev = int(id)
733 rev = int(id)
737 if str(rev) != id:
734 if str(rev) != id:
738 raise ValueError
735 raise ValueError
739 if rev < 0:
736 if rev < 0:
740 rev = len(self) + rev
737 rev = len(self) + rev
741 if rev < 0 or rev >= len(self):
738 if rev < 0 or rev >= len(self):
742 raise ValueError
739 raise ValueError
743 return self.node(rev)
740 return self.node(rev)
744 except (ValueError, OverflowError):
741 except (ValueError, OverflowError):
745 pass
742 pass
746 if len(id) == 40:
743 if len(id) == 40:
747 try:
744 try:
748 # a full hex nodeid?
745 # a full hex nodeid?
749 node = bin(id)
746 node = bin(id)
750 self.rev(node)
747 self.rev(node)
751 return node
748 return node
752 except (TypeError, LookupError):
749 except (TypeError, LookupError):
753 pass
750 pass
754
751
755 def _partialmatch(self, id):
752 def _partialmatch(self, id):
756 try:
753 try:
757 return self.index.partialmatch(id)
754 return self.index.partialmatch(id)
758 except RevlogError:
755 except RevlogError:
759 # parsers.c radix tree lookup gave multiple matches
756 # parsers.c radix tree lookup gave multiple matches
760 raise LookupError(id, self.indexfile, _("ambiguous identifier"))
757 raise LookupError(id, self.indexfile, _("ambiguous identifier"))
761 except (AttributeError, ValueError):
758 except (AttributeError, ValueError):
762 # we are pure python, or key was too short to search radix tree
759 # we are pure python, or key was too short to search radix tree
763 pass
760 pass
764
761
765 if id in self._pcache:
762 if id in self._pcache:
766 return self._pcache[id]
763 return self._pcache[id]
767
764
768 if len(id) < 40:
765 if len(id) < 40:
769 try:
766 try:
770 # hex(node)[:...]
767 # hex(node)[:...]
771 l = len(id) // 2 # grab an even number of digits
768 l = len(id) // 2 # grab an even number of digits
772 prefix = bin(id[:l * 2])
769 prefix = bin(id[:l * 2])
773 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
770 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
774 nl = [n for n in nl if hex(n).startswith(id)]
771 nl = [n for n in nl if hex(n).startswith(id)]
775 if len(nl) > 0:
772 if len(nl) > 0:
776 if len(nl) == 1:
773 if len(nl) == 1:
777 self._pcache[id] = nl[0]
774 self._pcache[id] = nl[0]
778 return nl[0]
775 return nl[0]
779 raise LookupError(id, self.indexfile,
776 raise LookupError(id, self.indexfile,
780 _('ambiguous identifier'))
777 _('ambiguous identifier'))
781 return None
778 return None
782 except TypeError:
779 except TypeError:
783 pass
780 pass
784
781
785 def lookup(self, id):
782 def lookup(self, id):
786 """locate a node based on:
783 """locate a node based on:
787 - revision number or str(revision number)
784 - revision number or str(revision number)
788 - nodeid or subset of hex nodeid
785 - nodeid or subset of hex nodeid
789 """
786 """
790 n = self._match(id)
787 n = self._match(id)
791 if n is not None:
788 if n is not None:
792 return n
789 return n
793 n = self._partialmatch(id)
790 n = self._partialmatch(id)
794 if n:
791 if n:
795 return n
792 return n
796
793
797 raise LookupError(id, self.indexfile, _('no match found'))
794 raise LookupError(id, self.indexfile, _('no match found'))
798
795
799 def cmp(self, node, text):
796 def cmp(self, node, text):
800 """compare text with a given file revision
797 """compare text with a given file revision
801
798
802 returns True if text is different than what is stored.
799 returns True if text is different than what is stored.
803 """
800 """
804 p1, p2 = self.parents(node)
801 p1, p2 = self.parents(node)
805 return hash(text, p1, p2) != node
802 return hash(text, p1, p2) != node
806
803
807 def _addchunk(self, offset, data):
804 def _addchunk(self, offset, data):
808 o, d = self._chunkcache
805 o, d = self._chunkcache
809 # try to add to existing cache
806 # try to add to existing cache
810 if o + len(d) == offset and len(d) + len(data) < _chunksize:
807 if o + len(d) == offset and len(d) + len(data) < _chunksize:
811 self._chunkcache = o, d + data
808 self._chunkcache = o, d + data
812 else:
809 else:
813 self._chunkcache = offset, data
810 self._chunkcache = offset, data
814
811
815 def _loadchunk(self, offset, length):
812 def _loadchunk(self, offset, length):
816 if self._inline:
813 if self._inline:
817 df = self.opener(self.indexfile)
814 df = self.opener(self.indexfile)
818 else:
815 else:
819 df = self.opener(self.datafile)
816 df = self.opener(self.datafile)
820
817
821 readahead = max(65536, length)
818 readahead = max(65536, length)
822 df.seek(offset)
819 df.seek(offset)
823 d = df.read(readahead)
820 d = df.read(readahead)
824 df.close()
821 df.close()
825 self._addchunk(offset, d)
822 self._addchunk(offset, d)
826 if readahead > length:
823 if readahead > length:
827 return util.buffer(d, 0, length)
824 return util.buffer(d, 0, length)
828 return d
825 return d
829
826
830 def _getchunk(self, offset, length):
827 def _getchunk(self, offset, length):
831 o, d = self._chunkcache
828 o, d = self._chunkcache
832 l = len(d)
829 l = len(d)
833
830
834 # is it in the cache?
831 # is it in the cache?
835 cachestart = offset - o
832 cachestart = offset - o
836 cacheend = cachestart + length
833 cacheend = cachestart + length
837 if cachestart >= 0 and cacheend <= l:
834 if cachestart >= 0 and cacheend <= l:
838 if cachestart == 0 and cacheend == l:
835 if cachestart == 0 and cacheend == l:
839 return d # avoid a copy
836 return d # avoid a copy
840 return util.buffer(d, cachestart, cacheend - cachestart)
837 return util.buffer(d, cachestart, cacheend - cachestart)
841
838
842 return self._loadchunk(offset, length)
839 return self._loadchunk(offset, length)
843
840
844 def _chunkraw(self, startrev, endrev):
841 def _chunkraw(self, startrev, endrev):
845 start = self.start(startrev)
842 start = self.start(startrev)
846 length = self.end(endrev) - start
843 length = self.end(endrev) - start
847 if self._inline:
844 if self._inline:
848 start += (startrev + 1) * self._io.size
845 start += (startrev + 1) * self._io.size
849 return self._getchunk(start, length)
846 return self._getchunk(start, length)
850
847
851 def _chunk(self, rev):
848 def _chunk(self, rev):
852 return decompress(self._chunkraw(rev, rev))
849 return decompress(self._chunkraw(rev, rev))
853
850
854 def _chunkbase(self, rev):
851 def _chunkbase(self, rev):
855 return self._chunk(rev)
852 return self._chunk(rev)
856
853
857 def _chunkclear(self):
854 def _chunkclear(self):
858 self._chunkcache = (0, '')
855 self._chunkcache = (0, '')
859
856
860 def deltaparent(self, rev):
857 def deltaparent(self, rev):
861 """return deltaparent of the given revision"""
858 """return deltaparent of the given revision"""
862 base = self.index[rev][3]
859 base = self.index[rev][3]
863 if base == rev:
860 if base == rev:
864 return nullrev
861 return nullrev
865 elif self._generaldelta:
862 elif self._generaldelta:
866 return base
863 return base
867 else:
864 else:
868 return rev - 1
865 return rev - 1
869
866
870 def revdiff(self, rev1, rev2):
867 def revdiff(self, rev1, rev2):
871 """return or calculate a delta between two revisions"""
868 """return or calculate a delta between two revisions"""
872 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
869 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
873 return str(self._chunk(rev2))
870 return str(self._chunk(rev2))
874
871
875 return mdiff.textdiff(self.revision(rev1),
872 return mdiff.textdiff(self.revision(rev1),
876 self.revision(rev2))
873 self.revision(rev2))
877
874
878 def revision(self, nodeorrev):
875 def revision(self, nodeorrev):
879 """return an uncompressed revision of a given node or revision
876 """return an uncompressed revision of a given node or revision
880 number.
877 number.
881 """
878 """
882 if isinstance(nodeorrev, int):
879 if isinstance(nodeorrev, int):
883 rev = nodeorrev
880 rev = nodeorrev
884 node = self.node(rev)
881 node = self.node(rev)
885 else:
882 else:
886 node = nodeorrev
883 node = nodeorrev
887 rev = None
884 rev = None
888
885
889 cachedrev = None
886 cachedrev = None
890 if node == nullid:
887 if node == nullid:
891 return ""
888 return ""
892 if self._cache:
889 if self._cache:
893 if self._cache[0] == node:
890 if self._cache[0] == node:
894 return self._cache[2]
891 return self._cache[2]
895 cachedrev = self._cache[1]
892 cachedrev = self._cache[1]
896
893
897 # look up what we need to read
894 # look up what we need to read
898 text = None
895 text = None
899 if rev is None:
896 if rev is None:
900 rev = self.rev(node)
897 rev = self.rev(node)
901
898
902 # check rev flags
899 # check rev flags
903 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
900 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
904 raise RevlogError(_('incompatible revision flag %x') %
901 raise RevlogError(_('incompatible revision flag %x') %
905 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
902 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
906
903
907 # build delta chain
904 # build delta chain
908 chain = []
905 chain = []
909 index = self.index # for performance
906 index = self.index # for performance
910 generaldelta = self._generaldelta
907 generaldelta = self._generaldelta
911 iterrev = rev
908 iterrev = rev
912 e = index[iterrev]
909 e = index[iterrev]
913 while iterrev != e[3] and iterrev != cachedrev:
910 while iterrev != e[3] and iterrev != cachedrev:
914 chain.append(iterrev)
911 chain.append(iterrev)
915 if generaldelta:
912 if generaldelta:
916 iterrev = e[3]
913 iterrev = e[3]
917 else:
914 else:
918 iterrev -= 1
915 iterrev -= 1
919 e = index[iterrev]
916 e = index[iterrev]
920 chain.reverse()
917 chain.reverse()
921 base = iterrev
918 base = iterrev
922
919
923 if iterrev == cachedrev:
920 if iterrev == cachedrev:
924 # cache hit
921 # cache hit
925 text = self._cache[2]
922 text = self._cache[2]
926
923
927 # drop cache to save memory
924 # drop cache to save memory
928 self._cache = None
925 self._cache = None
929
926
930 self._chunkraw(base, rev)
927 self._chunkraw(base, rev)
931 if text is None:
928 if text is None:
932 text = str(self._chunkbase(base))
929 text = str(self._chunkbase(base))
933
930
934 bins = [self._chunk(r) for r in chain]
931 bins = [self._chunk(r) for r in chain]
935 text = mdiff.patches(text, bins)
932 text = mdiff.patches(text, bins)
936
933
937 text = self._checkhash(text, node, rev)
934 text = self._checkhash(text, node, rev)
938
935
939 self._cache = (node, rev, text)
936 self._cache = (node, rev, text)
940 return text
937 return text
941
938
942 def _checkhash(self, text, node, rev):
939 def _checkhash(self, text, node, rev):
943 p1, p2 = self.parents(node)
940 p1, p2 = self.parents(node)
944 if node != hash(text, p1, p2):
941 if node != hash(text, p1, p2):
945 raise RevlogError(_("integrity check failed on %s:%d")
942 raise RevlogError(_("integrity check failed on %s:%d")
946 % (self.indexfile, rev))
943 % (self.indexfile, rev))
947 return text
944 return text
948
945
949 def checkinlinesize(self, tr, fp=None):
946 def checkinlinesize(self, tr, fp=None):
950 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
947 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
951 return
948 return
952
949
953 trinfo = tr.find(self.indexfile)
950 trinfo = tr.find(self.indexfile)
954 if trinfo is None:
951 if trinfo is None:
955 raise RevlogError(_("%s not found in the transaction")
952 raise RevlogError(_("%s not found in the transaction")
956 % self.indexfile)
953 % self.indexfile)
957
954
958 trindex = trinfo[2]
955 trindex = trinfo[2]
959 dataoff = self.start(trindex)
956 dataoff = self.start(trindex)
960
957
961 tr.add(self.datafile, dataoff)
958 tr.add(self.datafile, dataoff)
962
959
963 if fp:
960 if fp:
964 fp.flush()
961 fp.flush()
965 fp.close()
962 fp.close()
966
963
967 df = self.opener(self.datafile, 'w')
964 df = self.opener(self.datafile, 'w')
968 try:
965 try:
969 for r in self:
966 for r in self:
970 df.write(self._chunkraw(r, r))
967 df.write(self._chunkraw(r, r))
971 finally:
968 finally:
972 df.close()
969 df.close()
973
970
974 fp = self.opener(self.indexfile, 'w', atomictemp=True)
971 fp = self.opener(self.indexfile, 'w', atomictemp=True)
975 self.version &= ~(REVLOGNGINLINEDATA)
972 self.version &= ~(REVLOGNGINLINEDATA)
976 self._inline = False
973 self._inline = False
977 for i in self:
974 for i in self:
978 e = self._io.packentry(self.index[i], self.node, self.version, i)
975 e = self._io.packentry(self.index[i], self.node, self.version, i)
979 fp.write(e)
976 fp.write(e)
980
977
981 # if we don't call close, the temp file will never replace the
978 # if we don't call close, the temp file will never replace the
982 # real index
979 # real index
983 fp.close()
980 fp.close()
984
981
985 tr.replace(self.indexfile, trindex * self._io.size)
982 tr.replace(self.indexfile, trindex * self._io.size)
986 self._chunkclear()
983 self._chunkclear()
987
984
988 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
985 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
989 """add a revision to the log
986 """add a revision to the log
990
987
991 text - the revision data to add
988 text - the revision data to add
992 transaction - the transaction object used for rollback
989 transaction - the transaction object used for rollback
993 link - the linkrev data to add
990 link - the linkrev data to add
994 p1, p2 - the parent nodeids of the revision
991 p1, p2 - the parent nodeids of the revision
995 cachedelta - an optional precomputed delta
992 cachedelta - an optional precomputed delta
996 """
993 """
997 node = hash(text, p1, p2)
994 node = hash(text, p1, p2)
998 if node in self.nodemap:
995 if node in self.nodemap:
999 return node
996 return node
1000
997
1001 dfh = None
998 dfh = None
1002 if not self._inline:
999 if not self._inline:
1003 dfh = self.opener(self.datafile, "a")
1000 dfh = self.opener(self.datafile, "a")
1004 ifh = self.opener(self.indexfile, "a+")
1001 ifh = self.opener(self.indexfile, "a+")
1005 try:
1002 try:
1006 return self._addrevision(node, text, transaction, link, p1, p2,
1003 return self._addrevision(node, text, transaction, link, p1, p2,
1007 cachedelta, ifh, dfh)
1004 cachedelta, ifh, dfh)
1008 finally:
1005 finally:
1009 if dfh:
1006 if dfh:
1010 dfh.close()
1007 dfh.close()
1011 ifh.close()
1008 ifh.close()
1012
1009
1013 def compress(self, text):
1010 def compress(self, text):
1014 """ generate a possibly-compressed representation of text """
1011 """ generate a possibly-compressed representation of text """
1015 if not text:
1012 if not text:
1016 return ("", text)
1013 return ("", text)
1017 l = len(text)
1014 l = len(text)
1018 bin = None
1015 bin = None
1019 if l < 44:
1016 if l < 44:
1020 pass
1017 pass
1021 elif l > 1000000:
1018 elif l > 1000000:
1022 # zlib makes an internal copy, thus doubling memory usage for
1019 # zlib makes an internal copy, thus doubling memory usage for
1023 # large files, so lets do this in pieces
1020 # large files, so lets do this in pieces
1024 z = zlib.compressobj()
1021 z = zlib.compressobj()
1025 p = []
1022 p = []
1026 pos = 0
1023 pos = 0
1027 while pos < l:
1024 while pos < l:
1028 pos2 = pos + 2**20
1025 pos2 = pos + 2**20
1029 p.append(z.compress(text[pos:pos2]))
1026 p.append(z.compress(text[pos:pos2]))
1030 pos = pos2
1027 pos = pos2
1031 p.append(z.flush())
1028 p.append(z.flush())
1032 if sum(map(len, p)) < l:
1029 if sum(map(len, p)) < l:
1033 bin = "".join(p)
1030 bin = "".join(p)
1034 else:
1031 else:
1035 bin = _compress(text)
1032 bin = _compress(text)
1036 if bin is None or len(bin) > l:
1033 if bin is None or len(bin) > l:
1037 if text[0] == '\0':
1034 if text[0] == '\0':
1038 return ("", text)
1035 return ("", text)
1039 return ('u', text)
1036 return ('u', text)
1040 return ("", bin)
1037 return ("", bin)
1041
1038
1042 def _addrevision(self, node, text, transaction, link, p1, p2,
1039 def _addrevision(self, node, text, transaction, link, p1, p2,
1043 cachedelta, ifh, dfh):
1040 cachedelta, ifh, dfh):
1044 """internal function to add revisions to the log
1041 """internal function to add revisions to the log
1045
1042
1046 see addrevision for argument descriptions.
1043 see addrevision for argument descriptions.
1047 invariants:
1044 invariants:
1048 - text is optional (can be None); if not set, cachedelta must be set.
1045 - text is optional (can be None); if not set, cachedelta must be set.
1049 if both are set, they must correspond to each other.
1046 if both are set, they must correspond to each other.
1050 """
1047 """
1051 btext = [text]
1048 btext = [text]
1052 def buildtext():
1049 def buildtext():
1053 if btext[0] is not None:
1050 if btext[0] is not None:
1054 return btext[0]
1051 return btext[0]
1055 # flush any pending writes here so we can read it in revision
1052 # flush any pending writes here so we can read it in revision
1056 if dfh:
1053 if dfh:
1057 dfh.flush()
1054 dfh.flush()
1058 ifh.flush()
1055 ifh.flush()
1059 basetext = self.revision(self.node(cachedelta[0]))
1056 basetext = self.revision(self.node(cachedelta[0]))
1060 btext[0] = mdiff.patch(basetext, cachedelta[1])
1057 btext[0] = mdiff.patch(basetext, cachedelta[1])
1061 chk = hash(btext[0], p1, p2)
1058 chk = hash(btext[0], p1, p2)
1062 if chk != node:
1059 if chk != node:
1063 raise RevlogError(_("consistency error in delta"))
1060 raise RevlogError(_("consistency error in delta"))
1064 return btext[0]
1061 return btext[0]
1065
1062
1066 def builddelta(rev):
1063 def builddelta(rev):
1067 # can we use the cached delta?
1064 # can we use the cached delta?
1068 if cachedelta and cachedelta[0] == rev:
1065 if cachedelta and cachedelta[0] == rev:
1069 delta = cachedelta[1]
1066 delta = cachedelta[1]
1070 else:
1067 else:
1071 t = buildtext()
1068 t = buildtext()
1072 ptext = self.revision(self.node(rev))
1069 ptext = self.revision(self.node(rev))
1073 delta = mdiff.textdiff(ptext, t)
1070 delta = mdiff.textdiff(ptext, t)
1074 data = self.compress(delta)
1071 data = self.compress(delta)
1075 l = len(data[1]) + len(data[0])
1072 l = len(data[1]) + len(data[0])
1076 if basecache[0] == rev:
1073 if basecache[0] == rev:
1077 chainbase = basecache[1]
1074 chainbase = basecache[1]
1078 else:
1075 else:
1079 chainbase = self.chainbase(rev)
1076 chainbase = self.chainbase(rev)
1080 dist = l + offset - self.start(chainbase)
1077 dist = l + offset - self.start(chainbase)
1081 if self._generaldelta:
1078 if self._generaldelta:
1082 base = rev
1079 base = rev
1083 else:
1080 else:
1084 base = chainbase
1081 base = chainbase
1085 return dist, l, data, base, chainbase
1082 return dist, l, data, base, chainbase
1086
1083
1087 curr = len(self)
1084 curr = len(self)
1088 prev = curr - 1
1085 prev = curr - 1
1089 base = chainbase = curr
1086 base = chainbase = curr
1090 offset = self.end(prev)
1087 offset = self.end(prev)
1091 flags = 0
1088 flags = 0
1092 d = None
1089 d = None
1093 basecache = self._basecache
1090 basecache = self._basecache
1094 p1r, p2r = self.rev(p1), self.rev(p2)
1091 p1r, p2r = self.rev(p1), self.rev(p2)
1095
1092
1096 # should we try to build a delta?
1093 # should we try to build a delta?
1097 if prev != nullrev:
1094 if prev != nullrev:
1098 if self._generaldelta:
1095 if self._generaldelta:
1099 if p1r >= basecache[1]:
1096 if p1r >= basecache[1]:
1100 d = builddelta(p1r)
1097 d = builddelta(p1r)
1101 elif p2r >= basecache[1]:
1098 elif p2r >= basecache[1]:
1102 d = builddelta(p2r)
1099 d = builddelta(p2r)
1103 else:
1100 else:
1104 d = builddelta(prev)
1101 d = builddelta(prev)
1105 else:
1102 else:
1106 d = builddelta(prev)
1103 d = builddelta(prev)
1107 dist, l, data, base, chainbase = d
1104 dist, l, data, base, chainbase = d
1108
1105
1109 # full versions are inserted when the needed deltas
1106 # full versions are inserted when the needed deltas
1110 # become comparable to the uncompressed text
1107 # become comparable to the uncompressed text
1111 if text is None:
1108 if text is None:
1112 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1109 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1113 cachedelta[1])
1110 cachedelta[1])
1114 else:
1111 else:
1115 textlen = len(text)
1112 textlen = len(text)
1116 if d is None or dist > textlen * 2:
1113 if d is None or dist > textlen * 2:
1117 text = buildtext()
1114 text = buildtext()
1118 data = self.compress(text)
1115 data = self.compress(text)
1119 l = len(data[1]) + len(data[0])
1116 l = len(data[1]) + len(data[0])
1120 base = chainbase = curr
1117 base = chainbase = curr
1121
1118
1122 e = (offset_type(offset, flags), l, textlen,
1119 e = (offset_type(offset, flags), l, textlen,
1123 base, link, p1r, p2r, node)
1120 base, link, p1r, p2r, node)
1124 self.index.insert(-1, e)
1121 self.index.insert(-1, e)
1125 self.nodemap[node] = curr
1122 self.nodemap[node] = curr
1126
1123
1127 entry = self._io.packentry(e, self.node, self.version, curr)
1124 entry = self._io.packentry(e, self.node, self.version, curr)
1128 if not self._inline:
1125 if not self._inline:
1129 transaction.add(self.datafile, offset)
1126 transaction.add(self.datafile, offset)
1130 transaction.add(self.indexfile, curr * len(entry))
1127 transaction.add(self.indexfile, curr * len(entry))
1131 if data[0]:
1128 if data[0]:
1132 dfh.write(data[0])
1129 dfh.write(data[0])
1133 dfh.write(data[1])
1130 dfh.write(data[1])
1134 dfh.flush()
1131 dfh.flush()
1135 ifh.write(entry)
1132 ifh.write(entry)
1136 else:
1133 else:
1137 offset += curr * self._io.size
1134 offset += curr * self._io.size
1138 transaction.add(self.indexfile, offset, curr)
1135 transaction.add(self.indexfile, offset, curr)
1139 ifh.write(entry)
1136 ifh.write(entry)
1140 ifh.write(data[0])
1137 ifh.write(data[0])
1141 ifh.write(data[1])
1138 ifh.write(data[1])
1142 self.checkinlinesize(transaction, ifh)
1139 self.checkinlinesize(transaction, ifh)
1143
1140
1144 if type(text) == str: # only accept immutable objects
1141 if type(text) == str: # only accept immutable objects
1145 self._cache = (node, curr, text)
1142 self._cache = (node, curr, text)
1146 self._basecache = (curr, chainbase)
1143 self._basecache = (curr, chainbase)
1147 return node
1144 return node
1148
1145
1149 def group(self, nodelist, bundler, reorder=None):
1146 def group(self, nodelist, bundler, reorder=None):
1150 """Calculate a delta group, yielding a sequence of changegroup chunks
1147 """Calculate a delta group, yielding a sequence of changegroup chunks
1151 (strings).
1148 (strings).
1152
1149
1153 Given a list of changeset revs, return a set of deltas and
1150 Given a list of changeset revs, return a set of deltas and
1154 metadata corresponding to nodes. The first delta is
1151 metadata corresponding to nodes. The first delta is
1155 first parent(nodelist[0]) -> nodelist[0], the receiver is
1152 first parent(nodelist[0]) -> nodelist[0], the receiver is
1156 guaranteed to have this parent as it has all history before
1153 guaranteed to have this parent as it has all history before
1157 these changesets. In the case firstparent is nullrev the
1154 these changesets. In the case firstparent is nullrev the
1158 changegroup starts with a full revision.
1155 changegroup starts with a full revision.
1159 """
1156 """
1160
1157
1161 # if we don't have any revisions touched by these changesets, bail
1158 # if we don't have any revisions touched by these changesets, bail
1162 if len(nodelist) == 0:
1159 if len(nodelist) == 0:
1163 yield bundler.close()
1160 yield bundler.close()
1164 return
1161 return
1165
1162
1166 # for generaldelta revlogs, we linearize the revs; this will both be
1163 # for generaldelta revlogs, we linearize the revs; this will both be
1167 # much quicker and generate a much smaller bundle
1164 # much quicker and generate a much smaller bundle
1168 if (self._generaldelta and reorder is not False) or reorder:
1165 if (self._generaldelta and reorder is not False) or reorder:
1169 dag = dagutil.revlogdag(self)
1166 dag = dagutil.revlogdag(self)
1170 revs = set(self.rev(n) for n in nodelist)
1167 revs = set(self.rev(n) for n in nodelist)
1171 revs = dag.linearize(revs)
1168 revs = dag.linearize(revs)
1172 else:
1169 else:
1173 revs = sorted([self.rev(n) for n in nodelist])
1170 revs = sorted([self.rev(n) for n in nodelist])
1174
1171
1175 # add the parent of the first rev
1172 # add the parent of the first rev
1176 p = self.parentrevs(revs[0])[0]
1173 p = self.parentrevs(revs[0])[0]
1177 revs.insert(0, p)
1174 revs.insert(0, p)
1178
1175
1179 # build deltas
1176 # build deltas
1180 for r in xrange(len(revs) - 1):
1177 for r in xrange(len(revs) - 1):
1181 prev, curr = revs[r], revs[r + 1]
1178 prev, curr = revs[r], revs[r + 1]
1182 for c in bundler.revchunk(self, curr, prev):
1179 for c in bundler.revchunk(self, curr, prev):
1183 yield c
1180 yield c
1184
1181
1185 yield bundler.close()
1182 yield bundler.close()
1186
1183
1187 def addgroup(self, bundle, linkmapper, transaction):
1184 def addgroup(self, bundle, linkmapper, transaction):
1188 """
1185 """
1189 add a delta group
1186 add a delta group
1190
1187
1191 given a set of deltas, add them to the revision log. the
1188 given a set of deltas, add them to the revision log. the
1192 first delta is against its parent, which should be in our
1189 first delta is against its parent, which should be in our
1193 log, the rest are against the previous delta.
1190 log, the rest are against the previous delta.
1194 """
1191 """
1195
1192
1196 # track the base of the current delta log
1193 # track the base of the current delta log
1197 content = []
1194 content = []
1198 node = None
1195 node = None
1199
1196
1200 r = len(self)
1197 r = len(self)
1201 end = 0
1198 end = 0
1202 if r:
1199 if r:
1203 end = self.end(r - 1)
1200 end = self.end(r - 1)
1204 ifh = self.opener(self.indexfile, "a+")
1201 ifh = self.opener(self.indexfile, "a+")
1205 isize = r * self._io.size
1202 isize = r * self._io.size
1206 if self._inline:
1203 if self._inline:
1207 transaction.add(self.indexfile, end + isize, r)
1204 transaction.add(self.indexfile, end + isize, r)
1208 dfh = None
1205 dfh = None
1209 else:
1206 else:
1210 transaction.add(self.indexfile, isize, r)
1207 transaction.add(self.indexfile, isize, r)
1211 transaction.add(self.datafile, end)
1208 transaction.add(self.datafile, end)
1212 dfh = self.opener(self.datafile, "a")
1209 dfh = self.opener(self.datafile, "a")
1213
1210
1214 try:
1211 try:
1215 # loop through our set of deltas
1212 # loop through our set of deltas
1216 chain = None
1213 chain = None
1217 while True:
1214 while True:
1218 chunkdata = bundle.deltachunk(chain)
1215 chunkdata = bundle.deltachunk(chain)
1219 if not chunkdata:
1216 if not chunkdata:
1220 break
1217 break
1221 node = chunkdata['node']
1218 node = chunkdata['node']
1222 p1 = chunkdata['p1']
1219 p1 = chunkdata['p1']
1223 p2 = chunkdata['p2']
1220 p2 = chunkdata['p2']
1224 cs = chunkdata['cs']
1221 cs = chunkdata['cs']
1225 deltabase = chunkdata['deltabase']
1222 deltabase = chunkdata['deltabase']
1226 delta = chunkdata['delta']
1223 delta = chunkdata['delta']
1227
1224
1228 content.append(node)
1225 content.append(node)
1229
1226
1230 link = linkmapper(cs)
1227 link = linkmapper(cs)
1231 if node in self.nodemap:
1228 if node in self.nodemap:
1232 # this can happen if two branches make the same change
1229 # this can happen if two branches make the same change
1233 chain = node
1230 chain = node
1234 continue
1231 continue
1235
1232
1236 for p in (p1, p2):
1233 for p in (p1, p2):
1237 if p not in self.nodemap:
1234 if p not in self.nodemap:
1238 raise LookupError(p, self.indexfile,
1235 raise LookupError(p, self.indexfile,
1239 _('unknown parent'))
1236 _('unknown parent'))
1240
1237
1241 if deltabase not in self.nodemap:
1238 if deltabase not in self.nodemap:
1242 raise LookupError(deltabase, self.indexfile,
1239 raise LookupError(deltabase, self.indexfile,
1243 _('unknown delta base'))
1240 _('unknown delta base'))
1244
1241
1245 baserev = self.rev(deltabase)
1242 baserev = self.rev(deltabase)
1246 chain = self._addrevision(node, None, transaction, link,
1243 chain = self._addrevision(node, None, transaction, link,
1247 p1, p2, (baserev, delta), ifh, dfh)
1244 p1, p2, (baserev, delta), ifh, dfh)
1248 if not dfh and not self._inline:
1245 if not dfh and not self._inline:
1249 # addrevision switched from inline to conventional
1246 # addrevision switched from inline to conventional
1250 # reopen the index
1247 # reopen the index
1251 ifh.close()
1248 ifh.close()
1252 dfh = self.opener(self.datafile, "a")
1249 dfh = self.opener(self.datafile, "a")
1253 ifh = self.opener(self.indexfile, "a")
1250 ifh = self.opener(self.indexfile, "a")
1254 finally:
1251 finally:
1255 if dfh:
1252 if dfh:
1256 dfh.close()
1253 dfh.close()
1257 ifh.close()
1254 ifh.close()
1258
1255
1259 return content
1256 return content
1260
1257
1261 def strip(self, minlink, transaction):
1258 def strip(self, minlink, transaction):
1262 """truncate the revlog on the first revision with a linkrev >= minlink
1259 """truncate the revlog on the first revision with a linkrev >= minlink
1263
1260
1264 This function is called when we're stripping revision minlink and
1261 This function is called when we're stripping revision minlink and
1265 its descendants from the repository.
1262 its descendants from the repository.
1266
1263
1267 We have to remove all revisions with linkrev >= minlink, because
1264 We have to remove all revisions with linkrev >= minlink, because
1268 the equivalent changelog revisions will be renumbered after the
1265 the equivalent changelog revisions will be renumbered after the
1269 strip.
1266 strip.
1270
1267
1271 So we truncate the revlog on the first of these revisions, and
1268 So we truncate the revlog on the first of these revisions, and
1272 trust that the caller has saved the revisions that shouldn't be
1269 trust that the caller has saved the revisions that shouldn't be
1273 removed and that it'll re-add them after this truncation.
1270 removed and that it'll re-add them after this truncation.
1274 """
1271 """
1275 if len(self) == 0:
1272 if len(self) == 0:
1276 return
1273 return
1277
1274
1278 for rev in self:
1275 for rev in self:
1279 if self.index[rev][4] >= minlink:
1276 if self.index[rev][4] >= minlink:
1280 break
1277 break
1281 else:
1278 else:
1282 return
1279 return
1283
1280
1284 # first truncate the files on disk
1281 # first truncate the files on disk
1285 end = self.start(rev)
1282 end = self.start(rev)
1286 if not self._inline:
1283 if not self._inline:
1287 transaction.add(self.datafile, end)
1284 transaction.add(self.datafile, end)
1288 end = rev * self._io.size
1285 end = rev * self._io.size
1289 else:
1286 else:
1290 end += rev * self._io.size
1287 end += rev * self._io.size
1291
1288
1292 transaction.add(self.indexfile, end)
1289 transaction.add(self.indexfile, end)
1293
1290
1294 # then reset internal state in memory to forget those revisions
1291 # then reset internal state in memory to forget those revisions
1295 self._cache = None
1292 self._cache = None
1296 self._chunkclear()
1293 self._chunkclear()
1297 for x in xrange(rev, len(self)):
1294 for x in xrange(rev, len(self)):
1298 del self.nodemap[self.node(x)]
1295 del self.nodemap[self.node(x)]
1299
1296
1300 del self.index[rev:-1]
1297 del self.index[rev:-1]
1301
1298
1302 def checksize(self):
1299 def checksize(self):
1303 expected = 0
1300 expected = 0
1304 if len(self):
1301 if len(self):
1305 expected = max(0, self.end(len(self) - 1))
1302 expected = max(0, self.end(len(self) - 1))
1306
1303
1307 try:
1304 try:
1308 f = self.opener(self.datafile)
1305 f = self.opener(self.datafile)
1309 f.seek(0, 2)
1306 f.seek(0, 2)
1310 actual = f.tell()
1307 actual = f.tell()
1311 f.close()
1308 f.close()
1312 dd = actual - expected
1309 dd = actual - expected
1313 except IOError, inst:
1310 except IOError, inst:
1314 if inst.errno != errno.ENOENT:
1311 if inst.errno != errno.ENOENT:
1315 raise
1312 raise
1316 dd = 0
1313 dd = 0
1317
1314
1318 try:
1315 try:
1319 f = self.opener(self.indexfile)
1316 f = self.opener(self.indexfile)
1320 f.seek(0, 2)
1317 f.seek(0, 2)
1321 actual = f.tell()
1318 actual = f.tell()
1322 f.close()
1319 f.close()
1323 s = self._io.size
1320 s = self._io.size
1324 i = max(0, actual // s)
1321 i = max(0, actual // s)
1325 di = actual - (i * s)
1322 di = actual - (i * s)
1326 if self._inline:
1323 if self._inline:
1327 databytes = 0
1324 databytes = 0
1328 for r in self:
1325 for r in self:
1329 databytes += max(0, self.length(r))
1326 databytes += max(0, self.length(r))
1330 dd = 0
1327 dd = 0
1331 di = actual - len(self) * s - databytes
1328 di = actual - len(self) * s - databytes
1332 except IOError, inst:
1329 except IOError, inst:
1333 if inst.errno != errno.ENOENT:
1330 if inst.errno != errno.ENOENT:
1334 raise
1331 raise
1335 di = 0
1332 di = 0
1336
1333
1337 return (dd, di)
1334 return (dd, di)
1338
1335
1339 def files(self):
1336 def files(self):
1340 res = [self.indexfile]
1337 res = [self.indexfile]
1341 if not self._inline:
1338 if not self._inline:
1342 res.append(self.datafile)
1339 res.append(self.datafile)
1343 return res
1340 return res
General Comments 0
You need to be logged in to leave comments. Login now