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