##// END OF EJS Templates
revlog: mark cache private
Matt Mackall -
r4984:b4066fcb default
parent child Browse files
Show More
@@ -1,251 +1,251 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 e = (revlog.offset_type(start, 0), size, -1, None, link,
62 62 self.rev(p1), self.rev(p2), node)
63 63 self.basemap[n] = prev
64 64 self.index.insert(-1, e)
65 65 self.nodemap[node] = n
66 66 prev = node
67 67 n += 1
68 68
69 69 def bundle(self, rev):
70 70 """is rev from the bundle"""
71 71 if rev < 0:
72 72 return False
73 73 return rev in self.basemap
74 74 def bundlebase(self, rev): return self.basemap[rev]
75 75 def chunk(self, rev, df=None, cachelen=4096):
76 76 # Warning: in case of bundle, the diff is against bundlebase,
77 77 # not against rev - 1
78 78 # XXX: could use some caching
79 79 if not self.bundle(rev):
80 80 return revlog.revlog.chunk(self, rev, df, cachelen)
81 81 self.bundlefile.seek(self.start(rev))
82 82 return self.bundlefile.read(self.length(rev))
83 83
84 84 def revdiff(self, rev1, rev2):
85 85 """return or calculate a delta between two revisions"""
86 86 if self.bundle(rev1) and self.bundle(rev2):
87 87 # hot path for bundle
88 88 revb = self.rev(self.bundlebase(rev2))
89 89 if revb == rev1:
90 90 return self.chunk(rev2)
91 91 elif not self.bundle(rev1) and not self.bundle(rev2):
92 92 return revlog.revlog.revdiff(self, rev1, rev2)
93 93
94 94 return self.diff(self.revision(self.node(rev1)),
95 95 self.revision(self.node(rev2)))
96 96
97 97 def revision(self, node):
98 98 """return an uncompressed revision of a given"""
99 99 if node == nullid: return ""
100 100
101 101 text = None
102 102 chain = []
103 103 iter_node = node
104 104 rev = self.rev(iter_node)
105 105 # reconstruct the revision if it is from a changegroup
106 106 while self.bundle(rev):
107 if self.cache and self.cache[0] == iter_node:
108 text = self.cache[2]
107 if self._cache and self._cache[0] == iter_node:
108 text = self._cache[2]
109 109 break
110 110 chain.append(rev)
111 111 iter_node = self.bundlebase(rev)
112 112 rev = self.rev(iter_node)
113 113 if text is None:
114 114 text = revlog.revlog.revision(self, iter_node)
115 115
116 116 while chain:
117 117 delta = self.chunk(chain.pop())
118 118 text = self.patches(text, [delta])
119 119
120 120 p1, p2 = self.parents(node)
121 121 if node != revlog.hash(text, p1, p2):
122 122 raise revlog.RevlogError(_("integrity check failed on %s:%d")
123 123 % (self.datafile, self.rev(node)))
124 124
125 self.cache = (node, self.rev(node), text)
125 self._cache = (node, self.rev(node), text)
126 126 return text
127 127
128 128 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
129 129 raise NotImplementedError
130 130 def addgroup(self, revs, linkmapper, transaction, unique=0):
131 131 raise NotImplementedError
132 132 def strip(self, rev, minlink):
133 133 raise NotImplementedError
134 134 def checksize(self):
135 135 raise NotImplementedError
136 136
137 137 class bundlechangelog(bundlerevlog, changelog.changelog):
138 138 def __init__(self, opener, bundlefile):
139 139 changelog.changelog.__init__(self, opener)
140 140 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
141 141
142 142 class bundlemanifest(bundlerevlog, manifest.manifest):
143 143 def __init__(self, opener, bundlefile, linkmapper):
144 144 manifest.manifest.__init__(self, opener)
145 145 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
146 146 linkmapper)
147 147
148 148 class bundlefilelog(bundlerevlog, filelog.filelog):
149 149 def __init__(self, opener, path, bundlefile, linkmapper):
150 150 filelog.filelog.__init__(self, opener, path)
151 151 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
152 152 linkmapper)
153 153
154 154 class bundlerepository(localrepo.localrepository):
155 155 def __init__(self, ui, path, bundlename):
156 156 localrepo.localrepository.__init__(self, ui, path)
157 157
158 158 self._url = 'bundle:' + bundlename
159 159 if path: self._url += '+' + path
160 160
161 161 self.tempfile = None
162 162 self.bundlefile = open(bundlename, "rb")
163 163 header = self.bundlefile.read(6)
164 164 if not header.startswith("HG"):
165 165 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
166 166 elif not header.startswith("HG10"):
167 167 raise util.Abort(_("%s: unknown bundle version") % bundlename)
168 168 elif header == "HG10BZ":
169 169 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
170 170 suffix=".hg10un", dir=self.path)
171 171 self.tempfile = temp
172 172 fptemp = os.fdopen(fdtemp, 'wb')
173 173 def generator(f):
174 174 zd = bz2.BZ2Decompressor()
175 175 zd.decompress("BZ")
176 176 for chunk in f:
177 177 yield zd.decompress(chunk)
178 178 gen = generator(util.filechunkiter(self.bundlefile, 4096))
179 179
180 180 try:
181 181 fptemp.write("HG10UN")
182 182 for chunk in gen:
183 183 fptemp.write(chunk)
184 184 finally:
185 185 fptemp.close()
186 186 self.bundlefile.close()
187 187
188 188 self.bundlefile = open(self.tempfile, "rb")
189 189 # seek right after the header
190 190 self.bundlefile.seek(6)
191 191 elif header == "HG10UN":
192 192 # nothing to do
193 193 pass
194 194 else:
195 195 raise util.Abort(_("%s: unknown bundle compression type")
196 196 % bundlename)
197 197 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
198 198 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
199 199 self.changelog.rev)
200 200 # dict with the mapping 'filename' -> position in the bundle
201 201 self.bundlefilespos = {}
202 202 while 1:
203 203 f = changegroup.getchunk(self.bundlefile)
204 204 if not f:
205 205 break
206 206 self.bundlefilespos[f] = self.bundlefile.tell()
207 207 for c in changegroup.chunkiter(self.bundlefile):
208 208 pass
209 209
210 210 def url(self):
211 211 return self._url
212 212
213 213 def dev(self):
214 214 return -1
215 215
216 216 def file(self, f):
217 217 if f[0] == '/':
218 218 f = f[1:]
219 219 if f in self.bundlefilespos:
220 220 self.bundlefile.seek(self.bundlefilespos[f])
221 221 return bundlefilelog(self.sopener, f, self.bundlefile,
222 222 self.changelog.rev)
223 223 else:
224 224 return filelog.filelog(self.sopener, f)
225 225
226 226 def close(self):
227 227 """Close assigned bundle file immediately."""
228 228 self.bundlefile.close()
229 229
230 230 def __del__(self):
231 231 bundlefile = getattr(self, 'bundlefile', None)
232 232 if bundlefile and not bundlefile.closed:
233 233 bundlefile.close()
234 234 tempfile = getattr(self, 'tempfile', None)
235 235 if tempfile is not None:
236 236 os.unlink(tempfile)
237 237
238 238 def instance(ui, path, create):
239 239 if create:
240 240 raise util.Abort(_('cannot create new bundle repository'))
241 241 path = util.drop_scheme('file', path)
242 242 if path.startswith('bundle:'):
243 243 path = util.drop_scheme('bundle', path)
244 244 s = path.split("+", 1)
245 245 if len(s) == 1:
246 246 repopath, bundlename = "", s[0]
247 247 else:
248 248 repopath, bundlename = s
249 249 else:
250 250 repopath, bundlename = '', path
251 251 return bundlerepository(ui, repopath, bundlename)
@@ -1,1278 +1,1277 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:
46 46 return ("", text)
47 47 if len(text) < 44:
48 48 if text[0] == '\0':
49 49 return ("", text)
50 50 return ('u', text)
51 51 bin = zlib.compress(text)
52 52 if len(bin) > len(text):
53 53 if text[0] == '\0':
54 54 return ("", text)
55 55 return ('u', text)
56 56 return ("", bin)
57 57
58 58 def decompress(bin):
59 59 """ decompress the given input """
60 60 if not bin:
61 61 return bin
62 62 t = bin[0]
63 63 if t == '\0':
64 64 return bin
65 65 if t == 'x':
66 66 return zlib.decompress(bin)
67 67 if t == 'u':
68 68 return bin[1:]
69 69 raise RevlogError(_("unknown compression type %r") % t)
70 70
71 71 indexformatv0 = ">4l20s20s20s"
72 72 v0shaoffset = 56
73 73 # index ng:
74 74 # 6 bytes offset
75 75 # 2 bytes flags
76 76 # 4 bytes compressed length
77 77 # 4 bytes uncompressed length
78 78 # 4 bytes: base rev
79 79 # 4 bytes link rev
80 80 # 4 bytes parent 1 rev
81 81 # 4 bytes parent 2 rev
82 82 # 32 bytes: nodeid
83 83 indexformatng = ">Qiiiiii20s12x"
84 84 ngshaoffset = 32
85 85 versionformat = ">I"
86 86
87 87 class lazyparser(object):
88 88 """
89 89 this class avoids the need to parse the entirety of large indices
90 90 """
91 91
92 92 # lazyparser is not safe to use on windows if win32 extensions not
93 93 # available. it keeps file handle open, which make it not possible
94 94 # to break hardlinks on local cloned repos.
95 95 safe_to_use = os.name != 'nt' or (not util.is_win_9x() and
96 96 hasattr(util, 'win32api'))
97 97
98 98 def __init__(self, dataf, size):
99 99 self.dataf = dataf
100 100 self.s = struct.calcsize(indexformatng)
101 101 self.datasize = size
102 102 self.l = size/self.s
103 103 self.index = [None] * self.l
104 104 self.map = {nullid: nullrev}
105 105 self.allmap = 0
106 106 self.all = 0
107 107 self.mapfind_count = 0
108 108
109 109 def loadmap(self):
110 110 """
111 111 during a commit, we need to make sure the rev being added is
112 112 not a duplicate. This requires loading the entire index,
113 113 which is fairly slow. loadmap can load up just the node map,
114 114 which takes much less time.
115 115 """
116 116 if self.allmap:
117 117 return
118 118 end = self.datasize
119 119 self.allmap = 1
120 120 cur = 0
121 121 count = 0
122 122 blocksize = self.s * 256
123 123 self.dataf.seek(0)
124 124 while cur < end:
125 125 data = self.dataf.read(blocksize)
126 126 off = 0
127 127 for x in xrange(256):
128 128 n = data[off + ngshaoffset:off + ngshaoffset + 20]
129 129 self.map[n] = count
130 130 count += 1
131 131 if count >= self.l:
132 132 break
133 133 off += self.s
134 134 cur += blocksize
135 135
136 136 def loadblock(self, blockstart, blocksize, data=None):
137 137 if self.all:
138 138 return
139 139 if data is None:
140 140 self.dataf.seek(blockstart)
141 141 if blockstart + blocksize > self.datasize:
142 142 # the revlog may have grown since we've started running,
143 143 # but we don't have space in self.index for more entries.
144 144 # limit blocksize so that we don't get too much data.
145 145 blocksize = max(self.datasize - blockstart, 0)
146 146 data = self.dataf.read(blocksize)
147 147 lend = len(data) / self.s
148 148 i = blockstart / self.s
149 149 off = 0
150 150 # lazyindex supports __delitem__
151 151 if lend > len(self.index) - i:
152 152 lend = len(self.index) - i
153 153 for x in xrange(lend):
154 154 if self.index[i + x] == None:
155 155 b = data[off : off + self.s]
156 156 self.index[i + x] = b
157 157 n = b[ngshaoffset:ngshaoffset + 20]
158 158 self.map[n] = i + x
159 159 off += self.s
160 160
161 161 def findnode(self, node):
162 162 """search backwards through the index file for a specific node"""
163 163 if self.allmap:
164 164 return None
165 165
166 166 # hg log will cause many many searches for the manifest
167 167 # nodes. After we get called a few times, just load the whole
168 168 # thing.
169 169 if self.mapfind_count > 8:
170 170 self.loadmap()
171 171 if node in self.map:
172 172 return node
173 173 return None
174 174 self.mapfind_count += 1
175 175 last = self.l - 1
176 176 while self.index[last] != None:
177 177 if last == 0:
178 178 self.all = 1
179 179 self.allmap = 1
180 180 return None
181 181 last -= 1
182 182 end = (last + 1) * self.s
183 183 blocksize = self.s * 256
184 184 while end >= 0:
185 185 start = max(end - blocksize, 0)
186 186 self.dataf.seek(start)
187 187 data = self.dataf.read(end - start)
188 188 findend = end - start
189 189 while True:
190 190 # we're searching backwards, so weh have to make sure
191 191 # we don't find a changeset where this node is a parent
192 192 off = data.rfind(node, 0, findend)
193 193 findend = off
194 194 if off >= 0:
195 195 i = off / self.s
196 196 off = i * self.s
197 197 n = data[off + ngshaoffset:off + ngshaoffset + 20]
198 198 if n == node:
199 199 self.map[n] = i + start / self.s
200 200 return node
201 201 else:
202 202 break
203 203 end -= blocksize
204 204 return None
205 205
206 206 def loadindex(self, i=None, end=None):
207 207 if self.all:
208 208 return
209 209 all = False
210 210 if i == None:
211 211 blockstart = 0
212 212 blocksize = (512 / self.s) * self.s
213 213 end = self.datasize
214 214 all = True
215 215 else:
216 216 if end:
217 217 blockstart = i * self.s
218 218 end = end * self.s
219 219 blocksize = end - blockstart
220 220 else:
221 221 blockstart = (i & ~63) * self.s
222 222 blocksize = self.s * 64
223 223 end = blockstart + blocksize
224 224 while blockstart < end:
225 225 self.loadblock(blockstart, blocksize)
226 226 blockstart += blocksize
227 227 if all:
228 228 self.all = True
229 229
230 230 class lazyindex(object):
231 231 """a lazy version of the index array"""
232 232 def __init__(self, parser):
233 233 self.p = parser
234 234 def __len__(self):
235 235 return len(self.p.index)
236 236 def load(self, pos):
237 237 if pos < 0:
238 238 pos += len(self.p.index)
239 239 self.p.loadindex(pos)
240 240 return self.p.index[pos]
241 241 def __getitem__(self, pos):
242 242 ret = self.p.index[pos] or self.load(pos)
243 243 if isinstance(ret, str):
244 244 ret = struct.unpack(indexformatng, ret)
245 245 return ret
246 246 def __setitem__(self, pos, item):
247 247 self.p.index[pos] = item
248 248 def __delitem__(self, pos):
249 249 del self.p.index[pos]
250 250 def insert(self, pos, e):
251 251 self.p.index.insert(pos, e)
252 252 def append(self, e):
253 253 self.p.index.append(e)
254 254
255 255 class lazymap(object):
256 256 """a lazy version of the node map"""
257 257 def __init__(self, parser):
258 258 self.p = parser
259 259 def load(self, key):
260 260 n = self.p.findnode(key)
261 261 if n == None:
262 262 raise KeyError(key)
263 263 def __contains__(self, key):
264 264 if key in self.p.map:
265 265 return True
266 266 self.p.loadmap()
267 267 return key in self.p.map
268 268 def __iter__(self):
269 269 yield nullid
270 270 for i in xrange(self.p.l):
271 271 ret = self.p.index[i]
272 272 if not ret:
273 273 self.p.loadindex(i)
274 274 ret = self.p.index[i]
275 275 if isinstance(ret, str):
276 276 ret = struct.unpack(indexformatng, ret)
277 277 yield ret[7]
278 278 def __getitem__(self, key):
279 279 try:
280 280 return self.p.map[key]
281 281 except KeyError:
282 282 try:
283 283 self.load(key)
284 284 return self.p.map[key]
285 285 except KeyError:
286 286 raise KeyError("node " + hex(key))
287 287 def __setitem__(self, key, val):
288 288 self.p.map[key] = val
289 289 def __delitem__(self, key):
290 290 del self.p.map[key]
291 291
292 292 class RevlogError(Exception):
293 293 pass
294 294 class LookupError(RevlogError):
295 295 pass
296 296
297 297 def getoffset(q):
298 298 if q & 0xFFFF:
299 299 raise RevlogError(_('incompatible revision flag %x') % q)
300 300 return int(q >> 16)
301 301
302 302 def gettype(q):
303 303 return int(q & 0xFFFF)
304 304
305 305 def offset_type(offset, type):
306 306 return long(long(offset) << 16 | type)
307 307
308 308 class revlogoldio(object):
309 309 def __init__(self):
310 310 self.size = struct.calcsize(indexformatv0)
311 311
312 312 def parseindex(self, fp, st, inline):
313 313 s = self.size
314 314 index = []
315 315 nodemap = {nullid: nullrev}
316 316 n = off = 0
317 317 data = fp.read()
318 318 l = len(data)
319 319 while off + s <= l:
320 320 cur = data[off:off + s]
321 321 off += s
322 322 e = struct.unpack(indexformatv0, cur)
323 323 # transform to revlogv1 format
324 324 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
325 325 nodemap[e[4]], nodemap[e[5]], e[6])
326 326 index.append(e2)
327 327 nodemap[e[6]] = n
328 328 n += 1
329 329
330 330 return index, nodemap, None
331 331
332 332 class revlogio(object):
333 333 def __init__(self):
334 334 self.size = struct.calcsize(indexformatng)
335 335
336 336 def parseindex(self, fp, st, inline):
337 337 if (lazyparser.safe_to_use and not inline and
338 338 st and st.st_size > 1000000):
339 339 # big index, let's parse it on demand
340 340 parser = lazyparser(fp, st.st_size)
341 341 index = lazyindex(parser)
342 342 nodemap = lazymap(parser)
343 343 e = list(index[0])
344 344 type = gettype(e[0])
345 345 e[0] = offset_type(0, type)
346 346 index[0] = e
347 347 return index, nodemap, None
348 348
349 349 s = self.size
350 350 cache = None
351 351 index = []
352 352 nodemap = {nullid: nullrev}
353 353 n = off = 0
354 354 # if we're not using lazymap, always read the whole index
355 355 data = fp.read()
356 356 l = len(data)
357 357 if inline:
358 358 cache = (0, data)
359 359 while off + s <= l:
360 360 e = struct.unpack(indexformatng, data[off:off + s])
361 361 index.append(e)
362 362 nodemap[e[7]] = n
363 363 n += 1
364 364 off += s
365 365 if inline:
366 366 if e[1] < 0:
367 367 break
368 368 off += e[1]
369 369
370 370 e = list(index[0])
371 371 type = gettype(e[0])
372 372 e[0] = offset_type(0, type)
373 373 index[0] = e
374 374
375 375 return index, nodemap, cache
376 376
377 377 class revlog(object):
378 378 """
379 379 the underlying revision storage object
380 380
381 381 A revlog consists of two parts, an index and the revision data.
382 382
383 383 The index is a file with a fixed record size containing
384 384 information on each revision, includings its nodeid (hash), the
385 385 nodeids of its parents, the position and offset of its data within
386 386 the data file, and the revision it's based on. Finally, each entry
387 387 contains a linkrev entry that can serve as a pointer to external
388 388 data.
389 389
390 390 The revision data itself is a linear collection of data chunks.
391 391 Each chunk represents a revision and is usually represented as a
392 392 delta against the previous chunk. To bound lookup time, runs of
393 393 deltas are limited to about 2 times the length of the original
394 394 version data. This makes retrieval of a version proportional to
395 395 its size, or O(1) relative to the number of revisions.
396 396
397 397 Both pieces of the revlog are written to in an append-only
398 398 fashion, which means we never need to rewrite a file to insert or
399 399 remove data, and can use some simple techniques to avoid the need
400 400 for locking while reading.
401 401 """
402 402 def __init__(self, opener, indexfile):
403 403 """
404 404 create a revlog object
405 405
406 406 opener is a function that abstracts the file opening operation
407 407 and can be used to implement COW semantics or the like.
408 408 """
409 409 self.indexfile = indexfile
410 410 self.datafile = indexfile[:-2] + ".d"
411 411 self.opener = opener
412
413 412 self.indexstat = None
414 self.cache = None
413 self._cache = None
415 414 self._chunkcache = None
416 415 self.defversion = REVLOG_DEFAULT_VERSION
417 416 if hasattr(opener, "defversion"):
418 417 self.defversion = opener.defversion
419 418 if self.defversion & REVLOGNG:
420 419 self.defversion |= REVLOGNGINLINEDATA
421 420 self._load()
422 421
423 422 def _load(self):
424 423 v = self.defversion
425 424 try:
426 425 f = self.opener(self.indexfile)
427 426 i = f.read(4)
428 427 f.seek(0)
429 428 if len(i) > 0:
430 429 v = struct.unpack(versionformat, i)[0]
431 430 except IOError, inst:
432 431 if inst.errno != errno.ENOENT:
433 432 raise
434 433 i = ""
435 434 else:
436 435 try:
437 436 st = util.fstat(f)
438 437 except AttributeError, inst:
439 438 st = None
440 439 else:
441 440 oldst = self.indexstat
442 441 if (oldst and st.st_dev == oldst.st_dev
443 442 and st.st_ino == oldst.st_ino
444 443 and st.st_mtime == oldst.st_mtime
445 444 and st.st_ctime == oldst.st_ctime
446 445 and st.st_size == oldst.st_size):
447 446 return
448 447 self.indexstat = st
449 448 flags = v & ~0xFFFF
450 449 fmt = v & 0xFFFF
451 450 if fmt == REVLOGV0:
452 451 if flags:
453 452 raise RevlogError(_("index %s unknown flags %#04x for format v0")
454 453 % (self.indexfile, flags >> 16))
455 454 elif fmt == REVLOGNG:
456 455 if flags & ~REVLOGNGINLINEDATA:
457 456 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
458 457 % (self.indexfile, flags >> 16))
459 458 else:
460 459 raise RevlogError(_("index %s unknown format %d")
461 460 % (self.indexfile, fmt))
462 461 self.version = v
463 462 self._inline = v & REVLOGNGINLINEDATA
464 463 self.nodemap = {nullid: nullrev}
465 464 self.index = []
466 465 self._io = revlogio()
467 466 if self.version == REVLOGV0:
468 467 self._io = revlogoldio()
469 468 if i:
470 469 d = self._io.parseindex(f, st, self._inline)
471 470 self.index, self.nodemap, self._chunkcache = d
472 471 # add the magic null revision at -1
473 472 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
474 473
475 474 def _loadindex(self, start, end):
476 475 """load a block of indexes all at once from the lazy parser"""
477 476 if isinstance(self.index, lazyindex):
478 477 self.index.p.loadindex(start, end)
479 478
480 479 def _loadindexmap(self):
481 480 """loads both the map and the index from the lazy parser"""
482 481 if isinstance(self.index, lazyindex):
483 482 p = self.index.p
484 483 p.loadindex()
485 484 self.nodemap = p.map
486 485
487 486 def _loadmap(self):
488 487 """loads the map from the lazy parser"""
489 488 if isinstance(self.nodemap, lazymap):
490 489 self.nodemap.p.loadmap()
491 490 self.nodemap = self.nodemap.p.map
492 491
493 492 def tip(self):
494 493 return self.node(len(self.index) - 2)
495 494 def count(self):
496 495 return len(self.index) - 1
497 496
498 497 def rev(self, node):
499 498 try:
500 499 return self.nodemap[node]
501 500 except KeyError:
502 501 raise LookupError(_('%s: no node %s') % (self.indexfile, hex(node)))
503 502 def node(self, rev):
504 503 return self.index[rev][7]
505 504 def linkrev(self, node):
506 505 return self.index[self.rev(node)][4]
507 506 def parents(self, node):
508 507 d = self.index[self.rev(node)][5:7]
509 508 return (self.node(d[0]), self.node(d[1]))
510 509 def parentrevs(self, rev):
511 510 return self.index[rev][5:7]
512 511 def start(self, rev):
513 512 return getoffset(self.index[rev][0])
514 513 def end(self, rev):
515 514 return self.start(rev) + self.length(rev)
516 515 def length(self, rev):
517 516 return self.index[rev][1]
518 517 def base(self, rev):
519 518 return self.index[rev][3]
520 519
521 520 def size(self, rev):
522 521 """return the length of the uncompressed text for a given revision"""
523 522 l = self.index[rev][2]
524 523 if l >= 0:
525 524 return l
526 525
527 526 t = self.revision(self.node(rev))
528 527 return len(t)
529 528
530 529 # alternate implementation, The advantage to this code is it
531 530 # will be faster for a single revision. But, the results are not
532 531 # cached, so finding the size of every revision will be slower.
533 532 """
534 533 if self.cache and self.cache[1] == rev:
535 534 return len(self.cache[2])
536 535
537 536 base = self.base(rev)
538 537 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
539 538 base = self.cache[1]
540 539 text = self.cache[2]
541 540 else:
542 541 text = self.revision(self.node(base))
543 542
544 543 l = len(text)
545 544 for x in xrange(base + 1, rev + 1):
546 545 l = mdiff.patchedsize(l, self.chunk(x))
547 546 return l
548 547 """
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:
785 784 raise ValueError
786 785 if rev < 0:
787 786 rev = self.count() + rev
788 787 if rev < 0 or rev >= self.count():
789 788 raise ValueError
790 789 return self.node(rev)
791 790 except (ValueError, OverflowError):
792 791 pass
793 792 if len(id) == 40:
794 793 try:
795 794 # a full hex nodeid?
796 795 node = bin(id)
797 796 r = self.rev(node)
798 797 return node
799 798 except TypeError:
800 799 pass
801 800
802 801 def _partialmatch(self, id):
803 802 if len(id) < 40:
804 803 try:
805 804 # hex(node)[:...]
806 805 bin_id = bin(id[:len(id) & ~1]) # grab an even number of digits
807 806 node = None
808 807 for n in self.nodemap:
809 808 if n.startswith(bin_id) and hex(n).startswith(id):
810 809 if node is not None:
811 810 raise LookupError(_("Ambiguous identifier"))
812 811 node = n
813 812 if node is not None:
814 813 return node
815 814 except TypeError:
816 815 pass
817 816
818 817 def lookup(self, id):
819 818 """locate a node based on:
820 819 - revision number or str(revision number)
821 820 - nodeid or subset of hex nodeid
822 821 """
823 822 n = self._match(id)
824 823 if n is not None:
825 824 return n
826 825 n = self._partialmatch(id)
827 826 if n:
828 827 return n
829 828
830 829 raise LookupError(_("No match found"))
831 830
832 831 def cmp(self, node, text):
833 832 """compare text with a given file revision"""
834 833 p1, p2 = self.parents(node)
835 834 return hash(text, p1, p2) != node
836 835
837 836 def diff(self, a, b):
838 837 """return a delta between two revisions"""
839 838 return mdiff.textdiff(a, b)
840 839
841 840 def patches(self, t, pl):
842 841 """apply a list of patches to a string"""
843 842 return mdiff.patches(t, pl)
844 843
845 844 def chunk(self, rev, df=None, cachelen=4096):
846 845 start, length = self.start(rev), self.length(rev)
847 846 inline = self._inline
848 847 if inline:
849 848 start += (rev + 1) * self._io.size
850 849 end = start + length
851 850 def loadcache(df):
852 851 cache_length = max(cachelen, length) # 4k
853 852 if not df:
854 853 if inline:
855 854 df = self.opener(self.indexfile)
856 855 else:
857 856 df = self.opener(self.datafile)
858 857 df.seek(start)
859 858 self._chunkcache = (start, df.read(cache_length))
860 859
861 860 if not self._chunkcache:
862 861 loadcache(df)
863 862
864 863 cache_start = self._chunkcache[0]
865 864 cache_end = cache_start + len(self._chunkcache[1])
866 865 if start >= cache_start and end <= cache_end:
867 866 # it is cached
868 867 offset = start - cache_start
869 868 else:
870 869 loadcache(df)
871 870 offset = 0
872 871
873 872 return decompress(self._chunkcache[1][offset:offset + length])
874 873
875 874 def delta(self, node):
876 875 """return or calculate a delta between a node and its predecessor"""
877 876 r = self.rev(node)
878 877 return self.revdiff(r - 1, r)
879 878
880 879 def revdiff(self, rev1, rev2):
881 880 """return or calculate a delta between two revisions"""
882 881 b1 = self.base(rev1)
883 882 b2 = self.base(rev2)
884 883 if b1 == b2 and rev1 + 1 == rev2:
885 884 return self.chunk(rev2)
886 885 else:
887 886 return self.diff(self.revision(self.node(rev1)),
888 887 self.revision(self.node(rev2)))
889 888
890 889 def revision(self, node):
891 890 """return an uncompressed revision of a given"""
892 891 if node == nullid:
893 892 return ""
894 if self.cache and self.cache[0] == node:
895 return self.cache[2]
893 if self._cache and self._cache[0] == node:
894 return self._cache[2]
896 895
897 896 # look up what we need to read
898 897 text = None
899 898 rev = self.rev(node)
900 899 base = self.base(rev)
901 900
902 901 if self._inline:
903 902 # we probably have the whole chunk cached
904 903 df = None
905 904 else:
906 905 df = self.opener(self.datafile)
907 906
908 907 # do we have useful data cached?
909 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
910 base = self.cache[1]
911 text = self.cache[2]
908 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
909 base = self._cache[1]
910 text = self._cache[2]
912 911 self._loadindex(base, rev + 1)
913 912 else:
914 913 self._loadindex(base, rev + 1)
915 914 text = self.chunk(base, df=df)
916 915
917 916 bins = []
918 917 for r in xrange(base + 1, rev + 1):
919 918 bins.append(self.chunk(r, df=df))
920 919
921 920 text = self.patches(text, bins)
922 921
923 922 p1, p2 = self.parents(node)
924 923 if node != hash(text, p1, p2):
925 924 raise RevlogError(_("integrity check failed on %s:%d")
926 925 % (self.datafile, rev))
927 926
928 self.cache = (node, rev, text)
927 self._cache = (node, rev, text)
929 928 return text
930 929
931 930 def checkinlinesize(self, tr, fp=None):
932 931 if not self._inline:
933 932 return
934 933 if not fp:
935 934 fp = self.opener(self.indexfile, 'r')
936 935 fp.seek(0, 2)
937 936 size = fp.tell()
938 937 if size < 131072:
939 938 return
940 939 trinfo = tr.find(self.indexfile)
941 940 if trinfo == None:
942 941 raise RevlogError(_("%s not found in the transaction")
943 942 % self.indexfile)
944 943
945 944 trindex = trinfo[2]
946 945 dataoff = self.start(trindex)
947 946
948 947 tr.add(self.datafile, dataoff)
949 948 df = self.opener(self.datafile, 'w')
950 949 calc = self._io.size
951 950 for r in xrange(self.count()):
952 951 start = self.start(r) + (r + 1) * calc
953 952 length = self.length(r)
954 953 fp.seek(start)
955 954 d = fp.read(length)
956 955 df.write(d)
957 956 fp.close()
958 957 df.close()
959 958 fp = self.opener(self.indexfile, 'w', atomictemp=True)
960 959 self.version &= ~(REVLOGNGINLINEDATA)
961 960 self._inline = False
962 961 if self.count():
963 962 x = self.index[0]
964 963 e = struct.pack(indexformatng, *x)[4:]
965 964 l = struct.pack(versionformat, self.version)
966 965 fp.write(l)
967 966 fp.write(e)
968 967
969 968 for i in xrange(1, self.count()):
970 969 x = self.index[i]
971 970 e = struct.pack(indexformatng, *x)
972 971 fp.write(e)
973 972
974 973 # if we don't call rename, the temp file will never replace the
975 974 # real index
976 975 fp.rename()
977 976
978 977 tr.replace(self.indexfile, trindex * calc)
979 978 self._chunkcache = None
980 979
981 980 def addrevision(self, text, transaction, link, p1, p2, d=None):
982 981 """add a revision to the log
983 982
984 983 text - the revision data to add
985 984 transaction - the transaction object used for rollback
986 985 link - the linkrev data to add
987 986 p1, p2 - the parent nodeids of the revision
988 987 d - an optional precomputed delta
989 988 """
990 989 dfh = None
991 990 if not self._inline:
992 991 dfh = self.opener(self.datafile, "a")
993 992 ifh = self.opener(self.indexfile, "a+")
994 993 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
995 994
996 995 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
997 996 node = hash(text, p1, p2)
998 997 if node in self.nodemap:
999 998 return node
1000 999
1001 1000 curr = self.count()
1002 1001 prev = curr - 1
1003 1002 base = self.base(prev)
1004 1003 offset = self.end(prev)
1005 1004
1006 1005 if curr:
1007 1006 if not d:
1008 1007 ptext = self.revision(self.node(prev))
1009 1008 d = self.diff(ptext, text)
1010 1009 data = compress(d)
1011 1010 l = len(data[1]) + len(data[0])
1012 1011 dist = l + offset - self.start(base)
1013 1012
1014 1013 # full versions are inserted when the needed deltas
1015 1014 # become comparable to the uncompressed text
1016 1015 if not curr or dist > len(text) * 2:
1017 1016 data = compress(text)
1018 1017 l = len(data[1]) + len(data[0])
1019 1018 base = curr
1020 1019
1021 1020 e = (offset_type(offset, 0), l, len(text),
1022 1021 base, link, self.rev(p1), self.rev(p2), node)
1023 1022 self.index.insert(-1, e)
1024 1023 self.nodemap[node] = curr
1025 1024
1026 1025 if self.version == REVLOGV0:
1027 1026 e = (offset, l, base, link, p1, p2, node)
1028 1027 entry = struct.pack(indexformatv0, *e)
1029 1028 else:
1030 1029 entry = struct.pack(indexformatng, *e)
1031 1030 if not curr:
1032 1031 entry = struct.pack(versionformat, self.version) + entry[4:]
1033 1032
1034 1033 if not self._inline:
1035 1034 transaction.add(self.datafile, offset)
1036 1035 transaction.add(self.indexfile, curr * len(entry))
1037 1036 if data[0]:
1038 1037 dfh.write(data[0])
1039 1038 dfh.write(data[1])
1040 1039 dfh.flush()
1041 1040 ifh.write(entry)
1042 1041 else:
1043 1042 ifh.seek(0, 2)
1044 1043 transaction.add(self.indexfile, ifh.tell(), prev)
1045 1044 ifh.write(entry)
1046 1045 ifh.write(data[0])
1047 1046 ifh.write(data[1])
1048 1047 self.checkinlinesize(transaction, ifh)
1049 1048
1050 self.cache = (node, curr, text)
1049 self._cache = (node, curr, text)
1051 1050 return node
1052 1051
1053 1052 def ancestor(self, a, b):
1054 1053 """calculate the least common ancestor of nodes a and b"""
1055 1054
1056 1055 def parents(rev):
1057 1056 return [p for p in self.parentrevs(rev) if p != nullrev]
1058 1057
1059 1058 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1060 1059 if c is None:
1061 1060 return nullid
1062 1061
1063 1062 return self.node(c)
1064 1063
1065 1064 def group(self, nodelist, lookup, infocollect=None):
1066 1065 """calculate a delta group
1067 1066
1068 1067 Given a list of changeset revs, return a set of deltas and
1069 1068 metadata corresponding to nodes. the first delta is
1070 1069 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1071 1070 have this parent as it has all history before these
1072 1071 changesets. parent is parent[0]
1073 1072 """
1074 1073 revs = [self.rev(n) for n in nodelist]
1075 1074
1076 1075 # if we don't have any revisions touched by these changesets, bail
1077 1076 if not revs:
1078 1077 yield changegroup.closechunk()
1079 1078 return
1080 1079
1081 1080 # add the parent of the first rev
1082 1081 p = self.parents(self.node(revs[0]))[0]
1083 1082 revs.insert(0, self.rev(p))
1084 1083
1085 1084 # build deltas
1086 1085 for d in xrange(0, len(revs) - 1):
1087 1086 a, b = revs[d], revs[d + 1]
1088 1087 nb = self.node(b)
1089 1088
1090 1089 if infocollect is not None:
1091 1090 infocollect(nb)
1092 1091
1093 1092 d = self.revdiff(a, b)
1094 1093 p = self.parents(nb)
1095 1094 meta = nb + p[0] + p[1] + lookup(nb)
1096 1095 yield changegroup.genchunk("%s%s" % (meta, d))
1097 1096
1098 1097 yield changegroup.closechunk()
1099 1098
1100 1099 def addgroup(self, revs, linkmapper, transaction, unique=0):
1101 1100 """
1102 1101 add a delta group
1103 1102
1104 1103 given a set of deltas, add them to the revision log. the
1105 1104 first delta is against its parent, which should be in our
1106 1105 log, the rest are against the previous delta.
1107 1106 """
1108 1107
1109 1108 #track the base of the current delta log
1110 1109 r = self.count()
1111 1110 t = r - 1
1112 1111 node = None
1113 1112
1114 1113 base = prev = nullrev
1115 1114 start = end = textlen = 0
1116 1115 if r:
1117 1116 end = self.end(t)
1118 1117
1119 1118 ifh = self.opener(self.indexfile, "a+")
1120 1119 ifh.seek(0, 2)
1121 1120 transaction.add(self.indexfile, ifh.tell(), self.count())
1122 1121 if self._inline:
1123 1122 dfh = None
1124 1123 else:
1125 1124 transaction.add(self.datafile, end)
1126 1125 dfh = self.opener(self.datafile, "a")
1127 1126
1128 1127 # loop through our set of deltas
1129 1128 chain = None
1130 1129 for chunk in revs:
1131 1130 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1132 1131 link = linkmapper(cs)
1133 1132 if node in self.nodemap:
1134 1133 # this can happen if two branches make the same change
1135 1134 # if unique:
1136 1135 # raise RevlogError(_("already have %s") % hex(node[:4]))
1137 1136 chain = node
1138 1137 continue
1139 1138 delta = chunk[80:]
1140 1139
1141 1140 for p in (p1, p2):
1142 1141 if not p in self.nodemap:
1143 1142 raise LookupError(_("unknown parent %s") % short(p))
1144 1143
1145 1144 if not chain:
1146 1145 # retrieve the parent revision of the delta chain
1147 1146 chain = p1
1148 1147 if not chain in self.nodemap:
1149 1148 raise LookupError(_("unknown base %s") % short(chain[:4]))
1150 1149
1151 1150 # full versions are inserted when the needed deltas become
1152 1151 # comparable to the uncompressed text or when the previous
1153 1152 # version is not the one we have a delta against. We use
1154 1153 # the size of the previous full rev as a proxy for the
1155 1154 # current size.
1156 1155
1157 1156 if chain == prev:
1158 1157 tempd = compress(delta)
1159 1158 cdelta = tempd[0] + tempd[1]
1160 1159 textlen = mdiff.patchedsize(textlen, delta)
1161 1160
1162 1161 if chain != prev or (end - start + len(cdelta)) > textlen * 2:
1163 1162 # flush our writes here so we can read it in revision
1164 1163 if dfh:
1165 1164 dfh.flush()
1166 1165 ifh.flush()
1167 1166 text = self.revision(chain)
1168 1167 text = self.patches(text, [delta])
1169 1168 chk = self._addrevision(text, transaction, link, p1, p2, None,
1170 1169 ifh, dfh)
1171 1170 if not dfh and not self._inline:
1172 1171 # addrevision switched from inline to conventional
1173 1172 # reopen the index
1174 1173 dfh = self.opener(self.datafile, "a")
1175 1174 ifh = self.opener(self.indexfile, "a")
1176 1175 if chk != node:
1177 1176 raise RevlogError(_("consistency error adding group"))
1178 1177 textlen = len(text)
1179 1178 else:
1180 1179 e = (offset_type(end, 0), len(cdelta), textlen, base,
1181 1180 link, self.rev(p1), self.rev(p2), node)
1182 1181 self.index.insert(-1, e)
1183 1182 self.nodemap[node] = r
1184 1183 if self._inline:
1185 1184 ifh.write(struct.pack(indexformatng, *e))
1186 1185 ifh.write(cdelta)
1187 1186 self.checkinlinesize(transaction, ifh)
1188 1187 if not self._inline:
1189 1188 dfh = self.opener(self.datafile, "a")
1190 1189 ifh = self.opener(self.indexfile, "a")
1191 1190 else:
1192 1191 if self.version == REVLOGV0:
1193 1192 e = (end, len(cdelta), base, link, p1, p2, node)
1194 1193 entry = struct.pack(indexformatv0, *e)
1195 1194 else:
1196 1195 entry = struct.pack(indexformatng, *e)
1197 1196 dfh.write(cdelta)
1198 1197 ifh.write(entry)
1199 1198
1200 1199 t, r, chain, prev = r, r + 1, node, node
1201 1200 base = self.base(t)
1202 1201 start = self.start(base)
1203 1202 end = self.end(t)
1204 1203
1205 1204 return node
1206 1205
1207 1206 def strip(self, rev, minlink):
1208 1207 if self.count() == 0 or rev >= self.count():
1209 1208 return
1210 1209
1211 1210 if isinstance(self.index, lazyindex):
1212 1211 self._loadindexmap()
1213 1212
1214 1213 # When stripping away a revision, we need to make sure it
1215 1214 # does not actually belong to an older changeset.
1216 1215 # The minlink parameter defines the oldest revision
1217 1216 # we're allowed to strip away.
1218 1217 while minlink > self.index[rev][4]:
1219 1218 rev += 1
1220 1219 if rev >= self.count():
1221 1220 return
1222 1221
1223 1222 # first truncate the files on disk
1224 1223 end = self.start(rev)
1225 1224 if not self._inline:
1226 1225 df = self.opener(self.datafile, "a")
1227 1226 df.truncate(end)
1228 1227 end = rev * self._io.size
1229 1228 else:
1230 1229 end += rev * self._io.size
1231 1230
1232 1231 indexf = self.opener(self.indexfile, "a")
1233 1232 indexf.truncate(end)
1234 1233
1235 1234 # then reset internal state in memory to forget those revisions
1236 self.cache = None
1235 self._cache = None
1237 1236 self._chunkcache = None
1238 1237 for x in xrange(rev, self.count()):
1239 1238 del self.nodemap[self.node(x)]
1240 1239
1241 1240 del self.index[rev:-1]
1242 1241
1243 1242 def checksize(self):
1244 1243 expected = 0
1245 1244 if self.count():
1246 1245 expected = self.end(self.count() - 1)
1247 1246
1248 1247 try:
1249 1248 f = self.opener(self.datafile)
1250 1249 f.seek(0, 2)
1251 1250 actual = f.tell()
1252 1251 dd = actual - expected
1253 1252 except IOError, inst:
1254 1253 if inst.errno != errno.ENOENT:
1255 1254 raise
1256 1255 dd = 0
1257 1256
1258 1257 try:
1259 1258 f = self.opener(self.indexfile)
1260 1259 f.seek(0, 2)
1261 1260 actual = f.tell()
1262 1261 s = self._io.size
1263 1262 i = actual / s
1264 1263 di = actual - (i * s)
1265 1264 if self._inline:
1266 1265 databytes = 0
1267 1266 for r in xrange(self.count()):
1268 1267 databytes += self.length(r)
1269 1268 dd = 0
1270 1269 di = actual - self.count() * s - databytes
1271 1270 except IOError, inst:
1272 1271 if inst.errno != errno.ENOENT:
1273 1272 raise
1274 1273 di = 0
1275 1274
1276 1275 return (dd, di)
1277 1276
1278 1277
General Comments 0
You need to be logged in to leave comments. Login now