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