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