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