##// END OF EJS Templates
Add code to retrieve or construct a revlog delta
mpm@selenic.com -
r119:c7a66f97 default
parent child Browse files
Show More
@@ -1,492 +1,504
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # This provides efficient delta storage with O(1) retrieve and append
3 # This provides efficient delta storage with O(1) retrieve and append
4 # and O(changes) merge between branches
4 # and O(changes) merge between branches
5 #
5 #
6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
7 #
7 #
8 # This software may be used and distributed according to the terms
8 # This software may be used and distributed according to the terms
9 # of the GNU General Public License, incorporated herein by reference.
9 # of the GNU General Public License, incorporated herein by reference.
10
10
11 import zlib, struct, sha, os, tempfile, binascii
11 import zlib, struct, sha, os, tempfile, binascii
12 from mercurial import mdiff
12 from mercurial import mdiff
13
13
14 def hex(node): return binascii.hexlify(node)
14 def hex(node): return binascii.hexlify(node)
15 def bin(node): return binascii.unhexlify(node)
15 def bin(node): return binascii.unhexlify(node)
16 def short(node): return hex(node[:4])
16 def short(node): return hex(node[:4])
17
17
18 def compress(text):
18 def compress(text):
19 if not text: return text
19 if not text: return text
20 if len(text) < 44:
20 if len(text) < 44:
21 if text[0] == '\0': return text
21 if text[0] == '\0': return text
22 return 'u' + text
22 return 'u' + text
23 bin = zlib.compress(text)
23 bin = zlib.compress(text)
24 if len(bin) > len(text):
24 if len(bin) > len(text):
25 if text[0] == '\0': return text
25 if text[0] == '\0': return text
26 return 'u' + text
26 return 'u' + text
27 return bin
27 return bin
28
28
29 def decompress(bin):
29 def decompress(bin):
30 if not bin: return bin
30 if not bin: return bin
31 t = bin[0]
31 t = bin[0]
32 if t == '\0': return bin
32 if t == '\0': return bin
33 if t == 'x': return zlib.decompress(bin)
33 if t == 'x': return zlib.decompress(bin)
34 if t == 'u': return bin[1:]
34 if t == 'u': return bin[1:]
35 raise "unknown compression type %s" % t
35 raise "unknown compression type %s" % t
36
36
37 def hash(text, p1, p2):
37 def hash(text, p1, p2):
38 l = [p1, p2]
38 l = [p1, p2]
39 l.sort()
39 l.sort()
40 return sha.sha(l[0] + l[1] + text).digest()
40 return sha.sha(l[0] + l[1] + text).digest()
41
41
42 nullid = "\0" * 20
42 nullid = "\0" * 20
43 indexformat = ">4l20s20s20s"
43 indexformat = ">4l20s20s20s"
44
44
45 class lazyparser:
45 class lazyparser:
46 def __init__(self, data):
46 def __init__(self, data):
47 self.data = data
47 self.data = data
48 self.s = struct.calcsize(indexformat)
48 self.s = struct.calcsize(indexformat)
49 self.l = len(data)/self.s
49 self.l = len(data)/self.s
50 self.index = [None] * self.l
50 self.index = [None] * self.l
51 self.map = {nullid: -1}
51 self.map = {nullid: -1}
52
52
53 if 0:
53 if 0:
54 n = 0
54 n = 0
55 i = self.data
55 i = self.data
56 s = struct.calcsize(indexformat)
56 s = struct.calcsize(indexformat)
57 for f in xrange(0, len(i), s):
57 for f in xrange(0, len(i), s):
58 # offset, size, base, linkrev, p1, p2, nodeid
58 # offset, size, base, linkrev, p1, p2, nodeid
59 e = struct.unpack(indexformat, i[f:f + s])
59 e = struct.unpack(indexformat, i[f:f + s])
60 self.map[e[6]] = n
60 self.map[e[6]] = n
61 self.index.append(e)
61 self.index.append(e)
62 n += 1
62 n += 1
63
63
64 def load(self, pos):
64 def load(self, pos):
65 block = pos / 1000
65 block = pos / 1000
66 i = block * 1000
66 i = block * 1000
67 end = min(self.l, i + 1000)
67 end = min(self.l, i + 1000)
68 while i < end:
68 while i < end:
69 d = self.data[i * self.s: (i + 1) * self.s]
69 d = self.data[i * self.s: (i + 1) * self.s]
70 e = struct.unpack(indexformat, d)
70 e = struct.unpack(indexformat, d)
71 self.index[i] = e
71 self.index[i] = e
72 self.map[e[6]] = i
72 self.map[e[6]] = i
73 i += 1
73 i += 1
74
74
75 class lazyindex:
75 class lazyindex:
76 def __init__(self, parser):
76 def __init__(self, parser):
77 self.p = parser
77 self.p = parser
78 def __len__(self):
78 def __len__(self):
79 return len(self.p.index)
79 return len(self.p.index)
80 def load(self, pos):
80 def load(self, pos):
81 self.p.load(pos)
81 self.p.load(pos)
82 return self.p.index[pos]
82 return self.p.index[pos]
83 def __getitem__(self, pos):
83 def __getitem__(self, pos):
84 return self.p.index[pos] or self.load(pos)
84 return self.p.index[pos] or self.load(pos)
85 def append(self, e):
85 def append(self, e):
86 self.p.index.append(e)
86 self.p.index.append(e)
87
87
88 class lazymap:
88 class lazymap:
89 def __init__(self, parser):
89 def __init__(self, parser):
90 self.p = parser
90 self.p = parser
91 def load(self, key):
91 def load(self, key):
92 n = self.p.data.find(key)
92 n = self.p.data.find(key)
93 if n < 0: raise KeyError("node " + hex(key))
93 if n < 0: raise KeyError("node " + hex(key))
94 pos = n / self.p.s
94 pos = n / self.p.s
95 self.p.load(pos)
95 self.p.load(pos)
96 def __contains__(self, key):
96 def __contains__(self, key):
97 try:
97 try:
98 self[key]
98 self[key]
99 return True
99 return True
100 except KeyError:
100 except KeyError:
101 return False
101 return False
102 def __iter__(self):
102 def __iter__(self):
103 for i in xrange(self.p.l):
103 for i in xrange(self.p.l):
104 try:
104 try:
105 yield self.p.index[i][6]
105 yield self.p.index[i][6]
106 except:
106 except:
107 self.p.load(i)
107 self.p.load(i)
108 yield self.p.index[i][6]
108 yield self.p.index[i][6]
109 def __getitem__(self, key):
109 def __getitem__(self, key):
110 try:
110 try:
111 return self.p.map[key]
111 return self.p.map[key]
112 except KeyError:
112 except KeyError:
113 try:
113 try:
114 self.load(key)
114 self.load(key)
115 return self.p.map[key]
115 return self.p.map[key]
116 except KeyError:
116 except KeyError:
117 raise KeyError("node " + hex(key))
117 raise KeyError("node " + hex(key))
118 def __setitem__(self, key, val):
118 def __setitem__(self, key, val):
119 self.p.map[key] = val
119 self.p.map[key] = val
120
120
121 class revlog:
121 class revlog:
122 def __init__(self, opener, indexfile, datafile):
122 def __init__(self, opener, indexfile, datafile):
123 self.indexfile = indexfile
123 self.indexfile = indexfile
124 self.datafile = datafile
124 self.datafile = datafile
125 self.opener = opener
125 self.opener = opener
126 self.cache = None
126 self.cache = None
127
127
128 try:
128 try:
129 i = self.opener(self.indexfile).read()
129 i = self.opener(self.indexfile).read()
130 except IOError:
130 except IOError:
131 i = ""
131 i = ""
132
132
133 if len(i) > 10000:
133 if len(i) > 10000:
134 # big index, let's parse it on demand
134 # big index, let's parse it on demand
135 parser = lazyparser(i)
135 parser = lazyparser(i)
136 self.index = lazyindex(parser)
136 self.index = lazyindex(parser)
137 self.nodemap = lazymap(parser)
137 self.nodemap = lazymap(parser)
138 else:
138 else:
139 s = struct.calcsize(indexformat)
139 s = struct.calcsize(indexformat)
140 l = len(i) / s
140 l = len(i) / s
141 self.index = [None] * l
141 self.index = [None] * l
142 m = [None] * l
142 m = [None] * l
143
143
144 n = 0
144 n = 0
145 for f in xrange(0, len(i), s):
145 for f in xrange(0, len(i), s):
146 # offset, size, base, linkrev, p1, p2, nodeid
146 # offset, size, base, linkrev, p1, p2, nodeid
147 e = struct.unpack(indexformat, i[f:f + s])
147 e = struct.unpack(indexformat, i[f:f + s])
148 m[n] = (e[6], n)
148 m[n] = (e[6], n)
149 self.index[n] = e
149 self.index[n] = e
150 n += 1
150 n += 1
151
151
152 self.nodemap = dict(m)
152 self.nodemap = dict(m)
153 self.nodemap[nullid] = -1
153 self.nodemap[nullid] = -1
154
154
155
155
156 def tip(self): return self.node(len(self.index) - 1)
156 def tip(self): return self.node(len(self.index) - 1)
157 def count(self): return len(self.index)
157 def count(self): return len(self.index)
158 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
158 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
159 def rev(self, node): return self.nodemap[node]
159 def rev(self, node): return self.nodemap[node]
160 def linkrev(self, node): return self.index[self.nodemap[node]][3]
160 def linkrev(self, node): return self.index[self.nodemap[node]][3]
161 def parents(self, node):
161 def parents(self, node):
162 if node == nullid: return (nullid, nullid)
162 if node == nullid: return (nullid, nullid)
163 return self.index[self.nodemap[node]][4:6]
163 return self.index[self.nodemap[node]][4:6]
164
164
165 def start(self, rev): return self.index[rev][0]
165 def start(self, rev): return self.index[rev][0]
166 def length(self, rev): return self.index[rev][1]
166 def length(self, rev): return self.index[rev][1]
167 def end(self, rev): return self.start(rev) + self.length(rev)
167 def end(self, rev): return self.start(rev) + self.length(rev)
168 def base(self, rev): return self.index[rev][2]
168 def base(self, rev): return self.index[rev][2]
169
169
170 def lookup(self, id):
170 def lookup(self, id):
171 try:
171 try:
172 rev = int(id)
172 rev = int(id)
173 return self.node(rev)
173 return self.node(rev)
174 except ValueError:
174 except ValueError:
175 c = []
175 c = []
176 for n in self.nodemap:
176 for n in self.nodemap:
177 if id in hex(n):
177 if id in hex(n):
178 c.append(n)
178 c.append(n)
179 if len(c) > 1: raise KeyError("Ambiguous identifier")
179 if len(c) > 1: raise KeyError("Ambiguous identifier")
180 if len(c) < 1: raise KeyError("No match found")
180 if len(c) < 1: raise KeyError("No match found")
181 return c[0]
181 return c[0]
182
182
183 return None
183 return None
184
184
185 def diff(self, a, b):
185 def diff(self, a, b):
186 return mdiff.textdiff(a, b)
186 return mdiff.textdiff(a, b)
187
187
188 def patches(self, t, pl):
188 def patches(self, t, pl):
189 return mdiff.patches(t, pl)
189 return mdiff.patches(t, pl)
190
190
191 def delta(self, node):
192 r = self.rev(node)
193 b = self.base(r)
194 if r == b:
195 return self.diff(self.revision(self.node(r - 1)),
196 self.revision(node))
197 else:
198 f = self.opener(self.datafile)
199 f.seek(self.start(r))
200 data = f.read(self.length(r))
201 return decompress(data)
202
191 def revision(self, node):
203 def revision(self, node):
192 if node == nullid: return ""
204 if node == nullid: return ""
193 if self.cache and self.cache[0] == node: return self.cache[2]
205 if self.cache and self.cache[0] == node: return self.cache[2]
194
206
195 text = None
207 text = None
196 rev = self.rev(node)
208 rev = self.rev(node)
197 start, length, base, link, p1, p2, node = self.index[rev]
209 start, length, base, link, p1, p2, node = self.index[rev]
198 end = start + length
210 end = start + length
199 if base != rev: start = self.start(base)
211 if base != rev: start = self.start(base)
200
212
201 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
213 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
202 base = self.cache[1]
214 base = self.cache[1]
203 start = self.start(base + 1)
215 start = self.start(base + 1)
204 text = self.cache[2]
216 text = self.cache[2]
205 last = 0
217 last = 0
206
218
207 f = self.opener(self.datafile)
219 f = self.opener(self.datafile)
208 f.seek(start)
220 f.seek(start)
209 data = f.read(end - start)
221 data = f.read(end - start)
210
222
211 if not text:
223 if not text:
212 last = self.length(base)
224 last = self.length(base)
213 text = decompress(data[:last])
225 text = decompress(data[:last])
214
226
215 bins = []
227 bins = []
216 for r in xrange(base + 1, rev + 1):
228 for r in xrange(base + 1, rev + 1):
217 s = self.length(r)
229 s = self.length(r)
218 bins.append(decompress(data[last:last + s]))
230 bins.append(decompress(data[last:last + s]))
219 last = last + s
231 last = last + s
220
232
221 text = mdiff.patches(text, bins)
233 text = mdiff.patches(text, bins)
222
234
223 if node != hash(text, p1, p2):
235 if node != hash(text, p1, p2):
224 raise IOError("integrity check failed on %s:%d"
236 raise IOError("integrity check failed on %s:%d"
225 % (self.datafile, rev))
237 % (self.datafile, rev))
226
238
227 self.cache = (node, rev, text)
239 self.cache = (node, rev, text)
228 return text
240 return text
229
241
230 def addrevision(self, text, transaction, link, p1=None, p2=None):
242 def addrevision(self, text, transaction, link, p1=None, p2=None):
231 if text is None: text = ""
243 if text is None: text = ""
232 if p1 is None: p1 = self.tip()
244 if p1 is None: p1 = self.tip()
233 if p2 is None: p2 = nullid
245 if p2 is None: p2 = nullid
234
246
235 node = hash(text, p1, p2)
247 node = hash(text, p1, p2)
236
248
237 n = self.count()
249 n = self.count()
238 t = n - 1
250 t = n - 1
239
251
240 if n:
252 if n:
241 base = self.base(t)
253 base = self.base(t)
242 start = self.start(base)
254 start = self.start(base)
243 end = self.end(t)
255 end = self.end(t)
244 prev = self.revision(self.tip())
256 prev = self.revision(self.tip())
245 d = self.diff(prev, text)
257 d = self.diff(prev, text)
246 if self.patches(prev, [d]) != text:
258 if self.patches(prev, [d]) != text:
247 raise AssertionError("diff failed")
259 raise AssertionError("diff failed")
248 data = compress(d)
260 data = compress(d)
249 dist = end - start + len(data)
261 dist = end - start + len(data)
250
262
251 # full versions are inserted when the needed deltas
263 # full versions are inserted when the needed deltas
252 # become comparable to the uncompressed text
264 # become comparable to the uncompressed text
253 if not n or dist > len(text) * 2:
265 if not n or dist > len(text) * 2:
254 data = compress(text)
266 data = compress(text)
255 base = n
267 base = n
256 else:
268 else:
257 base = self.base(t)
269 base = self.base(t)
258
270
259 offset = 0
271 offset = 0
260 if t >= 0:
272 if t >= 0:
261 offset = self.end(t)
273 offset = self.end(t)
262
274
263 e = (offset, len(data), base, link, p1, p2, node)
275 e = (offset, len(data), base, link, p1, p2, node)
264
276
265 self.index.append(e)
277 self.index.append(e)
266 self.nodemap[node] = n
278 self.nodemap[node] = n
267 entry = struct.pack(indexformat, *e)
279 entry = struct.pack(indexformat, *e)
268
280
269 transaction.add(self.datafile, e[0])
281 transaction.add(self.datafile, e[0])
270 self.opener(self.datafile, "a").write(data)
282 self.opener(self.datafile, "a").write(data)
271 transaction.add(self.indexfile, n * len(entry))
283 transaction.add(self.indexfile, n * len(entry))
272 self.opener(self.indexfile, "a").write(entry)
284 self.opener(self.indexfile, "a").write(entry)
273
285
274 self.cache = (node, n, text)
286 self.cache = (node, n, text)
275 return node
287 return node
276
288
277 def ancestor(self, a, b):
289 def ancestor(self, a, b):
278 def expand(list, map):
290 def expand(list, map):
279 a = []
291 a = []
280 while list:
292 while list:
281 n = list.pop(0)
293 n = list.pop(0)
282 map[n] = 1
294 map[n] = 1
283 yield n
295 yield n
284 for p in self.parents(n):
296 for p in self.parents(n):
285 if p != nullid and p not in map:
297 if p != nullid and p not in map:
286 list.append(p)
298 list.append(p)
287 yield nullid
299 yield nullid
288
300
289 amap = {}
301 amap = {}
290 bmap = {}
302 bmap = {}
291 ag = expand([a], amap)
303 ag = expand([a], amap)
292 bg = expand([b], bmap)
304 bg = expand([b], bmap)
293 adone = bdone = 0
305 adone = bdone = 0
294
306
295 while not adone or not bdone:
307 while not adone or not bdone:
296 if not adone:
308 if not adone:
297 an = ag.next()
309 an = ag.next()
298 if an == nullid:
310 if an == nullid:
299 adone = 1
311 adone = 1
300 elif an in bmap:
312 elif an in bmap:
301 return an
313 return an
302 if not bdone:
314 if not bdone:
303 bn = bg.next()
315 bn = bg.next()
304 if bn == nullid:
316 if bn == nullid:
305 bdone = 1
317 bdone = 1
306 elif bn in amap:
318 elif bn in amap:
307 return bn
319 return bn
308
320
309 return nullid
321 return nullid
310
322
311 def group(self, linkmap):
323 def group(self, linkmap):
312 # given a list of changeset revs, return a set of deltas and
324 # given a list of changeset revs, return a set of deltas and
313 # metadata corresponding to nodes. the first delta is
325 # metadata corresponding to nodes. the first delta is
314 # parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
326 # parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
315 # have this parent as it has all history before these
327 # have this parent as it has all history before these
316 # changesets. parent is parent[0]
328 # changesets. parent is parent[0]
317
329
318 revs = []
330 revs = []
319 needed = {}
331 needed = {}
320
332
321 # find file nodes/revs that match changeset revs
333 # find file nodes/revs that match changeset revs
322 for i in xrange(0, self.count()):
334 for i in xrange(0, self.count()):
323 if self.index[i][3] in linkmap:
335 if self.index[i][3] in linkmap:
324 revs.append(i)
336 revs.append(i)
325 needed[i] = 1
337 needed[i] = 1
326
338
327 # if we don't have any revisions touched by these changesets, bail
339 # if we don't have any revisions touched by these changesets, bail
328 if not revs: return struct.pack(">l", 0)
340 if not revs: return struct.pack(">l", 0)
329
341
330 # add the parent of the first rev
342 # add the parent of the first rev
331 p = self.parents(self.node(revs[0]))[0]
343 p = self.parents(self.node(revs[0]))[0]
332 revs.insert(0, self.rev(p))
344 revs.insert(0, self.rev(p))
333
345
334 # for each delta that isn't contiguous in the log, we need to
346 # for each delta that isn't contiguous in the log, we need to
335 # reconstruct the base, reconstruct the result, and then
347 # reconstruct the base, reconstruct the result, and then
336 # calculate the delta. We also need to do this where we've
348 # calculate the delta. We also need to do this where we've
337 # stored a full version and not a delta
349 # stored a full version and not a delta
338 for i in xrange(0, len(revs) - 1):
350 for i in xrange(0, len(revs) - 1):
339 a, b = revs[i], revs[i + 1]
351 a, b = revs[i], revs[i + 1]
340 if a + 1 != b or self.base(b) == b:
352 if a + 1 != b or self.base(b) == b:
341 for j in xrange(self.base(a), a + 1):
353 for j in xrange(self.base(a), a + 1):
342 needed[j] = 1
354 needed[j] = 1
343 for j in xrange(self.base(b), b + 1):
355 for j in xrange(self.base(b), b + 1):
344 needed[j] = 1
356 needed[j] = 1
345
357
346 # calculate spans to retrieve from datafile
358 # calculate spans to retrieve from datafile
347 needed = needed.keys()
359 needed = needed.keys()
348 needed.sort()
360 needed.sort()
349 spans = []
361 spans = []
350 for n in needed:
362 for n in needed:
351 if n < 0: continue
363 if n < 0: continue
352 o = self.start(n)
364 o = self.start(n)
353 l = self.length(n)
365 l = self.length(n)
354 spans.append((o, l, [(n, l)]))
366 spans.append((o, l, [(n, l)]))
355
367
356 # merge spans
368 # merge spans
357 merge = [spans.pop(0)]
369 merge = [spans.pop(0)]
358 while spans:
370 while spans:
359 e = spans.pop(0)
371 e = spans.pop(0)
360 f = merge[-1]
372 f = merge[-1]
361 if e[0] == f[0] + f[1]:
373 if e[0] == f[0] + f[1]:
362 merge[-1] = (f[0], f[1] + e[1], f[2] + e[2])
374 merge[-1] = (f[0], f[1] + e[1], f[2] + e[2])
363 else:
375 else:
364 merge.append(e)
376 merge.append(e)
365
377
366 # read spans in, divide up chunks
378 # read spans in, divide up chunks
367 chunks = {}
379 chunks = {}
368 for span in merge:
380 for span in merge:
369 # we reopen the file for each span to make http happy for now
381 # we reopen the file for each span to make http happy for now
370 f = self.opener(self.datafile)
382 f = self.opener(self.datafile)
371 f.seek(span[0])
383 f.seek(span[0])
372 data = f.read(span[1])
384 data = f.read(span[1])
373
385
374 # divide up the span
386 # divide up the span
375 pos = 0
387 pos = 0
376 for r, l in span[2]:
388 for r, l in span[2]:
377 chunks[r] = data[pos: pos + l]
389 chunks[r] = data[pos: pos + l]
378 pos += l
390 pos += l
379
391
380 # helper to reconstruct intermediate versions
392 # helper to reconstruct intermediate versions
381 def construct(text, base, rev):
393 def construct(text, base, rev):
382 bins = [decompress(chunks[r]) for r in xrange(base + 1, rev + 1)]
394 bins = [decompress(chunks[r]) for r in xrange(base + 1, rev + 1)]
383 return mdiff.patches(text, bins)
395 return mdiff.patches(text, bins)
384
396
385 # build deltas
397 # build deltas
386 deltas = []
398 deltas = []
387 for d in xrange(0, len(revs) - 1):
399 for d in xrange(0, len(revs) - 1):
388 a, b = revs[d], revs[d + 1]
400 a, b = revs[d], revs[d + 1]
389 n = self.node(b)
401 n = self.node(b)
390
402
391 if a + 1 != b or self.base(b) == b:
403 if a + 1 != b or self.base(b) == b:
392 if a >= 0:
404 if a >= 0:
393 base = self.base(a)
405 base = self.base(a)
394 ta = decompress(chunks[self.base(a)])
406 ta = decompress(chunks[self.base(a)])
395 ta = construct(ta, base, a)
407 ta = construct(ta, base, a)
396 else:
408 else:
397 ta = ""
409 ta = ""
398
410
399 base = self.base(b)
411 base = self.base(b)
400 if a > base:
412 if a > base:
401 base = a
413 base = a
402 tb = ta
414 tb = ta
403 else:
415 else:
404 tb = decompress(chunks[self.base(b)])
416 tb = decompress(chunks[self.base(b)])
405 tb = construct(tb, base, b)
417 tb = construct(tb, base, b)
406 d = self.diff(ta, tb)
418 d = self.diff(ta, tb)
407 else:
419 else:
408 d = decompress(chunks[b])
420 d = decompress(chunks[b])
409
421
410 p = self.parents(n)
422 p = self.parents(n)
411 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
423 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
412 l = struct.pack(">l", len(meta) + len(d) + 4)
424 l = struct.pack(">l", len(meta) + len(d) + 4)
413 deltas.append(l + meta + d)
425 deltas.append(l + meta + d)
414
426
415 l = struct.pack(">l", sum(map(len, deltas)) + 4)
427 l = struct.pack(">l", sum(map(len, deltas)) + 4)
416 deltas.insert(0, l)
428 deltas.insert(0, l)
417 return "".join(deltas)
429 return "".join(deltas)
418
430
419 def addgroup(self, data, linkmapper, transaction):
431 def addgroup(self, data, linkmapper, transaction):
420 # given a set of deltas, add them to the revision log. the
432 # given a set of deltas, add them to the revision log. the
421 # first delta is against its parent, which should be in our
433 # first delta is against its parent, which should be in our
422 # log, the rest are against the previous delta.
434 # log, the rest are against the previous delta.
423
435
424 if not data: return self.tip()
436 if not data: return self.tip()
425
437
426 # retrieve the parent revision of the delta chain
438 # retrieve the parent revision of the delta chain
427 chain = data[24:44]
439 chain = data[24:44]
428 if not chain in self.nodemap:
440 if not chain in self.nodemap:
429 raise "unknown base %s" % short(chain[:4])
441 raise "unknown base %s" % short(chain[:4])
430
442
431 # track the base of the current delta log
443 # track the base of the current delta log
432 r = self.count()
444 r = self.count()
433 t = r - 1
445 t = r - 1
434
446
435 base = prev = -1
447 base = prev = -1
436 start = end = 0
448 start = end = 0
437 if r:
449 if r:
438 start = self.start(self.base(t))
450 start = self.start(self.base(t))
439 end = self.end(t)
451 end = self.end(t)
440 measure = self.length(self.base(t))
452 measure = self.length(self.base(t))
441 base = self.base(t)
453 base = self.base(t)
442 prev = self.tip()
454 prev = self.tip()
443
455
444 transaction.add(self.datafile, end)
456 transaction.add(self.datafile, end)
445 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
457 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
446 dfh = self.opener(self.datafile, "a")
458 dfh = self.opener(self.datafile, "a")
447 ifh = self.opener(self.indexfile, "a")
459 ifh = self.opener(self.indexfile, "a")
448
460
449 # loop through our set of deltas
461 # loop through our set of deltas
450 pos = 0
462 pos = 0
451 while pos < len(data):
463 while pos < len(data):
452 l, node, p1, p2, cs = struct.unpack(">l20s20s20s20s",
464 l, node, p1, p2, cs = struct.unpack(">l20s20s20s20s",
453 data[pos:pos+84])
465 data[pos:pos+84])
454 link = linkmapper(cs)
466 link = linkmapper(cs)
455 if node in self.nodemap:
467 if node in self.nodemap:
456 raise "already have %s" % hex(node[:4])
468 raise "already have %s" % hex(node[:4])
457 delta = data[pos + 84:pos + l]
469 delta = data[pos + 84:pos + l]
458 pos += l
470 pos += l
459
471
460 # full versions are inserted when the needed deltas become
472 # full versions are inserted when the needed deltas become
461 # comparable to the uncompressed text or when the previous
473 # comparable to the uncompressed text or when the previous
462 # version is not the one we have a delta against. We use
474 # version is not the one we have a delta against. We use
463 # the size of the previous full rev as a proxy for the
475 # the size of the previous full rev as a proxy for the
464 # current size.
476 # current size.
465
477
466 if chain == prev:
478 if chain == prev:
467 cdelta = compress(delta)
479 cdelta = compress(delta)
468
480
469 if chain != prev or (end - start + len(cdelta)) > measure * 2:
481 if chain != prev or (end - start + len(cdelta)) > measure * 2:
470 # flush our writes here so we can read it in revision
482 # flush our writes here so we can read it in revision
471 dfh.flush()
483 dfh.flush()
472 ifh.flush()
484 ifh.flush()
473 text = self.revision(chain)
485 text = self.revision(chain)
474 text = self.patches(text, [delta])
486 text = self.patches(text, [delta])
475 chk = self.addrevision(text, transaction, link, p1, p2)
487 chk = self.addrevision(text, transaction, link, p1, p2)
476 if chk != node:
488 if chk != node:
477 raise "consistency error adding group"
489 raise "consistency error adding group"
478 measure = len(text)
490 measure = len(text)
479 else:
491 else:
480 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
492 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
481 self.index.append(e)
493 self.index.append(e)
482 self.nodemap[node] = r
494 self.nodemap[node] = r
483 dfh.write(cdelta)
495 dfh.write(cdelta)
484 ifh.write(struct.pack(indexformat, *e))
496 ifh.write(struct.pack(indexformat, *e))
485
497
486 t, r, chain, prev = r, r + 1, node, node
498 t, r, chain, prev = r, r + 1, node, node
487 start = self.start(self.base(t))
499 start = self.start(self.base(t))
488 end = self.end(t)
500 end = self.end(t)
489
501
490 dfh.close()
502 dfh.close()
491 ifh.close()
503 ifh.close()
492 return node
504 return node
General Comments 0
You need to be logged in to leave comments. Login now