##// END OF EJS Templates
revlog: raise offset/type helpers to global scope
Matt Mackall -
r4918:e017d3a8 default
parent child Browse files
Show More
@@ -1,254 +1,254 b''
1 """
1 """
2 bundlerepo.py - repository class for viewing uncompressed bundles
2 bundlerepo.py - repository class for viewing uncompressed bundles
3
3
4 This provides a read-only repository interface to bundles as if
4 This provides a read-only repository interface to bundles as if
5 they were part of the actual repository.
5 they were part of the actual repository.
6
6
7 Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
7 Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import *
13 from node import *
14 from i18n import _
14 from i18n import _
15 import changegroup, util, os, struct, bz2, tempfile
15 import changegroup, util, os, struct, bz2, tempfile
16
16
17 import localrepo, changelog, manifest, filelog, revlog
17 import localrepo, changelog, manifest, filelog, revlog
18
18
19 class bundlerevlog(revlog.revlog):
19 class bundlerevlog(revlog.revlog):
20 def __init__(self, opener, indexfile, bundlefile,
20 def __init__(self, opener, indexfile, bundlefile,
21 linkmapper=None):
21 linkmapper=None):
22 # How it works:
22 # How it works:
23 # to retrieve a revision, we need to know the offset of
23 # to retrieve a revision, we need to know the offset of
24 # the revision in the bundlefile (an opened file).
24 # the revision in the bundlefile (an opened file).
25 #
25 #
26 # We store this offset in the index (start), to differentiate a
26 # We store this offset in the index (start), to differentiate a
27 # rev in the bundle and from a rev in the revlog, we check
27 # rev in the bundle and from a rev in the revlog, we check
28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
29 # (it is bigger since we store the node to which the delta is)
29 # (it is bigger since we store the node to which the delta is)
30 #
30 #
31 revlog.revlog.__init__(self, opener, indexfile)
31 revlog.revlog.__init__(self, opener, indexfile)
32 self.bundlefile = bundlefile
32 self.bundlefile = bundlefile
33 self.basemap = {}
33 self.basemap = {}
34 def chunkpositer():
34 def chunkpositer():
35 for chunk in changegroup.chunkiter(bundlefile):
35 for chunk in changegroup.chunkiter(bundlefile):
36 pos = bundlefile.tell()
36 pos = bundlefile.tell()
37 yield chunk, pos - len(chunk)
37 yield chunk, pos - len(chunk)
38 n = self.count()
38 n = self.count()
39 prev = None
39 prev = None
40 for chunk, start in chunkpositer():
40 for chunk, start in chunkpositer():
41 size = len(chunk)
41 size = len(chunk)
42 if size < 80:
42 if size < 80:
43 raise util.Abort("invalid changegroup")
43 raise util.Abort("invalid changegroup")
44 start += 80
44 start += 80
45 size -= 80
45 size -= 80
46 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
46 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
47 if node in self.nodemap:
47 if node in self.nodemap:
48 prev = node
48 prev = node
49 continue
49 continue
50 for p in (p1, p2):
50 for p in (p1, p2):
51 if not p in self.nodemap:
51 if not p in self.nodemap:
52 raise revlog.LookupError(_("unknown parent %s") % short(p1))
52 raise revlog.LookupError(_("unknown parent %s") % short(p1))
53 if linkmapper is None:
53 if linkmapper is None:
54 link = n
54 link = n
55 else:
55 else:
56 link = linkmapper(cs)
56 link = linkmapper(cs)
57
57
58 if not prev:
58 if not prev:
59 prev = p1
59 prev = p1
60 # start, size, base is not used, link, p1, p2, delta ref
60 # start, size, base is not used, link, p1, p2, delta ref
61 if self.version == revlog.REVLOGV0:
61 if self.version == revlog.REVLOGV0:
62 e = (start, size, None, link, p1, p2, node)
62 e = (start, size, None, link, p1, p2, node)
63 else:
63 else:
64 e = (self.offset_type(start, 0), size, -1, None, link,
64 e = (revlog.offset_type(start, 0), size, -1, None, link,
65 self.rev(p1), self.rev(p2), node)
65 self.rev(p1), self.rev(p2), node)
66 self.basemap[n] = prev
66 self.basemap[n] = prev
67 self.index.append(e)
67 self.index.append(e)
68 self.nodemap[node] = n
68 self.nodemap[node] = n
69 prev = node
69 prev = node
70 n += 1
70 n += 1
71
71
72 def bundle(self, rev):
72 def bundle(self, rev):
73 """is rev from the bundle"""
73 """is rev from the bundle"""
74 if rev < 0:
74 if rev < 0:
75 return False
75 return False
76 return rev in self.basemap
76 return rev in self.basemap
77 def bundlebase(self, rev): return self.basemap[rev]
77 def bundlebase(self, rev): return self.basemap[rev]
78 def chunk(self, rev, df=None, cachelen=4096):
78 def chunk(self, rev, df=None, cachelen=4096):
79 # Warning: in case of bundle, the diff is against bundlebase,
79 # Warning: in case of bundle, the diff is against bundlebase,
80 # not against rev - 1
80 # not against rev - 1
81 # XXX: could use some caching
81 # XXX: could use some caching
82 if not self.bundle(rev):
82 if not self.bundle(rev):
83 return revlog.revlog.chunk(self, rev, df, cachelen)
83 return revlog.revlog.chunk(self, rev, df, cachelen)
84 self.bundlefile.seek(self.start(rev))
84 self.bundlefile.seek(self.start(rev))
85 return self.bundlefile.read(self.length(rev))
85 return self.bundlefile.read(self.length(rev))
86
86
87 def revdiff(self, rev1, rev2):
87 def revdiff(self, rev1, rev2):
88 """return or calculate a delta between two revisions"""
88 """return or calculate a delta between two revisions"""
89 if self.bundle(rev1) and self.bundle(rev2):
89 if self.bundle(rev1) and self.bundle(rev2):
90 # hot path for bundle
90 # hot path for bundle
91 revb = self.rev(self.bundlebase(rev2))
91 revb = self.rev(self.bundlebase(rev2))
92 if revb == rev1:
92 if revb == rev1:
93 return self.chunk(rev2)
93 return self.chunk(rev2)
94 elif not self.bundle(rev1) and not self.bundle(rev2):
94 elif not self.bundle(rev1) and not self.bundle(rev2):
95 return revlog.revlog.revdiff(self, rev1, rev2)
95 return revlog.revlog.revdiff(self, rev1, rev2)
96
96
97 return self.diff(self.revision(self.node(rev1)),
97 return self.diff(self.revision(self.node(rev1)),
98 self.revision(self.node(rev2)))
98 self.revision(self.node(rev2)))
99
99
100 def revision(self, node):
100 def revision(self, node):
101 """return an uncompressed revision of a given"""
101 """return an uncompressed revision of a given"""
102 if node == nullid: return ""
102 if node == nullid: return ""
103
103
104 text = None
104 text = None
105 chain = []
105 chain = []
106 iter_node = node
106 iter_node = node
107 rev = self.rev(iter_node)
107 rev = self.rev(iter_node)
108 # reconstruct the revision if it is from a changegroup
108 # reconstruct the revision if it is from a changegroup
109 while self.bundle(rev):
109 while self.bundle(rev):
110 if self.cache and self.cache[0] == iter_node:
110 if self.cache and self.cache[0] == iter_node:
111 text = self.cache[2]
111 text = self.cache[2]
112 break
112 break
113 chain.append(rev)
113 chain.append(rev)
114 iter_node = self.bundlebase(rev)
114 iter_node = self.bundlebase(rev)
115 rev = self.rev(iter_node)
115 rev = self.rev(iter_node)
116 if text is None:
116 if text is None:
117 text = revlog.revlog.revision(self, iter_node)
117 text = revlog.revlog.revision(self, iter_node)
118
118
119 while chain:
119 while chain:
120 delta = self.chunk(chain.pop())
120 delta = self.chunk(chain.pop())
121 text = self.patches(text, [delta])
121 text = self.patches(text, [delta])
122
122
123 p1, p2 = self.parents(node)
123 p1, p2 = self.parents(node)
124 if node != revlog.hash(text, p1, p2):
124 if node != revlog.hash(text, p1, p2):
125 raise revlog.RevlogError(_("integrity check failed on %s:%d")
125 raise revlog.RevlogError(_("integrity check failed on %s:%d")
126 % (self.datafile, self.rev(node)))
126 % (self.datafile, self.rev(node)))
127
127
128 self.cache = (node, self.rev(node), text)
128 self.cache = (node, self.rev(node), text)
129 return text
129 return text
130
130
131 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
131 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
132 raise NotImplementedError
132 raise NotImplementedError
133 def addgroup(self, revs, linkmapper, transaction, unique=0):
133 def addgroup(self, revs, linkmapper, transaction, unique=0):
134 raise NotImplementedError
134 raise NotImplementedError
135 def strip(self, rev, minlink):
135 def strip(self, rev, minlink):
136 raise NotImplementedError
136 raise NotImplementedError
137 def checksize(self):
137 def checksize(self):
138 raise NotImplementedError
138 raise NotImplementedError
139
139
140 class bundlechangelog(bundlerevlog, changelog.changelog):
140 class bundlechangelog(bundlerevlog, changelog.changelog):
141 def __init__(self, opener, bundlefile):
141 def __init__(self, opener, bundlefile):
142 changelog.changelog.__init__(self, opener)
142 changelog.changelog.__init__(self, opener)
143 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
143 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
144
144
145 class bundlemanifest(bundlerevlog, manifest.manifest):
145 class bundlemanifest(bundlerevlog, manifest.manifest):
146 def __init__(self, opener, bundlefile, linkmapper):
146 def __init__(self, opener, bundlefile, linkmapper):
147 manifest.manifest.__init__(self, opener)
147 manifest.manifest.__init__(self, opener)
148 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
148 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
149 linkmapper)
149 linkmapper)
150
150
151 class bundlefilelog(bundlerevlog, filelog.filelog):
151 class bundlefilelog(bundlerevlog, filelog.filelog):
152 def __init__(self, opener, path, bundlefile, linkmapper):
152 def __init__(self, opener, path, bundlefile, linkmapper):
153 filelog.filelog.__init__(self, opener, path)
153 filelog.filelog.__init__(self, opener, path)
154 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
154 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
155 linkmapper)
155 linkmapper)
156
156
157 class bundlerepository(localrepo.localrepository):
157 class bundlerepository(localrepo.localrepository):
158 def __init__(self, ui, path, bundlename):
158 def __init__(self, ui, path, bundlename):
159 localrepo.localrepository.__init__(self, ui, path)
159 localrepo.localrepository.__init__(self, ui, path)
160
160
161 self._url = 'bundle:' + bundlename
161 self._url = 'bundle:' + bundlename
162 if path: self._url += '+' + path
162 if path: self._url += '+' + path
163
163
164 self.tempfile = None
164 self.tempfile = None
165 self.bundlefile = open(bundlename, "rb")
165 self.bundlefile = open(bundlename, "rb")
166 header = self.bundlefile.read(6)
166 header = self.bundlefile.read(6)
167 if not header.startswith("HG"):
167 if not header.startswith("HG"):
168 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
168 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
169 elif not header.startswith("HG10"):
169 elif not header.startswith("HG10"):
170 raise util.Abort(_("%s: unknown bundle version") % bundlename)
170 raise util.Abort(_("%s: unknown bundle version") % bundlename)
171 elif header == "HG10BZ":
171 elif header == "HG10BZ":
172 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
172 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
173 suffix=".hg10un", dir=self.path)
173 suffix=".hg10un", dir=self.path)
174 self.tempfile = temp
174 self.tempfile = temp
175 fptemp = os.fdopen(fdtemp, 'wb')
175 fptemp = os.fdopen(fdtemp, 'wb')
176 def generator(f):
176 def generator(f):
177 zd = bz2.BZ2Decompressor()
177 zd = bz2.BZ2Decompressor()
178 zd.decompress("BZ")
178 zd.decompress("BZ")
179 for chunk in f:
179 for chunk in f:
180 yield zd.decompress(chunk)
180 yield zd.decompress(chunk)
181 gen = generator(util.filechunkiter(self.bundlefile, 4096))
181 gen = generator(util.filechunkiter(self.bundlefile, 4096))
182
182
183 try:
183 try:
184 fptemp.write("HG10UN")
184 fptemp.write("HG10UN")
185 for chunk in gen:
185 for chunk in gen:
186 fptemp.write(chunk)
186 fptemp.write(chunk)
187 finally:
187 finally:
188 fptemp.close()
188 fptemp.close()
189 self.bundlefile.close()
189 self.bundlefile.close()
190
190
191 self.bundlefile = open(self.tempfile, "rb")
191 self.bundlefile = open(self.tempfile, "rb")
192 # seek right after the header
192 # seek right after the header
193 self.bundlefile.seek(6)
193 self.bundlefile.seek(6)
194 elif header == "HG10UN":
194 elif header == "HG10UN":
195 # nothing to do
195 # nothing to do
196 pass
196 pass
197 else:
197 else:
198 raise util.Abort(_("%s: unknown bundle compression type")
198 raise util.Abort(_("%s: unknown bundle compression type")
199 % bundlename)
199 % bundlename)
200 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
200 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
201 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
201 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
202 self.changelog.rev)
202 self.changelog.rev)
203 # dict with the mapping 'filename' -> position in the bundle
203 # dict with the mapping 'filename' -> position in the bundle
204 self.bundlefilespos = {}
204 self.bundlefilespos = {}
205 while 1:
205 while 1:
206 f = changegroup.getchunk(self.bundlefile)
206 f = changegroup.getchunk(self.bundlefile)
207 if not f:
207 if not f:
208 break
208 break
209 self.bundlefilespos[f] = self.bundlefile.tell()
209 self.bundlefilespos[f] = self.bundlefile.tell()
210 for c in changegroup.chunkiter(self.bundlefile):
210 for c in changegroup.chunkiter(self.bundlefile):
211 pass
211 pass
212
212
213 def url(self):
213 def url(self):
214 return self._url
214 return self._url
215
215
216 def dev(self):
216 def dev(self):
217 return -1
217 return -1
218
218
219 def file(self, f):
219 def file(self, f):
220 if f[0] == '/':
220 if f[0] == '/':
221 f = f[1:]
221 f = f[1:]
222 if f in self.bundlefilespos:
222 if f in self.bundlefilespos:
223 self.bundlefile.seek(self.bundlefilespos[f])
223 self.bundlefile.seek(self.bundlefilespos[f])
224 return bundlefilelog(self.sopener, f, self.bundlefile,
224 return bundlefilelog(self.sopener, f, self.bundlefile,
225 self.changelog.rev)
225 self.changelog.rev)
226 else:
226 else:
227 return filelog.filelog(self.sopener, f)
227 return filelog.filelog(self.sopener, f)
228
228
229 def close(self):
229 def close(self):
230 """Close assigned bundle file immediately."""
230 """Close assigned bundle file immediately."""
231 self.bundlefile.close()
231 self.bundlefile.close()
232
232
233 def __del__(self):
233 def __del__(self):
234 bundlefile = getattr(self, 'bundlefile', None)
234 bundlefile = getattr(self, 'bundlefile', None)
235 if bundlefile and not bundlefile.closed:
235 if bundlefile and not bundlefile.closed:
236 bundlefile.close()
236 bundlefile.close()
237 tempfile = getattr(self, 'tempfile', None)
237 tempfile = getattr(self, 'tempfile', None)
238 if tempfile is not None:
238 if tempfile is not None:
239 os.unlink(tempfile)
239 os.unlink(tempfile)
240
240
241 def instance(ui, path, create):
241 def instance(ui, path, create):
242 if create:
242 if create:
243 raise util.Abort(_('cannot create new bundle repository'))
243 raise util.Abort(_('cannot create new bundle repository'))
244 path = util.drop_scheme('file', path)
244 path = util.drop_scheme('file', path)
245 if path.startswith('bundle:'):
245 if path.startswith('bundle:'):
246 path = util.drop_scheme('bundle', path)
246 path = util.drop_scheme('bundle', path)
247 s = path.split("+", 1)
247 s = path.split("+", 1)
248 if len(s) == 1:
248 if len(s) == 1:
249 repopath, bundlename = "", s[0]
249 repopath, bundlename = "", s[0]
250 else:
250 else:
251 repopath, bundlename = s
251 repopath, bundlename = s
252 else:
252 else:
253 repopath, bundlename = '', path
253 repopath, bundlename = '', path
254 return bundlerepository(ui, repopath, bundlename)
254 return bundlerepository(ui, repopath, bundlename)
@@ -1,1301 +1,1300 b''
1 """
1 """
2 revlog.py - storage back-end for mercurial
2 revlog.py - storage back-end for mercurial
3
3
4 This provides efficient delta storage with O(1) retrieve and append
4 This provides efficient delta storage with O(1) retrieve and append
5 and O(changes) merge between branches
5 and O(changes) merge between branches
6
6
7 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
7 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import *
13 from node import *
14 from i18n import _
14 from i18n import _
15 import binascii, changegroup, errno, ancestor, mdiff, os
15 import binascii, changegroup, errno, ancestor, mdiff, os
16 import sha, struct, util, zlib
16 import sha, struct, util, zlib
17
17
18 # revlog version strings
18 # revlog version strings
19 REVLOGV0 = 0
19 REVLOGV0 = 0
20 REVLOGNG = 1
20 REVLOGNG = 1
21
21
22 # revlog flags
22 # revlog flags
23 REVLOGNGINLINEDATA = (1 << 16)
23 REVLOGNGINLINEDATA = (1 << 16)
24 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
24 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
25
25
26 REVLOG_DEFAULT_FORMAT = REVLOGNG
26 REVLOG_DEFAULT_FORMAT = REVLOGNG
27 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
27 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
28
28
29 def hash(text, p1, p2):
29 def hash(text, p1, p2):
30 """generate a hash from the given text and its parent hashes
30 """generate a hash from the given text and its parent hashes
31
31
32 This hash combines both the current file contents and its history
32 This hash combines both the current file contents and its history
33 in a manner that makes it easy to distinguish nodes with the same
33 in a manner that makes it easy to distinguish nodes with the same
34 content in the revision graph.
34 content in the revision graph.
35 """
35 """
36 l = [p1, p2]
36 l = [p1, p2]
37 l.sort()
37 l.sort()
38 s = sha.new(l[0])
38 s = sha.new(l[0])
39 s.update(l[1])
39 s.update(l[1])
40 s.update(text)
40 s.update(text)
41 return s.digest()
41 return s.digest()
42
42
43 def compress(text):
43 def compress(text):
44 """ generate a possibly-compressed representation of text """
44 """ generate a possibly-compressed representation of text """
45 if not text: return ("", text)
45 if not text: return ("", text)
46 if len(text) < 44:
46 if len(text) < 44:
47 if text[0] == '\0': return ("", text)
47 if text[0] == '\0': return ("", text)
48 return ('u', text)
48 return ('u', text)
49 bin = zlib.compress(text)
49 bin = zlib.compress(text)
50 if len(bin) > len(text):
50 if len(bin) > len(text):
51 if text[0] == '\0': return ("", text)
51 if text[0] == '\0': return ("", text)
52 return ('u', text)
52 return ('u', text)
53 return ("", bin)
53 return ("", bin)
54
54
55 def decompress(bin):
55 def decompress(bin):
56 """ decompress the given input """
56 """ decompress the given input """
57 if not bin: return bin
57 if not bin: return bin
58 t = bin[0]
58 t = bin[0]
59 if t == '\0': return bin
59 if t == '\0': return bin
60 if t == 'x': return zlib.decompress(bin)
60 if t == 'x': return zlib.decompress(bin)
61 if t == 'u': return bin[1:]
61 if t == 'u': return bin[1:]
62 raise RevlogError(_("unknown compression type %r") % t)
62 raise RevlogError(_("unknown compression type %r") % t)
63
63
64 indexformatv0 = ">4l20s20s20s"
64 indexformatv0 = ">4l20s20s20s"
65 v0shaoffset = 56
65 v0shaoffset = 56
66 # index ng:
66 # index ng:
67 # 6 bytes offset
67 # 6 bytes offset
68 # 2 bytes flags
68 # 2 bytes flags
69 # 4 bytes compressed length
69 # 4 bytes compressed length
70 # 4 bytes uncompressed length
70 # 4 bytes uncompressed length
71 # 4 bytes: base rev
71 # 4 bytes: base rev
72 # 4 bytes link rev
72 # 4 bytes link rev
73 # 4 bytes parent 1 rev
73 # 4 bytes parent 1 rev
74 # 4 bytes parent 2 rev
74 # 4 bytes parent 2 rev
75 # 32 bytes: nodeid
75 # 32 bytes: nodeid
76 indexformatng = ">Qiiiiii20s12x"
76 indexformatng = ">Qiiiiii20s12x"
77 ngshaoffset = 32
77 ngshaoffset = 32
78 versionformat = ">I"
78 versionformat = ">I"
79
79
80 class lazyparser(object):
80 class lazyparser(object):
81 """
81 """
82 this class avoids the need to parse the entirety of large indices
82 this class avoids the need to parse the entirety of large indices
83 """
83 """
84
84
85 # lazyparser is not safe to use on windows if win32 extensions not
85 # lazyparser is not safe to use on windows if win32 extensions not
86 # available. it keeps file handle open, which make it not possible
86 # available. it keeps file handle open, which make it not possible
87 # to break hardlinks on local cloned repos.
87 # to break hardlinks on local cloned repos.
88 safe_to_use = os.name != 'nt' or (not util.is_win_9x() and
88 safe_to_use = os.name != 'nt' or (not util.is_win_9x() and
89 hasattr(util, 'win32api'))
89 hasattr(util, 'win32api'))
90
90
91 def __init__(self, dataf, size, indexformat, shaoffset):
91 def __init__(self, dataf, size, indexformat, shaoffset):
92 self.dataf = dataf
92 self.dataf = dataf
93 self.format = indexformat
93 self.format = indexformat
94 self.s = struct.calcsize(indexformat)
94 self.s = struct.calcsize(indexformat)
95 self.indexformat = indexformat
95 self.indexformat = indexformat
96 self.datasize = size
96 self.datasize = size
97 self.l = size/self.s
97 self.l = size/self.s
98 self.index = [None] * self.l
98 self.index = [None] * self.l
99 self.map = {nullid: nullrev}
99 self.map = {nullid: nullrev}
100 self.allmap = 0
100 self.allmap = 0
101 self.all = 0
101 self.all = 0
102 self.mapfind_count = 0
102 self.mapfind_count = 0
103 self.shaoffset = shaoffset
103 self.shaoffset = shaoffset
104
104
105 def loadmap(self):
105 def loadmap(self):
106 """
106 """
107 during a commit, we need to make sure the rev being added is
107 during a commit, we need to make sure the rev being added is
108 not a duplicate. This requires loading the entire index,
108 not a duplicate. This requires loading the entire index,
109 which is fairly slow. loadmap can load up just the node map,
109 which is fairly slow. loadmap can load up just the node map,
110 which takes much less time.
110 which takes much less time.
111 """
111 """
112 if self.allmap: return
112 if self.allmap: return
113 end = self.datasize
113 end = self.datasize
114 self.allmap = 1
114 self.allmap = 1
115 cur = 0
115 cur = 0
116 count = 0
116 count = 0
117 blocksize = self.s * 256
117 blocksize = self.s * 256
118 self.dataf.seek(0)
118 self.dataf.seek(0)
119 while cur < end:
119 while cur < end:
120 data = self.dataf.read(blocksize)
120 data = self.dataf.read(blocksize)
121 off = 0
121 off = 0
122 for x in xrange(256):
122 for x in xrange(256):
123 n = data[off + self.shaoffset:off + self.shaoffset + 20]
123 n = data[off + self.shaoffset:off + self.shaoffset + 20]
124 self.map[n] = count
124 self.map[n] = count
125 count += 1
125 count += 1
126 if count >= self.l:
126 if count >= self.l:
127 break
127 break
128 off += self.s
128 off += self.s
129 cur += blocksize
129 cur += blocksize
130
130
131 def loadblock(self, blockstart, blocksize, data=None):
131 def loadblock(self, blockstart, blocksize, data=None):
132 if self.all: return
132 if self.all: return
133 if data is None:
133 if data is None:
134 self.dataf.seek(blockstart)
134 self.dataf.seek(blockstart)
135 if blockstart + blocksize > self.datasize:
135 if blockstart + blocksize > self.datasize:
136 # the revlog may have grown since we've started running,
136 # the revlog may have grown since we've started running,
137 # but we don't have space in self.index for more entries.
137 # but we don't have space in self.index for more entries.
138 # limit blocksize so that we don't get too much data.
138 # limit blocksize so that we don't get too much data.
139 blocksize = max(self.datasize - blockstart, 0)
139 blocksize = max(self.datasize - blockstart, 0)
140 data = self.dataf.read(blocksize)
140 data = self.dataf.read(blocksize)
141 lend = len(data) / self.s
141 lend = len(data) / self.s
142 i = blockstart / self.s
142 i = blockstart / self.s
143 off = 0
143 off = 0
144 # lazyindex supports __delitem__
144 # lazyindex supports __delitem__
145 if lend > len(self.index) - i:
145 if lend > len(self.index) - i:
146 lend = len(self.index) - i
146 lend = len(self.index) - i
147 for x in xrange(lend):
147 for x in xrange(lend):
148 if self.index[i + x] == None:
148 if self.index[i + x] == None:
149 b = data[off : off + self.s]
149 b = data[off : off + self.s]
150 self.index[i + x] = b
150 self.index[i + x] = b
151 n = b[self.shaoffset:self.shaoffset + 20]
151 n = b[self.shaoffset:self.shaoffset + 20]
152 self.map[n] = i + x
152 self.map[n] = i + x
153 off += self.s
153 off += self.s
154
154
155 def findnode(self, node):
155 def findnode(self, node):
156 """search backwards through the index file for a specific node"""
156 """search backwards through the index file for a specific node"""
157 if self.allmap: return None
157 if self.allmap: return None
158
158
159 # hg log will cause many many searches for the manifest
159 # hg log will cause many many searches for the manifest
160 # nodes. After we get called a few times, just load the whole
160 # nodes. After we get called a few times, just load the whole
161 # thing.
161 # thing.
162 if self.mapfind_count > 8:
162 if self.mapfind_count > 8:
163 self.loadmap()
163 self.loadmap()
164 if node in self.map:
164 if node in self.map:
165 return node
165 return node
166 return None
166 return None
167 self.mapfind_count += 1
167 self.mapfind_count += 1
168 last = self.l - 1
168 last = self.l - 1
169 while self.index[last] != None:
169 while self.index[last] != None:
170 if last == 0:
170 if last == 0:
171 self.all = 1
171 self.all = 1
172 self.allmap = 1
172 self.allmap = 1
173 return None
173 return None
174 last -= 1
174 last -= 1
175 end = (last + 1) * self.s
175 end = (last + 1) * self.s
176 blocksize = self.s * 256
176 blocksize = self.s * 256
177 while end >= 0:
177 while end >= 0:
178 start = max(end - blocksize, 0)
178 start = max(end - blocksize, 0)
179 self.dataf.seek(start)
179 self.dataf.seek(start)
180 data = self.dataf.read(end - start)
180 data = self.dataf.read(end - start)
181 findend = end - start
181 findend = end - start
182 while True:
182 while True:
183 # we're searching backwards, so weh have to make sure
183 # we're searching backwards, so weh have to make sure
184 # we don't find a changeset where this node is a parent
184 # we don't find a changeset where this node is a parent
185 off = data.rfind(node, 0, findend)
185 off = data.rfind(node, 0, findend)
186 findend = off
186 findend = off
187 if off >= 0:
187 if off >= 0:
188 i = off / self.s
188 i = off / self.s
189 off = i * self.s
189 off = i * self.s
190 n = data[off + self.shaoffset:off + self.shaoffset + 20]
190 n = data[off + self.shaoffset:off + self.shaoffset + 20]
191 if n == node:
191 if n == node:
192 self.map[n] = i + start / self.s
192 self.map[n] = i + start / self.s
193 return node
193 return node
194 else:
194 else:
195 break
195 break
196 end -= blocksize
196 end -= blocksize
197 return None
197 return None
198
198
199 def loadindex(self, i=None, end=None):
199 def loadindex(self, i=None, end=None):
200 if self.all: return
200 if self.all: return
201 all = False
201 all = False
202 if i == None:
202 if i == None:
203 blockstart = 0
203 blockstart = 0
204 blocksize = (512 / self.s) * self.s
204 blocksize = (512 / self.s) * self.s
205 end = self.datasize
205 end = self.datasize
206 all = True
206 all = True
207 else:
207 else:
208 if end:
208 if end:
209 blockstart = i * self.s
209 blockstart = i * self.s
210 end = end * self.s
210 end = end * self.s
211 blocksize = end - blockstart
211 blocksize = end - blockstart
212 else:
212 else:
213 blockstart = (i & ~63) * self.s
213 blockstart = (i & ~63) * self.s
214 blocksize = self.s * 64
214 blocksize = self.s * 64
215 end = blockstart + blocksize
215 end = blockstart + blocksize
216 while blockstart < end:
216 while blockstart < end:
217 self.loadblock(blockstart, blocksize)
217 self.loadblock(blockstart, blocksize)
218 blockstart += blocksize
218 blockstart += blocksize
219 if all: self.all = True
219 if all: self.all = True
220
220
221 class lazyindex(object):
221 class lazyindex(object):
222 """a lazy version of the index array"""
222 """a lazy version of the index array"""
223 def __init__(self, parser):
223 def __init__(self, parser):
224 self.p = parser
224 self.p = parser
225 def __len__(self):
225 def __len__(self):
226 return len(self.p.index)
226 return len(self.p.index)
227 def load(self, pos):
227 def load(self, pos):
228 if pos < 0:
228 if pos < 0:
229 pos += len(self.p.index)
229 pos += len(self.p.index)
230 self.p.loadindex(pos)
230 self.p.loadindex(pos)
231 return self.p.index[pos]
231 return self.p.index[pos]
232 def __getitem__(self, pos):
232 def __getitem__(self, pos):
233 ret = self.p.index[pos] or self.load(pos)
233 ret = self.p.index[pos] or self.load(pos)
234 if isinstance(ret, str):
234 if isinstance(ret, str):
235 ret = struct.unpack(self.p.indexformat, ret)
235 ret = struct.unpack(self.p.indexformat, ret)
236 return ret
236 return ret
237 def __setitem__(self, pos, item):
237 def __setitem__(self, pos, item):
238 self.p.index[pos] = item
238 self.p.index[pos] = item
239 def __delitem__(self, pos):
239 def __delitem__(self, pos):
240 del self.p.index[pos]
240 del self.p.index[pos]
241 def append(self, e):
241 def append(self, e):
242 self.p.index.append(e)
242 self.p.index.append(e)
243
243
244 class lazymap(object):
244 class lazymap(object):
245 """a lazy version of the node map"""
245 """a lazy version of the node map"""
246 def __init__(self, parser):
246 def __init__(self, parser):
247 self.p = parser
247 self.p = parser
248 def load(self, key):
248 def load(self, key):
249 n = self.p.findnode(key)
249 n = self.p.findnode(key)
250 if n == None:
250 if n == None:
251 raise KeyError(key)
251 raise KeyError(key)
252 def __contains__(self, key):
252 def __contains__(self, key):
253 if key in self.p.map:
253 if key in self.p.map:
254 return True
254 return True
255 self.p.loadmap()
255 self.p.loadmap()
256 return key in self.p.map
256 return key in self.p.map
257 def __iter__(self):
257 def __iter__(self):
258 yield nullid
258 yield nullid
259 for i in xrange(self.p.l):
259 for i in xrange(self.p.l):
260 ret = self.p.index[i]
260 ret = self.p.index[i]
261 if not ret:
261 if not ret:
262 self.p.loadindex(i)
262 self.p.loadindex(i)
263 ret = self.p.index[i]
263 ret = self.p.index[i]
264 if isinstance(ret, str):
264 if isinstance(ret, str):
265 ret = struct.unpack(self.p.indexformat, ret)
265 ret = struct.unpack(self.p.indexformat, ret)
266 yield ret[-1]
266 yield ret[-1]
267 def __getitem__(self, key):
267 def __getitem__(self, key):
268 try:
268 try:
269 return self.p.map[key]
269 return self.p.map[key]
270 except KeyError:
270 except KeyError:
271 try:
271 try:
272 self.load(key)
272 self.load(key)
273 return self.p.map[key]
273 return self.p.map[key]
274 except KeyError:
274 except KeyError:
275 raise KeyError("node " + hex(key))
275 raise KeyError("node " + hex(key))
276 def __setitem__(self, key, val):
276 def __setitem__(self, key, val):
277 self.p.map[key] = val
277 self.p.map[key] = val
278 def __delitem__(self, key):
278 def __delitem__(self, key):
279 del self.p.map[key]
279 del self.p.map[key]
280
280
281 class RevlogError(Exception): pass
281 class RevlogError(Exception): pass
282 class LookupError(RevlogError): pass
282 class LookupError(RevlogError): pass
283
283
284 def getoffset(q):
285 if q & 0xFFFF:
286 raise RevlogError(_('incompatible revision flag %x') % q)
287 return int(q >> 16)
288
289 def gettype(q):
290 return int(q & 0xFFFF)
291
292 def offset_type(offset, type):
293 return long(long(offset) << 16 | type)
294
284 class revlog(object):
295 class revlog(object):
285 """
296 """
286 the underlying revision storage object
297 the underlying revision storage object
287
298
288 A revlog consists of two parts, an index and the revision data.
299 A revlog consists of two parts, an index and the revision data.
289
300
290 The index is a file with a fixed record size containing
301 The index is a file with a fixed record size containing
291 information on each revision, includings its nodeid (hash), the
302 information on each revision, includings its nodeid (hash), the
292 nodeids of its parents, the position and offset of its data within
303 nodeids of its parents, the position and offset of its data within
293 the data file, and the revision it's based on. Finally, each entry
304 the data file, and the revision it's based on. Finally, each entry
294 contains a linkrev entry that can serve as a pointer to external
305 contains a linkrev entry that can serve as a pointer to external
295 data.
306 data.
296
307
297 The revision data itself is a linear collection of data chunks.
308 The revision data itself is a linear collection of data chunks.
298 Each chunk represents a revision and is usually represented as a
309 Each chunk represents a revision and is usually represented as a
299 delta against the previous chunk. To bound lookup time, runs of
310 delta against the previous chunk. To bound lookup time, runs of
300 deltas are limited to about 2 times the length of the original
311 deltas are limited to about 2 times the length of the original
301 version data. This makes retrieval of a version proportional to
312 version data. This makes retrieval of a version proportional to
302 its size, or O(1) relative to the number of revisions.
313 its size, or O(1) relative to the number of revisions.
303
314
304 Both pieces of the revlog are written to in an append-only
315 Both pieces of the revlog are written to in an append-only
305 fashion, which means we never need to rewrite a file to insert or
316 fashion, which means we never need to rewrite a file to insert or
306 remove data, and can use some simple techniques to avoid the need
317 remove data, and can use some simple techniques to avoid the need
307 for locking while reading.
318 for locking while reading.
308 """
319 """
309 def __init__(self, opener, indexfile):
320 def __init__(self, opener, indexfile):
310 """
321 """
311 create a revlog object
322 create a revlog object
312
323
313 opener is a function that abstracts the file opening operation
324 opener is a function that abstracts the file opening operation
314 and can be used to implement COW semantics or the like.
325 and can be used to implement COW semantics or the like.
315 """
326 """
316 self.indexfile = indexfile
327 self.indexfile = indexfile
317 self.datafile = indexfile[:-2] + ".d"
328 self.datafile = indexfile[:-2] + ".d"
318 self.opener = opener
329 self.opener = opener
319
330
320 self.indexstat = None
331 self.indexstat = None
321 self.cache = None
332 self.cache = None
322 self.chunkcache = None
333 self.chunkcache = None
323 self.defversion = REVLOG_DEFAULT_VERSION
334 self.defversion = REVLOG_DEFAULT_VERSION
324 if hasattr(opener, "defversion"):
335 if hasattr(opener, "defversion"):
325 self.defversion = opener.defversion
336 self.defversion = opener.defversion
326 if self.defversion & REVLOGNG:
337 if self.defversion & REVLOGNG:
327 self.defversion |= REVLOGNGINLINEDATA
338 self.defversion |= REVLOGNGINLINEDATA
328 self.load()
339 self.load()
329
340
330 def load(self):
341 def load(self):
331 v = self.defversion
342 v = self.defversion
332 try:
343 try:
333 f = self.opener(self.indexfile)
344 f = self.opener(self.indexfile)
334 i = f.read(4)
345 i = f.read(4)
335 f.seek(0)
346 f.seek(0)
347 if len(i) > 0:
348 v = struct.unpack(versionformat, i)[0]
336 except IOError, inst:
349 except IOError, inst:
337 if inst.errno != errno.ENOENT:
350 if inst.errno != errno.ENOENT:
338 raise
351 raise
339 i = ""
352 i = ""
340 else:
353 else:
341 try:
354 try:
342 st = util.fstat(f)
355 st = util.fstat(f)
343 except AttributeError, inst:
356 except AttributeError, inst:
344 st = None
357 st = None
345 else:
358 else:
346 oldst = self.indexstat
359 oldst = self.indexstat
347 if (oldst and st.st_dev == oldst.st_dev
360 if (oldst and st.st_dev == oldst.st_dev
348 and st.st_ino == oldst.st_ino
361 and st.st_ino == oldst.st_ino
349 and st.st_mtime == oldst.st_mtime
362 and st.st_mtime == oldst.st_mtime
350 and st.st_ctime == oldst.st_ctime
363 and st.st_ctime == oldst.st_ctime
351 and st.st_size == oldst.st_size):
364 and st.st_size == oldst.st_size):
352 return
365 return
353 self.indexstat = st
366 self.indexstat = st
354 if len(i) > 0:
355 v = struct.unpack(versionformat, i)[0]
356 flags = v & ~0xFFFF
367 flags = v & ~0xFFFF
357 fmt = v & 0xFFFF
368 fmt = v & 0xFFFF
358 if fmt == REVLOGV0:
369 if fmt == REVLOGV0:
359 if flags:
370 if flags:
360 raise RevlogError(_("index %s unknown flags %#04x for format v0")
371 raise RevlogError(_("index %s unknown flags %#04x for format v0")
361 % (self.indexfile, flags >> 16))
372 % (self.indexfile, flags >> 16))
362 elif fmt == REVLOGNG:
373 elif fmt == REVLOGNG:
363 if flags & ~REVLOGNGINLINEDATA:
374 if flags & ~REVLOGNGINLINEDATA:
364 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
375 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
365 % (self.indexfile, flags >> 16))
376 % (self.indexfile, flags >> 16))
366 else:
377 else:
367 raise RevlogError(_("index %s unknown format %d")
378 raise RevlogError(_("index %s unknown format %d")
368 % (self.indexfile, fmt))
379 % (self.indexfile, fmt))
369 self.version = v
380 self.version = v
370 if v == REVLOGV0:
381 if v == REVLOGV0:
371 self.indexformat = indexformatv0
382 self.indexformat = indexformatv0
372 shaoffset = v0shaoffset
383 shaoffset = v0shaoffset
373 else:
384 else:
374 self.indexformat = indexformatng
385 self.indexformat = indexformatng
375 shaoffset = ngshaoffset
386 shaoffset = ngshaoffset
376
387
377 if i:
388 if i:
378 if (lazyparser.safe_to_use and not self.inlinedata() and
389 if (lazyparser.safe_to_use and not self.inlinedata() and
379 st and st.st_size > 10000):
390 st and st.st_size > 10000):
380 # big index, let's parse it on demand
391 # big index, let's parse it on demand
381 parser = lazyparser(f, st.st_size, self.indexformat, shaoffset)
392 parser = lazyparser(f, st.st_size, self.indexformat, shaoffset)
382 self.index = lazyindex(parser)
393 self.index = lazyindex(parser)
383 self.nodemap = lazymap(parser)
394 self.nodemap = lazymap(parser)
384 else:
395 else:
385 self.parseindex(f, st)
396 self.parseindex(f, st)
386 if self.version != REVLOGV0:
397 if self.version != REVLOGV0:
387 e = list(self.index[0])
398 e = list(self.index[0])
388 type = self.ngtype(e[0])
399 type = gettype(e[0])
389 e[0] = self.offset_type(0, type)
400 e[0] = offset_type(0, type)
390 self.index[0] = e
401 self.index[0] = e
391 else:
402 else:
392 self.nodemap = {nullid: nullrev}
403 self.nodemap = {nullid: nullrev}
393 self.index = []
404 self.index = []
394
405
395
406
396 def parseindex(self, fp, st):
407 def parseindex(self, fp, st):
397 s = struct.calcsize(self.indexformat)
408 s = struct.calcsize(self.indexformat)
398 self.index = []
409 self.index = []
399 self.nodemap = {nullid: nullrev}
410 self.nodemap = {nullid: nullrev}
400 inline = self.inlinedata()
411 inline = self.inlinedata()
401 n = 0
412 n = 0
402 leftover = None
413 leftover = None
403 while True:
414 while True:
404 if st:
415 if st:
405 data = fp.read(65536)
416 data = fp.read(65536)
406 else:
417 else:
407 # hack for httprangereader, it doesn't do partial reads well
418 # hack for httprangereader, it doesn't do partial reads well
408 data = fp.read()
419 data = fp.read()
409 if not data:
420 if not data:
410 break
421 break
411 if n == 0 and self.inlinedata():
422 if n == 0 and self.inlinedata():
412 # cache the first chunk
423 # cache the first chunk
413 self.chunkcache = (0, data)
424 self.chunkcache = (0, data)
414 if leftover:
425 if leftover:
415 data = leftover + data
426 data = leftover + data
416 leftover = None
427 leftover = None
417 off = 0
428 off = 0
418 l = len(data)
429 l = len(data)
419 while off < l:
430 while off < l:
420 if l - off < s:
431 if l - off < s:
421 leftover = data[off:]
432 leftover = data[off:]
422 break
433 break
423 cur = data[off:off + s]
434 cur = data[off:off + s]
424 off += s
435 off += s
425 e = struct.unpack(self.indexformat, cur)
436 e = struct.unpack(self.indexformat, cur)
426 self.index.append(e)
437 self.index.append(e)
427 self.nodemap[e[-1]] = n
438 self.nodemap[e[-1]] = n
428 n += 1
439 n += 1
429 if inline:
440 if inline:
430 if e[1] < 0:
441 if e[1] < 0:
431 break
442 break
432 off += e[1]
443 off += e[1]
433 if off > l:
444 if off > l:
434 # some things don't seek well, just read it
445 # some things don't seek well, just read it
435 fp.read(off - l)
446 fp.read(off - l)
436 break
447 break
437 if not st:
448 if not st:
438 break
449 break
439
450
440
451
441 def ngoffset(self, q):
442 if q & 0xFFFF:
443 raise RevlogError(_('%s: incompatible revision flag %x') %
444 (self.indexfile, q))
445 return long(q >> 16)
446
447 def ngtype(self, q):
448 return int(q & 0xFFFF)
449
450 def offset_type(self, offset, type):
451 return long(long(offset) << 16 | type)
452
453 def loadindex(self, start, end):
452 def loadindex(self, start, end):
454 """load a block of indexes all at once from the lazy parser"""
453 """load a block of indexes all at once from the lazy parser"""
455 if isinstance(self.index, lazyindex):
454 if isinstance(self.index, lazyindex):
456 self.index.p.loadindex(start, end)
455 self.index.p.loadindex(start, end)
457
456
458 def loadindexmap(self):
457 def loadindexmap(self):
459 """loads both the map and the index from the lazy parser"""
458 """loads both the map and the index from the lazy parser"""
460 if isinstance(self.index, lazyindex):
459 if isinstance(self.index, lazyindex):
461 p = self.index.p
460 p = self.index.p
462 p.loadindex()
461 p.loadindex()
463 self.nodemap = p.map
462 self.nodemap = p.map
464
463
465 def loadmap(self):
464 def loadmap(self):
466 """loads the map from the lazy parser"""
465 """loads the map from the lazy parser"""
467 if isinstance(self.nodemap, lazymap):
466 if isinstance(self.nodemap, lazymap):
468 self.nodemap.p.loadmap()
467 self.nodemap.p.loadmap()
469 self.nodemap = self.nodemap.p.map
468 self.nodemap = self.nodemap.p.map
470
469
471 def inlinedata(self): return self.version & REVLOGNGINLINEDATA
470 def inlinedata(self): return self.version & REVLOGNGINLINEDATA
472 def tip(self): return self.node(len(self.index) - 1)
471 def tip(self): return self.node(len(self.index) - 1)
473 def count(self): return len(self.index)
472 def count(self): return len(self.index)
474 def node(self, rev):
473 def node(self, rev):
475 return rev == nullrev and nullid or self.index[rev][-1]
474 return rev == nullrev and nullid or self.index[rev][-1]
476 def rev(self, node):
475 def rev(self, node):
477 try:
476 try:
478 return self.nodemap[node]
477 return self.nodemap[node]
479 except KeyError:
478 except KeyError:
480 raise LookupError(_('%s: no node %s') % (self.indexfile, hex(node)))
479 raise LookupError(_('%s: no node %s') % (self.indexfile, hex(node)))
481 def linkrev(self, node):
480 def linkrev(self, node):
482 return (node == nullid) and nullrev or self.index[self.rev(node)][-4]
481 return (node == nullid) and nullrev or self.index[self.rev(node)][-4]
483 def parents(self, node):
482 def parents(self, node):
484 if node == nullid: return (nullid, nullid)
483 if node == nullid: return (nullid, nullid)
485 r = self.rev(node)
484 r = self.rev(node)
486 d = self.index[r][-3:-1]
485 d = self.index[r][-3:-1]
487 if self.version == REVLOGV0:
486 if self.version == REVLOGV0:
488 return d
487 return d
489 return (self.node(d[0]), self.node(d[1]))
488 return (self.node(d[0]), self.node(d[1]))
490 def parentrevs(self, rev):
489 def parentrevs(self, rev):
491 if rev == nullrev:
490 if rev == nullrev:
492 return (nullrev, nullrev)
491 return (nullrev, nullrev)
493 d = self.index[rev][-3:-1]
492 d = self.index[rev][-3:-1]
494 if self.version == REVLOGV0:
493 if self.version == REVLOGV0:
495 return (self.rev(d[0]), self.rev(d[1]))
494 return (self.rev(d[0]), self.rev(d[1]))
496 return d
495 return d
497 def start(self, rev):
496 def start(self, rev):
498 if rev == nullrev:
497 if rev == nullrev:
499 return 0
498 return 0
500 if self.version != REVLOGV0:
499 if self.version != REVLOGV0:
501 return self.ngoffset(self.index[rev][0])
500 return getoffset(self.index[rev][0])
502 return self.index[rev][0]
501 return self.index[rev][0]
503
502
504 def end(self, rev): return self.start(rev) + self.length(rev)
503 def end(self, rev): return self.start(rev) + self.length(rev)
505
504
506 def size(self, rev):
505 def size(self, rev):
507 """return the length of the uncompressed text for a given revision"""
506 """return the length of the uncompressed text for a given revision"""
508 if rev == nullrev:
507 if rev == nullrev:
509 return 0
508 return 0
510 l = -1
509 l = -1
511 if self.version != REVLOGV0:
510 if self.version != REVLOGV0:
512 l = self.index[rev][2]
511 l = self.index[rev][2]
513 if l >= 0:
512 if l >= 0:
514 return l
513 return l
515
514
516 t = self.revision(self.node(rev))
515 t = self.revision(self.node(rev))
517 return len(t)
516 return len(t)
518
517
519 # alternate implementation, The advantage to this code is it
518 # alternate implementation, The advantage to this code is it
520 # will be faster for a single revision. But, the results are not
519 # will be faster for a single revision. But, the results are not
521 # cached, so finding the size of every revision will be slower.
520 # cached, so finding the size of every revision will be slower.
522 """
521 """
523 if self.cache and self.cache[1] == rev:
522 if self.cache and self.cache[1] == rev:
524 return len(self.cache[2])
523 return len(self.cache[2])
525
524
526 base = self.base(rev)
525 base = self.base(rev)
527 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
526 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
528 base = self.cache[1]
527 base = self.cache[1]
529 text = self.cache[2]
528 text = self.cache[2]
530 else:
529 else:
531 text = self.revision(self.node(base))
530 text = self.revision(self.node(base))
532
531
533 l = len(text)
532 l = len(text)
534 for x in xrange(base + 1, rev + 1):
533 for x in xrange(base + 1, rev + 1):
535 l = mdiff.patchedsize(l, self.chunk(x))
534 l = mdiff.patchedsize(l, self.chunk(x))
536 return l
535 return l
537 """
536 """
538
537
539 def length(self, rev):
538 def length(self, rev):
540 if rev == nullrev:
539 if rev == nullrev:
541 return 0
540 return 0
542 else:
541 else:
543 return self.index[rev][1]
542 return self.index[rev][1]
544 def base(self, rev):
543 def base(self, rev):
545 if (rev == nullrev):
544 if (rev == nullrev):
546 return nullrev
545 return nullrev
547 else:
546 else:
548 return self.index[rev][-5]
547 return self.index[rev][-5]
549
548
550 def reachable(self, node, stop=None):
549 def reachable(self, node, stop=None):
551 """return a hash of all nodes ancestral to a given node, including
550 """return a hash of all nodes ancestral to a given node, including
552 the node itself, stopping when stop is matched"""
551 the node itself, stopping when stop is matched"""
553 reachable = {}
552 reachable = {}
554 visit = [node]
553 visit = [node]
555 reachable[node] = 1
554 reachable[node] = 1
556 if stop:
555 if stop:
557 stopn = self.rev(stop)
556 stopn = self.rev(stop)
558 else:
557 else:
559 stopn = 0
558 stopn = 0
560 while visit:
559 while visit:
561 n = visit.pop(0)
560 n = visit.pop(0)
562 if n == stop:
561 if n == stop:
563 continue
562 continue
564 if n == nullid:
563 if n == nullid:
565 continue
564 continue
566 for p in self.parents(n):
565 for p in self.parents(n):
567 if self.rev(p) < stopn:
566 if self.rev(p) < stopn:
568 continue
567 continue
569 if p not in reachable:
568 if p not in reachable:
570 reachable[p] = 1
569 reachable[p] = 1
571 visit.append(p)
570 visit.append(p)
572 return reachable
571 return reachable
573
572
574 def nodesbetween(self, roots=None, heads=None):
573 def nodesbetween(self, roots=None, heads=None):
575 """Return a tuple containing three elements. Elements 1 and 2 contain
574 """Return a tuple containing three elements. Elements 1 and 2 contain
576 a final list bases and heads after all the unreachable ones have been
575 a final list bases and heads after all the unreachable ones have been
577 pruned. Element 0 contains a topologically sorted list of all
576 pruned. Element 0 contains a topologically sorted list of all
578
577
579 nodes that satisfy these constraints:
578 nodes that satisfy these constraints:
580 1. All nodes must be descended from a node in roots (the nodes on
579 1. All nodes must be descended from a node in roots (the nodes on
581 roots are considered descended from themselves).
580 roots are considered descended from themselves).
582 2. All nodes must also be ancestors of a node in heads (the nodes in
581 2. All nodes must also be ancestors of a node in heads (the nodes in
583 heads are considered to be their own ancestors).
582 heads are considered to be their own ancestors).
584
583
585 If roots is unspecified, nullid is assumed as the only root.
584 If roots is unspecified, nullid is assumed as the only root.
586 If heads is unspecified, it is taken to be the output of the
585 If heads is unspecified, it is taken to be the output of the
587 heads method (i.e. a list of all nodes in the repository that
586 heads method (i.e. a list of all nodes in the repository that
588 have no children)."""
587 have no children)."""
589 nonodes = ([], [], [])
588 nonodes = ([], [], [])
590 if roots is not None:
589 if roots is not None:
591 roots = list(roots)
590 roots = list(roots)
592 if not roots:
591 if not roots:
593 return nonodes
592 return nonodes
594 lowestrev = min([self.rev(n) for n in roots])
593 lowestrev = min([self.rev(n) for n in roots])
595 else:
594 else:
596 roots = [nullid] # Everybody's a descendent of nullid
595 roots = [nullid] # Everybody's a descendent of nullid
597 lowestrev = nullrev
596 lowestrev = nullrev
598 if (lowestrev == nullrev) and (heads is None):
597 if (lowestrev == nullrev) and (heads is None):
599 # We want _all_ the nodes!
598 # We want _all_ the nodes!
600 return ([self.node(r) for r in xrange(0, self.count())],
599 return ([self.node(r) for r in xrange(0, self.count())],
601 [nullid], list(self.heads()))
600 [nullid], list(self.heads()))
602 if heads is None:
601 if heads is None:
603 # All nodes are ancestors, so the latest ancestor is the last
602 # All nodes are ancestors, so the latest ancestor is the last
604 # node.
603 # node.
605 highestrev = self.count() - 1
604 highestrev = self.count() - 1
606 # Set ancestors to None to signal that every node is an ancestor.
605 # Set ancestors to None to signal that every node is an ancestor.
607 ancestors = None
606 ancestors = None
608 # Set heads to an empty dictionary for later discovery of heads
607 # Set heads to an empty dictionary for later discovery of heads
609 heads = {}
608 heads = {}
610 else:
609 else:
611 heads = list(heads)
610 heads = list(heads)
612 if not heads:
611 if not heads:
613 return nonodes
612 return nonodes
614 ancestors = {}
613 ancestors = {}
615 # Turn heads into a dictionary so we can remove 'fake' heads.
614 # Turn heads into a dictionary so we can remove 'fake' heads.
616 # Also, later we will be using it to filter out the heads we can't
615 # Also, later we will be using it to filter out the heads we can't
617 # find from roots.
616 # find from roots.
618 heads = dict.fromkeys(heads, 0)
617 heads = dict.fromkeys(heads, 0)
619 # Start at the top and keep marking parents until we're done.
618 # Start at the top and keep marking parents until we're done.
620 nodestotag = heads.keys()
619 nodestotag = heads.keys()
621 # Remember where the top was so we can use it as a limit later.
620 # Remember where the top was so we can use it as a limit later.
622 highestrev = max([self.rev(n) for n in nodestotag])
621 highestrev = max([self.rev(n) for n in nodestotag])
623 while nodestotag:
622 while nodestotag:
624 # grab a node to tag
623 # grab a node to tag
625 n = nodestotag.pop()
624 n = nodestotag.pop()
626 # Never tag nullid
625 # Never tag nullid
627 if n == nullid:
626 if n == nullid:
628 continue
627 continue
629 # A node's revision number represents its place in a
628 # A node's revision number represents its place in a
630 # topologically sorted list of nodes.
629 # topologically sorted list of nodes.
631 r = self.rev(n)
630 r = self.rev(n)
632 if r >= lowestrev:
631 if r >= lowestrev:
633 if n not in ancestors:
632 if n not in ancestors:
634 # If we are possibly a descendent of one of the roots
633 # If we are possibly a descendent of one of the roots
635 # and we haven't already been marked as an ancestor
634 # and we haven't already been marked as an ancestor
636 ancestors[n] = 1 # Mark as ancestor
635 ancestors[n] = 1 # Mark as ancestor
637 # Add non-nullid parents to list of nodes to tag.
636 # Add non-nullid parents to list of nodes to tag.
638 nodestotag.extend([p for p in self.parents(n) if
637 nodestotag.extend([p for p in self.parents(n) if
639 p != nullid])
638 p != nullid])
640 elif n in heads: # We've seen it before, is it a fake head?
639 elif n in heads: # We've seen it before, is it a fake head?
641 # So it is, real heads should not be the ancestors of
640 # So it is, real heads should not be the ancestors of
642 # any other heads.
641 # any other heads.
643 heads.pop(n)
642 heads.pop(n)
644 if not ancestors:
643 if not ancestors:
645 return nonodes
644 return nonodes
646 # Now that we have our set of ancestors, we want to remove any
645 # Now that we have our set of ancestors, we want to remove any
647 # roots that are not ancestors.
646 # roots that are not ancestors.
648
647
649 # If one of the roots was nullid, everything is included anyway.
648 # If one of the roots was nullid, everything is included anyway.
650 if lowestrev > nullrev:
649 if lowestrev > nullrev:
651 # But, since we weren't, let's recompute the lowest rev to not
650 # But, since we weren't, let's recompute the lowest rev to not
652 # include roots that aren't ancestors.
651 # include roots that aren't ancestors.
653
652
654 # Filter out roots that aren't ancestors of heads
653 # Filter out roots that aren't ancestors of heads
655 roots = [n for n in roots if n in ancestors]
654 roots = [n for n in roots if n in ancestors]
656 # Recompute the lowest revision
655 # Recompute the lowest revision
657 if roots:
656 if roots:
658 lowestrev = min([self.rev(n) for n in roots])
657 lowestrev = min([self.rev(n) for n in roots])
659 else:
658 else:
660 # No more roots? Return empty list
659 # No more roots? Return empty list
661 return nonodes
660 return nonodes
662 else:
661 else:
663 # We are descending from nullid, and don't need to care about
662 # We are descending from nullid, and don't need to care about
664 # any other roots.
663 # any other roots.
665 lowestrev = nullrev
664 lowestrev = nullrev
666 roots = [nullid]
665 roots = [nullid]
667 # Transform our roots list into a 'set' (i.e. a dictionary where the
666 # Transform our roots list into a 'set' (i.e. a dictionary where the
668 # values don't matter.
667 # values don't matter.
669 descendents = dict.fromkeys(roots, 1)
668 descendents = dict.fromkeys(roots, 1)
670 # Also, keep the original roots so we can filter out roots that aren't
669 # Also, keep the original roots so we can filter out roots that aren't
671 # 'real' roots (i.e. are descended from other roots).
670 # 'real' roots (i.e. are descended from other roots).
672 roots = descendents.copy()
671 roots = descendents.copy()
673 # Our topologically sorted list of output nodes.
672 # Our topologically sorted list of output nodes.
674 orderedout = []
673 orderedout = []
675 # Don't start at nullid since we don't want nullid in our output list,
674 # Don't start at nullid since we don't want nullid in our output list,
676 # and if nullid shows up in descedents, empty parents will look like
675 # and if nullid shows up in descedents, empty parents will look like
677 # they're descendents.
676 # they're descendents.
678 for r in xrange(max(lowestrev, 0), highestrev + 1):
677 for r in xrange(max(lowestrev, 0), highestrev + 1):
679 n = self.node(r)
678 n = self.node(r)
680 isdescendent = False
679 isdescendent = False
681 if lowestrev == nullrev: # Everybody is a descendent of nullid
680 if lowestrev == nullrev: # Everybody is a descendent of nullid
682 isdescendent = True
681 isdescendent = True
683 elif n in descendents:
682 elif n in descendents:
684 # n is already a descendent
683 # n is already a descendent
685 isdescendent = True
684 isdescendent = True
686 # This check only needs to be done here because all the roots
685 # This check only needs to be done here because all the roots
687 # will start being marked is descendents before the loop.
686 # will start being marked is descendents before the loop.
688 if n in roots:
687 if n in roots:
689 # If n was a root, check if it's a 'real' root.
688 # If n was a root, check if it's a 'real' root.
690 p = tuple(self.parents(n))
689 p = tuple(self.parents(n))
691 # If any of its parents are descendents, it's not a root.
690 # If any of its parents are descendents, it's not a root.
692 if (p[0] in descendents) or (p[1] in descendents):
691 if (p[0] in descendents) or (p[1] in descendents):
693 roots.pop(n)
692 roots.pop(n)
694 else:
693 else:
695 p = tuple(self.parents(n))
694 p = tuple(self.parents(n))
696 # A node is a descendent if either of its parents are
695 # A node is a descendent if either of its parents are
697 # descendents. (We seeded the dependents list with the roots
696 # descendents. (We seeded the dependents list with the roots
698 # up there, remember?)
697 # up there, remember?)
699 if (p[0] in descendents) or (p[1] in descendents):
698 if (p[0] in descendents) or (p[1] in descendents):
700 descendents[n] = 1
699 descendents[n] = 1
701 isdescendent = True
700 isdescendent = True
702 if isdescendent and ((ancestors is None) or (n in ancestors)):
701 if isdescendent and ((ancestors is None) or (n in ancestors)):
703 # Only include nodes that are both descendents and ancestors.
702 # Only include nodes that are both descendents and ancestors.
704 orderedout.append(n)
703 orderedout.append(n)
705 if (ancestors is not None) and (n in heads):
704 if (ancestors is not None) and (n in heads):
706 # We're trying to figure out which heads are reachable
705 # We're trying to figure out which heads are reachable
707 # from roots.
706 # from roots.
708 # Mark this head as having been reached
707 # Mark this head as having been reached
709 heads[n] = 1
708 heads[n] = 1
710 elif ancestors is None:
709 elif ancestors is None:
711 # Otherwise, we're trying to discover the heads.
710 # Otherwise, we're trying to discover the heads.
712 # Assume this is a head because if it isn't, the next step
711 # Assume this is a head because if it isn't, the next step
713 # will eventually remove it.
712 # will eventually remove it.
714 heads[n] = 1
713 heads[n] = 1
715 # But, obviously its parents aren't.
714 # But, obviously its parents aren't.
716 for p in self.parents(n):
715 for p in self.parents(n):
717 heads.pop(p, None)
716 heads.pop(p, None)
718 heads = [n for n in heads.iterkeys() if heads[n] != 0]
717 heads = [n for n in heads.iterkeys() if heads[n] != 0]
719 roots = roots.keys()
718 roots = roots.keys()
720 assert orderedout
719 assert orderedout
721 assert roots
720 assert roots
722 assert heads
721 assert heads
723 return (orderedout, roots, heads)
722 return (orderedout, roots, heads)
724
723
725 def heads(self, start=None, stop=None):
724 def heads(self, start=None, stop=None):
726 """return the list of all nodes that have no children
725 """return the list of all nodes that have no children
727
726
728 if start is specified, only heads that are descendants of
727 if start is specified, only heads that are descendants of
729 start will be returned
728 start will be returned
730 if stop is specified, it will consider all the revs from stop
729 if stop is specified, it will consider all the revs from stop
731 as if they had no children
730 as if they had no children
732 """
731 """
733 if start is None:
732 if start is None:
734 start = nullid
733 start = nullid
735 if stop is None:
734 if stop is None:
736 stop = []
735 stop = []
737 stoprevs = dict.fromkeys([self.rev(n) for n in stop])
736 stoprevs = dict.fromkeys([self.rev(n) for n in stop])
738 startrev = self.rev(start)
737 startrev = self.rev(start)
739 reachable = {startrev: 1}
738 reachable = {startrev: 1}
740 heads = {startrev: 1}
739 heads = {startrev: 1}
741
740
742 parentrevs = self.parentrevs
741 parentrevs = self.parentrevs
743 for r in xrange(startrev + 1, self.count()):
742 for r in xrange(startrev + 1, self.count()):
744 for p in parentrevs(r):
743 for p in parentrevs(r):
745 if p in reachable:
744 if p in reachable:
746 if r not in stoprevs:
745 if r not in stoprevs:
747 reachable[r] = 1
746 reachable[r] = 1
748 heads[r] = 1
747 heads[r] = 1
749 if p in heads and p not in stoprevs:
748 if p in heads and p not in stoprevs:
750 del heads[p]
749 del heads[p]
751
750
752 return [self.node(r) for r in heads]
751 return [self.node(r) for r in heads]
753
752
754 def children(self, node):
753 def children(self, node):
755 """find the children of a given node"""
754 """find the children of a given node"""
756 c = []
755 c = []
757 p = self.rev(node)
756 p = self.rev(node)
758 for r in range(p + 1, self.count()):
757 for r in range(p + 1, self.count()):
759 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
758 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
760 if prevs:
759 if prevs:
761 for pr in prevs:
760 for pr in prevs:
762 if pr == p:
761 if pr == p:
763 c.append(self.node(r))
762 c.append(self.node(r))
764 elif p == nullrev:
763 elif p == nullrev:
765 c.append(self.node(r))
764 c.append(self.node(r))
766 return c
765 return c
767
766
768 def _match(self, id):
767 def _match(self, id):
769 if isinstance(id, (long, int)):
768 if isinstance(id, (long, int)):
770 # rev
769 # rev
771 return self.node(id)
770 return self.node(id)
772 if len(id) == 20:
771 if len(id) == 20:
773 # possibly a binary node
772 # possibly a binary node
774 # odds of a binary node being all hex in ASCII are 1 in 10**25
773 # odds of a binary node being all hex in ASCII are 1 in 10**25
775 try:
774 try:
776 node = id
775 node = id
777 r = self.rev(node) # quick search the index
776 r = self.rev(node) # quick search the index
778 return node
777 return node
779 except LookupError:
778 except LookupError:
780 pass # may be partial hex id
779 pass # may be partial hex id
781 try:
780 try:
782 # str(rev)
781 # str(rev)
783 rev = int(id)
782 rev = int(id)
784 if str(rev) != id: raise ValueError
783 if str(rev) != id: raise ValueError
785 if rev < 0: rev = self.count() + rev
784 if rev < 0: rev = self.count() + rev
786 if rev < 0 or rev >= self.count(): raise ValueError
785 if rev < 0 or rev >= self.count(): raise ValueError
787 return self.node(rev)
786 return self.node(rev)
788 except (ValueError, OverflowError):
787 except (ValueError, OverflowError):
789 pass
788 pass
790 if len(id) == 40:
789 if len(id) == 40:
791 try:
790 try:
792 # a full hex nodeid?
791 # a full hex nodeid?
793 node = bin(id)
792 node = bin(id)
794 r = self.rev(node)
793 r = self.rev(node)
795 return node
794 return node
796 except TypeError:
795 except TypeError:
797 pass
796 pass
798
797
799 def _partialmatch(self, id):
798 def _partialmatch(self, id):
800 if len(id) < 40:
799 if len(id) < 40:
801 try:
800 try:
802 # hex(node)[:...]
801 # hex(node)[:...]
803 bin_id = bin(id[:len(id) & ~1]) # grab an even number of digits
802 bin_id = bin(id[:len(id) & ~1]) # grab an even number of digits
804 node = None
803 node = None
805 for n in self.nodemap:
804 for n in self.nodemap:
806 if n.startswith(bin_id) and hex(n).startswith(id):
805 if n.startswith(bin_id) and hex(n).startswith(id):
807 if node is not None:
806 if node is not None:
808 raise LookupError(_("Ambiguous identifier"))
807 raise LookupError(_("Ambiguous identifier"))
809 node = n
808 node = n
810 if node is not None:
809 if node is not None:
811 return node
810 return node
812 except TypeError:
811 except TypeError:
813 pass
812 pass
814
813
815 def lookup(self, id):
814 def lookup(self, id):
816 """locate a node based on:
815 """locate a node based on:
817 - revision number or str(revision number)
816 - revision number or str(revision number)
818 - nodeid or subset of hex nodeid
817 - nodeid or subset of hex nodeid
819 """
818 """
820
819
821 n = self._match(id)
820 n = self._match(id)
822 if n is not None:
821 if n is not None:
823 return n
822 return n
824 n = self._partialmatch(id)
823 n = self._partialmatch(id)
825 if n:
824 if n:
826 return n
825 return n
827
826
828 raise LookupError(_("No match found"))
827 raise LookupError(_("No match found"))
829
828
830 def cmp(self, node, text):
829 def cmp(self, node, text):
831 """compare text with a given file revision"""
830 """compare text with a given file revision"""
832 p1, p2 = self.parents(node)
831 p1, p2 = self.parents(node)
833 return hash(text, p1, p2) != node
832 return hash(text, p1, p2) != node
834
833
835 def makenode(self, node, text):
834 def makenode(self, node, text):
836 """calculate a file nodeid for text, descended or possibly
835 """calculate a file nodeid for text, descended or possibly
837 unchanged from node"""
836 unchanged from node"""
838
837
839 if self.cmp(node, text):
838 if self.cmp(node, text):
840 return hash(text, node, nullid)
839 return hash(text, node, nullid)
841 return node
840 return node
842
841
843 def diff(self, a, b):
842 def diff(self, a, b):
844 """return a delta between two revisions"""
843 """return a delta between two revisions"""
845 return mdiff.textdiff(a, b)
844 return mdiff.textdiff(a, b)
846
845
847 def patches(self, t, pl):
846 def patches(self, t, pl):
848 """apply a list of patches to a string"""
847 """apply a list of patches to a string"""
849 return mdiff.patches(t, pl)
848 return mdiff.patches(t, pl)
850
849
851 def chunk(self, rev, df=None, cachelen=4096):
850 def chunk(self, rev, df=None, cachelen=4096):
852 start, length = self.start(rev), self.length(rev)
851 start, length = self.start(rev), self.length(rev)
853 inline = self.inlinedata()
852 inline = self.inlinedata()
854 if inline:
853 if inline:
855 start += (rev + 1) * struct.calcsize(self.indexformat)
854 start += (rev + 1) * struct.calcsize(self.indexformat)
856 end = start + length
855 end = start + length
857 def loadcache(df):
856 def loadcache(df):
858 cache_length = max(cachelen, length) # 4k
857 cache_length = max(cachelen, length) # 4k
859 if not df:
858 if not df:
860 if inline:
859 if inline:
861 df = self.opener(self.indexfile)
860 df = self.opener(self.indexfile)
862 else:
861 else:
863 df = self.opener(self.datafile)
862 df = self.opener(self.datafile)
864 df.seek(start)
863 df.seek(start)
865 self.chunkcache = (start, df.read(cache_length))
864 self.chunkcache = (start, df.read(cache_length))
866
865
867 if not self.chunkcache:
866 if not self.chunkcache:
868 loadcache(df)
867 loadcache(df)
869
868
870 cache_start = self.chunkcache[0]
869 cache_start = self.chunkcache[0]
871 cache_end = cache_start + len(self.chunkcache[1])
870 cache_end = cache_start + len(self.chunkcache[1])
872 if start >= cache_start and end <= cache_end:
871 if start >= cache_start and end <= cache_end:
873 # it is cached
872 # it is cached
874 offset = start - cache_start
873 offset = start - cache_start
875 else:
874 else:
876 loadcache(df)
875 loadcache(df)
877 offset = 0
876 offset = 0
878
877
879 #def checkchunk():
878 #def checkchunk():
880 # df = self.opener(self.datafile)
879 # df = self.opener(self.datafile)
881 # df.seek(start)
880 # df.seek(start)
882 # return df.read(length)
881 # return df.read(length)
883 #assert s == checkchunk()
882 #assert s == checkchunk()
884 return decompress(self.chunkcache[1][offset:offset + length])
883 return decompress(self.chunkcache[1][offset:offset + length])
885
884
886 def delta(self, node):
885 def delta(self, node):
887 """return or calculate a delta between a node and its predecessor"""
886 """return or calculate a delta between a node and its predecessor"""
888 r = self.rev(node)
887 r = self.rev(node)
889 return self.revdiff(r - 1, r)
888 return self.revdiff(r - 1, r)
890
889
891 def revdiff(self, rev1, rev2):
890 def revdiff(self, rev1, rev2):
892 """return or calculate a delta between two revisions"""
891 """return or calculate a delta between two revisions"""
893 b1 = self.base(rev1)
892 b1 = self.base(rev1)
894 b2 = self.base(rev2)
893 b2 = self.base(rev2)
895 if b1 == b2 and rev1 + 1 == rev2:
894 if b1 == b2 and rev1 + 1 == rev2:
896 return self.chunk(rev2)
895 return self.chunk(rev2)
897 else:
896 else:
898 return self.diff(self.revision(self.node(rev1)),
897 return self.diff(self.revision(self.node(rev1)),
899 self.revision(self.node(rev2)))
898 self.revision(self.node(rev2)))
900
899
901 def revision(self, node):
900 def revision(self, node):
902 """return an uncompressed revision of a given"""
901 """return an uncompressed revision of a given"""
903 if node == nullid: return ""
902 if node == nullid: return ""
904 if self.cache and self.cache[0] == node: return self.cache[2]
903 if self.cache and self.cache[0] == node: return self.cache[2]
905
904
906 # look up what we need to read
905 # look up what we need to read
907 text = None
906 text = None
908 rev = self.rev(node)
907 rev = self.rev(node)
909 base = self.base(rev)
908 base = self.base(rev)
910
909
911 if self.inlinedata():
910 if self.inlinedata():
912 # we probably have the whole chunk cached
911 # we probably have the whole chunk cached
913 df = None
912 df = None
914 else:
913 else:
915 df = self.opener(self.datafile)
914 df = self.opener(self.datafile)
916
915
917 # do we have useful data cached?
916 # do we have useful data cached?
918 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
917 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
919 base = self.cache[1]
918 base = self.cache[1]
920 text = self.cache[2]
919 text = self.cache[2]
921 self.loadindex(base, rev + 1)
920 self.loadindex(base, rev + 1)
922 else:
921 else:
923 self.loadindex(base, rev + 1)
922 self.loadindex(base, rev + 1)
924 text = self.chunk(base, df=df)
923 text = self.chunk(base, df=df)
925
924
926 bins = []
925 bins = []
927 for r in xrange(base + 1, rev + 1):
926 for r in xrange(base + 1, rev + 1):
928 bins.append(self.chunk(r, df=df))
927 bins.append(self.chunk(r, df=df))
929
928
930 text = self.patches(text, bins)
929 text = self.patches(text, bins)
931
930
932 p1, p2 = self.parents(node)
931 p1, p2 = self.parents(node)
933 if node != hash(text, p1, p2):
932 if node != hash(text, p1, p2):
934 raise RevlogError(_("integrity check failed on %s:%d")
933 raise RevlogError(_("integrity check failed on %s:%d")
935 % (self.datafile, rev))
934 % (self.datafile, rev))
936
935
937 self.cache = (node, rev, text)
936 self.cache = (node, rev, text)
938 return text
937 return text
939
938
940 def checkinlinesize(self, tr, fp=None):
939 def checkinlinesize(self, tr, fp=None):
941 if not self.inlinedata():
940 if not self.inlinedata():
942 return
941 return
943 if not fp:
942 if not fp:
944 fp = self.opener(self.indexfile, 'r')
943 fp = self.opener(self.indexfile, 'r')
945 fp.seek(0, 2)
944 fp.seek(0, 2)
946 size = fp.tell()
945 size = fp.tell()
947 if size < 131072:
946 if size < 131072:
948 return
947 return
949 trinfo = tr.find(self.indexfile)
948 trinfo = tr.find(self.indexfile)
950 if trinfo == None:
949 if trinfo == None:
951 raise RevlogError(_("%s not found in the transaction")
950 raise RevlogError(_("%s not found in the transaction")
952 % self.indexfile)
951 % self.indexfile)
953
952
954 trindex = trinfo[2]
953 trindex = trinfo[2]
955 dataoff = self.start(trindex)
954 dataoff = self.start(trindex)
956
955
957 tr.add(self.datafile, dataoff)
956 tr.add(self.datafile, dataoff)
958 df = self.opener(self.datafile, 'w')
957 df = self.opener(self.datafile, 'w')
959 calc = struct.calcsize(self.indexformat)
958 calc = struct.calcsize(self.indexformat)
960 for r in xrange(self.count()):
959 for r in xrange(self.count()):
961 start = self.start(r) + (r + 1) * calc
960 start = self.start(r) + (r + 1) * calc
962 length = self.length(r)
961 length = self.length(r)
963 fp.seek(start)
962 fp.seek(start)
964 d = fp.read(length)
963 d = fp.read(length)
965 df.write(d)
964 df.write(d)
966 fp.close()
965 fp.close()
967 df.close()
966 df.close()
968 fp = self.opener(self.indexfile, 'w', atomictemp=True)
967 fp = self.opener(self.indexfile, 'w', atomictemp=True)
969 self.version &= ~(REVLOGNGINLINEDATA)
968 self.version &= ~(REVLOGNGINLINEDATA)
970 if self.count():
969 if self.count():
971 x = self.index[0]
970 x = self.index[0]
972 e = struct.pack(self.indexformat, *x)[4:]
971 e = struct.pack(self.indexformat, *x)[4:]
973 l = struct.pack(versionformat, self.version)
972 l = struct.pack(versionformat, self.version)
974 fp.write(l)
973 fp.write(l)
975 fp.write(e)
974 fp.write(e)
976
975
977 for i in xrange(1, self.count()):
976 for i in xrange(1, self.count()):
978 x = self.index[i]
977 x = self.index[i]
979 e = struct.pack(self.indexformat, *x)
978 e = struct.pack(self.indexformat, *x)
980 fp.write(e)
979 fp.write(e)
981
980
982 # if we don't call rename, the temp file will never replace the
981 # if we don't call rename, the temp file will never replace the
983 # real index
982 # real index
984 fp.rename()
983 fp.rename()
985
984
986 tr.replace(self.indexfile, trindex * calc)
985 tr.replace(self.indexfile, trindex * calc)
987 self.chunkcache = None
986 self.chunkcache = None
988
987
989 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
988 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
990 """add a revision to the log
989 """add a revision to the log
991
990
992 text - the revision data to add
991 text - the revision data to add
993 transaction - the transaction object used for rollback
992 transaction - the transaction object used for rollback
994 link - the linkrev data to add
993 link - the linkrev data to add
995 p1, p2 - the parent nodeids of the revision
994 p1, p2 - the parent nodeids of the revision
996 d - an optional precomputed delta
995 d - an optional precomputed delta
997 """
996 """
998 if not self.inlinedata():
997 if not self.inlinedata():
999 dfh = self.opener(self.datafile, "a")
998 dfh = self.opener(self.datafile, "a")
1000 else:
999 else:
1001 dfh = None
1000 dfh = None
1002 ifh = self.opener(self.indexfile, "a+")
1001 ifh = self.opener(self.indexfile, "a+")
1003 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1002 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1004
1003
1005 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1004 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1006 if text is None: text = ""
1005 if text is None: text = ""
1007 if p1 is None: p1 = self.tip()
1006 if p1 is None: p1 = self.tip()
1008 if p2 is None: p2 = nullid
1007 if p2 is None: p2 = nullid
1009
1008
1010 node = hash(text, p1, p2)
1009 node = hash(text, p1, p2)
1011
1010
1012 if node in self.nodemap:
1011 if node in self.nodemap:
1013 return node
1012 return node
1014
1013
1015 n = self.count()
1014 n = self.count()
1016 t = n - 1
1015 t = n - 1
1017
1016
1018 if n:
1017 if n:
1019 base = self.base(t)
1018 base = self.base(t)
1020 start = self.start(base)
1019 start = self.start(base)
1021 end = self.end(t)
1020 end = self.end(t)
1022 if not d:
1021 if not d:
1023 prev = self.revision(self.tip())
1022 prev = self.revision(self.tip())
1024 d = self.diff(prev, text)
1023 d = self.diff(prev, text)
1025 data = compress(d)
1024 data = compress(d)
1026 l = len(data[1]) + len(data[0])
1025 l = len(data[1]) + len(data[0])
1027 dist = end - start + l
1026 dist = end - start + l
1028
1027
1029 # full versions are inserted when the needed deltas
1028 # full versions are inserted when the needed deltas
1030 # become comparable to the uncompressed text
1029 # become comparable to the uncompressed text
1031 if not n or dist > len(text) * 2:
1030 if not n or dist > len(text) * 2:
1032 data = compress(text)
1031 data = compress(text)
1033 l = len(data[1]) + len(data[0])
1032 l = len(data[1]) + len(data[0])
1034 base = n
1033 base = n
1035 else:
1034 else:
1036 base = self.base(t)
1035 base = self.base(t)
1037
1036
1038 offset = 0
1037 offset = 0
1039 if t >= 0:
1038 if t >= 0:
1040 offset = self.end(t)
1039 offset = self.end(t)
1041
1040
1042 if self.version == REVLOGV0:
1041 if self.version == REVLOGV0:
1043 e = (offset, l, base, link, p1, p2, node)
1042 e = (offset, l, base, link, p1, p2, node)
1044 else:
1043 else:
1045 e = (self.offset_type(offset, 0), l, len(text),
1044 e = (offset_type(offset, 0), l, len(text),
1046 base, link, self.rev(p1), self.rev(p2), node)
1045 base, link, self.rev(p1), self.rev(p2), node)
1047
1046
1048 self.index.append(e)
1047 self.index.append(e)
1049 self.nodemap[node] = n
1048 self.nodemap[node] = n
1050 entry = struct.pack(self.indexformat, *e)
1049 entry = struct.pack(self.indexformat, *e)
1051
1050
1052 if not self.inlinedata():
1051 if not self.inlinedata():
1053 transaction.add(self.datafile, offset)
1052 transaction.add(self.datafile, offset)
1054 transaction.add(self.indexfile, n * len(entry))
1053 transaction.add(self.indexfile, n * len(entry))
1055 if data[0]:
1054 if data[0]:
1056 dfh.write(data[0])
1055 dfh.write(data[0])
1057 dfh.write(data[1])
1056 dfh.write(data[1])
1058 dfh.flush()
1057 dfh.flush()
1059 else:
1058 else:
1060 ifh.seek(0, 2)
1059 ifh.seek(0, 2)
1061 transaction.add(self.indexfile, ifh.tell(), self.count() - 1)
1060 transaction.add(self.indexfile, ifh.tell(), self.count() - 1)
1062
1061
1063 if len(self.index) == 1 and self.version != REVLOGV0:
1062 if len(self.index) == 1 and self.version != REVLOGV0:
1064 l = struct.pack(versionformat, self.version)
1063 l = struct.pack(versionformat, self.version)
1065 ifh.write(l)
1064 ifh.write(l)
1066 entry = entry[4:]
1065 entry = entry[4:]
1067
1066
1068 ifh.write(entry)
1067 ifh.write(entry)
1069
1068
1070 if self.inlinedata():
1069 if self.inlinedata():
1071 ifh.write(data[0])
1070 ifh.write(data[0])
1072 ifh.write(data[1])
1071 ifh.write(data[1])
1073 self.checkinlinesize(transaction, ifh)
1072 self.checkinlinesize(transaction, ifh)
1074
1073
1075 self.cache = (node, n, text)
1074 self.cache = (node, n, text)
1076 return node
1075 return node
1077
1076
1078 def ancestor(self, a, b):
1077 def ancestor(self, a, b):
1079 """calculate the least common ancestor of nodes a and b"""
1078 """calculate the least common ancestor of nodes a and b"""
1080
1079
1081 def parents(rev):
1080 def parents(rev):
1082 return [p for p in self.parentrevs(rev) if p != nullrev]
1081 return [p for p in self.parentrevs(rev) if p != nullrev]
1083
1082
1084 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1083 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1085 if c is None:
1084 if c is None:
1086 return nullid
1085 return nullid
1087
1086
1088 return self.node(c)
1087 return self.node(c)
1089
1088
1090 def group(self, nodelist, lookup, infocollect=None):
1089 def group(self, nodelist, lookup, infocollect=None):
1091 """calculate a delta group
1090 """calculate a delta group
1092
1091
1093 Given a list of changeset revs, return a set of deltas and
1092 Given a list of changeset revs, return a set of deltas and
1094 metadata corresponding to nodes. the first delta is
1093 metadata corresponding to nodes. the first delta is
1095 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1094 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1096 have this parent as it has all history before these
1095 have this parent as it has all history before these
1097 changesets. parent is parent[0]
1096 changesets. parent is parent[0]
1098 """
1097 """
1099 revs = [self.rev(n) for n in nodelist]
1098 revs = [self.rev(n) for n in nodelist]
1100
1099
1101 # if we don't have any revisions touched by these changesets, bail
1100 # if we don't have any revisions touched by these changesets, bail
1102 if not revs:
1101 if not revs:
1103 yield changegroup.closechunk()
1102 yield changegroup.closechunk()
1104 return
1103 return
1105
1104
1106 # add the parent of the first rev
1105 # add the parent of the first rev
1107 p = self.parents(self.node(revs[0]))[0]
1106 p = self.parents(self.node(revs[0]))[0]
1108 revs.insert(0, self.rev(p))
1107 revs.insert(0, self.rev(p))
1109
1108
1110 # build deltas
1109 # build deltas
1111 for d in xrange(0, len(revs) - 1):
1110 for d in xrange(0, len(revs) - 1):
1112 a, b = revs[d], revs[d + 1]
1111 a, b = revs[d], revs[d + 1]
1113 nb = self.node(b)
1112 nb = self.node(b)
1114
1113
1115 if infocollect is not None:
1114 if infocollect is not None:
1116 infocollect(nb)
1115 infocollect(nb)
1117
1116
1118 d = self.revdiff(a, b)
1117 d = self.revdiff(a, b)
1119 p = self.parents(nb)
1118 p = self.parents(nb)
1120 meta = nb + p[0] + p[1] + lookup(nb)
1119 meta = nb + p[0] + p[1] + lookup(nb)
1121 yield changegroup.genchunk("%s%s" % (meta, d))
1120 yield changegroup.genchunk("%s%s" % (meta, d))
1122
1121
1123 yield changegroup.closechunk()
1122 yield changegroup.closechunk()
1124
1123
1125 def addgroup(self, revs, linkmapper, transaction, unique=0):
1124 def addgroup(self, revs, linkmapper, transaction, unique=0):
1126 """
1125 """
1127 add a delta group
1126 add a delta group
1128
1127
1129 given a set of deltas, add them to the revision log. the
1128 given a set of deltas, add them to the revision log. the
1130 first delta is against its parent, which should be in our
1129 first delta is against its parent, which should be in our
1131 log, the rest are against the previous delta.
1130 log, the rest are against the previous delta.
1132 """
1131 """
1133
1132
1134 #track the base of the current delta log
1133 #track the base of the current delta log
1135 r = self.count()
1134 r = self.count()
1136 t = r - 1
1135 t = r - 1
1137 node = None
1136 node = None
1138
1137
1139 base = prev = nullrev
1138 base = prev = nullrev
1140 start = end = textlen = 0
1139 start = end = textlen = 0
1141 if r:
1140 if r:
1142 end = self.end(t)
1141 end = self.end(t)
1143
1142
1144 ifh = self.opener(self.indexfile, "a+")
1143 ifh = self.opener(self.indexfile, "a+")
1145 ifh.seek(0, 2)
1144 ifh.seek(0, 2)
1146 transaction.add(self.indexfile, ifh.tell(), self.count())
1145 transaction.add(self.indexfile, ifh.tell(), self.count())
1147 if self.inlinedata():
1146 if self.inlinedata():
1148 dfh = None
1147 dfh = None
1149 else:
1148 else:
1150 transaction.add(self.datafile, end)
1149 transaction.add(self.datafile, end)
1151 dfh = self.opener(self.datafile, "a")
1150 dfh = self.opener(self.datafile, "a")
1152
1151
1153 # loop through our set of deltas
1152 # loop through our set of deltas
1154 chain = None
1153 chain = None
1155 for chunk in revs:
1154 for chunk in revs:
1156 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1155 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1157 link = linkmapper(cs)
1156 link = linkmapper(cs)
1158 if node in self.nodemap:
1157 if node in self.nodemap:
1159 # this can happen if two branches make the same change
1158 # this can happen if two branches make the same change
1160 # if unique:
1159 # if unique:
1161 # raise RevlogError(_("already have %s") % hex(node[:4]))
1160 # raise RevlogError(_("already have %s") % hex(node[:4]))
1162 chain = node
1161 chain = node
1163 continue
1162 continue
1164 delta = chunk[80:]
1163 delta = chunk[80:]
1165
1164
1166 for p in (p1, p2):
1165 for p in (p1, p2):
1167 if not p in self.nodemap:
1166 if not p in self.nodemap:
1168 raise LookupError(_("unknown parent %s") % short(p))
1167 raise LookupError(_("unknown parent %s") % short(p))
1169
1168
1170 if not chain:
1169 if not chain:
1171 # retrieve the parent revision of the delta chain
1170 # retrieve the parent revision of the delta chain
1172 chain = p1
1171 chain = p1
1173 if not chain in self.nodemap:
1172 if not chain in self.nodemap:
1174 raise LookupError(_("unknown base %s") % short(chain[:4]))
1173 raise LookupError(_("unknown base %s") % short(chain[:4]))
1175
1174
1176 # full versions are inserted when the needed deltas become
1175 # full versions are inserted when the needed deltas become
1177 # comparable to the uncompressed text or when the previous
1176 # comparable to the uncompressed text or when the previous
1178 # version is not the one we have a delta against. We use
1177 # version is not the one we have a delta against. We use
1179 # the size of the previous full rev as a proxy for the
1178 # the size of the previous full rev as a proxy for the
1180 # current size.
1179 # current size.
1181
1180
1182 if chain == prev:
1181 if chain == prev:
1183 tempd = compress(delta)
1182 tempd = compress(delta)
1184 cdelta = tempd[0] + tempd[1]
1183 cdelta = tempd[0] + tempd[1]
1185 textlen = mdiff.patchedsize(textlen, delta)
1184 textlen = mdiff.patchedsize(textlen, delta)
1186
1185
1187 if chain != prev or (end - start + len(cdelta)) > textlen * 2:
1186 if chain != prev or (end - start + len(cdelta)) > textlen * 2:
1188 # flush our writes here so we can read it in revision
1187 # flush our writes here so we can read it in revision
1189 if dfh:
1188 if dfh:
1190 dfh.flush()
1189 dfh.flush()
1191 ifh.flush()
1190 ifh.flush()
1192 text = self.revision(chain)
1191 text = self.revision(chain)
1193 text = self.patches(text, [delta])
1192 text = self.patches(text, [delta])
1194 chk = self._addrevision(text, transaction, link, p1, p2, None,
1193 chk = self._addrevision(text, transaction, link, p1, p2, None,
1195 ifh, dfh)
1194 ifh, dfh)
1196 if not dfh and not self.inlinedata():
1195 if not dfh and not self.inlinedata():
1197 # addrevision switched from inline to conventional
1196 # addrevision switched from inline to conventional
1198 # reopen the index
1197 # reopen the index
1199 dfh = self.opener(self.datafile, "a")
1198 dfh = self.opener(self.datafile, "a")
1200 ifh = self.opener(self.indexfile, "a")
1199 ifh = self.opener(self.indexfile, "a")
1201 if chk != node:
1200 if chk != node:
1202 raise RevlogError(_("consistency error adding group"))
1201 raise RevlogError(_("consistency error adding group"))
1203 textlen = len(text)
1202 textlen = len(text)
1204 else:
1203 else:
1205 if self.version == REVLOGV0:
1204 if self.version == REVLOGV0:
1206 e = (end, len(cdelta), base, link, p1, p2, node)
1205 e = (end, len(cdelta), base, link, p1, p2, node)
1207 else:
1206 else:
1208 e = (self.offset_type(end, 0), len(cdelta), textlen, base,
1207 e = (offset_type(end, 0), len(cdelta), textlen, base,
1209 link, self.rev(p1), self.rev(p2), node)
1208 link, self.rev(p1), self.rev(p2), node)
1210 self.index.append(e)
1209 self.index.append(e)
1211 self.nodemap[node] = r
1210 self.nodemap[node] = r
1212 if self.inlinedata():
1211 if self.inlinedata():
1213 ifh.write(struct.pack(self.indexformat, *e))
1212 ifh.write(struct.pack(self.indexformat, *e))
1214 ifh.write(cdelta)
1213 ifh.write(cdelta)
1215 self.checkinlinesize(transaction, ifh)
1214 self.checkinlinesize(transaction, ifh)
1216 if not self.inlinedata():
1215 if not self.inlinedata():
1217 dfh = self.opener(self.datafile, "a")
1216 dfh = self.opener(self.datafile, "a")
1218 ifh = self.opener(self.indexfile, "a")
1217 ifh = self.opener(self.indexfile, "a")
1219 else:
1218 else:
1220 dfh.write(cdelta)
1219 dfh.write(cdelta)
1221 ifh.write(struct.pack(self.indexformat, *e))
1220 ifh.write(struct.pack(self.indexformat, *e))
1222
1221
1223 t, r, chain, prev = r, r + 1, node, node
1222 t, r, chain, prev = r, r + 1, node, node
1224 base = self.base(t)
1223 base = self.base(t)
1225 start = self.start(base)
1224 start = self.start(base)
1226 end = self.end(t)
1225 end = self.end(t)
1227
1226
1228 return node
1227 return node
1229
1228
1230 def strip(self, rev, minlink):
1229 def strip(self, rev, minlink):
1231 if self.count() == 0 or rev >= self.count():
1230 if self.count() == 0 or rev >= self.count():
1232 return
1231 return
1233
1232
1234 if isinstance(self.index, lazyindex):
1233 if isinstance(self.index, lazyindex):
1235 self.loadindexmap()
1234 self.loadindexmap()
1236
1235
1237 # When stripping away a revision, we need to make sure it
1236 # When stripping away a revision, we need to make sure it
1238 # does not actually belong to an older changeset.
1237 # does not actually belong to an older changeset.
1239 # The minlink parameter defines the oldest revision
1238 # The minlink parameter defines the oldest revision
1240 # we're allowed to strip away.
1239 # we're allowed to strip away.
1241 while minlink > self.index[rev][-4]:
1240 while minlink > self.index[rev][-4]:
1242 rev += 1
1241 rev += 1
1243 if rev >= self.count():
1242 if rev >= self.count():
1244 return
1243 return
1245
1244
1246 # first truncate the files on disk
1245 # first truncate the files on disk
1247 end = self.start(rev)
1246 end = self.start(rev)
1248 if not self.inlinedata():
1247 if not self.inlinedata():
1249 df = self.opener(self.datafile, "a")
1248 df = self.opener(self.datafile, "a")
1250 df.truncate(end)
1249 df.truncate(end)
1251 end = rev * struct.calcsize(self.indexformat)
1250 end = rev * struct.calcsize(self.indexformat)
1252 else:
1251 else:
1253 end += rev * struct.calcsize(self.indexformat)
1252 end += rev * struct.calcsize(self.indexformat)
1254
1253
1255 indexf = self.opener(self.indexfile, "a")
1254 indexf = self.opener(self.indexfile, "a")
1256 indexf.truncate(end)
1255 indexf.truncate(end)
1257
1256
1258 # then reset internal state in memory to forget those revisions
1257 # then reset internal state in memory to forget those revisions
1259 self.cache = None
1258 self.cache = None
1260 self.chunkcache = None
1259 self.chunkcache = None
1261 for x in xrange(rev, self.count()):
1260 for x in xrange(rev, self.count()):
1262 del self.nodemap[self.node(x)]
1261 del self.nodemap[self.node(x)]
1263
1262
1264 del self.index[rev:]
1263 del self.index[rev:]
1265
1264
1266 def checksize(self):
1265 def checksize(self):
1267 expected = 0
1266 expected = 0
1268 if self.count():
1267 if self.count():
1269 expected = self.end(self.count() - 1)
1268 expected = self.end(self.count() - 1)
1270
1269
1271 try:
1270 try:
1272 f = self.opener(self.datafile)
1271 f = self.opener(self.datafile)
1273 f.seek(0, 2)
1272 f.seek(0, 2)
1274 actual = f.tell()
1273 actual = f.tell()
1275 dd = actual - expected
1274 dd = actual - expected
1276 except IOError, inst:
1275 except IOError, inst:
1277 if inst.errno != errno.ENOENT:
1276 if inst.errno != errno.ENOENT:
1278 raise
1277 raise
1279 dd = 0
1278 dd = 0
1280
1279
1281 try:
1280 try:
1282 f = self.opener(self.indexfile)
1281 f = self.opener(self.indexfile)
1283 f.seek(0, 2)
1282 f.seek(0, 2)
1284 actual = f.tell()
1283 actual = f.tell()
1285 s = struct.calcsize(self.indexformat)
1284 s = struct.calcsize(self.indexformat)
1286 i = actual / s
1285 i = actual / s
1287 di = actual - (i * s)
1286 di = actual - (i * s)
1288 if self.inlinedata():
1287 if self.inlinedata():
1289 databytes = 0
1288 databytes = 0
1290 for r in xrange(self.count()):
1289 for r in xrange(self.count()):
1291 databytes += self.length(r)
1290 databytes += self.length(r)
1292 dd = 0
1291 dd = 0
1293 di = actual - self.count() * s - databytes
1292 di = actual - self.count() * s - databytes
1294 except IOError, inst:
1293 except IOError, inst:
1295 if inst.errno != errno.ENOENT:
1294 if inst.errno != errno.ENOENT:
1296 raise
1295 raise
1297 di = 0
1296 di = 0
1298
1297
1299 return (dd, di)
1298 return (dd, di)
1300
1299
1301
1300
General Comments 0
You need to be logged in to leave comments. Login now