225 if accumulate: accumulate(t) |
225 if accumulate: accumulate(t) |
226 self.addrevision(t, transaction, linkseq.next(), e[1], e[2]) |
226 self.addrevision(t, transaction, linkseq.next(), e[1], e[2]) |
227 |
227 |
228 # return the unmerged heads for later resolving |
228 # return the unmerged heads for later resolving |
229 return (old, self.tip()) |
229 return (old, self.tip()) |
|
230 |
|
231 def group(self, linkmap): |
|
232 # given a list of changeset revs, return a set of deltas and |
|
233 # metadata corresponding to nodes the first delta is |
|
234 # parent(nodes[0]) -> nodes[0] the receiver is guaranteed to |
|
235 # have this parent as it has all history before these |
|
236 # changesets. parent is parent[0] |
|
237 |
|
238 revs = [] |
|
239 needed = {} |
|
240 |
|
241 # find file nodes/revs that match changeset revs |
|
242 for i in xrange(0, self.count()): |
|
243 if self.index[i][3] in linkmap: |
|
244 revs.append(i) |
|
245 needed[i] = 1 |
|
246 |
|
247 # if we don't have any revisions touched by these changesets, bail |
|
248 if not revs: return struct.pack(">l", 0) |
|
249 |
|
250 # add the parent of the first rev |
|
251 p = self.parents(self.node(revs[0]))[0] |
|
252 revs.insert(0, self.rev(p)) |
|
253 |
|
254 # for each delta that isn't contiguous in the log, we need to |
|
255 # reconstruct the base, reconstruct the result, and then |
|
256 # calculate the delta. We also need to do this where we've |
|
257 # stored a full version and not a delta |
|
258 for i in xrange(0, len(revs) - 1): |
|
259 a, b = revs[i], revs[i + 1] |
|
260 if a + 1 != b or self.base(b) == b: |
|
261 for j in xrange(self.base(a), a + 1): |
|
262 needed[j] = 1 |
|
263 for j in xrange(self.base(b), b + 1): |
|
264 needed[j] = 1 |
|
265 |
|
266 # calculate spans to retrieve from datafile |
|
267 needed = needed.keys() |
|
268 needed.sort() |
|
269 spans = [] |
|
270 for n in needed: |
|
271 if n < 0: continue |
|
272 o = self.start(n) |
|
273 l = self.length(n) |
|
274 spans.append((o, l, [(n, l)])) |
|
275 |
|
276 # merge spans |
|
277 merge = [spans.pop(0)] |
|
278 while spans: |
|
279 e = spans.pop(0) |
|
280 f = merge[-1] |
|
281 if e[0] == f[0] + f[1]: |
|
282 merge[-1] = (f[0], f[1] + e[1], f[2] + e[2]) |
|
283 else: |
|
284 merge.append(e) |
|
285 |
|
286 # read spans in, divide up chunks |
|
287 chunks = {} |
|
288 for span in merge: |
|
289 # we reopen the file for each span to make http happy for now |
|
290 f = self.opener(self.datafile) |
|
291 f.seek(span[0]) |
|
292 data = f.read(span[1]) |
|
293 |
|
294 # divide up the span |
|
295 pos = 0 |
|
296 for r, l in span[2]: |
|
297 chunks[r] = data[pos: pos + l] |
|
298 pos += l |
|
299 |
|
300 # helper to reconstruct intermediate versions |
|
301 def construct(text, base, rev): |
|
302 for r in range(base + 1, rev + 1): |
|
303 b = decompress(chunks[r]) |
|
304 text = self.patch(text, b) |
|
305 return text |
|
306 |
|
307 # build deltas |
|
308 deltas = [] |
|
309 for d in range(0, len(revs) - 1): |
|
310 a, b = revs[d], revs[d + 1] |
|
311 n = self.node(b) |
|
312 |
|
313 if a + 1 != b or self.base(b) == b: |
|
314 if a >= 0: |
|
315 base = self.base(a) |
|
316 ta = decompress(chunks[self.base(a)]) |
|
317 ta = construct(ta, base, a) |
|
318 else: |
|
319 ta = "" |
|
320 |
|
321 base = self.base(b) |
|
322 if a > base: |
|
323 base = a |
|
324 tb = ta |
|
325 else: |
|
326 tb = decompress(chunks[self.base(b)]) |
|
327 tb = construct(tb, base, b) |
|
328 d = self.diff(ta, tb) |
|
329 else: |
|
330 d = decompress(chunks[b]) |
|
331 |
|
332 p = self.parents(n) |
|
333 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)] |
|
334 l = struct.pack(">l", len(meta) + len(d) + 4) |
|
335 deltas.append(l + meta + d) |
|
336 |
|
337 l = struct.pack(">l", sum(map(len, deltas)) + 4) |
|
338 deltas.insert(0, l) |
|
339 return "".join(deltas) |
|
340 |
|
341 def addgroup(self, data, linkmapper, transaction): |
|
342 # given a set of deltas, add them to the revision log. the |
|
343 # first delta is against its parent, which should be in our |
|
344 # log, the rest are against the previous delta. |
|
345 |
|
346 if len(data) <= 4: return |
|
347 |
|
348 # retrieve the parent revision of the delta chain |
|
349 chain = data[28:48] |
|
350 text = self.revision(chain) |
|
351 |
|
352 # track the base of the current delta log |
|
353 r = self.count() |
|
354 t = r - 1 |
|
355 |
|
356 base = prev = -1 |
|
357 start = end = 0 |
|
358 if r: |
|
359 start = self.start(self.base(t)) |
|
360 end = self.end(t) |
|
361 measure = self.length(self.base(t)) |
|
362 base = self.base(t) |
|
363 prev = self.tip() |
|
364 |
|
365 transaction.add(self.datafile, end) |
|
366 transaction.add(self.indexfile, r * struct.calcsize(indexformat)) |
|
367 dfh = self.opener(self.datafile, "a") |
|
368 ifh = self.opener(self.indexfile, "a") |
|
369 |
|
370 # loop through our set of deltas |
|
371 pos = 4 |
|
372 while pos < len(data): |
|
373 l, node, p1, p2, cs = struct.unpack(">l20s20s20s20s", |
|
374 data[pos:pos+84]) |
|
375 link = linkmapper(cs) |
|
376 delta = data[pos + 84:pos + l] |
|
377 pos += l |
|
378 |
|
379 # full versions are inserted when the needed deltas become |
|
380 # comparable to the uncompressed text or when the previous |
|
381 # version is not the one we have a delta against. We use |
|
382 # the size of the previous full rev as a proxy for the |
|
383 # current size. |
|
384 |
|
385 if chain == prev: |
|
386 cdelta = compress(delta) |
|
387 |
|
388 if chain != prev or (end - start + len(cdelta)) > measure * 2: |
|
389 # flush our writes here so we can read it in revision |
|
390 dfh.flush() |
|
391 ifh.flush() |
|
392 text = self.revision(self.node(t)) |
|
393 text = self.patch(text, delta) |
|
394 chk = self.addrevision(text, transaction, link, p1, p2) |
|
395 if chk != node: |
|
396 raise "consistency error adding group" |
|
397 measure = len(text) |
|
398 else: |
|
399 e = (end, len(cdelta), self.base(t), link, p1, p2, node) |
|
400 self.index.append(e) |
|
401 self.nodemap[node] = r |
|
402 dfh.write(cdelta) |
|
403 ifh.write(struct.pack(indexformat, *e)) |
|
404 |
|
405 t, r = r, r + 1 |
|
406 chain = prev |
|
407 start = self.start(self.base(t)) |
|
408 end = self.end(t) |
|
409 |
|
410 dfh.close() |
|
411 ifh.close() |
|
412 return node |