tikhomirov@36: /* tikhomirov@36: * Copyright (c) 2011 Artem Tikhomirov tikhomirov@36: */ tikhomirov@36: package com.tmate.hgkit.ll; tikhomirov@36: tikhomirov@36: import java.io.File; tikhomirov@36: import java.io.IOException; tikhomirov@36: import java.util.LinkedList; tikhomirov@36: import java.util.List; tikhomirov@36: tikhomirov@36: import com.tmate.hgkit.fs.DataAccess; tikhomirov@36: import com.tmate.hgkit.fs.DataAccessProvider; tikhomirov@36: tikhomirov@36: /** tikhomirov@36: * @see http://mercurial.selenic.com/wiki/BundleFormat tikhomirov@36: * tikhomirov@36: * @author artem tikhomirov@36: */ tikhomirov@36: public class HgBundle { tikhomirov@36: tikhomirov@36: private final File bundleFile; tikhomirov@36: private final DataAccessProvider accessProvider; tikhomirov@36: tikhomirov@36: public HgBundle(DataAccessProvider dap, File bundle) { tikhomirov@36: accessProvider = dap; tikhomirov@36: bundleFile = bundle; tikhomirov@36: } tikhomirov@36: tikhomirov@37: public void changes(HgRepository hgRepo) throws IOException { tikhomirov@37: DataAccess da = accessProvider.create(bundleFile); tikhomirov@41: DigestHelper dh = new DigestHelper(); tikhomirov@37: try { tikhomirov@37: List changelogGroup = readGroup(da); tikhomirov@39: if (changelogGroup.isEmpty()) { tikhomirov@39: throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log? tikhomirov@39: } tikhomirov@39: // XXX in fact, bundle not necessarily starts with the first revision missing in hgRepo tikhomirov@39: // need to 'scroll' till the last one common. tikhomirov@39: final Nodeid base = changelogGroup.get(0).firstParent(); tikhomirov@39: if (!hgRepo.getChangelog().isKnown(base)) { tikhomirov@39: throw new IllegalArgumentException("unknown parent"); tikhomirov@39: } tikhomirov@39: // BundleFormat wiki says: tikhomirov@39: // Each Changelog entry patches the result of all previous patches tikhomirov@39: // (the previous, or parent patch of a given patch p is the patch that has a node equal to p's p1 field) tikhomirov@39: byte[] baseRevContent = hgRepo.getChangelog().content(base); tikhomirov@37: for (GroupElement ge : changelogGroup) { tikhomirov@37: int resultLen = 10000; // XXX calculate based on baseRevContent.length and ge.patches tikhomirov@37: byte[] csetContent = RevlogStream.apply(baseRevContent, resultLen, ge.patches); tikhomirov@41: // wiki suggests sha1_digest(min(p1,p2) ++ max(p1,p2) ++ final_text), tikhomirov@42: dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid? tikhomirov@42: if (!ge.node().equalsTo(dh.asBinary())) { tikhomirov@42: throw new IllegalStateException("Integrity check failed on " + bundleFile + ", node:" + ge.node()); tikhomirov@42: } tikhomirov@37: Changeset cs = Changeset.parse(csetContent, 0, csetContent.length); tikhomirov@37: cs.dump(); tikhomirov@37: baseRevContent = csetContent; tikhomirov@37: } tikhomirov@37: } finally { tikhomirov@37: da.done(); tikhomirov@37: } tikhomirov@37: } tikhomirov@37: tikhomirov@37: public void dump() throws IOException { tikhomirov@36: DataAccess da = accessProvider.create(bundleFile); tikhomirov@36: try { tikhomirov@36: LinkedList names = new LinkedList(); tikhomirov@36: if (!da.isEmpty()) { tikhomirov@36: System.out.println("Changelog group"); tikhomirov@36: List changelogGroup = readGroup(da); tikhomirov@36: for (GroupElement ge : changelogGroup) { tikhomirov@37: System.out.printf(" %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size()); tikhomirov@36: } tikhomirov@36: System.out.println("Manifest group"); tikhomirov@36: List manifestGroup = readGroup(da); tikhomirov@36: for (GroupElement ge : manifestGroup) { tikhomirov@37: System.out.printf(" %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size()); tikhomirov@36: } tikhomirov@36: while (!da.isEmpty()) { tikhomirov@36: int fnameLen = da.readInt(); tikhomirov@36: if (fnameLen <= 4) { tikhomirov@36: break; // null chunk, the last one. tikhomirov@36: } tikhomirov@36: byte[] fname = new byte[fnameLen - 4]; tikhomirov@36: da.readBytes(fname, 0, fname.length); tikhomirov@36: names.add(new String(fname)); tikhomirov@36: List fileGroup = readGroup(da); tikhomirov@36: System.out.println(names.getLast()); tikhomirov@36: for (GroupElement ge : fileGroup) { tikhomirov@37: System.out.printf(" %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size()); tikhomirov@36: } tikhomirov@36: } tikhomirov@36: } tikhomirov@36: System.out.println(names.size()); tikhomirov@36: for (String s : names) { tikhomirov@36: System.out.println(s); tikhomirov@36: } tikhomirov@36: } finally { tikhomirov@36: da.done(); tikhomirov@36: } tikhomirov@36: } tikhomirov@36: tikhomirov@36: private static List readGroup(DataAccess da) throws IOException { tikhomirov@36: int len = da.readInt(); tikhomirov@36: LinkedList rv = new LinkedList(); tikhomirov@36: while (len > 4 && !da.isEmpty()) { tikhomirov@36: byte[] nb = new byte[80]; tikhomirov@36: da.readBytes(nb, 0, 80); tikhomirov@36: int dataLength = len-84; tikhomirov@36: LinkedList patches = new LinkedList(); tikhomirov@36: while (dataLength > 0) { tikhomirov@36: RevlogStream.PatchRecord pr = RevlogStream.PatchRecord.read(da); tikhomirov@36: patches.add(pr); tikhomirov@36: dataLength -= pr.len + 12; tikhomirov@36: } tikhomirov@36: rv.add(new GroupElement(nb, patches)); tikhomirov@36: len = da.isEmpty() ? 0 : da.readInt(); tikhomirov@36: } tikhomirov@36: return rv; tikhomirov@36: } tikhomirov@36: tikhomirov@36: static class GroupElement { tikhomirov@36: private byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192 tikhomirov@36: private List patches; tikhomirov@36: tikhomirov@36: GroupElement(byte[] fourNodeids, List patchList) { tikhomirov@36: assert fourNodeids != null && fourNodeids.length == 80; tikhomirov@36: // patchList.size() > 0 tikhomirov@36: header = fourNodeids; tikhomirov@36: patches = patchList; tikhomirov@36: } tikhomirov@36: public Nodeid node() { tikhomirov@36: return Nodeid.fromBinary(header, 0); tikhomirov@36: } tikhomirov@36: public Nodeid firstParent() { tikhomirov@36: return Nodeid.fromBinary(header, 20); tikhomirov@36: } tikhomirov@36: public Nodeid secondParent() { tikhomirov@36: return Nodeid.fromBinary(header, 40); tikhomirov@36: } tikhomirov@37: public Nodeid cset() { // cs seems to be changeset tikhomirov@36: return Nodeid.fromBinary(header, 60); tikhomirov@36: } tikhomirov@36: } tikhomirov@36: }