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