##// END OF EJS Templates
revlog: add __contains__ for fast membership test...
Yuya Nishihara -
r24030:828dc8db default
parent child Browse files
Show More
@@ -1,380 +1,385 b''
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class 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 from node import bin, hex, nullid
8 from node import bin, hex, nullid
9 from i18n import _
9 from i18n import _
10 import util, error, revlog, encoding
10 import util, error, revlog, encoding
11
11
12 _defaultextra = {'branch': 'default'}
12 _defaultextra = {'branch': 'default'}
13
13
14 def _string_escape(text):
14 def _string_escape(text):
15 """
15 """
16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
18 >>> s
18 >>> s
19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
20 >>> res = _string_escape(s)
20 >>> res = _string_escape(s)
21 >>> s == res.decode('string_escape')
21 >>> s == res.decode('string_escape')
22 True
22 True
23 """
23 """
24 # subset of the string_escape codec
24 # subset of the string_escape codec
25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
26 return text.replace('\0', '\\0')
26 return text.replace('\0', '\\0')
27
27
28 def decodeextra(text):
28 def decodeextra(text):
29 """
29 """
30 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
30 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
31 ... ).iteritems())
31 ... ).iteritems())
32 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
32 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
33 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
33 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
34 ... 'baz': chr(92) + chr(0) + '2'})
34 ... 'baz': chr(92) + chr(0) + '2'})
35 ... ).iteritems())
35 ... ).iteritems())
36 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
36 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
37 """
37 """
38 extra = _defaultextra.copy()
38 extra = _defaultextra.copy()
39 for l in text.split('\0'):
39 for l in text.split('\0'):
40 if l:
40 if l:
41 if '\\0' in l:
41 if '\\0' in l:
42 # fix up \0 without getting into trouble with \\0
42 # fix up \0 without getting into trouble with \\0
43 l = l.replace('\\\\', '\\\\\n')
43 l = l.replace('\\\\', '\\\\\n')
44 l = l.replace('\\0', '\0')
44 l = l.replace('\\0', '\0')
45 l = l.replace('\n', '')
45 l = l.replace('\n', '')
46 k, v = l.decode('string_escape').split(':', 1)
46 k, v = l.decode('string_escape').split(':', 1)
47 extra[k] = v
47 extra[k] = v
48 return extra
48 return extra
49
49
50 def encodeextra(d):
50 def encodeextra(d):
51 # keys must be sorted to produce a deterministic changelog entry
51 # keys must be sorted to produce a deterministic changelog entry
52 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
52 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
53 return "\0".join(items)
53 return "\0".join(items)
54
54
55 def stripdesc(desc):
55 def stripdesc(desc):
56 """strip trailing whitespace and leading and trailing empty lines"""
56 """strip trailing whitespace and leading and trailing empty lines"""
57 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
57 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
58
58
59 class appender(object):
59 class appender(object):
60 '''the changelog index must be updated last on disk, so we use this class
60 '''the changelog index must be updated last on disk, so we use this class
61 to delay writes to it'''
61 to delay writes to it'''
62 def __init__(self, vfs, name, mode, buf):
62 def __init__(self, vfs, name, mode, buf):
63 self.data = buf
63 self.data = buf
64 fp = vfs(name, mode)
64 fp = vfs(name, mode)
65 self.fp = fp
65 self.fp = fp
66 self.offset = fp.tell()
66 self.offset = fp.tell()
67 self.size = vfs.fstat(fp).st_size
67 self.size = vfs.fstat(fp).st_size
68
68
69 def end(self):
69 def end(self):
70 return self.size + len("".join(self.data))
70 return self.size + len("".join(self.data))
71 def tell(self):
71 def tell(self):
72 return self.offset
72 return self.offset
73 def flush(self):
73 def flush(self):
74 pass
74 pass
75 def close(self):
75 def close(self):
76 self.fp.close()
76 self.fp.close()
77
77
78 def seek(self, offset, whence=0):
78 def seek(self, offset, whence=0):
79 '''virtual file offset spans real file and data'''
79 '''virtual file offset spans real file and data'''
80 if whence == 0:
80 if whence == 0:
81 self.offset = offset
81 self.offset = offset
82 elif whence == 1:
82 elif whence == 1:
83 self.offset += offset
83 self.offset += offset
84 elif whence == 2:
84 elif whence == 2:
85 self.offset = self.end() + offset
85 self.offset = self.end() + offset
86 if self.offset < self.size:
86 if self.offset < self.size:
87 self.fp.seek(self.offset)
87 self.fp.seek(self.offset)
88
88
89 def read(self, count=-1):
89 def read(self, count=-1):
90 '''only trick here is reads that span real file and data'''
90 '''only trick here is reads that span real file and data'''
91 ret = ""
91 ret = ""
92 if self.offset < self.size:
92 if self.offset < self.size:
93 s = self.fp.read(count)
93 s = self.fp.read(count)
94 ret = s
94 ret = s
95 self.offset += len(s)
95 self.offset += len(s)
96 if count > 0:
96 if count > 0:
97 count -= len(s)
97 count -= len(s)
98 if count != 0:
98 if count != 0:
99 doff = self.offset - self.size
99 doff = self.offset - self.size
100 self.data.insert(0, "".join(self.data))
100 self.data.insert(0, "".join(self.data))
101 del self.data[1:]
101 del self.data[1:]
102 s = self.data[0][doff:doff + count]
102 s = self.data[0][doff:doff + count]
103 self.offset += len(s)
103 self.offset += len(s)
104 ret += s
104 ret += s
105 return ret
105 return ret
106
106
107 def write(self, s):
107 def write(self, s):
108 self.data.append(str(s))
108 self.data.append(str(s))
109 self.offset += len(s)
109 self.offset += len(s)
110
110
111 def _divertopener(opener, target):
111 def _divertopener(opener, target):
112 """build an opener that writes in 'target.a' instead of 'target'"""
112 """build an opener that writes in 'target.a' instead of 'target'"""
113 def _divert(name, mode='r'):
113 def _divert(name, mode='r'):
114 if name != target:
114 if name != target:
115 return opener(name, mode)
115 return opener(name, mode)
116 return opener(name + ".a", mode)
116 return opener(name + ".a", mode)
117 return _divert
117 return _divert
118
118
119 def _delayopener(opener, target, buf):
119 def _delayopener(opener, target, buf):
120 """build an opener that stores chunks in 'buf' instead of 'target'"""
120 """build an opener that stores chunks in 'buf' instead of 'target'"""
121 def _delay(name, mode='r'):
121 def _delay(name, mode='r'):
122 if name != target:
122 if name != target:
123 return opener(name, mode)
123 return opener(name, mode)
124 return appender(opener, name, mode, buf)
124 return appender(opener, name, mode, buf)
125 return _delay
125 return _delay
126
126
127 class changelog(revlog.revlog):
127 class changelog(revlog.revlog):
128 def __init__(self, opener):
128 def __init__(self, opener):
129 revlog.revlog.__init__(self, opener, "00changelog.i")
129 revlog.revlog.__init__(self, opener, "00changelog.i")
130 if self._initempty:
130 if self._initempty:
131 # changelogs don't benefit from generaldelta
131 # changelogs don't benefit from generaldelta
132 self.version &= ~revlog.REVLOGGENERALDELTA
132 self.version &= ~revlog.REVLOGGENERALDELTA
133 self._generaldelta = False
133 self._generaldelta = False
134 self._realopener = opener
134 self._realopener = opener
135 self._delayed = False
135 self._delayed = False
136 self._delaybuf = None
136 self._delaybuf = None
137 self._divert = False
137 self._divert = False
138 self.filteredrevs = frozenset()
138 self.filteredrevs = frozenset()
139
139
140 def tip(self):
140 def tip(self):
141 """filtered version of revlog.tip"""
141 """filtered version of revlog.tip"""
142 for i in xrange(len(self) -1, -2, -1):
142 for i in xrange(len(self) -1, -2, -1):
143 if i not in self.filteredrevs:
143 if i not in self.filteredrevs:
144 return self.node(i)
144 return self.node(i)
145
145
146 def __contains__(self, rev):
147 """filtered version of revlog.__contains__"""
148 return (revlog.revlog.__contains__(self, rev)
149 and rev not in self.filteredrevs)
150
146 def __iter__(self):
151 def __iter__(self):
147 """filtered version of revlog.__iter__"""
152 """filtered version of revlog.__iter__"""
148 if len(self.filteredrevs) == 0:
153 if len(self.filteredrevs) == 0:
149 return revlog.revlog.__iter__(self)
154 return revlog.revlog.__iter__(self)
150
155
151 def filterediter():
156 def filterediter():
152 for i in xrange(len(self)):
157 for i in xrange(len(self)):
153 if i not in self.filteredrevs:
158 if i not in self.filteredrevs:
154 yield i
159 yield i
155
160
156 return filterediter()
161 return filterediter()
157
162
158 def revs(self, start=0, stop=None):
163 def revs(self, start=0, stop=None):
159 """filtered version of revlog.revs"""
164 """filtered version of revlog.revs"""
160 for i in super(changelog, self).revs(start, stop):
165 for i in super(changelog, self).revs(start, stop):
161 if i not in self.filteredrevs:
166 if i not in self.filteredrevs:
162 yield i
167 yield i
163
168
164 @util.propertycache
169 @util.propertycache
165 def nodemap(self):
170 def nodemap(self):
166 # XXX need filtering too
171 # XXX need filtering too
167 self.rev(self.node(0))
172 self.rev(self.node(0))
168 return self._nodecache
173 return self._nodecache
169
174
170 def hasnode(self, node):
175 def hasnode(self, node):
171 """filtered version of revlog.hasnode"""
176 """filtered version of revlog.hasnode"""
172 try:
177 try:
173 i = self.rev(node)
178 i = self.rev(node)
174 return i not in self.filteredrevs
179 return i not in self.filteredrevs
175 except KeyError:
180 except KeyError:
176 return False
181 return False
177
182
178 def headrevs(self):
183 def headrevs(self):
179 if self.filteredrevs:
184 if self.filteredrevs:
180 try:
185 try:
181 return self.index.headrevsfiltered(self.filteredrevs)
186 return self.index.headrevsfiltered(self.filteredrevs)
182 # AttributeError covers non-c-extension environments and
187 # AttributeError covers non-c-extension environments and
183 # old c extensions without filter handling.
188 # old c extensions without filter handling.
184 except AttributeError:
189 except AttributeError:
185 return self._headrevs()
190 return self._headrevs()
186
191
187 return super(changelog, self).headrevs()
192 return super(changelog, self).headrevs()
188
193
189 def strip(self, *args, **kwargs):
194 def strip(self, *args, **kwargs):
190 # XXX make something better than assert
195 # XXX make something better than assert
191 # We can't expect proper strip behavior if we are filtered.
196 # We can't expect proper strip behavior if we are filtered.
192 assert not self.filteredrevs
197 assert not self.filteredrevs
193 super(changelog, self).strip(*args, **kwargs)
198 super(changelog, self).strip(*args, **kwargs)
194
199
195 def rev(self, node):
200 def rev(self, node):
196 """filtered version of revlog.rev"""
201 """filtered version of revlog.rev"""
197 r = super(changelog, self).rev(node)
202 r = super(changelog, self).rev(node)
198 if r in self.filteredrevs:
203 if r in self.filteredrevs:
199 raise error.FilteredLookupError(hex(node), self.indexfile,
204 raise error.FilteredLookupError(hex(node), self.indexfile,
200 _('filtered node'))
205 _('filtered node'))
201 return r
206 return r
202
207
203 def node(self, rev):
208 def node(self, rev):
204 """filtered version of revlog.node"""
209 """filtered version of revlog.node"""
205 if rev in self.filteredrevs:
210 if rev in self.filteredrevs:
206 raise error.FilteredIndexError(rev)
211 raise error.FilteredIndexError(rev)
207 return super(changelog, self).node(rev)
212 return super(changelog, self).node(rev)
208
213
209 def linkrev(self, rev):
214 def linkrev(self, rev):
210 """filtered version of revlog.linkrev"""
215 """filtered version of revlog.linkrev"""
211 if rev in self.filteredrevs:
216 if rev in self.filteredrevs:
212 raise error.FilteredIndexError(rev)
217 raise error.FilteredIndexError(rev)
213 return super(changelog, self).linkrev(rev)
218 return super(changelog, self).linkrev(rev)
214
219
215 def parentrevs(self, rev):
220 def parentrevs(self, rev):
216 """filtered version of revlog.parentrevs"""
221 """filtered version of revlog.parentrevs"""
217 if rev in self.filteredrevs:
222 if rev in self.filteredrevs:
218 raise error.FilteredIndexError(rev)
223 raise error.FilteredIndexError(rev)
219 return super(changelog, self).parentrevs(rev)
224 return super(changelog, self).parentrevs(rev)
220
225
221 def flags(self, rev):
226 def flags(self, rev):
222 """filtered version of revlog.flags"""
227 """filtered version of revlog.flags"""
223 if rev in self.filteredrevs:
228 if rev in self.filteredrevs:
224 raise error.FilteredIndexError(rev)
229 raise error.FilteredIndexError(rev)
225 return super(changelog, self).flags(rev)
230 return super(changelog, self).flags(rev)
226
231
227 def delayupdate(self, tr):
232 def delayupdate(self, tr):
228 "delay visibility of index updates to other readers"
233 "delay visibility of index updates to other readers"
229
234
230 if not self._delayed:
235 if not self._delayed:
231 if len(self) == 0:
236 if len(self) == 0:
232 self._divert = True
237 self._divert = True
233 if self._realopener.exists(self.indexfile + '.a'):
238 if self._realopener.exists(self.indexfile + '.a'):
234 self._realopener.unlink(self.indexfile + '.a')
239 self._realopener.unlink(self.indexfile + '.a')
235 self.opener = _divertopener(self._realopener, self.indexfile)
240 self.opener = _divertopener(self._realopener, self.indexfile)
236 else:
241 else:
237 self._delaybuf = []
242 self._delaybuf = []
238 self.opener = _delayopener(self._realopener, self.indexfile,
243 self.opener = _delayopener(self._realopener, self.indexfile,
239 self._delaybuf)
244 self._delaybuf)
240 self._delayed = True
245 self._delayed = True
241 tr.addpending('cl-%i' % id(self), self._writepending)
246 tr.addpending('cl-%i' % id(self), self._writepending)
242 tr.addfinalize('cl-%i' % id(self), self._finalize)
247 tr.addfinalize('cl-%i' % id(self), self._finalize)
243
248
244 def _finalize(self, tr):
249 def _finalize(self, tr):
245 "finalize index updates"
250 "finalize index updates"
246 self._delayed = False
251 self._delayed = False
247 self.opener = self._realopener
252 self.opener = self._realopener
248 # move redirected index data back into place
253 # move redirected index data back into place
249 if self._divert:
254 if self._divert:
250 assert not self._delaybuf
255 assert not self._delaybuf
251 tmpname = self.indexfile + ".a"
256 tmpname = self.indexfile + ".a"
252 nfile = self.opener.open(tmpname)
257 nfile = self.opener.open(tmpname)
253 nfile.close()
258 nfile.close()
254 self.opener.rename(tmpname, self.indexfile)
259 self.opener.rename(tmpname, self.indexfile)
255 elif self._delaybuf:
260 elif self._delaybuf:
256 fp = self.opener(self.indexfile, 'a')
261 fp = self.opener(self.indexfile, 'a')
257 fp.write("".join(self._delaybuf))
262 fp.write("".join(self._delaybuf))
258 fp.close()
263 fp.close()
259 self._delaybuf = None
264 self._delaybuf = None
260 self._divert = False
265 self._divert = False
261 # split when we're done
266 # split when we're done
262 self.checkinlinesize(tr)
267 self.checkinlinesize(tr)
263
268
264 def readpending(self, file):
269 def readpending(self, file):
265 r = revlog.revlog(self.opener, file)
270 r = revlog.revlog(self.opener, file)
266 self.index = r.index
271 self.index = r.index
267 self.nodemap = r.nodemap
272 self.nodemap = r.nodemap
268 self._nodecache = r._nodecache
273 self._nodecache = r._nodecache
269 self._chunkcache = r._chunkcache
274 self._chunkcache = r._chunkcache
270
275
271 def _writepending(self, tr):
276 def _writepending(self, tr):
272 "create a file containing the unfinalized state for pretxnchangegroup"
277 "create a file containing the unfinalized state for pretxnchangegroup"
273 if self._delaybuf:
278 if self._delaybuf:
274 # make a temporary copy of the index
279 # make a temporary copy of the index
275 fp1 = self._realopener(self.indexfile)
280 fp1 = self._realopener(self.indexfile)
276 pendingfilename = self.indexfile + ".a"
281 pendingfilename = self.indexfile + ".a"
277 # register as a temp file to ensure cleanup on failure
282 # register as a temp file to ensure cleanup on failure
278 tr.registertmp(pendingfilename)
283 tr.registertmp(pendingfilename)
279 # write existing data
284 # write existing data
280 fp2 = self._realopener(pendingfilename, "w")
285 fp2 = self._realopener(pendingfilename, "w")
281 fp2.write(fp1.read())
286 fp2.write(fp1.read())
282 # add pending data
287 # add pending data
283 fp2.write("".join(self._delaybuf))
288 fp2.write("".join(self._delaybuf))
284 fp2.close()
289 fp2.close()
285 # switch modes so finalize can simply rename
290 # switch modes so finalize can simply rename
286 self._delaybuf = None
291 self._delaybuf = None
287 self._divert = True
292 self._divert = True
288 self.opener = _divertopener(self._realopener, self.indexfile)
293 self.opener = _divertopener(self._realopener, self.indexfile)
289
294
290 if self._divert:
295 if self._divert:
291 return True
296 return True
292
297
293 return False
298 return False
294
299
295 def checkinlinesize(self, tr, fp=None):
300 def checkinlinesize(self, tr, fp=None):
296 if not self._delayed:
301 if not self._delayed:
297 revlog.revlog.checkinlinesize(self, tr, fp)
302 revlog.revlog.checkinlinesize(self, tr, fp)
298
303
299 def read(self, node):
304 def read(self, node):
300 """
305 """
301 format used:
306 format used:
302 nodeid\n : manifest node in ascii
307 nodeid\n : manifest node in ascii
303 user\n : user, no \n or \r allowed
308 user\n : user, no \n or \r allowed
304 time tz extra\n : date (time is int or float, timezone is int)
309 time tz extra\n : date (time is int or float, timezone is int)
305 : extra is metadata, encoded and separated by '\0'
310 : extra is metadata, encoded and separated by '\0'
306 : older versions ignore it
311 : older versions ignore it
307 files\n\n : files modified by the cset, no \n or \r allowed
312 files\n\n : files modified by the cset, no \n or \r allowed
308 (.*) : comment (free text, ideally utf-8)
313 (.*) : comment (free text, ideally utf-8)
309
314
310 changelog v0 doesn't use extra
315 changelog v0 doesn't use extra
311 """
316 """
312 text = self.revision(node)
317 text = self.revision(node)
313 if not text:
318 if not text:
314 return (nullid, "", (0, 0), [], "", _defaultextra)
319 return (nullid, "", (0, 0), [], "", _defaultextra)
315 last = text.index("\n\n")
320 last = text.index("\n\n")
316 desc = encoding.tolocal(text[last + 2:])
321 desc = encoding.tolocal(text[last + 2:])
317 l = text[:last].split('\n')
322 l = text[:last].split('\n')
318 manifest = bin(l[0])
323 manifest = bin(l[0])
319 user = encoding.tolocal(l[1])
324 user = encoding.tolocal(l[1])
320
325
321 tdata = l[2].split(' ', 2)
326 tdata = l[2].split(' ', 2)
322 if len(tdata) != 3:
327 if len(tdata) != 3:
323 time = float(tdata[0])
328 time = float(tdata[0])
324 try:
329 try:
325 # various tools did silly things with the time zone field.
330 # various tools did silly things with the time zone field.
326 timezone = int(tdata[1])
331 timezone = int(tdata[1])
327 except ValueError:
332 except ValueError:
328 timezone = 0
333 timezone = 0
329 extra = _defaultextra
334 extra = _defaultextra
330 else:
335 else:
331 time, timezone = float(tdata[0]), int(tdata[1])
336 time, timezone = float(tdata[0]), int(tdata[1])
332 extra = decodeextra(tdata[2])
337 extra = decodeextra(tdata[2])
333
338
334 files = l[3:]
339 files = l[3:]
335 return (manifest, user, (time, timezone), files, desc, extra)
340 return (manifest, user, (time, timezone), files, desc, extra)
336
341
337 def add(self, manifest, files, desc, transaction, p1, p2,
342 def add(self, manifest, files, desc, transaction, p1, p2,
338 user, date=None, extra=None):
343 user, date=None, extra=None):
339 # Convert to UTF-8 encoded bytestrings as the very first
344 # Convert to UTF-8 encoded bytestrings as the very first
340 # thing: calling any method on a localstr object will turn it
345 # thing: calling any method on a localstr object will turn it
341 # into a str object and the cached UTF-8 string is thus lost.
346 # into a str object and the cached UTF-8 string is thus lost.
342 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
347 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
343
348
344 user = user.strip()
349 user = user.strip()
345 # An empty username or a username with a "\n" will make the
350 # An empty username or a username with a "\n" will make the
346 # revision text contain two "\n\n" sequences -> corrupt
351 # revision text contain two "\n\n" sequences -> corrupt
347 # repository since read cannot unpack the revision.
352 # repository since read cannot unpack the revision.
348 if not user:
353 if not user:
349 raise error.RevlogError(_("empty username"))
354 raise error.RevlogError(_("empty username"))
350 if "\n" in user:
355 if "\n" in user:
351 raise error.RevlogError(_("username %s contains a newline")
356 raise error.RevlogError(_("username %s contains a newline")
352 % repr(user))
357 % repr(user))
353
358
354 desc = stripdesc(desc)
359 desc = stripdesc(desc)
355
360
356 if date:
361 if date:
357 parseddate = "%d %d" % util.parsedate(date)
362 parseddate = "%d %d" % util.parsedate(date)
358 else:
363 else:
359 parseddate = "%d %d" % util.makedate()
364 parseddate = "%d %d" % util.makedate()
360 if extra:
365 if extra:
361 branch = extra.get("branch")
366 branch = extra.get("branch")
362 if branch in ("default", ""):
367 if branch in ("default", ""):
363 del extra["branch"]
368 del extra["branch"]
364 elif branch in (".", "null", "tip"):
369 elif branch in (".", "null", "tip"):
365 raise error.RevlogError(_('the name \'%s\' is reserved')
370 raise error.RevlogError(_('the name \'%s\' is reserved')
366 % branch)
371 % branch)
367 if extra:
372 if extra:
368 extra = encodeextra(extra)
373 extra = encodeextra(extra)
369 parseddate = "%s %s" % (parseddate, extra)
374 parseddate = "%s %s" % (parseddate, extra)
370 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
375 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
371 text = "\n".join(l)
376 text = "\n".join(l)
372 return self.addrevision(text, transaction, len(self), p1, p2)
377 return self.addrevision(text, transaction, len(self), p1, p2)
373
378
374 def branchinfo(self, rev):
379 def branchinfo(self, rev):
375 """return the branch name and open/close state of a revision
380 """return the branch name and open/close state of a revision
376
381
377 This function exists because creating a changectx object
382 This function exists because creating a changectx object
378 just to access this is costly."""
383 just to access this is costly."""
379 extra = self.read(rev)[5]
384 extra = self.read(rev)[5]
380 return encoding.tolocal(extra.get("branch")), 'close' in extra
385 return encoding.tolocal(extra.get("branch")), 'close' in extra
@@ -1,1541 +1,1543 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, templatefilters
17 import ancestor, mdiff, parsers, error, util, templatefilters
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_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
37 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
38 REVIDX_DEFAULT_FLAGS = 0
38 REVIDX_DEFAULT_FLAGS = 0
39 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
39 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
40
40
41 # max size of revlog with inline data
41 # max size of revlog with inline data
42 _maxinline = 131072
42 _maxinline = 131072
43 _chunksize = 1048576
43 _chunksize = 1048576
44
44
45 RevlogError = error.RevlogError
45 RevlogError = error.RevlogError
46 LookupError = error.LookupError
46 LookupError = error.LookupError
47 CensoredNodeError = error.CensoredNodeError
47 CensoredNodeError = error.CensoredNodeError
48
48
49 def getoffset(q):
49 def getoffset(q):
50 return int(q >> 16)
50 return int(q >> 16)
51
51
52 def gettype(q):
52 def gettype(q):
53 return int(q & 0xFFFF)
53 return int(q & 0xFFFF)
54
54
55 def offset_type(offset, type):
55 def offset_type(offset, type):
56 return long(long(offset) << 16 | type)
56 return long(long(offset) << 16 | type)
57
57
58 _nullhash = _sha(nullid)
58 _nullhash = _sha(nullid)
59
59
60 def hash(text, p1, p2):
60 def hash(text, p1, p2):
61 """generate a hash from the given text and its parent hashes
61 """generate a hash from the given text and its parent hashes
62
62
63 This hash combines both the current file contents and its history
63 This hash combines both the current file contents and its history
64 in a manner that makes it easy to distinguish nodes with the same
64 in a manner that makes it easy to distinguish nodes with the same
65 content in the revision graph.
65 content in the revision graph.
66 """
66 """
67 # As of now, if one of the parent node is null, p2 is null
67 # As of now, if one of the parent node is null, p2 is null
68 if p2 == nullid:
68 if p2 == nullid:
69 # deep copy of a hash is faster than creating one
69 # deep copy of a hash is faster than creating one
70 s = _nullhash.copy()
70 s = _nullhash.copy()
71 s.update(p1)
71 s.update(p1)
72 else:
72 else:
73 # none of the parent nodes are nullid
73 # none of the parent nodes are nullid
74 l = [p1, p2]
74 l = [p1, p2]
75 l.sort()
75 l.sort()
76 s = _sha(l[0])
76 s = _sha(l[0])
77 s.update(l[1])
77 s.update(l[1])
78 s.update(text)
78 s.update(text)
79 return s.digest()
79 return s.digest()
80
80
81 def decompress(bin):
81 def decompress(bin):
82 """ decompress the given input """
82 """ decompress the given input """
83 if not bin:
83 if not bin:
84 return bin
84 return bin
85 t = bin[0]
85 t = bin[0]
86 if t == '\0':
86 if t == '\0':
87 return bin
87 return bin
88 if t == 'x':
88 if t == 'x':
89 try:
89 try:
90 return _decompress(bin)
90 return _decompress(bin)
91 except zlib.error, e:
91 except zlib.error, e:
92 raise RevlogError(_("revlog decompress error: %s") % str(e))
92 raise RevlogError(_("revlog decompress error: %s") % str(e))
93 if t == 'u':
93 if t == 'u':
94 return bin[1:]
94 return bin[1:]
95 raise RevlogError(_("unknown compression type %r") % t)
95 raise RevlogError(_("unknown compression type %r") % t)
96
96
97 # index v0:
97 # index v0:
98 # 4 bytes: offset
98 # 4 bytes: offset
99 # 4 bytes: compressed length
99 # 4 bytes: compressed length
100 # 4 bytes: base rev
100 # 4 bytes: base rev
101 # 4 bytes: link rev
101 # 4 bytes: link rev
102 # 32 bytes: parent 1 nodeid
102 # 32 bytes: parent 1 nodeid
103 # 32 bytes: parent 2 nodeid
103 # 32 bytes: parent 2 nodeid
104 # 32 bytes: nodeid
104 # 32 bytes: nodeid
105 indexformatv0 = ">4l20s20s20s"
105 indexformatv0 = ">4l20s20s20s"
106 v0shaoffset = 56
106 v0shaoffset = 56
107
107
108 class revlogoldio(object):
108 class revlogoldio(object):
109 def __init__(self):
109 def __init__(self):
110 self.size = struct.calcsize(indexformatv0)
110 self.size = struct.calcsize(indexformatv0)
111
111
112 def parseindex(self, data, inline):
112 def parseindex(self, data, inline):
113 s = self.size
113 s = self.size
114 index = []
114 index = []
115 nodemap = {nullid: nullrev}
115 nodemap = {nullid: nullrev}
116 n = off = 0
116 n = off = 0
117 l = len(data)
117 l = len(data)
118 while off + s <= l:
118 while off + s <= l:
119 cur = data[off:off + s]
119 cur = data[off:off + s]
120 off += s
120 off += s
121 e = _unpack(indexformatv0, cur)
121 e = _unpack(indexformatv0, cur)
122 # transform to revlogv1 format
122 # transform to revlogv1 format
123 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
123 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
124 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
124 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
125 index.append(e2)
125 index.append(e2)
126 nodemap[e[6]] = n
126 nodemap[e[6]] = n
127 n += 1
127 n += 1
128
128
129 # add the magic null revision at -1
129 # add the magic null revision at -1
130 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
130 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
131
131
132 return index, nodemap, None
132 return index, nodemap, None
133
133
134 def packentry(self, entry, node, version, rev):
134 def packentry(self, entry, node, version, rev):
135 if gettype(entry[0]):
135 if gettype(entry[0]):
136 raise RevlogError(_("index entry flags need RevlogNG"))
136 raise RevlogError(_("index entry flags need RevlogNG"))
137 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
137 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
138 node(entry[5]), node(entry[6]), entry[7])
138 node(entry[5]), node(entry[6]), entry[7])
139 return _pack(indexformatv0, *e2)
139 return _pack(indexformatv0, *e2)
140
140
141 # index ng:
141 # index ng:
142 # 6 bytes: offset
142 # 6 bytes: offset
143 # 2 bytes: flags
143 # 2 bytes: flags
144 # 4 bytes: compressed length
144 # 4 bytes: compressed length
145 # 4 bytes: uncompressed length
145 # 4 bytes: uncompressed length
146 # 4 bytes: base rev
146 # 4 bytes: base rev
147 # 4 bytes: link rev
147 # 4 bytes: link rev
148 # 4 bytes: parent 1 rev
148 # 4 bytes: parent 1 rev
149 # 4 bytes: parent 2 rev
149 # 4 bytes: parent 2 rev
150 # 32 bytes: nodeid
150 # 32 bytes: nodeid
151 indexformatng = ">Qiiiiii20s12x"
151 indexformatng = ">Qiiiiii20s12x"
152 ngshaoffset = 32
152 ngshaoffset = 32
153 versionformat = ">I"
153 versionformat = ">I"
154
154
155 class revlogio(object):
155 class revlogio(object):
156 def __init__(self):
156 def __init__(self):
157 self.size = struct.calcsize(indexformatng)
157 self.size = struct.calcsize(indexformatng)
158
158
159 def parseindex(self, data, inline):
159 def parseindex(self, data, inline):
160 # call the C implementation to parse the index data
160 # call the C implementation to parse the index data
161 index, cache = parsers.parse_index2(data, inline)
161 index, cache = parsers.parse_index2(data, inline)
162 return index, getattr(index, 'nodemap', None), cache
162 return index, getattr(index, 'nodemap', None), cache
163
163
164 def packentry(self, entry, node, version, rev):
164 def packentry(self, entry, node, version, rev):
165 p = _pack(indexformatng, *entry)
165 p = _pack(indexformatng, *entry)
166 if rev == 0:
166 if rev == 0:
167 p = _pack(versionformat, version) + p[4:]
167 p = _pack(versionformat, version) + p[4:]
168 return p
168 return p
169
169
170 class revlog(object):
170 class revlog(object):
171 """
171 """
172 the underlying revision storage object
172 the underlying revision storage object
173
173
174 A revlog consists of two parts, an index and the revision data.
174 A revlog consists of two parts, an index and the revision data.
175
175
176 The index is a file with a fixed record size containing
176 The index is a file with a fixed record size containing
177 information on each revision, including its nodeid (hash), the
177 information on each revision, including its nodeid (hash), the
178 nodeids of its parents, the position and offset of its data within
178 nodeids of its parents, the position and offset of its data within
179 the data file, and the revision it's based on. Finally, each entry
179 the data file, and the revision it's based on. Finally, each entry
180 contains a linkrev entry that can serve as a pointer to external
180 contains a linkrev entry that can serve as a pointer to external
181 data.
181 data.
182
182
183 The revision data itself is a linear collection of data chunks.
183 The revision data itself is a linear collection of data chunks.
184 Each chunk represents a revision and is usually represented as a
184 Each chunk represents a revision and is usually represented as a
185 delta against the previous chunk. To bound lookup time, runs of
185 delta against the previous chunk. To bound lookup time, runs of
186 deltas are limited to about 2 times the length of the original
186 deltas are limited to about 2 times the length of the original
187 version data. This makes retrieval of a version proportional to
187 version data. This makes retrieval of a version proportional to
188 its size, or O(1) relative to the number of revisions.
188 its size, or O(1) relative to the number of revisions.
189
189
190 Both pieces of the revlog are written to in an append-only
190 Both pieces of the revlog are written to in an append-only
191 fashion, which means we never need to rewrite a file to insert or
191 fashion, which means we never need to rewrite a file to insert or
192 remove data, and can use some simple techniques to avoid the need
192 remove data, and can use some simple techniques to avoid the need
193 for locking while reading.
193 for locking while reading.
194 """
194 """
195 def __init__(self, opener, indexfile):
195 def __init__(self, opener, indexfile):
196 """
196 """
197 create a revlog object
197 create a revlog object
198
198
199 opener is a function that abstracts the file opening operation
199 opener is a function that abstracts the file opening operation
200 and can be used to implement COW semantics or the like.
200 and can be used to implement COW semantics or the like.
201 """
201 """
202 self.indexfile = indexfile
202 self.indexfile = indexfile
203 self.datafile = indexfile[:-2] + ".d"
203 self.datafile = indexfile[:-2] + ".d"
204 self.opener = opener
204 self.opener = opener
205 self._cache = None
205 self._cache = None
206 self._basecache = None
206 self._basecache = None
207 self._chunkcache = (0, '')
207 self._chunkcache = (0, '')
208 self._chunkcachesize = 65536
208 self._chunkcachesize = 65536
209 self._maxchainlen = None
209 self._maxchainlen = None
210 self.index = []
210 self.index = []
211 self._pcache = {}
211 self._pcache = {}
212 self._nodecache = {nullid: nullrev}
212 self._nodecache = {nullid: nullrev}
213 self._nodepos = None
213 self._nodepos = None
214
214
215 v = REVLOG_DEFAULT_VERSION
215 v = REVLOG_DEFAULT_VERSION
216 opts = getattr(opener, 'options', None)
216 opts = getattr(opener, 'options', None)
217 if opts is not None:
217 if opts is not None:
218 if 'revlogv1' in opts:
218 if 'revlogv1' in opts:
219 if 'generaldelta' in opts:
219 if 'generaldelta' in opts:
220 v |= REVLOGGENERALDELTA
220 v |= REVLOGGENERALDELTA
221 else:
221 else:
222 v = 0
222 v = 0
223 if 'chunkcachesize' in opts:
223 if 'chunkcachesize' in opts:
224 self._chunkcachesize = opts['chunkcachesize']
224 self._chunkcachesize = opts['chunkcachesize']
225 if 'maxchainlen' in opts:
225 if 'maxchainlen' in opts:
226 self._maxchainlen = opts['maxchainlen']
226 self._maxchainlen = opts['maxchainlen']
227
227
228 if self._chunkcachesize <= 0:
228 if self._chunkcachesize <= 0:
229 raise RevlogError(_('revlog chunk cache size %r is not greater '
229 raise RevlogError(_('revlog chunk cache size %r is not greater '
230 'than 0') % self._chunkcachesize)
230 'than 0') % self._chunkcachesize)
231 elif self._chunkcachesize & (self._chunkcachesize - 1):
231 elif self._chunkcachesize & (self._chunkcachesize - 1):
232 raise RevlogError(_('revlog chunk cache size %r is not a power '
232 raise RevlogError(_('revlog chunk cache size %r is not a power '
233 'of 2') % self._chunkcachesize)
233 'of 2') % self._chunkcachesize)
234
234
235 i = ''
235 i = ''
236 self._initempty = True
236 self._initempty = True
237 try:
237 try:
238 f = self.opener(self.indexfile)
238 f = self.opener(self.indexfile)
239 i = f.read()
239 i = f.read()
240 f.close()
240 f.close()
241 if len(i) > 0:
241 if len(i) > 0:
242 v = struct.unpack(versionformat, i[:4])[0]
242 v = struct.unpack(versionformat, i[:4])[0]
243 self._initempty = False
243 self._initempty = False
244 except IOError, inst:
244 except IOError, inst:
245 if inst.errno != errno.ENOENT:
245 if inst.errno != errno.ENOENT:
246 raise
246 raise
247
247
248 self.version = v
248 self.version = v
249 self._inline = v & REVLOGNGINLINEDATA
249 self._inline = v & REVLOGNGINLINEDATA
250 self._generaldelta = v & REVLOGGENERALDELTA
250 self._generaldelta = v & REVLOGGENERALDELTA
251 flags = v & ~0xFFFF
251 flags = v & ~0xFFFF
252 fmt = v & 0xFFFF
252 fmt = v & 0xFFFF
253 if fmt == REVLOGV0 and flags:
253 if fmt == REVLOGV0 and flags:
254 raise RevlogError(_("index %s unknown flags %#04x for format v0")
254 raise RevlogError(_("index %s unknown flags %#04x for format v0")
255 % (self.indexfile, flags >> 16))
255 % (self.indexfile, flags >> 16))
256 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
256 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
257 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
257 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
258 % (self.indexfile, flags >> 16))
258 % (self.indexfile, flags >> 16))
259 elif fmt > REVLOGNG:
259 elif fmt > REVLOGNG:
260 raise RevlogError(_("index %s unknown format %d")
260 raise RevlogError(_("index %s unknown format %d")
261 % (self.indexfile, fmt))
261 % (self.indexfile, fmt))
262
262
263 self._io = revlogio()
263 self._io = revlogio()
264 if self.version == REVLOGV0:
264 if self.version == REVLOGV0:
265 self._io = revlogoldio()
265 self._io = revlogoldio()
266 try:
266 try:
267 d = self._io.parseindex(i, self._inline)
267 d = self._io.parseindex(i, self._inline)
268 except (ValueError, IndexError):
268 except (ValueError, IndexError):
269 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
269 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
270 self.index, nodemap, self._chunkcache = d
270 self.index, nodemap, self._chunkcache = d
271 if nodemap is not None:
271 if nodemap is not None:
272 self.nodemap = self._nodecache = nodemap
272 self.nodemap = self._nodecache = nodemap
273 if not self._chunkcache:
273 if not self._chunkcache:
274 self._chunkclear()
274 self._chunkclear()
275 # revnum -> (chain-length, sum-delta-length)
275 # revnum -> (chain-length, sum-delta-length)
276 self._chaininfocache = {}
276 self._chaininfocache = {}
277
277
278 def tip(self):
278 def tip(self):
279 return self.node(len(self.index) - 2)
279 return self.node(len(self.index) - 2)
280 def __contains__(self, rev):
281 return 0 <= rev < len(self)
280 def __len__(self):
282 def __len__(self):
281 return len(self.index) - 1
283 return len(self.index) - 1
282 def __iter__(self):
284 def __iter__(self):
283 return iter(xrange(len(self)))
285 return iter(xrange(len(self)))
284 def revs(self, start=0, stop=None):
286 def revs(self, start=0, stop=None):
285 """iterate over all rev in this revlog (from start to stop)"""
287 """iterate over all rev in this revlog (from start to stop)"""
286 step = 1
288 step = 1
287 if stop is not None:
289 if stop is not None:
288 if start > stop:
290 if start > stop:
289 step = -1
291 step = -1
290 stop += step
292 stop += step
291 else:
293 else:
292 stop = len(self)
294 stop = len(self)
293 return xrange(start, stop, step)
295 return xrange(start, stop, step)
294
296
295 @util.propertycache
297 @util.propertycache
296 def nodemap(self):
298 def nodemap(self):
297 self.rev(self.node(0))
299 self.rev(self.node(0))
298 return self._nodecache
300 return self._nodecache
299
301
300 def hasnode(self, node):
302 def hasnode(self, node):
301 try:
303 try:
302 self.rev(node)
304 self.rev(node)
303 return True
305 return True
304 except KeyError:
306 except KeyError:
305 return False
307 return False
306
308
307 def clearcaches(self):
309 def clearcaches(self):
308 try:
310 try:
309 self._nodecache.clearcaches()
311 self._nodecache.clearcaches()
310 except AttributeError:
312 except AttributeError:
311 self._nodecache = {nullid: nullrev}
313 self._nodecache = {nullid: nullrev}
312 self._nodepos = None
314 self._nodepos = None
313
315
314 def rev(self, node):
316 def rev(self, node):
315 try:
317 try:
316 return self._nodecache[node]
318 return self._nodecache[node]
317 except TypeError:
319 except TypeError:
318 raise
320 raise
319 except RevlogError:
321 except RevlogError:
320 # parsers.c radix tree lookup failed
322 # parsers.c radix tree lookup failed
321 raise LookupError(node, self.indexfile, _('no node'))
323 raise LookupError(node, self.indexfile, _('no node'))
322 except KeyError:
324 except KeyError:
323 # pure python cache lookup failed
325 # pure python cache lookup failed
324 n = self._nodecache
326 n = self._nodecache
325 i = self.index
327 i = self.index
326 p = self._nodepos
328 p = self._nodepos
327 if p is None:
329 if p is None:
328 p = len(i) - 2
330 p = len(i) - 2
329 for r in xrange(p, -1, -1):
331 for r in xrange(p, -1, -1):
330 v = i[r][7]
332 v = i[r][7]
331 n[v] = r
333 n[v] = r
332 if v == node:
334 if v == node:
333 self._nodepos = r - 1
335 self._nodepos = r - 1
334 return r
336 return r
335 raise LookupError(node, self.indexfile, _('no node'))
337 raise LookupError(node, self.indexfile, _('no node'))
336
338
337 def node(self, rev):
339 def node(self, rev):
338 return self.index[rev][7]
340 return self.index[rev][7]
339 def linkrev(self, rev):
341 def linkrev(self, rev):
340 return self.index[rev][4]
342 return self.index[rev][4]
341 def parents(self, node):
343 def parents(self, node):
342 i = self.index
344 i = self.index
343 d = i[self.rev(node)]
345 d = i[self.rev(node)]
344 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
346 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
345 def parentrevs(self, rev):
347 def parentrevs(self, rev):
346 return self.index[rev][5:7]
348 return self.index[rev][5:7]
347 def start(self, rev):
349 def start(self, rev):
348 return int(self.index[rev][0] >> 16)
350 return int(self.index[rev][0] >> 16)
349 def end(self, rev):
351 def end(self, rev):
350 return self.start(rev) + self.length(rev)
352 return self.start(rev) + self.length(rev)
351 def length(self, rev):
353 def length(self, rev):
352 return self.index[rev][1]
354 return self.index[rev][1]
353 def chainbase(self, rev):
355 def chainbase(self, rev):
354 index = self.index
356 index = self.index
355 base = index[rev][3]
357 base = index[rev][3]
356 while base != rev:
358 while base != rev:
357 rev = base
359 rev = base
358 base = index[rev][3]
360 base = index[rev][3]
359 return base
361 return base
360 def chainlen(self, rev):
362 def chainlen(self, rev):
361 return self._chaininfo(rev)[0]
363 return self._chaininfo(rev)[0]
362
364
363 def _chaininfo(self, rev):
365 def _chaininfo(self, rev):
364 chaininfocache = self._chaininfocache
366 chaininfocache = self._chaininfocache
365 if rev in chaininfocache:
367 if rev in chaininfocache:
366 return chaininfocache[rev]
368 return chaininfocache[rev]
367 index = self.index
369 index = self.index
368 generaldelta = self._generaldelta
370 generaldelta = self._generaldelta
369 iterrev = rev
371 iterrev = rev
370 e = index[iterrev]
372 e = index[iterrev]
371 clen = 0
373 clen = 0
372 compresseddeltalen = 0
374 compresseddeltalen = 0
373 while iterrev != e[3]:
375 while iterrev != e[3]:
374 clen += 1
376 clen += 1
375 compresseddeltalen += e[1]
377 compresseddeltalen += e[1]
376 if generaldelta:
378 if generaldelta:
377 iterrev = e[3]
379 iterrev = e[3]
378 else:
380 else:
379 iterrev -= 1
381 iterrev -= 1
380 if iterrev in chaininfocache:
382 if iterrev in chaininfocache:
381 t = chaininfocache[iterrev]
383 t = chaininfocache[iterrev]
382 clen += t[0]
384 clen += t[0]
383 compresseddeltalen += t[1]
385 compresseddeltalen += t[1]
384 break
386 break
385 e = index[iterrev]
387 e = index[iterrev]
386 else:
388 else:
387 # Add text length of base since decompressing that also takes
389 # Add text length of base since decompressing that also takes
388 # work. For cache hits the length is already included.
390 # work. For cache hits the length is already included.
389 compresseddeltalen += e[1]
391 compresseddeltalen += e[1]
390 r = (clen, compresseddeltalen)
392 r = (clen, compresseddeltalen)
391 chaininfocache[rev] = r
393 chaininfocache[rev] = r
392 return r
394 return r
393
395
394 def flags(self, rev):
396 def flags(self, rev):
395 return self.index[rev][0] & 0xFFFF
397 return self.index[rev][0] & 0xFFFF
396 def rawsize(self, rev):
398 def rawsize(self, rev):
397 """return the length of the uncompressed text for a given revision"""
399 """return the length of the uncompressed text for a given revision"""
398 l = self.index[rev][2]
400 l = self.index[rev][2]
399 if l >= 0:
401 if l >= 0:
400 return l
402 return l
401
403
402 t = self.revision(self.node(rev))
404 t = self.revision(self.node(rev))
403 return len(t)
405 return len(t)
404 size = rawsize
406 size = rawsize
405
407
406 def ancestors(self, revs, stoprev=0, inclusive=False):
408 def ancestors(self, revs, stoprev=0, inclusive=False):
407 """Generate the ancestors of 'revs' in reverse topological order.
409 """Generate the ancestors of 'revs' in reverse topological order.
408 Does not generate revs lower than stoprev.
410 Does not generate revs lower than stoprev.
409
411
410 See the documentation for ancestor.lazyancestors for more details."""
412 See the documentation for ancestor.lazyancestors for more details."""
411
413
412 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
414 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
413 inclusive=inclusive)
415 inclusive=inclusive)
414
416
415 def descendants(self, revs):
417 def descendants(self, revs):
416 """Generate the descendants of 'revs' in revision order.
418 """Generate the descendants of 'revs' in revision order.
417
419
418 Yield a sequence of revision numbers starting with a child of
420 Yield a sequence of revision numbers starting with a child of
419 some rev in revs, i.e., each revision is *not* considered a
421 some rev in revs, i.e., each revision is *not* considered a
420 descendant of itself. Results are ordered by revision number (a
422 descendant of itself. Results are ordered by revision number (a
421 topological sort)."""
423 topological sort)."""
422 first = min(revs)
424 first = min(revs)
423 if first == nullrev:
425 if first == nullrev:
424 for i in self:
426 for i in self:
425 yield i
427 yield i
426 return
428 return
427
429
428 seen = set(revs)
430 seen = set(revs)
429 for i in self.revs(start=first + 1):
431 for i in self.revs(start=first + 1):
430 for x in self.parentrevs(i):
432 for x in self.parentrevs(i):
431 if x != nullrev and x in seen:
433 if x != nullrev and x in seen:
432 seen.add(i)
434 seen.add(i)
433 yield i
435 yield i
434 break
436 break
435
437
436 def findcommonmissing(self, common=None, heads=None):
438 def findcommonmissing(self, common=None, heads=None):
437 """Return a tuple of the ancestors of common and the ancestors of heads
439 """Return a tuple of the ancestors of common and the ancestors of heads
438 that are not ancestors of common. In revset terminology, we return the
440 that are not ancestors of common. In revset terminology, we return the
439 tuple:
441 tuple:
440
442
441 ::common, (::heads) - (::common)
443 ::common, (::heads) - (::common)
442
444
443 The list is sorted by revision number, meaning it is
445 The list is sorted by revision number, meaning it is
444 topologically sorted.
446 topologically sorted.
445
447
446 'heads' and 'common' are both lists of node IDs. If heads is
448 'heads' and 'common' are both lists of node IDs. If heads is
447 not supplied, uses all of the revlog's heads. If common is not
449 not supplied, uses all of the revlog's heads. If common is not
448 supplied, uses nullid."""
450 supplied, uses nullid."""
449 if common is None:
451 if common is None:
450 common = [nullid]
452 common = [nullid]
451 if heads is None:
453 if heads is None:
452 heads = self.heads()
454 heads = self.heads()
453
455
454 common = [self.rev(n) for n in common]
456 common = [self.rev(n) for n in common]
455 heads = [self.rev(n) for n in heads]
457 heads = [self.rev(n) for n in heads]
456
458
457 # we want the ancestors, but inclusive
459 # we want the ancestors, but inclusive
458 class lazyset(object):
460 class lazyset(object):
459 def __init__(self, lazyvalues):
461 def __init__(self, lazyvalues):
460 self.addedvalues = set()
462 self.addedvalues = set()
461 self.lazyvalues = lazyvalues
463 self.lazyvalues = lazyvalues
462
464
463 def __contains__(self, value):
465 def __contains__(self, value):
464 return value in self.addedvalues or value in self.lazyvalues
466 return value in self.addedvalues or value in self.lazyvalues
465
467
466 def __iter__(self):
468 def __iter__(self):
467 added = self.addedvalues
469 added = self.addedvalues
468 for r in added:
470 for r in added:
469 yield r
471 yield r
470 for r in self.lazyvalues:
472 for r in self.lazyvalues:
471 if not r in added:
473 if not r in added:
472 yield r
474 yield r
473
475
474 def add(self, value):
476 def add(self, value):
475 self.addedvalues.add(value)
477 self.addedvalues.add(value)
476
478
477 def update(self, values):
479 def update(self, values):
478 self.addedvalues.update(values)
480 self.addedvalues.update(values)
479
481
480 has = lazyset(self.ancestors(common))
482 has = lazyset(self.ancestors(common))
481 has.add(nullrev)
483 has.add(nullrev)
482 has.update(common)
484 has.update(common)
483
485
484 # take all ancestors from heads that aren't in has
486 # take all ancestors from heads that aren't in has
485 missing = set()
487 missing = set()
486 visit = util.deque(r for r in heads if r not in has)
488 visit = util.deque(r for r in heads if r not in has)
487 while visit:
489 while visit:
488 r = visit.popleft()
490 r = visit.popleft()
489 if r in missing:
491 if r in missing:
490 continue
492 continue
491 else:
493 else:
492 missing.add(r)
494 missing.add(r)
493 for p in self.parentrevs(r):
495 for p in self.parentrevs(r):
494 if p not in has:
496 if p not in has:
495 visit.append(p)
497 visit.append(p)
496 missing = list(missing)
498 missing = list(missing)
497 missing.sort()
499 missing.sort()
498 return has, [self.node(r) for r in missing]
500 return has, [self.node(r) for r in missing]
499
501
500 def incrementalmissingrevs(self, common=None):
502 def incrementalmissingrevs(self, common=None):
501 """Return an object that can be used to incrementally compute the
503 """Return an object that can be used to incrementally compute the
502 revision numbers of the ancestors of arbitrary sets that are not
504 revision numbers of the ancestors of arbitrary sets that are not
503 ancestors of common. This is an ancestor.incrementalmissingancestors
505 ancestors of common. This is an ancestor.incrementalmissingancestors
504 object.
506 object.
505
507
506 'common' is a list of revision numbers. If common is not supplied, uses
508 'common' is a list of revision numbers. If common is not supplied, uses
507 nullrev.
509 nullrev.
508 """
510 """
509 if common is None:
511 if common is None:
510 common = [nullrev]
512 common = [nullrev]
511
513
512 return ancestor.incrementalmissingancestors(self.parentrevs, common)
514 return ancestor.incrementalmissingancestors(self.parentrevs, common)
513
515
514 def findmissingrevs(self, common=None, heads=None):
516 def findmissingrevs(self, common=None, heads=None):
515 """Return the revision numbers of the ancestors of heads that
517 """Return the revision numbers of the ancestors of heads that
516 are not ancestors of common.
518 are not ancestors of common.
517
519
518 More specifically, return a list of revision numbers corresponding to
520 More specifically, return a list of revision numbers corresponding to
519 nodes N such that every N satisfies the following constraints:
521 nodes N such that every N satisfies the following constraints:
520
522
521 1. N is an ancestor of some node in 'heads'
523 1. N is an ancestor of some node in 'heads'
522 2. N is not an ancestor of any node in 'common'
524 2. N is not an ancestor of any node in 'common'
523
525
524 The list is sorted by revision number, meaning it is
526 The list is sorted by revision number, meaning it is
525 topologically sorted.
527 topologically sorted.
526
528
527 'heads' and 'common' are both lists of revision numbers. If heads is
529 'heads' and 'common' are both lists of revision numbers. If heads is
528 not supplied, uses all of the revlog's heads. If common is not
530 not supplied, uses all of the revlog's heads. If common is not
529 supplied, uses nullid."""
531 supplied, uses nullid."""
530 if common is None:
532 if common is None:
531 common = [nullrev]
533 common = [nullrev]
532 if heads is None:
534 if heads is None:
533 heads = self.headrevs()
535 heads = self.headrevs()
534
536
535 inc = self.incrementalmissingrevs(common=common)
537 inc = self.incrementalmissingrevs(common=common)
536 return inc.missingancestors(heads)
538 return inc.missingancestors(heads)
537
539
538 def findmissing(self, common=None, heads=None):
540 def findmissing(self, common=None, heads=None):
539 """Return the ancestors of heads that are not ancestors of common.
541 """Return the ancestors of heads that are not ancestors of common.
540
542
541 More specifically, return a list of nodes N such that every N
543 More specifically, return a list of nodes N such that every N
542 satisfies the following constraints:
544 satisfies the following constraints:
543
545
544 1. N is an ancestor of some node in 'heads'
546 1. N is an ancestor of some node in 'heads'
545 2. N is not an ancestor of any node in 'common'
547 2. N is not an ancestor of any node in 'common'
546
548
547 The list is sorted by revision number, meaning it is
549 The list is sorted by revision number, meaning it is
548 topologically sorted.
550 topologically sorted.
549
551
550 'heads' and 'common' are both lists of node IDs. If heads is
552 'heads' and 'common' are both lists of node IDs. If heads is
551 not supplied, uses all of the revlog's heads. If common is not
553 not supplied, uses all of the revlog's heads. If common is not
552 supplied, uses nullid."""
554 supplied, uses nullid."""
553 if common is None:
555 if common is None:
554 common = [nullid]
556 common = [nullid]
555 if heads is None:
557 if heads is None:
556 heads = self.heads()
558 heads = self.heads()
557
559
558 common = [self.rev(n) for n in common]
560 common = [self.rev(n) for n in common]
559 heads = [self.rev(n) for n in heads]
561 heads = [self.rev(n) for n in heads]
560
562
561 inc = self.incrementalmissingrevs(common=common)
563 inc = self.incrementalmissingrevs(common=common)
562 return [self.node(r) for r in inc.missingancestors(heads)]
564 return [self.node(r) for r in inc.missingancestors(heads)]
563
565
564 def nodesbetween(self, roots=None, heads=None):
566 def nodesbetween(self, roots=None, heads=None):
565 """Return a topological path from 'roots' to 'heads'.
567 """Return a topological path from 'roots' to 'heads'.
566
568
567 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
569 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
568 topologically sorted list of all nodes N that satisfy both of
570 topologically sorted list of all nodes N that satisfy both of
569 these constraints:
571 these constraints:
570
572
571 1. N is a descendant of some node in 'roots'
573 1. N is a descendant of some node in 'roots'
572 2. N is an ancestor of some node in 'heads'
574 2. N is an ancestor of some node in 'heads'
573
575
574 Every node is considered to be both a descendant and an ancestor
576 Every node is considered to be both a descendant and an ancestor
575 of itself, so every reachable node in 'roots' and 'heads' will be
577 of itself, so every reachable node in 'roots' and 'heads' will be
576 included in 'nodes'.
578 included in 'nodes'.
577
579
578 'outroots' is the list of reachable nodes in 'roots', i.e., the
580 'outroots' is the list of reachable nodes in 'roots', i.e., the
579 subset of 'roots' that is returned in 'nodes'. Likewise,
581 subset of 'roots' that is returned in 'nodes'. Likewise,
580 'outheads' is the subset of 'heads' that is also in 'nodes'.
582 'outheads' is the subset of 'heads' that is also in 'nodes'.
581
583
582 'roots' and 'heads' are both lists of node IDs. If 'roots' is
584 'roots' and 'heads' are both lists of node IDs. If 'roots' is
583 unspecified, uses nullid as the only root. If 'heads' is
585 unspecified, uses nullid as the only root. If 'heads' is
584 unspecified, uses list of all of the revlog's heads."""
586 unspecified, uses list of all of the revlog's heads."""
585 nonodes = ([], [], [])
587 nonodes = ([], [], [])
586 if roots is not None:
588 if roots is not None:
587 roots = list(roots)
589 roots = list(roots)
588 if not roots:
590 if not roots:
589 return nonodes
591 return nonodes
590 lowestrev = min([self.rev(n) for n in roots])
592 lowestrev = min([self.rev(n) for n in roots])
591 else:
593 else:
592 roots = [nullid] # Everybody's a descendant of nullid
594 roots = [nullid] # Everybody's a descendant of nullid
593 lowestrev = nullrev
595 lowestrev = nullrev
594 if (lowestrev == nullrev) and (heads is None):
596 if (lowestrev == nullrev) and (heads is None):
595 # We want _all_ the nodes!
597 # We want _all_ the nodes!
596 return ([self.node(r) for r in self], [nullid], list(self.heads()))
598 return ([self.node(r) for r in self], [nullid], list(self.heads()))
597 if heads is None:
599 if heads is None:
598 # All nodes are ancestors, so the latest ancestor is the last
600 # All nodes are ancestors, so the latest ancestor is the last
599 # node.
601 # node.
600 highestrev = len(self) - 1
602 highestrev = len(self) - 1
601 # Set ancestors to None to signal that every node is an ancestor.
603 # Set ancestors to None to signal that every node is an ancestor.
602 ancestors = None
604 ancestors = None
603 # Set heads to an empty dictionary for later discovery of heads
605 # Set heads to an empty dictionary for later discovery of heads
604 heads = {}
606 heads = {}
605 else:
607 else:
606 heads = list(heads)
608 heads = list(heads)
607 if not heads:
609 if not heads:
608 return nonodes
610 return nonodes
609 ancestors = set()
611 ancestors = set()
610 # Turn heads into a dictionary so we can remove 'fake' heads.
612 # Turn heads into a dictionary so we can remove 'fake' heads.
611 # Also, later we will be using it to filter out the heads we can't
613 # Also, later we will be using it to filter out the heads we can't
612 # find from roots.
614 # find from roots.
613 heads = dict.fromkeys(heads, False)
615 heads = dict.fromkeys(heads, False)
614 # Start at the top and keep marking parents until we're done.
616 # Start at the top and keep marking parents until we're done.
615 nodestotag = set(heads)
617 nodestotag = set(heads)
616 # Remember where the top was so we can use it as a limit later.
618 # Remember where the top was so we can use it as a limit later.
617 highestrev = max([self.rev(n) for n in nodestotag])
619 highestrev = max([self.rev(n) for n in nodestotag])
618 while nodestotag:
620 while nodestotag:
619 # grab a node to tag
621 # grab a node to tag
620 n = nodestotag.pop()
622 n = nodestotag.pop()
621 # Never tag nullid
623 # Never tag nullid
622 if n == nullid:
624 if n == nullid:
623 continue
625 continue
624 # A node's revision number represents its place in a
626 # A node's revision number represents its place in a
625 # topologically sorted list of nodes.
627 # topologically sorted list of nodes.
626 r = self.rev(n)
628 r = self.rev(n)
627 if r >= lowestrev:
629 if r >= lowestrev:
628 if n not in ancestors:
630 if n not in ancestors:
629 # If we are possibly a descendant of one of the roots
631 # If we are possibly a descendant of one of the roots
630 # and we haven't already been marked as an ancestor
632 # and we haven't already been marked as an ancestor
631 ancestors.add(n) # Mark as ancestor
633 ancestors.add(n) # Mark as ancestor
632 # Add non-nullid parents to list of nodes to tag.
634 # Add non-nullid parents to list of nodes to tag.
633 nodestotag.update([p for p in self.parents(n) if
635 nodestotag.update([p for p in self.parents(n) if
634 p != nullid])
636 p != nullid])
635 elif n in heads: # We've seen it before, is it a fake head?
637 elif n in heads: # We've seen it before, is it a fake head?
636 # So it is, real heads should not be the ancestors of
638 # So it is, real heads should not be the ancestors of
637 # any other heads.
639 # any other heads.
638 heads.pop(n)
640 heads.pop(n)
639 if not ancestors:
641 if not ancestors:
640 return nonodes
642 return nonodes
641 # Now that we have our set of ancestors, we want to remove any
643 # Now that we have our set of ancestors, we want to remove any
642 # roots that are not ancestors.
644 # roots that are not ancestors.
643
645
644 # If one of the roots was nullid, everything is included anyway.
646 # If one of the roots was nullid, everything is included anyway.
645 if lowestrev > nullrev:
647 if lowestrev > nullrev:
646 # But, since we weren't, let's recompute the lowest rev to not
648 # But, since we weren't, let's recompute the lowest rev to not
647 # include roots that aren't ancestors.
649 # include roots that aren't ancestors.
648
650
649 # Filter out roots that aren't ancestors of heads
651 # Filter out roots that aren't ancestors of heads
650 roots = [n for n in roots if n in ancestors]
652 roots = [n for n in roots if n in ancestors]
651 # Recompute the lowest revision
653 # Recompute the lowest revision
652 if roots:
654 if roots:
653 lowestrev = min([self.rev(n) for n in roots])
655 lowestrev = min([self.rev(n) for n in roots])
654 else:
656 else:
655 # No more roots? Return empty list
657 # No more roots? Return empty list
656 return nonodes
658 return nonodes
657 else:
659 else:
658 # We are descending from nullid, and don't need to care about
660 # We are descending from nullid, and don't need to care about
659 # any other roots.
661 # any other roots.
660 lowestrev = nullrev
662 lowestrev = nullrev
661 roots = [nullid]
663 roots = [nullid]
662 # Transform our roots list into a set.
664 # Transform our roots list into a set.
663 descendants = set(roots)
665 descendants = set(roots)
664 # Also, keep the original roots so we can filter out roots that aren't
666 # Also, keep the original roots so we can filter out roots that aren't
665 # 'real' roots (i.e. are descended from other roots).
667 # 'real' roots (i.e. are descended from other roots).
666 roots = descendants.copy()
668 roots = descendants.copy()
667 # Our topologically sorted list of output nodes.
669 # Our topologically sorted list of output nodes.
668 orderedout = []
670 orderedout = []
669 # Don't start at nullid since we don't want nullid in our output list,
671 # Don't start at nullid since we don't want nullid in our output list,
670 # and if nullid shows up in descendants, empty parents will look like
672 # and if nullid shows up in descendants, empty parents will look like
671 # they're descendants.
673 # they're descendants.
672 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
674 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
673 n = self.node(r)
675 n = self.node(r)
674 isdescendant = False
676 isdescendant = False
675 if lowestrev == nullrev: # Everybody is a descendant of nullid
677 if lowestrev == nullrev: # Everybody is a descendant of nullid
676 isdescendant = True
678 isdescendant = True
677 elif n in descendants:
679 elif n in descendants:
678 # n is already a descendant
680 # n is already a descendant
679 isdescendant = True
681 isdescendant = True
680 # This check only needs to be done here because all the roots
682 # This check only needs to be done here because all the roots
681 # will start being marked is descendants before the loop.
683 # will start being marked is descendants before the loop.
682 if n in roots:
684 if n in roots:
683 # If n was a root, check if it's a 'real' root.
685 # If n was a root, check if it's a 'real' root.
684 p = tuple(self.parents(n))
686 p = tuple(self.parents(n))
685 # If any of its parents are descendants, it's not a root.
687 # If any of its parents are descendants, it's not a root.
686 if (p[0] in descendants) or (p[1] in descendants):
688 if (p[0] in descendants) or (p[1] in descendants):
687 roots.remove(n)
689 roots.remove(n)
688 else:
690 else:
689 p = tuple(self.parents(n))
691 p = tuple(self.parents(n))
690 # A node is a descendant if either of its parents are
692 # A node is a descendant if either of its parents are
691 # descendants. (We seeded the dependents list with the roots
693 # descendants. (We seeded the dependents list with the roots
692 # up there, remember?)
694 # up there, remember?)
693 if (p[0] in descendants) or (p[1] in descendants):
695 if (p[0] in descendants) or (p[1] in descendants):
694 descendants.add(n)
696 descendants.add(n)
695 isdescendant = True
697 isdescendant = True
696 if isdescendant and ((ancestors is None) or (n in ancestors)):
698 if isdescendant and ((ancestors is None) or (n in ancestors)):
697 # Only include nodes that are both descendants and ancestors.
699 # Only include nodes that are both descendants and ancestors.
698 orderedout.append(n)
700 orderedout.append(n)
699 if (ancestors is not None) and (n in heads):
701 if (ancestors is not None) and (n in heads):
700 # We're trying to figure out which heads are reachable
702 # We're trying to figure out which heads are reachable
701 # from roots.
703 # from roots.
702 # Mark this head as having been reached
704 # Mark this head as having been reached
703 heads[n] = True
705 heads[n] = True
704 elif ancestors is None:
706 elif ancestors is None:
705 # Otherwise, we're trying to discover the heads.
707 # Otherwise, we're trying to discover the heads.
706 # Assume this is a head because if it isn't, the next step
708 # Assume this is a head because if it isn't, the next step
707 # will eventually remove it.
709 # will eventually remove it.
708 heads[n] = True
710 heads[n] = True
709 # But, obviously its parents aren't.
711 # But, obviously its parents aren't.
710 for p in self.parents(n):
712 for p in self.parents(n):
711 heads.pop(p, None)
713 heads.pop(p, None)
712 heads = [n for n, flag in heads.iteritems() if flag]
714 heads = [n for n, flag in heads.iteritems() if flag]
713 roots = list(roots)
715 roots = list(roots)
714 assert orderedout
716 assert orderedout
715 assert roots
717 assert roots
716 assert heads
718 assert heads
717 return (orderedout, roots, heads)
719 return (orderedout, roots, heads)
718
720
719 def headrevs(self):
721 def headrevs(self):
720 try:
722 try:
721 return self.index.headrevs()
723 return self.index.headrevs()
722 except AttributeError:
724 except AttributeError:
723 return self._headrevs()
725 return self._headrevs()
724
726
725 def _headrevs(self):
727 def _headrevs(self):
726 count = len(self)
728 count = len(self)
727 if not count:
729 if not count:
728 return [nullrev]
730 return [nullrev]
729 # we won't iter over filtered rev so nobody is a head at start
731 # we won't iter over filtered rev so nobody is a head at start
730 ishead = [0] * (count + 1)
732 ishead = [0] * (count + 1)
731 index = self.index
733 index = self.index
732 for r in self:
734 for r in self:
733 ishead[r] = 1 # I may be an head
735 ishead[r] = 1 # I may be an head
734 e = index[r]
736 e = index[r]
735 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
737 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
736 return [r for r, val in enumerate(ishead) if val]
738 return [r for r, val in enumerate(ishead) if val]
737
739
738 def heads(self, start=None, stop=None):
740 def heads(self, start=None, stop=None):
739 """return the list of all nodes that have no children
741 """return the list of all nodes that have no children
740
742
741 if start is specified, only heads that are descendants of
743 if start is specified, only heads that are descendants of
742 start will be returned
744 start will be returned
743 if stop is specified, it will consider all the revs from stop
745 if stop is specified, it will consider all the revs from stop
744 as if they had no children
746 as if they had no children
745 """
747 """
746 if start is None and stop is None:
748 if start is None and stop is None:
747 if not len(self):
749 if not len(self):
748 return [nullid]
750 return [nullid]
749 return [self.node(r) for r in self.headrevs()]
751 return [self.node(r) for r in self.headrevs()]
750
752
751 if start is None:
753 if start is None:
752 start = nullid
754 start = nullid
753 if stop is None:
755 if stop is None:
754 stop = []
756 stop = []
755 stoprevs = set([self.rev(n) for n in stop])
757 stoprevs = set([self.rev(n) for n in stop])
756 startrev = self.rev(start)
758 startrev = self.rev(start)
757 reachable = set((startrev,))
759 reachable = set((startrev,))
758 heads = set((startrev,))
760 heads = set((startrev,))
759
761
760 parentrevs = self.parentrevs
762 parentrevs = self.parentrevs
761 for r in self.revs(start=startrev + 1):
763 for r in self.revs(start=startrev + 1):
762 for p in parentrevs(r):
764 for p in parentrevs(r):
763 if p in reachable:
765 if p in reachable:
764 if r not in stoprevs:
766 if r not in stoprevs:
765 reachable.add(r)
767 reachable.add(r)
766 heads.add(r)
768 heads.add(r)
767 if p in heads and p not in stoprevs:
769 if p in heads and p not in stoprevs:
768 heads.remove(p)
770 heads.remove(p)
769
771
770 return [self.node(r) for r in heads]
772 return [self.node(r) for r in heads]
771
773
772 def children(self, node):
774 def children(self, node):
773 """find the children of a given node"""
775 """find the children of a given node"""
774 c = []
776 c = []
775 p = self.rev(node)
777 p = self.rev(node)
776 for r in self.revs(start=p + 1):
778 for r in self.revs(start=p + 1):
777 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
779 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
778 if prevs:
780 if prevs:
779 for pr in prevs:
781 for pr in prevs:
780 if pr == p:
782 if pr == p:
781 c.append(self.node(r))
783 c.append(self.node(r))
782 elif p == nullrev:
784 elif p == nullrev:
783 c.append(self.node(r))
785 c.append(self.node(r))
784 return c
786 return c
785
787
786 def descendant(self, start, end):
788 def descendant(self, start, end):
787 if start == nullrev:
789 if start == nullrev:
788 return True
790 return True
789 for i in self.descendants([start]):
791 for i in self.descendants([start]):
790 if i == end:
792 if i == end:
791 return True
793 return True
792 elif i > end:
794 elif i > end:
793 break
795 break
794 return False
796 return False
795
797
796 def commonancestorsheads(self, a, b):
798 def commonancestorsheads(self, a, b):
797 """calculate all the heads of the common ancestors of nodes a and b"""
799 """calculate all the heads of the common ancestors of nodes a and b"""
798 a, b = self.rev(a), self.rev(b)
800 a, b = self.rev(a), self.rev(b)
799 try:
801 try:
800 ancs = self.index.commonancestorsheads(a, b)
802 ancs = self.index.commonancestorsheads(a, b)
801 except (AttributeError, OverflowError): # C implementation failed
803 except (AttributeError, OverflowError): # C implementation failed
802 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
804 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
803 return map(self.node, ancs)
805 return map(self.node, ancs)
804
806
805 def isancestor(self, a, b):
807 def isancestor(self, a, b):
806 """return True if node a is an ancestor of node b
808 """return True if node a is an ancestor of node b
807
809
808 The implementation of this is trivial but the use of
810 The implementation of this is trivial but the use of
809 commonancestorsheads is not."""
811 commonancestorsheads is not."""
810 return a in self.commonancestorsheads(a, b)
812 return a in self.commonancestorsheads(a, b)
811
813
812 def ancestor(self, a, b):
814 def ancestor(self, a, b):
813 """calculate the "best" common ancestor of nodes a and b"""
815 """calculate the "best" common ancestor of nodes a and b"""
814
816
815 a, b = self.rev(a), self.rev(b)
817 a, b = self.rev(a), self.rev(b)
816 try:
818 try:
817 ancs = self.index.ancestors(a, b)
819 ancs = self.index.ancestors(a, b)
818 except (AttributeError, OverflowError):
820 except (AttributeError, OverflowError):
819 ancs = ancestor.ancestors(self.parentrevs, a, b)
821 ancs = ancestor.ancestors(self.parentrevs, a, b)
820 if ancs:
822 if ancs:
821 # choose a consistent winner when there's a tie
823 # choose a consistent winner when there's a tie
822 return min(map(self.node, ancs))
824 return min(map(self.node, ancs))
823 return nullid
825 return nullid
824
826
825 def _match(self, id):
827 def _match(self, id):
826 if isinstance(id, int):
828 if isinstance(id, int):
827 # rev
829 # rev
828 return self.node(id)
830 return self.node(id)
829 if len(id) == 20:
831 if len(id) == 20:
830 # possibly a binary node
832 # possibly a binary node
831 # odds of a binary node being all hex in ASCII are 1 in 10**25
833 # odds of a binary node being all hex in ASCII are 1 in 10**25
832 try:
834 try:
833 node = id
835 node = id
834 self.rev(node) # quick search the index
836 self.rev(node) # quick search the index
835 return node
837 return node
836 except LookupError:
838 except LookupError:
837 pass # may be partial hex id
839 pass # may be partial hex id
838 try:
840 try:
839 # str(rev)
841 # str(rev)
840 rev = int(id)
842 rev = int(id)
841 if str(rev) != id:
843 if str(rev) != id:
842 raise ValueError
844 raise ValueError
843 if rev < 0:
845 if rev < 0:
844 rev = len(self) + rev
846 rev = len(self) + rev
845 if rev < 0 or rev >= len(self):
847 if rev < 0 or rev >= len(self):
846 raise ValueError
848 raise ValueError
847 return self.node(rev)
849 return self.node(rev)
848 except (ValueError, OverflowError):
850 except (ValueError, OverflowError):
849 pass
851 pass
850 if len(id) == 40:
852 if len(id) == 40:
851 try:
853 try:
852 # a full hex nodeid?
854 # a full hex nodeid?
853 node = bin(id)
855 node = bin(id)
854 self.rev(node)
856 self.rev(node)
855 return node
857 return node
856 except (TypeError, LookupError):
858 except (TypeError, LookupError):
857 pass
859 pass
858
860
859 def _partialmatch(self, id):
861 def _partialmatch(self, id):
860 try:
862 try:
861 n = self.index.partialmatch(id)
863 n = self.index.partialmatch(id)
862 if n and self.hasnode(n):
864 if n and self.hasnode(n):
863 return n
865 return n
864 return None
866 return None
865 except RevlogError:
867 except RevlogError:
866 # parsers.c radix tree lookup gave multiple matches
868 # parsers.c radix tree lookup gave multiple matches
867 # fall through to slow path that filters hidden revisions
869 # fall through to slow path that filters hidden revisions
868 pass
870 pass
869 except (AttributeError, ValueError):
871 except (AttributeError, ValueError):
870 # we are pure python, or key was too short to search radix tree
872 # we are pure python, or key was too short to search radix tree
871 pass
873 pass
872
874
873 if id in self._pcache:
875 if id in self._pcache:
874 return self._pcache[id]
876 return self._pcache[id]
875
877
876 if len(id) < 40:
878 if len(id) < 40:
877 try:
879 try:
878 # hex(node)[:...]
880 # hex(node)[:...]
879 l = len(id) // 2 # grab an even number of digits
881 l = len(id) // 2 # grab an even number of digits
880 prefix = bin(id[:l * 2])
882 prefix = bin(id[:l * 2])
881 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
883 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
882 nl = [n for n in nl if hex(n).startswith(id) and
884 nl = [n for n in nl if hex(n).startswith(id) and
883 self.hasnode(n)]
885 self.hasnode(n)]
884 if len(nl) > 0:
886 if len(nl) > 0:
885 if len(nl) == 1:
887 if len(nl) == 1:
886 self._pcache[id] = nl[0]
888 self._pcache[id] = nl[0]
887 return nl[0]
889 return nl[0]
888 raise LookupError(id, self.indexfile,
890 raise LookupError(id, self.indexfile,
889 _('ambiguous identifier'))
891 _('ambiguous identifier'))
890 return None
892 return None
891 except TypeError:
893 except TypeError:
892 pass
894 pass
893
895
894 def lookup(self, id):
896 def lookup(self, id):
895 """locate a node based on:
897 """locate a node based on:
896 - revision number or str(revision number)
898 - revision number or str(revision number)
897 - nodeid or subset of hex nodeid
899 - nodeid or subset of hex nodeid
898 """
900 """
899 n = self._match(id)
901 n = self._match(id)
900 if n is not None:
902 if n is not None:
901 return n
903 return n
902 n = self._partialmatch(id)
904 n = self._partialmatch(id)
903 if n:
905 if n:
904 return n
906 return n
905
907
906 raise LookupError(id, self.indexfile, _('no match found'))
908 raise LookupError(id, self.indexfile, _('no match found'))
907
909
908 def cmp(self, node, text):
910 def cmp(self, node, text):
909 """compare text with a given file revision
911 """compare text with a given file revision
910
912
911 returns True if text is different than what is stored.
913 returns True if text is different than what is stored.
912 """
914 """
913 p1, p2 = self.parents(node)
915 p1, p2 = self.parents(node)
914 return hash(text, p1, p2) != node
916 return hash(text, p1, p2) != node
915
917
916 def _addchunk(self, offset, data):
918 def _addchunk(self, offset, data):
917 o, d = self._chunkcache
919 o, d = self._chunkcache
918 # try to add to existing cache
920 # try to add to existing cache
919 if o + len(d) == offset and len(d) + len(data) < _chunksize:
921 if o + len(d) == offset and len(d) + len(data) < _chunksize:
920 self._chunkcache = o, d + data
922 self._chunkcache = o, d + data
921 else:
923 else:
922 self._chunkcache = offset, data
924 self._chunkcache = offset, data
923
925
924 def _loadchunk(self, offset, length):
926 def _loadchunk(self, offset, length):
925 if self._inline:
927 if self._inline:
926 df = self.opener(self.indexfile)
928 df = self.opener(self.indexfile)
927 else:
929 else:
928 df = self.opener(self.datafile)
930 df = self.opener(self.datafile)
929
931
930 # Cache data both forward and backward around the requested
932 # Cache data both forward and backward around the requested
931 # data, in a fixed size window. This helps speed up operations
933 # data, in a fixed size window. This helps speed up operations
932 # involving reading the revlog backwards.
934 # involving reading the revlog backwards.
933 cachesize = self._chunkcachesize
935 cachesize = self._chunkcachesize
934 realoffset = offset & ~(cachesize - 1)
936 realoffset = offset & ~(cachesize - 1)
935 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
937 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
936 - realoffset)
938 - realoffset)
937 df.seek(realoffset)
939 df.seek(realoffset)
938 d = df.read(reallength)
940 d = df.read(reallength)
939 df.close()
941 df.close()
940 self._addchunk(realoffset, d)
942 self._addchunk(realoffset, d)
941 if offset != realoffset or reallength != length:
943 if offset != realoffset or reallength != length:
942 return util.buffer(d, offset - realoffset, length)
944 return util.buffer(d, offset - realoffset, length)
943 return d
945 return d
944
946
945 def _getchunk(self, offset, length):
947 def _getchunk(self, offset, length):
946 o, d = self._chunkcache
948 o, d = self._chunkcache
947 l = len(d)
949 l = len(d)
948
950
949 # is it in the cache?
951 # is it in the cache?
950 cachestart = offset - o
952 cachestart = offset - o
951 cacheend = cachestart + length
953 cacheend = cachestart + length
952 if cachestart >= 0 and cacheend <= l:
954 if cachestart >= 0 and cacheend <= l:
953 if cachestart == 0 and cacheend == l:
955 if cachestart == 0 and cacheend == l:
954 return d # avoid a copy
956 return d # avoid a copy
955 return util.buffer(d, cachestart, cacheend - cachestart)
957 return util.buffer(d, cachestart, cacheend - cachestart)
956
958
957 return self._loadchunk(offset, length)
959 return self._loadchunk(offset, length)
958
960
959 def _chunkraw(self, startrev, endrev):
961 def _chunkraw(self, startrev, endrev):
960 start = self.start(startrev)
962 start = self.start(startrev)
961 end = self.end(endrev)
963 end = self.end(endrev)
962 if self._inline:
964 if self._inline:
963 start += (startrev + 1) * self._io.size
965 start += (startrev + 1) * self._io.size
964 end += (endrev + 1) * self._io.size
966 end += (endrev + 1) * self._io.size
965 length = end - start
967 length = end - start
966 return self._getchunk(start, length)
968 return self._getchunk(start, length)
967
969
968 def _chunk(self, rev):
970 def _chunk(self, rev):
969 return decompress(self._chunkraw(rev, rev))
971 return decompress(self._chunkraw(rev, rev))
970
972
971 def _chunks(self, revs):
973 def _chunks(self, revs):
972 '''faster version of [self._chunk(rev) for rev in revs]
974 '''faster version of [self._chunk(rev) for rev in revs]
973
975
974 Assumes that revs is in ascending order.'''
976 Assumes that revs is in ascending order.'''
975 if not revs:
977 if not revs:
976 return []
978 return []
977 start = self.start
979 start = self.start
978 length = self.length
980 length = self.length
979 inline = self._inline
981 inline = self._inline
980 iosize = self._io.size
982 iosize = self._io.size
981 buffer = util.buffer
983 buffer = util.buffer
982
984
983 l = []
985 l = []
984 ladd = l.append
986 ladd = l.append
985
987
986 # preload the cache
988 # preload the cache
987 try:
989 try:
988 while True:
990 while True:
989 # ensure that the cache doesn't change out from under us
991 # ensure that the cache doesn't change out from under us
990 _cache = self._chunkcache
992 _cache = self._chunkcache
991 self._chunkraw(revs[0], revs[-1])
993 self._chunkraw(revs[0], revs[-1])
992 if _cache == self._chunkcache:
994 if _cache == self._chunkcache:
993 break
995 break
994 offset, data = _cache
996 offset, data = _cache
995 except OverflowError:
997 except OverflowError:
996 # issue4215 - we can't cache a run of chunks greater than
998 # issue4215 - we can't cache a run of chunks greater than
997 # 2G on Windows
999 # 2G on Windows
998 return [self._chunk(rev) for rev in revs]
1000 return [self._chunk(rev) for rev in revs]
999
1001
1000 for rev in revs:
1002 for rev in revs:
1001 chunkstart = start(rev)
1003 chunkstart = start(rev)
1002 if inline:
1004 if inline:
1003 chunkstart += (rev + 1) * iosize
1005 chunkstart += (rev + 1) * iosize
1004 chunklength = length(rev)
1006 chunklength = length(rev)
1005 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1007 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1006
1008
1007 return l
1009 return l
1008
1010
1009 def _chunkclear(self):
1011 def _chunkclear(self):
1010 self._chunkcache = (0, '')
1012 self._chunkcache = (0, '')
1011
1013
1012 def deltaparent(self, rev):
1014 def deltaparent(self, rev):
1013 """return deltaparent of the given revision"""
1015 """return deltaparent of the given revision"""
1014 base = self.index[rev][3]
1016 base = self.index[rev][3]
1015 if base == rev:
1017 if base == rev:
1016 return nullrev
1018 return nullrev
1017 elif self._generaldelta:
1019 elif self._generaldelta:
1018 return base
1020 return base
1019 else:
1021 else:
1020 return rev - 1
1022 return rev - 1
1021
1023
1022 def revdiff(self, rev1, rev2):
1024 def revdiff(self, rev1, rev2):
1023 """return or calculate a delta between two revisions"""
1025 """return or calculate a delta between two revisions"""
1024 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1026 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1025 return str(self._chunk(rev2))
1027 return str(self._chunk(rev2))
1026
1028
1027 return mdiff.textdiff(self.revision(rev1),
1029 return mdiff.textdiff(self.revision(rev1),
1028 self.revision(rev2))
1030 self.revision(rev2))
1029
1031
1030 def revision(self, nodeorrev):
1032 def revision(self, nodeorrev):
1031 """return an uncompressed revision of a given node or revision
1033 """return an uncompressed revision of a given node or revision
1032 number.
1034 number.
1033 """
1035 """
1034 if isinstance(nodeorrev, int):
1036 if isinstance(nodeorrev, int):
1035 rev = nodeorrev
1037 rev = nodeorrev
1036 node = self.node(rev)
1038 node = self.node(rev)
1037 else:
1039 else:
1038 node = nodeorrev
1040 node = nodeorrev
1039 rev = None
1041 rev = None
1040
1042
1041 _cache = self._cache # grab local copy of cache to avoid thread race
1043 _cache = self._cache # grab local copy of cache to avoid thread race
1042 cachedrev = None
1044 cachedrev = None
1043 if node == nullid:
1045 if node == nullid:
1044 return ""
1046 return ""
1045 if _cache:
1047 if _cache:
1046 if _cache[0] == node:
1048 if _cache[0] == node:
1047 return _cache[2]
1049 return _cache[2]
1048 cachedrev = _cache[1]
1050 cachedrev = _cache[1]
1049
1051
1050 # look up what we need to read
1052 # look up what we need to read
1051 text = None
1053 text = None
1052 if rev is None:
1054 if rev is None:
1053 rev = self.rev(node)
1055 rev = self.rev(node)
1054
1056
1055 # check rev flags
1057 # check rev flags
1056 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1058 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1057 raise RevlogError(_('incompatible revision flag %x') %
1059 raise RevlogError(_('incompatible revision flag %x') %
1058 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1060 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1059
1061
1060 # build delta chain
1062 # build delta chain
1061 chain = []
1063 chain = []
1062 index = self.index # for performance
1064 index = self.index # for performance
1063 generaldelta = self._generaldelta
1065 generaldelta = self._generaldelta
1064 iterrev = rev
1066 iterrev = rev
1065 e = index[iterrev]
1067 e = index[iterrev]
1066 while iterrev != e[3] and iterrev != cachedrev:
1068 while iterrev != e[3] and iterrev != cachedrev:
1067 chain.append(iterrev)
1069 chain.append(iterrev)
1068 if generaldelta:
1070 if generaldelta:
1069 iterrev = e[3]
1071 iterrev = e[3]
1070 else:
1072 else:
1071 iterrev -= 1
1073 iterrev -= 1
1072 e = index[iterrev]
1074 e = index[iterrev]
1073
1075
1074 if iterrev == cachedrev:
1076 if iterrev == cachedrev:
1075 # cache hit
1077 # cache hit
1076 text = _cache[2]
1078 text = _cache[2]
1077 else:
1079 else:
1078 chain.append(iterrev)
1080 chain.append(iterrev)
1079 chain.reverse()
1081 chain.reverse()
1080
1082
1081 # drop cache to save memory
1083 # drop cache to save memory
1082 self._cache = None
1084 self._cache = None
1083
1085
1084 bins = self._chunks(chain)
1086 bins = self._chunks(chain)
1085 if text is None:
1087 if text is None:
1086 text = str(bins[0])
1088 text = str(bins[0])
1087 bins = bins[1:]
1089 bins = bins[1:]
1088
1090
1089 text = mdiff.patches(text, bins)
1091 text = mdiff.patches(text, bins)
1090
1092
1091 text = self._checkhash(text, node, rev)
1093 text = self._checkhash(text, node, rev)
1092
1094
1093 self._cache = (node, rev, text)
1095 self._cache = (node, rev, text)
1094 return text
1096 return text
1095
1097
1096 def hash(self, text, p1, p2):
1098 def hash(self, text, p1, p2):
1097 """Compute a node hash.
1099 """Compute a node hash.
1098
1100
1099 Available as a function so that subclasses can replace the hash
1101 Available as a function so that subclasses can replace the hash
1100 as needed.
1102 as needed.
1101 """
1103 """
1102 return hash(text, p1, p2)
1104 return hash(text, p1, p2)
1103
1105
1104 def _checkhash(self, text, node, rev):
1106 def _checkhash(self, text, node, rev):
1105 p1, p2 = self.parents(node)
1107 p1, p2 = self.parents(node)
1106 self.checkhash(text, p1, p2, node, rev)
1108 self.checkhash(text, p1, p2, node, rev)
1107 return text
1109 return text
1108
1110
1109 def checkhash(self, text, p1, p2, node, rev=None):
1111 def checkhash(self, text, p1, p2, node, rev=None):
1110 if node != self.hash(text, p1, p2):
1112 if node != self.hash(text, p1, p2):
1111 revornode = rev
1113 revornode = rev
1112 if revornode is None:
1114 if revornode is None:
1113 revornode = templatefilters.short(hex(node))
1115 revornode = templatefilters.short(hex(node))
1114 raise RevlogError(_("integrity check failed on %s:%s")
1116 raise RevlogError(_("integrity check failed on %s:%s")
1115 % (self.indexfile, revornode))
1117 % (self.indexfile, revornode))
1116
1118
1117 def checkinlinesize(self, tr, fp=None):
1119 def checkinlinesize(self, tr, fp=None):
1118 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1120 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1119 return
1121 return
1120
1122
1121 trinfo = tr.find(self.indexfile)
1123 trinfo = tr.find(self.indexfile)
1122 if trinfo is None:
1124 if trinfo is None:
1123 raise RevlogError(_("%s not found in the transaction")
1125 raise RevlogError(_("%s not found in the transaction")
1124 % self.indexfile)
1126 % self.indexfile)
1125
1127
1126 trindex = trinfo[2]
1128 trindex = trinfo[2]
1127 dataoff = self.start(trindex)
1129 dataoff = self.start(trindex)
1128
1130
1129 tr.add(self.datafile, dataoff)
1131 tr.add(self.datafile, dataoff)
1130
1132
1131 if fp:
1133 if fp:
1132 fp.flush()
1134 fp.flush()
1133 fp.close()
1135 fp.close()
1134
1136
1135 df = self.opener(self.datafile, 'w')
1137 df = self.opener(self.datafile, 'w')
1136 try:
1138 try:
1137 for r in self:
1139 for r in self:
1138 df.write(self._chunkraw(r, r))
1140 df.write(self._chunkraw(r, r))
1139 finally:
1141 finally:
1140 df.close()
1142 df.close()
1141
1143
1142 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1144 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1143 self.version &= ~(REVLOGNGINLINEDATA)
1145 self.version &= ~(REVLOGNGINLINEDATA)
1144 self._inline = False
1146 self._inline = False
1145 for i in self:
1147 for i in self:
1146 e = self._io.packentry(self.index[i], self.node, self.version, i)
1148 e = self._io.packentry(self.index[i], self.node, self.version, i)
1147 fp.write(e)
1149 fp.write(e)
1148
1150
1149 # if we don't call close, the temp file will never replace the
1151 # if we don't call close, the temp file will never replace the
1150 # real index
1152 # real index
1151 fp.close()
1153 fp.close()
1152
1154
1153 tr.replace(self.indexfile, trindex * self._io.size)
1155 tr.replace(self.indexfile, trindex * self._io.size)
1154 self._chunkclear()
1156 self._chunkclear()
1155
1157
1156 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1158 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1157 node=None):
1159 node=None):
1158 """add a revision to the log
1160 """add a revision to the log
1159
1161
1160 text - the revision data to add
1162 text - the revision data to add
1161 transaction - the transaction object used for rollback
1163 transaction - the transaction object used for rollback
1162 link - the linkrev data to add
1164 link - the linkrev data to add
1163 p1, p2 - the parent nodeids of the revision
1165 p1, p2 - the parent nodeids of the revision
1164 cachedelta - an optional precomputed delta
1166 cachedelta - an optional precomputed delta
1165 node - nodeid of revision; typically node is not specified, and it is
1167 node - nodeid of revision; typically node is not specified, and it is
1166 computed by default as hash(text, p1, p2), however subclasses might
1168 computed by default as hash(text, p1, p2), however subclasses might
1167 use different hashing method (and override checkhash() in such case)
1169 use different hashing method (and override checkhash() in such case)
1168 """
1170 """
1169 if link == nullrev:
1171 if link == nullrev:
1170 raise RevlogError(_("attempted to add linkrev -1 to %s")
1172 raise RevlogError(_("attempted to add linkrev -1 to %s")
1171 % self.indexfile)
1173 % self.indexfile)
1172 node = node or self.hash(text, p1, p2)
1174 node = node or self.hash(text, p1, p2)
1173 if node in self.nodemap:
1175 if node in self.nodemap:
1174 return node
1176 return node
1175
1177
1176 dfh = None
1178 dfh = None
1177 if not self._inline:
1179 if not self._inline:
1178 dfh = self.opener(self.datafile, "a")
1180 dfh = self.opener(self.datafile, "a")
1179 ifh = self.opener(self.indexfile, "a+")
1181 ifh = self.opener(self.indexfile, "a+")
1180 try:
1182 try:
1181 return self._addrevision(node, text, transaction, link, p1, p2,
1183 return self._addrevision(node, text, transaction, link, p1, p2,
1182 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1184 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1183 finally:
1185 finally:
1184 if dfh:
1186 if dfh:
1185 dfh.close()
1187 dfh.close()
1186 ifh.close()
1188 ifh.close()
1187
1189
1188 def compress(self, text):
1190 def compress(self, text):
1189 """ generate a possibly-compressed representation of text """
1191 """ generate a possibly-compressed representation of text """
1190 if not text:
1192 if not text:
1191 return ("", text)
1193 return ("", text)
1192 l = len(text)
1194 l = len(text)
1193 bin = None
1195 bin = None
1194 if l < 44:
1196 if l < 44:
1195 pass
1197 pass
1196 elif l > 1000000:
1198 elif l > 1000000:
1197 # zlib makes an internal copy, thus doubling memory usage for
1199 # zlib makes an internal copy, thus doubling memory usage for
1198 # large files, so lets do this in pieces
1200 # large files, so lets do this in pieces
1199 z = zlib.compressobj()
1201 z = zlib.compressobj()
1200 p = []
1202 p = []
1201 pos = 0
1203 pos = 0
1202 while pos < l:
1204 while pos < l:
1203 pos2 = pos + 2**20
1205 pos2 = pos + 2**20
1204 p.append(z.compress(text[pos:pos2]))
1206 p.append(z.compress(text[pos:pos2]))
1205 pos = pos2
1207 pos = pos2
1206 p.append(z.flush())
1208 p.append(z.flush())
1207 if sum(map(len, p)) < l:
1209 if sum(map(len, p)) < l:
1208 bin = "".join(p)
1210 bin = "".join(p)
1209 else:
1211 else:
1210 bin = _compress(text)
1212 bin = _compress(text)
1211 if bin is None or len(bin) > l:
1213 if bin is None or len(bin) > l:
1212 if text[0] == '\0':
1214 if text[0] == '\0':
1213 return ("", text)
1215 return ("", text)
1214 return ('u', text)
1216 return ('u', text)
1215 return ("", bin)
1217 return ("", bin)
1216
1218
1217 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1219 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1218 cachedelta, ifh, dfh):
1220 cachedelta, ifh, dfh):
1219 """internal function to add revisions to the log
1221 """internal function to add revisions to the log
1220
1222
1221 see addrevision for argument descriptions.
1223 see addrevision for argument descriptions.
1222 invariants:
1224 invariants:
1223 - text is optional (can be None); if not set, cachedelta must be set.
1225 - text is optional (can be None); if not set, cachedelta must be set.
1224 if both are set, they must correspond to each other.
1226 if both are set, they must correspond to each other.
1225 """
1227 """
1226 btext = [text]
1228 btext = [text]
1227 def buildtext():
1229 def buildtext():
1228 if btext[0] is not None:
1230 if btext[0] is not None:
1229 return btext[0]
1231 return btext[0]
1230 # flush any pending writes here so we can read it in revision
1232 # flush any pending writes here so we can read it in revision
1231 if dfh:
1233 if dfh:
1232 dfh.flush()
1234 dfh.flush()
1233 ifh.flush()
1235 ifh.flush()
1234 basetext = self.revision(self.node(cachedelta[0]))
1236 basetext = self.revision(self.node(cachedelta[0]))
1235 btext[0] = mdiff.patch(basetext, cachedelta[1])
1237 btext[0] = mdiff.patch(basetext, cachedelta[1])
1236 try:
1238 try:
1237 self.checkhash(btext[0], p1, p2, node)
1239 self.checkhash(btext[0], p1, p2, node)
1238 if flags & REVIDX_ISCENSORED:
1240 if flags & REVIDX_ISCENSORED:
1239 raise RevlogError(_('node %s is not censored') % node)
1241 raise RevlogError(_('node %s is not censored') % node)
1240 except CensoredNodeError:
1242 except CensoredNodeError:
1241 # must pass the censored index flag to add censored revisions
1243 # must pass the censored index flag to add censored revisions
1242 if not flags & REVIDX_ISCENSORED:
1244 if not flags & REVIDX_ISCENSORED:
1243 raise
1245 raise
1244 return btext[0]
1246 return btext[0]
1245
1247
1246 def builddelta(rev):
1248 def builddelta(rev):
1247 # can we use the cached delta?
1249 # can we use the cached delta?
1248 if cachedelta and cachedelta[0] == rev:
1250 if cachedelta and cachedelta[0] == rev:
1249 delta = cachedelta[1]
1251 delta = cachedelta[1]
1250 else:
1252 else:
1251 t = buildtext()
1253 t = buildtext()
1252 ptext = self.revision(self.node(rev))
1254 ptext = self.revision(self.node(rev))
1253 delta = mdiff.textdiff(ptext, t)
1255 delta = mdiff.textdiff(ptext, t)
1254 data = self.compress(delta)
1256 data = self.compress(delta)
1255 l = len(data[1]) + len(data[0])
1257 l = len(data[1]) + len(data[0])
1256 if basecache[0] == rev:
1258 if basecache[0] == rev:
1257 chainbase = basecache[1]
1259 chainbase = basecache[1]
1258 else:
1260 else:
1259 chainbase = self.chainbase(rev)
1261 chainbase = self.chainbase(rev)
1260 dist = l + offset - self.start(chainbase)
1262 dist = l + offset - self.start(chainbase)
1261 if self._generaldelta:
1263 if self._generaldelta:
1262 base = rev
1264 base = rev
1263 else:
1265 else:
1264 base = chainbase
1266 base = chainbase
1265 chainlen, compresseddeltalen = self._chaininfo(rev)
1267 chainlen, compresseddeltalen = self._chaininfo(rev)
1266 chainlen += 1
1268 chainlen += 1
1267 compresseddeltalen += l
1269 compresseddeltalen += l
1268 return dist, l, data, base, chainbase, chainlen, compresseddeltalen
1270 return dist, l, data, base, chainbase, chainlen, compresseddeltalen
1269
1271
1270 curr = len(self)
1272 curr = len(self)
1271 prev = curr - 1
1273 prev = curr - 1
1272 base = chainbase = curr
1274 base = chainbase = curr
1273 chainlen = None
1275 chainlen = None
1274 offset = self.end(prev)
1276 offset = self.end(prev)
1275 d = None
1277 d = None
1276 if self._basecache is None:
1278 if self._basecache is None:
1277 self._basecache = (prev, self.chainbase(prev))
1279 self._basecache = (prev, self.chainbase(prev))
1278 basecache = self._basecache
1280 basecache = self._basecache
1279 p1r, p2r = self.rev(p1), self.rev(p2)
1281 p1r, p2r = self.rev(p1), self.rev(p2)
1280
1282
1281 # should we try to build a delta?
1283 # should we try to build a delta?
1282 if prev != nullrev:
1284 if prev != nullrev:
1283 if self._generaldelta:
1285 if self._generaldelta:
1284 if p1r >= basecache[1]:
1286 if p1r >= basecache[1]:
1285 d = builddelta(p1r)
1287 d = builddelta(p1r)
1286 elif p2r >= basecache[1]:
1288 elif p2r >= basecache[1]:
1287 d = builddelta(p2r)
1289 d = builddelta(p2r)
1288 else:
1290 else:
1289 d = builddelta(prev)
1291 d = builddelta(prev)
1290 else:
1292 else:
1291 d = builddelta(prev)
1293 d = builddelta(prev)
1292 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1294 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1293
1295
1294 # full versions are inserted when the needed deltas
1296 # full versions are inserted when the needed deltas
1295 # become comparable to the uncompressed text
1297 # become comparable to the uncompressed text
1296 if text is None:
1298 if text is None:
1297 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1299 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1298 cachedelta[1])
1300 cachedelta[1])
1299 else:
1301 else:
1300 textlen = len(text)
1302 textlen = len(text)
1301
1303
1302 # - 'dist' is the distance from the base revision -- bounding it limits
1304 # - 'dist' is the distance from the base revision -- bounding it limits
1303 # the amount of I/O we need to do.
1305 # the amount of I/O we need to do.
1304 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1306 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1305 # to apply -- bounding it limits the amount of CPU we consume.
1307 # to apply -- bounding it limits the amount of CPU we consume.
1306 if (d is None or dist > textlen * 4 or l > textlen or
1308 if (d is None or dist > textlen * 4 or l > textlen or
1307 compresseddeltalen > textlen * 2 or
1309 compresseddeltalen > textlen * 2 or
1308 (self._maxchainlen and chainlen > self._maxchainlen)):
1310 (self._maxchainlen and chainlen > self._maxchainlen)):
1309 text = buildtext()
1311 text = buildtext()
1310 data = self.compress(text)
1312 data = self.compress(text)
1311 l = len(data[1]) + len(data[0])
1313 l = len(data[1]) + len(data[0])
1312 base = chainbase = curr
1314 base = chainbase = curr
1313
1315
1314 e = (offset_type(offset, flags), l, textlen,
1316 e = (offset_type(offset, flags), l, textlen,
1315 base, link, p1r, p2r, node)
1317 base, link, p1r, p2r, node)
1316 self.index.insert(-1, e)
1318 self.index.insert(-1, e)
1317 self.nodemap[node] = curr
1319 self.nodemap[node] = curr
1318
1320
1319 entry = self._io.packentry(e, self.node, self.version, curr)
1321 entry = self._io.packentry(e, self.node, self.version, curr)
1320 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1322 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1321
1323
1322 if type(text) == str: # only accept immutable objects
1324 if type(text) == str: # only accept immutable objects
1323 self._cache = (node, curr, text)
1325 self._cache = (node, curr, text)
1324 self._basecache = (curr, chainbase)
1326 self._basecache = (curr, chainbase)
1325 return node
1327 return node
1326
1328
1327 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1329 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1328 curr = len(self) - 1
1330 curr = len(self) - 1
1329 if not self._inline:
1331 if not self._inline:
1330 transaction.add(self.datafile, offset)
1332 transaction.add(self.datafile, offset)
1331 transaction.add(self.indexfile, curr * len(entry))
1333 transaction.add(self.indexfile, curr * len(entry))
1332 if data[0]:
1334 if data[0]:
1333 dfh.write(data[0])
1335 dfh.write(data[0])
1334 dfh.write(data[1])
1336 dfh.write(data[1])
1335 dfh.flush()
1337 dfh.flush()
1336 ifh.write(entry)
1338 ifh.write(entry)
1337 else:
1339 else:
1338 offset += curr * self._io.size
1340 offset += curr * self._io.size
1339 transaction.add(self.indexfile, offset, curr)
1341 transaction.add(self.indexfile, offset, curr)
1340 ifh.write(entry)
1342 ifh.write(entry)
1341 ifh.write(data[0])
1343 ifh.write(data[0])
1342 ifh.write(data[1])
1344 ifh.write(data[1])
1343 self.checkinlinesize(transaction, ifh)
1345 self.checkinlinesize(transaction, ifh)
1344
1346
1345 def addgroup(self, bundle, linkmapper, transaction):
1347 def addgroup(self, bundle, linkmapper, transaction):
1346 """
1348 """
1347 add a delta group
1349 add a delta group
1348
1350
1349 given a set of deltas, add them to the revision log. the
1351 given a set of deltas, add them to the revision log. the
1350 first delta is against its parent, which should be in our
1352 first delta is against its parent, which should be in our
1351 log, the rest are against the previous delta.
1353 log, the rest are against the previous delta.
1352 """
1354 """
1353
1355
1354 # track the base of the current delta log
1356 # track the base of the current delta log
1355 content = []
1357 content = []
1356 node = None
1358 node = None
1357
1359
1358 r = len(self)
1360 r = len(self)
1359 end = 0
1361 end = 0
1360 if r:
1362 if r:
1361 end = self.end(r - 1)
1363 end = self.end(r - 1)
1362 ifh = self.opener(self.indexfile, "a+")
1364 ifh = self.opener(self.indexfile, "a+")
1363 isize = r * self._io.size
1365 isize = r * self._io.size
1364 if self._inline:
1366 if self._inline:
1365 transaction.add(self.indexfile, end + isize, r)
1367 transaction.add(self.indexfile, end + isize, r)
1366 dfh = None
1368 dfh = None
1367 else:
1369 else:
1368 transaction.add(self.indexfile, isize, r)
1370 transaction.add(self.indexfile, isize, r)
1369 transaction.add(self.datafile, end)
1371 transaction.add(self.datafile, end)
1370 dfh = self.opener(self.datafile, "a")
1372 dfh = self.opener(self.datafile, "a")
1371
1373
1372 try:
1374 try:
1373 # loop through our set of deltas
1375 # loop through our set of deltas
1374 chain = None
1376 chain = None
1375 while True:
1377 while True:
1376 chunkdata = bundle.deltachunk(chain)
1378 chunkdata = bundle.deltachunk(chain)
1377 if not chunkdata:
1379 if not chunkdata:
1378 break
1380 break
1379 node = chunkdata['node']
1381 node = chunkdata['node']
1380 p1 = chunkdata['p1']
1382 p1 = chunkdata['p1']
1381 p2 = chunkdata['p2']
1383 p2 = chunkdata['p2']
1382 cs = chunkdata['cs']
1384 cs = chunkdata['cs']
1383 deltabase = chunkdata['deltabase']
1385 deltabase = chunkdata['deltabase']
1384 delta = chunkdata['delta']
1386 delta = chunkdata['delta']
1385
1387
1386 content.append(node)
1388 content.append(node)
1387
1389
1388 link = linkmapper(cs)
1390 link = linkmapper(cs)
1389 if node in self.nodemap:
1391 if node in self.nodemap:
1390 # this can happen if two branches make the same change
1392 # this can happen if two branches make the same change
1391 chain = node
1393 chain = node
1392 continue
1394 continue
1393
1395
1394 for p in (p1, p2):
1396 for p in (p1, p2):
1395 if p not in self.nodemap:
1397 if p not in self.nodemap:
1396 raise LookupError(p, self.indexfile,
1398 raise LookupError(p, self.indexfile,
1397 _('unknown parent'))
1399 _('unknown parent'))
1398
1400
1399 if deltabase not in self.nodemap:
1401 if deltabase not in self.nodemap:
1400 raise LookupError(deltabase, self.indexfile,
1402 raise LookupError(deltabase, self.indexfile,
1401 _('unknown delta base'))
1403 _('unknown delta base'))
1402
1404
1403 baserev = self.rev(deltabase)
1405 baserev = self.rev(deltabase)
1404 chain = self._addrevision(node, None, transaction, link,
1406 chain = self._addrevision(node, None, transaction, link,
1405 p1, p2, REVIDX_DEFAULT_FLAGS,
1407 p1, p2, REVIDX_DEFAULT_FLAGS,
1406 (baserev, delta), ifh, dfh)
1408 (baserev, delta), ifh, dfh)
1407 if not dfh and not self._inline:
1409 if not dfh and not self._inline:
1408 # addrevision switched from inline to conventional
1410 # addrevision switched from inline to conventional
1409 # reopen the index
1411 # reopen the index
1410 ifh.close()
1412 ifh.close()
1411 dfh = self.opener(self.datafile, "a")
1413 dfh = self.opener(self.datafile, "a")
1412 ifh = self.opener(self.indexfile, "a")
1414 ifh = self.opener(self.indexfile, "a")
1413 finally:
1415 finally:
1414 if dfh:
1416 if dfh:
1415 dfh.close()
1417 dfh.close()
1416 ifh.close()
1418 ifh.close()
1417
1419
1418 return content
1420 return content
1419
1421
1420 def getstrippoint(self, minlink):
1422 def getstrippoint(self, minlink):
1421 """find the minimum rev that must be stripped to strip the linkrev
1423 """find the minimum rev that must be stripped to strip the linkrev
1422
1424
1423 Returns a tuple containing the minimum rev and a set of all revs that
1425 Returns a tuple containing the minimum rev and a set of all revs that
1424 have linkrevs that will be broken by this strip.
1426 have linkrevs that will be broken by this strip.
1425 """
1427 """
1426 brokenrevs = set()
1428 brokenrevs = set()
1427 strippoint = len(self)
1429 strippoint = len(self)
1428
1430
1429 heads = {}
1431 heads = {}
1430 futurelargelinkrevs = set()
1432 futurelargelinkrevs = set()
1431 for head in self.headrevs():
1433 for head in self.headrevs():
1432 headlinkrev = self.linkrev(head)
1434 headlinkrev = self.linkrev(head)
1433 heads[head] = headlinkrev
1435 heads[head] = headlinkrev
1434 if headlinkrev >= minlink:
1436 if headlinkrev >= minlink:
1435 futurelargelinkrevs.add(headlinkrev)
1437 futurelargelinkrevs.add(headlinkrev)
1436
1438
1437 # This algorithm involves walking down the rev graph, starting at the
1439 # This algorithm involves walking down the rev graph, starting at the
1438 # heads. Since the revs are topologically sorted according to linkrev,
1440 # heads. Since the revs are topologically sorted according to linkrev,
1439 # once all head linkrevs are below the minlink, we know there are
1441 # once all head linkrevs are below the minlink, we know there are
1440 # no more revs that could have a linkrev greater than minlink.
1442 # no more revs that could have a linkrev greater than minlink.
1441 # So we can stop walking.
1443 # So we can stop walking.
1442 while futurelargelinkrevs:
1444 while futurelargelinkrevs:
1443 strippoint -= 1
1445 strippoint -= 1
1444 linkrev = heads.pop(strippoint)
1446 linkrev = heads.pop(strippoint)
1445
1447
1446 if linkrev < minlink:
1448 if linkrev < minlink:
1447 brokenrevs.add(strippoint)
1449 brokenrevs.add(strippoint)
1448 else:
1450 else:
1449 futurelargelinkrevs.remove(linkrev)
1451 futurelargelinkrevs.remove(linkrev)
1450
1452
1451 for p in self.parentrevs(strippoint):
1453 for p in self.parentrevs(strippoint):
1452 if p != nullrev:
1454 if p != nullrev:
1453 plinkrev = self.linkrev(p)
1455 plinkrev = self.linkrev(p)
1454 heads[p] = plinkrev
1456 heads[p] = plinkrev
1455 if plinkrev >= minlink:
1457 if plinkrev >= minlink:
1456 futurelargelinkrevs.add(plinkrev)
1458 futurelargelinkrevs.add(plinkrev)
1457
1459
1458 return strippoint, brokenrevs
1460 return strippoint, brokenrevs
1459
1461
1460 def strip(self, minlink, transaction):
1462 def strip(self, minlink, transaction):
1461 """truncate the revlog on the first revision with a linkrev >= minlink
1463 """truncate the revlog on the first revision with a linkrev >= minlink
1462
1464
1463 This function is called when we're stripping revision minlink and
1465 This function is called when we're stripping revision minlink and
1464 its descendants from the repository.
1466 its descendants from the repository.
1465
1467
1466 We have to remove all revisions with linkrev >= minlink, because
1468 We have to remove all revisions with linkrev >= minlink, because
1467 the equivalent changelog revisions will be renumbered after the
1469 the equivalent changelog revisions will be renumbered after the
1468 strip.
1470 strip.
1469
1471
1470 So we truncate the revlog on the first of these revisions, and
1472 So we truncate the revlog on the first of these revisions, and
1471 trust that the caller has saved the revisions that shouldn't be
1473 trust that the caller has saved the revisions that shouldn't be
1472 removed and that it'll re-add them after this truncation.
1474 removed and that it'll re-add them after this truncation.
1473 """
1475 """
1474 if len(self) == 0:
1476 if len(self) == 0:
1475 return
1477 return
1476
1478
1477 rev, _ = self.getstrippoint(minlink)
1479 rev, _ = self.getstrippoint(minlink)
1478 if rev == len(self):
1480 if rev == len(self):
1479 return
1481 return
1480
1482
1481 # first truncate the files on disk
1483 # first truncate the files on disk
1482 end = self.start(rev)
1484 end = self.start(rev)
1483 if not self._inline:
1485 if not self._inline:
1484 transaction.add(self.datafile, end)
1486 transaction.add(self.datafile, end)
1485 end = rev * self._io.size
1487 end = rev * self._io.size
1486 else:
1488 else:
1487 end += rev * self._io.size
1489 end += rev * self._io.size
1488
1490
1489 transaction.add(self.indexfile, end)
1491 transaction.add(self.indexfile, end)
1490
1492
1491 # then reset internal state in memory to forget those revisions
1493 # then reset internal state in memory to forget those revisions
1492 self._cache = None
1494 self._cache = None
1493 self._chaininfocache = {}
1495 self._chaininfocache = {}
1494 self._chunkclear()
1496 self._chunkclear()
1495 for x in xrange(rev, len(self)):
1497 for x in xrange(rev, len(self)):
1496 del self.nodemap[self.node(x)]
1498 del self.nodemap[self.node(x)]
1497
1499
1498 del self.index[rev:-1]
1500 del self.index[rev:-1]
1499
1501
1500 def checksize(self):
1502 def checksize(self):
1501 expected = 0
1503 expected = 0
1502 if len(self):
1504 if len(self):
1503 expected = max(0, self.end(len(self) - 1))
1505 expected = max(0, self.end(len(self) - 1))
1504
1506
1505 try:
1507 try:
1506 f = self.opener(self.datafile)
1508 f = self.opener(self.datafile)
1507 f.seek(0, 2)
1509 f.seek(0, 2)
1508 actual = f.tell()
1510 actual = f.tell()
1509 f.close()
1511 f.close()
1510 dd = actual - expected
1512 dd = actual - expected
1511 except IOError, inst:
1513 except IOError, inst:
1512 if inst.errno != errno.ENOENT:
1514 if inst.errno != errno.ENOENT:
1513 raise
1515 raise
1514 dd = 0
1516 dd = 0
1515
1517
1516 try:
1518 try:
1517 f = self.opener(self.indexfile)
1519 f = self.opener(self.indexfile)
1518 f.seek(0, 2)
1520 f.seek(0, 2)
1519 actual = f.tell()
1521 actual = f.tell()
1520 f.close()
1522 f.close()
1521 s = self._io.size
1523 s = self._io.size
1522 i = max(0, actual // s)
1524 i = max(0, actual // s)
1523 di = actual - (i * s)
1525 di = actual - (i * s)
1524 if self._inline:
1526 if self._inline:
1525 databytes = 0
1527 databytes = 0
1526 for r in self:
1528 for r in self:
1527 databytes += max(0, self.length(r))
1529 databytes += max(0, self.length(r))
1528 dd = 0
1530 dd = 0
1529 di = actual - len(self) * s - databytes
1531 di = actual - len(self) * s - databytes
1530 except IOError, inst:
1532 except IOError, inst:
1531 if inst.errno != errno.ENOENT:
1533 if inst.errno != errno.ENOENT:
1532 raise
1534 raise
1533 di = 0
1535 di = 0
1534
1536
1535 return (dd, di)
1537 return (dd, di)
1536
1538
1537 def files(self):
1539 def files(self):
1538 res = [self.indexfile]
1540 res = [self.indexfile]
1539 if not self._inline:
1541 if not self._inline:
1540 res.append(self.datafile)
1542 res.append(self.datafile)
1541 return res
1543 return res
General Comments 0
You need to be logged in to leave comments. Login now