##// END OF EJS Templates
revlog: backout e5750c6716eb...
Matt Mackall -
r16533:95698ee2 stable
parent child Browse files
Show More
@@ -1,1308 +1,1308
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(65536, 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 util.buffer(d, 0, 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 util.buffer(d, cachestart, cacheend - cachestart)
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 str(self._chunk(rev2))
867 return str(self._chunk(rev2))
868
868
869 return mdiff.textdiff(self.revision(rev1),
869 return mdiff.textdiff(self.revision(rev1),
870 self.revision(rev2))
870 self.revision(rev2))
871
871
872 def revision(self, nodeorrev):
872 def revision(self, nodeorrev):
873 """return an uncompressed revision of a given node or revision
873 """return an uncompressed revision of a given node or revision
874 number.
874 number.
875 """
875 """
876 if isinstance(nodeorrev, int):
876 if isinstance(nodeorrev, int):
877 rev = nodeorrev
877 rev = nodeorrev
878 node = self.node(rev)
878 node = self.node(rev)
879 else:
879 else:
880 node = nodeorrev
880 node = nodeorrev
881 rev = None
881 rev = None
882
882
883 cachedrev = None
883 cachedrev = None
884 if node == nullid:
884 if node == nullid:
885 return ""
885 return ""
886 if self._cache:
886 if self._cache:
887 if self._cache[0] == node:
887 if self._cache[0] == node:
888 return self._cache[2]
888 return self._cache[2]
889 cachedrev = self._cache[1]
889 cachedrev = self._cache[1]
890
890
891 # look up what we need to read
891 # look up what we need to read
892 text = None
892 text = None
893 if rev is None:
893 if rev is None:
894 rev = self.rev(node)
894 rev = self.rev(node)
895
895
896 # check rev flags
896 # check rev flags
897 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
897 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
898 raise RevlogError(_('incompatible revision flag %x') %
898 raise RevlogError(_('incompatible revision flag %x') %
899 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
899 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
900
900
901 # build delta chain
901 # build delta chain
902 chain = []
902 chain = []
903 index = self.index # for performance
903 index = self.index # for performance
904 generaldelta = self._generaldelta
904 generaldelta = self._generaldelta
905 iterrev = rev
905 iterrev = rev
906 e = index[iterrev]
906 e = index[iterrev]
907 while iterrev != e[3] and iterrev != cachedrev:
907 while iterrev != e[3] and iterrev != cachedrev:
908 chain.append(iterrev)
908 chain.append(iterrev)
909 if generaldelta:
909 if generaldelta:
910 iterrev = e[3]
910 iterrev = e[3]
911 else:
911 else:
912 iterrev -= 1
912 iterrev -= 1
913 e = index[iterrev]
913 e = index[iterrev]
914 chain.reverse()
914 chain.reverse()
915 base = iterrev
915 base = iterrev
916
916
917 if iterrev == cachedrev:
917 if iterrev == cachedrev:
918 # cache hit
918 # cache hit
919 text = self._cache[2]
919 text = self._cache[2]
920
920
921 # drop cache to save memory
921 # drop cache to save memory
922 self._cache = None
922 self._cache = None
923
923
924 self._chunkraw(base, rev)
924 self._chunkraw(base, rev)
925 if text is None:
925 if text is None:
926 text = str(self._chunkbase(base))
926 text = str(self._chunkbase(base))
927
927
928 bins = [self._chunk(r) for r in chain]
928 bins = [self._chunk(r) for r in chain]
929 text = mdiff.patches(text, bins)
929 text = mdiff.patches(text, bins)
930
930
931 text = self._checkhash(text, node, rev)
931 text = self._checkhash(text, node, rev)
932
932
933 self._cache = (node, rev, text)
933 self._cache = (node, rev, text)
934 return text
934 return text
935
935
936 def _checkhash(self, text, node, rev):
936 def _checkhash(self, text, node, rev):
937 p1, p2 = self.parents(node)
937 p1, p2 = self.parents(node)
938 if node != hash(text, p1, p2):
938 if node != hash(text, p1, p2):
939 raise RevlogError(_("integrity check failed on %s:%d")
939 raise RevlogError(_("integrity check failed on %s:%d")
940 % (self.indexfile, rev))
940 % (self.indexfile, rev))
941 return text
941 return text
942
942
943 def checkinlinesize(self, tr, fp=None):
943 def checkinlinesize(self, tr, fp=None):
944 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
944 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
945 return
945 return
946
946
947 trinfo = tr.find(self.indexfile)
947 trinfo = tr.find(self.indexfile)
948 if trinfo is None:
948 if trinfo is None:
949 raise RevlogError(_("%s not found in the transaction")
949 raise RevlogError(_("%s not found in the transaction")
950 % self.indexfile)
950 % self.indexfile)
951
951
952 trindex = trinfo[2]
952 trindex = trinfo[2]
953 dataoff = self.start(trindex)
953 dataoff = self.start(trindex)
954
954
955 tr.add(self.datafile, dataoff)
955 tr.add(self.datafile, dataoff)
956
956
957 if fp:
957 if fp:
958 fp.flush()
958 fp.flush()
959 fp.close()
959 fp.close()
960
960
961 df = self.opener(self.datafile, 'w')
961 df = self.opener(self.datafile, 'w')
962 try:
962 try:
963 for r in self:
963 for r in self:
964 df.write(self._chunkraw(r, r))
964 df.write(self._chunkraw(r, r))
965 finally:
965 finally:
966 df.close()
966 df.close()
967
967
968 fp = self.opener(self.indexfile, 'w', atomictemp=True)
968 fp = self.opener(self.indexfile, 'w', atomictemp=True)
969 self.version &= ~(REVLOGNGINLINEDATA)
969 self.version &= ~(REVLOGNGINLINEDATA)
970 self._inline = False
970 self._inline = False
971 for i in self:
971 for i in self:
972 e = self._io.packentry(self.index[i], self.node, self.version, i)
972 e = self._io.packentry(self.index[i], self.node, self.version, i)
973 fp.write(e)
973 fp.write(e)
974
974
975 # if we don't call close, the temp file will never replace the
975 # if we don't call close, the temp file will never replace the
976 # real index
976 # real index
977 fp.close()
977 fp.close()
978
978
979 tr.replace(self.indexfile, trindex * self._io.size)
979 tr.replace(self.indexfile, trindex * self._io.size)
980 self._chunkclear()
980 self._chunkclear()
981
981
982 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
982 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
983 """add a revision to the log
983 """add a revision to the log
984
984
985 text - the revision data to add
985 text - the revision data to add
986 transaction - the transaction object used for rollback
986 transaction - the transaction object used for rollback
987 link - the linkrev data to add
987 link - the linkrev data to add
988 p1, p2 - the parent nodeids of the revision
988 p1, p2 - the parent nodeids of the revision
989 cachedelta - an optional precomputed delta
989 cachedelta - an optional precomputed delta
990 """
990 """
991 node = hash(text, p1, p2)
991 node = hash(text, p1, p2)
992 if node in self.nodemap:
992 if node in self.nodemap:
993 return node
993 return node
994
994
995 dfh = None
995 dfh = None
996 if not self._inline:
996 if not self._inline:
997 dfh = self.opener(self.datafile, "a")
997 dfh = self.opener(self.datafile, "a")
998 ifh = self.opener(self.indexfile, "a+")
998 ifh = self.opener(self.indexfile, "a+")
999 try:
999 try:
1000 return self._addrevision(node, text, transaction, link, p1, p2,
1000 return self._addrevision(node, text, transaction, link, p1, p2,
1001 cachedelta, ifh, dfh)
1001 cachedelta, ifh, dfh)
1002 finally:
1002 finally:
1003 if dfh:
1003 if dfh:
1004 dfh.close()
1004 dfh.close()
1005 ifh.close()
1005 ifh.close()
1006
1006
1007 def _addrevision(self, node, text, transaction, link, p1, p2,
1007 def _addrevision(self, node, text, transaction, link, p1, p2,
1008 cachedelta, ifh, dfh):
1008 cachedelta, ifh, dfh):
1009 """internal function to add revisions to the log
1009 """internal function to add revisions to the log
1010
1010
1011 see addrevision for argument descriptions.
1011 see addrevision for argument descriptions.
1012 invariants:
1012 invariants:
1013 - text is optional (can be None); if not set, cachedelta must be set.
1013 - text is optional (can be None); if not set, cachedelta must be set.
1014 if both are set, they must correspond to eachother.
1014 if both are set, they must correspond to eachother.
1015 """
1015 """
1016 btext = [text]
1016 btext = [text]
1017 def buildtext():
1017 def buildtext():
1018 if btext[0] is not None:
1018 if btext[0] is not None:
1019 return btext[0]
1019 return btext[0]
1020 # flush any pending writes here so we can read it in revision
1020 # flush any pending writes here so we can read it in revision
1021 if dfh:
1021 if dfh:
1022 dfh.flush()
1022 dfh.flush()
1023 ifh.flush()
1023 ifh.flush()
1024 basetext = self.revision(self.node(cachedelta[0]))
1024 basetext = self.revision(self.node(cachedelta[0]))
1025 btext[0] = mdiff.patch(basetext, cachedelta[1])
1025 btext[0] = mdiff.patch(basetext, cachedelta[1])
1026 chk = hash(btext[0], p1, p2)
1026 chk = hash(btext[0], p1, p2)
1027 if chk != node:
1027 if chk != node:
1028 raise RevlogError(_("consistency error in delta"))
1028 raise RevlogError(_("consistency error in delta"))
1029 return btext[0]
1029 return btext[0]
1030
1030
1031 def builddelta(rev):
1031 def builddelta(rev):
1032 # can we use the cached delta?
1032 # can we use the cached delta?
1033 if cachedelta and cachedelta[0] == rev:
1033 if cachedelta and cachedelta[0] == rev:
1034 delta = cachedelta[1]
1034 delta = cachedelta[1]
1035 else:
1035 else:
1036 t = buildtext()
1036 t = buildtext()
1037 ptext = self.revision(self.node(rev))
1037 ptext = self.revision(self.node(rev))
1038 delta = mdiff.textdiff(ptext, t)
1038 delta = mdiff.textdiff(ptext, t)
1039 data = compress(delta)
1039 data = compress(delta)
1040 l = len(data[1]) + len(data[0])
1040 l = len(data[1]) + len(data[0])
1041 if basecache[0] == rev:
1041 if basecache[0] == rev:
1042 chainbase = basecache[1]
1042 chainbase = basecache[1]
1043 else:
1043 else:
1044 chainbase = self.chainbase(rev)
1044 chainbase = self.chainbase(rev)
1045 dist = l + offset - self.start(chainbase)
1045 dist = l + offset - self.start(chainbase)
1046 if self._generaldelta:
1046 if self._generaldelta:
1047 base = rev
1047 base = rev
1048 else:
1048 else:
1049 base = chainbase
1049 base = chainbase
1050 return dist, l, data, base, chainbase
1050 return dist, l, data, base, chainbase
1051
1051
1052 curr = len(self)
1052 curr = len(self)
1053 prev = curr - 1
1053 prev = curr - 1
1054 base = chainbase = curr
1054 base = chainbase = curr
1055 offset = self.end(prev)
1055 offset = self.end(prev)
1056 flags = 0
1056 flags = 0
1057 d = None
1057 d = None
1058 basecache = self._basecache
1058 basecache = self._basecache
1059 p1r, p2r = self.rev(p1), self.rev(p2)
1059 p1r, p2r = self.rev(p1), self.rev(p2)
1060
1060
1061 # should we try to build a delta?
1061 # should we try to build a delta?
1062 if prev != nullrev:
1062 if prev != nullrev:
1063 if self._generaldelta:
1063 if self._generaldelta:
1064 if p1r >= basecache[1]:
1064 if p1r >= basecache[1]:
1065 d = builddelta(p1r)
1065 d = builddelta(p1r)
1066 elif p2r >= basecache[1]:
1066 elif p2r >= basecache[1]:
1067 d = builddelta(p2r)
1067 d = builddelta(p2r)
1068 else:
1068 else:
1069 d = builddelta(prev)
1069 d = builddelta(prev)
1070 else:
1070 else:
1071 d = builddelta(prev)
1071 d = builddelta(prev)
1072 dist, l, data, base, chainbase = d
1072 dist, l, data, base, chainbase = d
1073
1073
1074 # full versions are inserted when the needed deltas
1074 # full versions are inserted when the needed deltas
1075 # become comparable to the uncompressed text
1075 # become comparable to the uncompressed text
1076 if text is None:
1076 if text is None:
1077 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1077 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1078 cachedelta[1])
1078 cachedelta[1])
1079 else:
1079 else:
1080 textlen = len(text)
1080 textlen = len(text)
1081 if d is None or dist > textlen * 2:
1081 if d is None or dist > textlen * 2:
1082 text = buildtext()
1082 text = buildtext()
1083 data = compress(text)
1083 data = compress(text)
1084 l = len(data[1]) + len(data[0])
1084 l = len(data[1]) + len(data[0])
1085 base = chainbase = curr
1085 base = chainbase = curr
1086
1086
1087 e = (offset_type(offset, flags), l, textlen,
1087 e = (offset_type(offset, flags), l, textlen,
1088 base, link, p1r, p2r, node)
1088 base, link, p1r, p2r, node)
1089 self.index.insert(-1, e)
1089 self.index.insert(-1, e)
1090 self.nodemap[node] = curr
1090 self.nodemap[node] = curr
1091
1091
1092 entry = self._io.packentry(e, self.node, self.version, curr)
1092 entry = self._io.packentry(e, self.node, self.version, curr)
1093 if not self._inline:
1093 if not self._inline:
1094 transaction.add(self.datafile, offset)
1094 transaction.add(self.datafile, offset)
1095 transaction.add(self.indexfile, curr * len(entry))
1095 transaction.add(self.indexfile, curr * len(entry))
1096 if data[0]:
1096 if data[0]:
1097 dfh.write(data[0])
1097 dfh.write(data[0])
1098 dfh.write(data[1])
1098 dfh.write(data[1])
1099 dfh.flush()
1099 dfh.flush()
1100 ifh.write(entry)
1100 ifh.write(entry)
1101 else:
1101 else:
1102 offset += curr * self._io.size
1102 offset += curr * self._io.size
1103 transaction.add(self.indexfile, offset, curr)
1103 transaction.add(self.indexfile, offset, curr)
1104 ifh.write(entry)
1104 ifh.write(entry)
1105 ifh.write(data[0])
1105 ifh.write(data[0])
1106 ifh.write(data[1])
1106 ifh.write(data[1])
1107 self.checkinlinesize(transaction, ifh)
1107 self.checkinlinesize(transaction, ifh)
1108
1108
1109 if type(text) == str: # only accept immutable objects
1109 if type(text) == str: # only accept immutable objects
1110 self._cache = (node, curr, text)
1110 self._cache = (node, curr, text)
1111 self._basecache = (curr, chainbase)
1111 self._basecache = (curr, chainbase)
1112 return node
1112 return node
1113
1113
1114 def group(self, nodelist, bundler, reorder=None):
1114 def group(self, nodelist, bundler, reorder=None):
1115 """Calculate a delta group, yielding a sequence of changegroup chunks
1115 """Calculate a delta group, yielding a sequence of changegroup chunks
1116 (strings).
1116 (strings).
1117
1117
1118 Given a list of changeset revs, return a set of deltas and
1118 Given a list of changeset revs, return a set of deltas and
1119 metadata corresponding to nodes. The first delta is
1119 metadata corresponding to nodes. The first delta is
1120 first parent(nodelist[0]) -> nodelist[0], the receiver is
1120 first parent(nodelist[0]) -> nodelist[0], the receiver is
1121 guaranteed to have this parent as it has all history before
1121 guaranteed to have this parent as it has all history before
1122 these changesets. In the case firstparent is nullrev the
1122 these changesets. In the case firstparent is nullrev the
1123 changegroup starts with a full revision.
1123 changegroup starts with a full revision.
1124 """
1124 """
1125
1125
1126 # if we don't have any revisions touched by these changesets, bail
1126 # if we don't have any revisions touched by these changesets, bail
1127 if len(nodelist) == 0:
1127 if len(nodelist) == 0:
1128 yield bundler.close()
1128 yield bundler.close()
1129 return
1129 return
1130
1130
1131 # for generaldelta revlogs, we linearize the revs; this will both be
1131 # for generaldelta revlogs, we linearize the revs; this will both be
1132 # much quicker and generate a much smaller bundle
1132 # much quicker and generate a much smaller bundle
1133 if (self._generaldelta and reorder is not False) or reorder:
1133 if (self._generaldelta and reorder is not False) or reorder:
1134 dag = dagutil.revlogdag(self)
1134 dag = dagutil.revlogdag(self)
1135 revs = set(self.rev(n) for n in nodelist)
1135 revs = set(self.rev(n) for n in nodelist)
1136 revs = dag.linearize(revs)
1136 revs = dag.linearize(revs)
1137 else:
1137 else:
1138 revs = sorted([self.rev(n) for n in nodelist])
1138 revs = sorted([self.rev(n) for n in nodelist])
1139
1139
1140 # add the parent of the first rev
1140 # add the parent of the first rev
1141 p = self.parentrevs(revs[0])[0]
1141 p = self.parentrevs(revs[0])[0]
1142 revs.insert(0, p)
1142 revs.insert(0, p)
1143
1143
1144 # build deltas
1144 # build deltas
1145 for r in xrange(len(revs) - 1):
1145 for r in xrange(len(revs) - 1):
1146 prev, curr = revs[r], revs[r + 1]
1146 prev, curr = revs[r], revs[r + 1]
1147 for c in bundler.revchunk(self, curr, prev):
1147 for c in bundler.revchunk(self, curr, prev):
1148 yield c
1148 yield c
1149
1149
1150 yield bundler.close()
1150 yield bundler.close()
1151
1151
1152 def addgroup(self, bundle, linkmapper, transaction):
1152 def addgroup(self, bundle, linkmapper, transaction):
1153 """
1153 """
1154 add a delta group
1154 add a delta group
1155
1155
1156 given a set of deltas, add them to the revision log. the
1156 given a set of deltas, add them to the revision log. the
1157 first delta is against its parent, which should be in our
1157 first delta is against its parent, which should be in our
1158 log, the rest are against the previous delta.
1158 log, the rest are against the previous delta.
1159 """
1159 """
1160
1160
1161 # track the base of the current delta log
1161 # track the base of the current delta log
1162 content = []
1162 content = []
1163 node = None
1163 node = None
1164
1164
1165 r = len(self)
1165 r = len(self)
1166 end = 0
1166 end = 0
1167 if r:
1167 if r:
1168 end = self.end(r - 1)
1168 end = self.end(r - 1)
1169 ifh = self.opener(self.indexfile, "a+")
1169 ifh = self.opener(self.indexfile, "a+")
1170 isize = r * self._io.size
1170 isize = r * self._io.size
1171 if self._inline:
1171 if self._inline:
1172 transaction.add(self.indexfile, end + isize, r)
1172 transaction.add(self.indexfile, end + isize, r)
1173 dfh = None
1173 dfh = None
1174 else:
1174 else:
1175 transaction.add(self.indexfile, isize, r)
1175 transaction.add(self.indexfile, isize, r)
1176 transaction.add(self.datafile, end)
1176 transaction.add(self.datafile, end)
1177 dfh = self.opener(self.datafile, "a")
1177 dfh = self.opener(self.datafile, "a")
1178
1178
1179 try:
1179 try:
1180 # loop through our set of deltas
1180 # loop through our set of deltas
1181 chain = None
1181 chain = None
1182 while True:
1182 while True:
1183 chunkdata = bundle.deltachunk(chain)
1183 chunkdata = bundle.deltachunk(chain)
1184 if not chunkdata:
1184 if not chunkdata:
1185 break
1185 break
1186 node = chunkdata['node']
1186 node = chunkdata['node']
1187 p1 = chunkdata['p1']
1187 p1 = chunkdata['p1']
1188 p2 = chunkdata['p2']
1188 p2 = chunkdata['p2']
1189 cs = chunkdata['cs']
1189 cs = chunkdata['cs']
1190 deltabase = chunkdata['deltabase']
1190 deltabase = chunkdata['deltabase']
1191 delta = chunkdata['delta']
1191 delta = chunkdata['delta']
1192
1192
1193 content.append(node)
1193 content.append(node)
1194
1194
1195 link = linkmapper(cs)
1195 link = linkmapper(cs)
1196 if node in self.nodemap:
1196 if node in self.nodemap:
1197 # this can happen if two branches make the same change
1197 # this can happen if two branches make the same change
1198 chain = node
1198 chain = node
1199 continue
1199 continue
1200
1200
1201 for p in (p1, p2):
1201 for p in (p1, p2):
1202 if not p in self.nodemap:
1202 if not p in self.nodemap:
1203 raise LookupError(p, self.indexfile,
1203 raise LookupError(p, self.indexfile,
1204 _('unknown parent'))
1204 _('unknown parent'))
1205
1205
1206 if deltabase not in self.nodemap:
1206 if deltabase not in self.nodemap:
1207 raise LookupError(deltabase, self.indexfile,
1207 raise LookupError(deltabase, self.indexfile,
1208 _('unknown delta base'))
1208 _('unknown delta base'))
1209
1209
1210 baserev = self.rev(deltabase)
1210 baserev = self.rev(deltabase)
1211 chain = self._addrevision(node, None, transaction, link,
1211 chain = self._addrevision(node, None, transaction, link,
1212 p1, p2, (baserev, delta), ifh, dfh)
1212 p1, p2, (baserev, delta), ifh, dfh)
1213 if not dfh and not self._inline:
1213 if not dfh and not self._inline:
1214 # addrevision switched from inline to conventional
1214 # addrevision switched from inline to conventional
1215 # reopen the index
1215 # reopen the index
1216 ifh.close()
1216 ifh.close()
1217 dfh = self.opener(self.datafile, "a")
1217 dfh = self.opener(self.datafile, "a")
1218 ifh = self.opener(self.indexfile, "a")
1218 ifh = self.opener(self.indexfile, "a")
1219 finally:
1219 finally:
1220 if dfh:
1220 if dfh:
1221 dfh.close()
1221 dfh.close()
1222 ifh.close()
1222 ifh.close()
1223
1223
1224 return content
1224 return content
1225
1225
1226 def strip(self, minlink, transaction):
1226 def strip(self, minlink, transaction):
1227 """truncate the revlog on the first revision with a linkrev >= minlink
1227 """truncate the revlog on the first revision with a linkrev >= minlink
1228
1228
1229 This function is called when we're stripping revision minlink and
1229 This function is called when we're stripping revision minlink and
1230 its descendants from the repository.
1230 its descendants from the repository.
1231
1231
1232 We have to remove all revisions with linkrev >= minlink, because
1232 We have to remove all revisions with linkrev >= minlink, because
1233 the equivalent changelog revisions will be renumbered after the
1233 the equivalent changelog revisions will be renumbered after the
1234 strip.
1234 strip.
1235
1235
1236 So we truncate the revlog on the first of these revisions, and
1236 So we truncate the revlog on the first of these revisions, and
1237 trust that the caller has saved the revisions that shouldn't be
1237 trust that the caller has saved the revisions that shouldn't be
1238 removed and that it'll re-add them after this truncation.
1238 removed and that it'll re-add them after this truncation.
1239 """
1239 """
1240 if len(self) == 0:
1240 if len(self) == 0:
1241 return
1241 return
1242
1242
1243 for rev in self:
1243 for rev in self:
1244 if self.index[rev][4] >= minlink:
1244 if self.index[rev][4] >= minlink:
1245 break
1245 break
1246 else:
1246 else:
1247 return
1247 return
1248
1248
1249 # first truncate the files on disk
1249 # first truncate the files on disk
1250 end = self.start(rev)
1250 end = self.start(rev)
1251 if not self._inline:
1251 if not self._inline:
1252 transaction.add(self.datafile, end)
1252 transaction.add(self.datafile, end)
1253 end = rev * self._io.size
1253 end = rev * self._io.size
1254 else:
1254 else:
1255 end += rev * self._io.size
1255 end += rev * self._io.size
1256
1256
1257 transaction.add(self.indexfile, end)
1257 transaction.add(self.indexfile, end)
1258
1258
1259 # then reset internal state in memory to forget those revisions
1259 # then reset internal state in memory to forget those revisions
1260 self._cache = None
1260 self._cache = None
1261 self._chunkclear()
1261 self._chunkclear()
1262 for x in xrange(rev, len(self)):
1262 for x in xrange(rev, len(self)):
1263 del self.nodemap[self.node(x)]
1263 del self.nodemap[self.node(x)]
1264
1264
1265 del self.index[rev:-1]
1265 del self.index[rev:-1]
1266
1266
1267 def checksize(self):
1267 def checksize(self):
1268 expected = 0
1268 expected = 0
1269 if len(self):
1269 if len(self):
1270 expected = max(0, self.end(len(self) - 1))
1270 expected = max(0, self.end(len(self) - 1))
1271
1271
1272 try:
1272 try:
1273 f = self.opener(self.datafile)
1273 f = self.opener(self.datafile)
1274 f.seek(0, 2)
1274 f.seek(0, 2)
1275 actual = f.tell()
1275 actual = f.tell()
1276 f.close()
1276 f.close()
1277 dd = actual - expected
1277 dd = actual - expected
1278 except IOError, inst:
1278 except IOError, inst:
1279 if inst.errno != errno.ENOENT:
1279 if inst.errno != errno.ENOENT:
1280 raise
1280 raise
1281 dd = 0
1281 dd = 0
1282
1282
1283 try:
1283 try:
1284 f = self.opener(self.indexfile)
1284 f = self.opener(self.indexfile)
1285 f.seek(0, 2)
1285 f.seek(0, 2)
1286 actual = f.tell()
1286 actual = f.tell()
1287 f.close()
1287 f.close()
1288 s = self._io.size
1288 s = self._io.size
1289 i = max(0, actual // s)
1289 i = max(0, actual // s)
1290 di = actual - (i * s)
1290 di = actual - (i * s)
1291 if self._inline:
1291 if self._inline:
1292 databytes = 0
1292 databytes = 0
1293 for r in self:
1293 for r in self:
1294 databytes += max(0, self.length(r))
1294 databytes += max(0, self.length(r))
1295 dd = 0
1295 dd = 0
1296 di = actual - len(self) * s - databytes
1296 di = actual - len(self) * s - databytes
1297 except IOError, inst:
1297 except IOError, inst:
1298 if inst.errno != errno.ENOENT:
1298 if inst.errno != errno.ENOENT:
1299 raise
1299 raise
1300 di = 0
1300 di = 0
1301
1301
1302 return (dd, di)
1302 return (dd, di)
1303
1303
1304 def files(self):
1304 def files(self):
1305 res = [self.indexfile]
1305 res = [self.indexfile]
1306 if not self._inline:
1306 if not self._inline:
1307 res.append(self.datafile)
1307 res.append(self.datafile)
1308 return res
1308 return res
General Comments 0
You need to be logged in to leave comments. Login now