diff src/org/tmatesoft/hg/repo/HgBundle.java @ 74:6f1b88693d48

Complete refactoring to org.tmatesoft
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 24 Jan 2011 03:14:45 +0100
parents src/com/tmate/hgkit/ll/HgBundle.java@b01500fe2604
children c677e1593919
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgBundle.java	Mon Jan 24 03:14:45 2011 +0100
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@svnkit.com
+ */
+package org.tmatesoft.hg.repo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.DataAccessProvider;
+import org.tmatesoft.hg.internal.DigestHelper;
+
+
+/**
+ * @see http://mercurial.selenic.com/wiki/BundleFormat
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgBundle {
+
+	private final File bundleFile;
+	private final DataAccessProvider accessProvider;
+
+	public HgBundle(DataAccessProvider dap, File bundle) {
+		accessProvider = dap;
+		bundleFile = bundle;
+	}
+
+	public void changes(HgRepository hgRepo) throws IOException {
+		DataAccess da = accessProvider.create(bundleFile);
+		DigestHelper dh = new DigestHelper();
+		try {
+			List<GroupElement> changelogGroup = readGroup(da);
+			if (changelogGroup.isEmpty()) {
+				throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log?
+			}
+			// XXX in fact, bundle not necessarily starts with the first revision missing in hgRepo
+			// need to 'scroll' till the last one common.
+			final Nodeid base = changelogGroup.get(0).firstParent();
+			if (!hgRepo.getChangelog().isKnown(base)) {
+				throw new IllegalArgumentException("unknown parent");
+			}
+			// BundleFormat wiki says:
+			// Each Changelog entry patches the result of all previous patches 
+			// (the previous, or parent patch of a given patch p is the patch that has a node equal to p's p1 field)
+			byte[] baseRevContent = hgRepo.getChangelog().content(base);
+			for (GroupElement ge : changelogGroup) {
+				byte[] csetContent = RevlogStream.apply(baseRevContent, -1, ge.patches);
+				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?
+				if (!ge.node().equalsTo(dh.asBinary())) {
+					throw new IllegalStateException("Integrity check failed on " + bundleFile + ", node:" + ge.node());
+				}
+				Changeset cs = Changeset.parse(csetContent, 0, csetContent.length);
+				System.out.println(cs.toString());
+				baseRevContent = csetContent;
+			}
+		} finally {
+			da.done();
+		}
+	}
+
+	public void dump() throws IOException {
+		DataAccess da = accessProvider.create(bundleFile);
+		try {
+			LinkedList<String> names = new LinkedList<String>();
+			if (!da.isEmpty()) {
+				System.out.println("Changelog group");
+				List<GroupElement> changelogGroup = readGroup(da);
+				for (GroupElement ge : changelogGroup) {
+					System.out.printf("  %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size());
+				}
+				System.out.println("Manifest group");
+				List<GroupElement> manifestGroup = readGroup(da);
+				for (GroupElement ge : manifestGroup) {
+					System.out.printf("  %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size());
+				}
+				while (!da.isEmpty()) {
+					int fnameLen = da.readInt();
+					if (fnameLen <= 4) {
+						break; // null chunk, the last one.
+					}
+					byte[] fname = new byte[fnameLen - 4];
+					da.readBytes(fname, 0, fname.length);
+					names.add(new String(fname));
+					List<GroupElement> fileGroup = readGroup(da);
+					System.out.println(names.getLast());
+					for (GroupElement ge : fileGroup) {
+						System.out.printf("  %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size());
+					}
+				}
+			}
+			System.out.println(names.size());
+			for (String s : names) {
+				System.out.println(s);
+			}
+		} finally {
+			da.done();
+		}
+	}
+
+	private static List<GroupElement> readGroup(DataAccess da) throws IOException {
+		int len = da.readInt();
+		LinkedList<GroupElement> rv = new LinkedList<HgBundle.GroupElement>();
+		while (len > 4 && !da.isEmpty()) {
+			byte[] nb = new byte[80];
+			da.readBytes(nb, 0, 80);
+			int dataLength = len-84;
+			LinkedList<RevlogStream.PatchRecord> patches = new LinkedList<RevlogStream.PatchRecord>();
+			while (dataLength > 0) {
+				RevlogStream.PatchRecord pr = RevlogStream.PatchRecord.read(da);
+				patches.add(pr);
+				dataLength -= pr.len + 12;
+			}
+			rv.add(new GroupElement(nb, patches));
+			len = da.isEmpty() ? 0 : da.readInt();
+		}
+		return rv;
+	}
+
+	static class GroupElement {
+		private byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192
+		private List<RevlogStream.PatchRecord> patches;
+		
+		GroupElement(byte[] fourNodeids, List<RevlogStream.PatchRecord> patchList) {
+			assert fourNodeids != null && fourNodeids.length == 80;
+			// patchList.size() > 0
+			header = fourNodeids;
+			patches = patchList;
+		}
+		public Nodeid node() {
+			return Nodeid.fromBinary(header, 0);
+		}
+		public Nodeid firstParent() {
+			return Nodeid.fromBinary(header, 20);
+		}
+		public Nodeid secondParent() {
+			return Nodeid.fromBinary(header, 40);
+		}
+		public Nodeid cset() { // cs seems to be changeset
+			return Nodeid.fromBinary(header, 60);
+		}
+	}
+}