changeset 3:24bb4f365164

Rudimentary log functionality with basic infrastructure is in place
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 20 Dec 2010 02:50:36 +0100
parents 08db726a0fb7
children aa1912c70b36
files src/com/tmate/hgkit/console/Cat.java src/com/tmate/hgkit/console/Log.java src/com/tmate/hgkit/console/Main.java src/com/tmate/hgkit/ll/Changelog.java src/com/tmate/hgkit/ll/Changeset.java src/com/tmate/hgkit/ll/HgDataFile.java src/com/tmate/hgkit/ll/HgRepository.java src/com/tmate/hgkit/ll/LocalHgRepo.java src/com/tmate/hgkit/ll/Revlog.java src/com/tmate/hgkit/ll/RevlogStream.java
diffstat 10 files changed, 293 insertions(+), 136 deletions(-) [+]
line wrap: on
line diff
--- a/src/com/tmate/hgkit/console/Cat.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/console/Cat.java	Mon Dec 20 02:50:36 2010 +0100
@@ -19,5 +19,7 @@
 			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
 			return;
 		}
+		byte[] tipContent = hgRepo.getFileNode("hello.c").content();
+		System.out.println(new String(tipContent));
 	}
 }
--- a/src/com/tmate/hgkit/console/Log.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/console/Log.java	Mon Dec 20 02:50:36 2010 +0100
@@ -21,18 +21,22 @@
 			return;
 		}
 		System.out.println(hgRepo.getLocation());
-		final Changeset.Callback callback = new Changeset.Callback() {
+		final Changeset.Inspector callback = new Changeset.Inspector() {
 			
 			public void next(Changeset cset) {
-				System.out.println();
+				System.out.println("==>");
+				cset.dump();
 			}
 		};
 		HgDataFile f1 = hgRepo.getFileNode("hello.c");
 		System.out.println("Complete of a file:");
 		f1.history(callback);
-		System.out.println("Range 1-3:");
-		f1.history(1,3, callback);
 		//
+//		System.out.println("\n\n=========================");
+//		System.out.println("Range 1-3:");
+//		f1.history(1,3, callback);
+		//
+		System.out.println("\n\n=========================");
 		System.out.println("Complete of a repo:");
 		hgRepo.getChangelog().all(callback);
 		//new ChangelogWalker().setFile("hello.c").setRevisionRange(1, 4).accept(new Visitor);
--- a/src/com/tmate/hgkit/console/Main.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/console/Main.java	Mon Dec 20 02:50:36 2010 +0100
@@ -6,12 +6,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.List;
-import java.util.zip.Deflater;
 import java.util.zip.Inflater;
 
 import com.tmate.hgkit.ll.Changeset;
@@ -23,9 +18,11 @@
 public class Main {
 
 	public static void main(String[] args) throws Exception {
+		//String filename = "store/00changelog.i";
+		String filename = "store/data/hello.c.i";
 		LinkedList<Changeset> changelog = new LinkedList<Changeset>();
 		//
-		DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("/temp/hg/hello/" + ".hg/store/00changelog.i"))));
+		DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("/temp/hg/hello/.hg/" + filename))));
 		DataInput di = dis;
 		dis.mark(10);
 		int versionField = di.readInt();
@@ -51,6 +48,7 @@
 			dis.skip(12);
 			System.out.printf("%14d %6X %10d %10d %10d %10d %8d %8d     %040x\n", offset, flags, compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, new BigInteger(buf));
 			if (inlineData) {
+				String resultString;
 				byte[] data = new byte[compressedLen];
 				di.readFully(data);
 				if (data[0] == 0x78 /* 'x' */) {
@@ -59,35 +57,13 @@
 					byte[] result = new byte[actualLen*2];
 					int resultLen = zlib.inflate(result);
 					zlib.end();
-					if (resultLen != actualLen) {
-						System.err.printf("Expected:%d, decomressed to:%d bytes\n", actualLen, resultLen);
-					}
-					String resultString;
-					if (baseRevision != entryCount) {
-						// this is a patch
-						byte[] baseRevContent = changelog.get(baseRevision).rawData;
-						LinkedList<PatchRecord> bins = new LinkedList<PatchRecord>();
-						int p1, p2, len, patchElementIndex = 0;
-						do {
-							final int x = patchElementIndex;
-							p1 = (result[x] << 24) | (result[x+1] << 16) | (result[x+2] << 8) | result[x+3];
-							p2 = (result[x+4] << 24) | (result[x+5] << 16) | (result[x+6] << 8) | result[x+7];
-							len = (result[x+8] << 24) | (result[x+9] << 16) | (result[x+10] << 8) | result[x+11];
-							System.out.printf("%4d %4d %4d\n", p1, p2, len);
-							patchElementIndex += 12 + len;
-							bins.add(new PatchRecord(p1, p2, len, result, x+12));
-						} while (patchElementIndex < resultLen);
-						// 
-						result = apply(baseRevContent, bins);
-						resultLen = result.length;
-					}
 					resultString = new String(result, 0, resultLen, "UTF-8");
-					System.out.println(resultString);
-					entryCount++;
-					Changeset changeset = new Changeset();
-					changeset.read(result, 0, resultLen);
-					changelog.add(changeset);
-				} // TODO else if uncompressed
+				} else if (data[0] == 0x75 /* 'u' */) {
+					resultString = new String(data, 1, data.length - 1, "UTF-8");
+				} else {
+					resultString = new String(data);
+				}
+				System.out.println(resultString);
 			}
 		}
 		dis.close();
@@ -100,36 +76,4 @@
 			System.out.println("<");
 		}
 	}
-
-
-	// mpatch.c : apply()
-	private static byte[] apply(byte[] baseRevisionContent, List<PatchRecord> patch) {
-		byte[] tempBuf = new byte[512]; // XXX
-		int last = 0, destIndex = 0;
-		for (PatchRecord pr : patch) {
-			System.arraycopy(baseRevisionContent, last, tempBuf, destIndex, pr.start-last);
-			destIndex += pr.start - last;
-			System.arraycopy(pr.data, 0, tempBuf, destIndex, pr.data.length);
-			destIndex += pr.data.length;
-			last = pr.end;
-		}
-		System.arraycopy(baseRevisionContent, last, tempBuf, destIndex, baseRevisionContent.length - last);
-		destIndex += baseRevisionContent.length - last; // total length
-		byte[] rv = new byte[destIndex];
-		System.arraycopy(tempBuf, 0, rv, 0, destIndex);
-		return rv;
-	}
-
-	static class PatchRecord { // copy of struct frag from mpatch.c
-		int start, end, len;
-		byte[] data;
-
-		public PatchRecord(int p1, int p2, int len, byte[] src, int srcOffset) {
-		start = p1;
-				end = p2;
-				this.len = len;
-				data = new byte[len];
-				System.arraycopy(src, srcOffset, data, 0, len);
-		}
-	}
 }
--- a/src/com/tmate/hgkit/ll/Changelog.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/ll/Changelog.java	Mon Dec 20 02:50:36 2010 +0100
@@ -3,11 +3,8 @@
  */
 package com.tmate.hgkit.ll;
 
-import java.io.DataInput;
-import java.io.EOFException;
-import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -16,35 +13,52 @@
  */
 public class Changelog extends Revlog {
 
-	private RevlogStream content;
+	private final RevlogStream content;
 
-	/*package-local*/ Changelog(HgRepository hgRepo) {
+	/*package-local*/ Changelog(HgRepository hgRepo, RevlogStream content) {
 		super(hgRepo);
-		content = hgRepo.resolve(".hg/store/00changelog.i");
+		this.content = content;
 	}
 
-	public List<Changeset> all() {
-		throw HgRepository.notImplemented();
-	}
-	
-	public void all(Changeset.Callback callback) {
-		throw HgRepository.notImplemented();
+	public void all(final Changeset.Inspector inspector) {
+		Revlog.Inspector i = new Revlog.Inspector() {
+			
+			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) {
+				Changeset cset = Changeset.parse(data, 0, data.length);
+				// XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse
+				inspector.next(cset);
+			}
+		};
+		content.iterate(0, content.revisionCount() - 1, true, i);
 	}
 
 	public List<Changeset> range(int start, int end) {
-		//read from outline[start].start .. (outline[end].start + outline[end].length)
-		// parse changesets
 		final ArrayList<Changeset> rv = new ArrayList<Changeset>(end - start + 1);
 		Revlog.Inspector i = new Revlog.Inspector() {
 			
-			public void next(int compressedLen, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) {
-				// TODO Auto-generated method stub
-				Changeset.parse(data);
-				i.add();
-				throw HgRepository.notImplemented();
+			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) {
+				Changeset cset = Changeset.parse(data, 0, data.length);
+				rv.add(cset);
 			}
 		};
 		content.iterate(start, end, true, i);
 		return rv; 
 	}
+
+	public void range(final Changeset.Inspector inspector, final int... revisions) {
+		if (revisions == null || revisions.length == 0) {
+			return;
+		}
+		Revlog.Inspector i = new Revlog.Inspector() {
+			
+			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) {
+				if (Arrays.binarySearch(revisions, revisionNumber) >= 0) {
+					Changeset cset = Changeset.parse(data, 0, data.length);
+					inspector.next(cset);
+				}
+			}
+		};
+		Arrays.sort(revisions);
+		content.iterate(revisions[0], revisions[revisions.length - 1], true, i);
+	}
 }
--- a/src/com/tmate/hgkit/ll/Changeset.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/ll/Changeset.java	Mon Dec 20 02:50:36 2010 +0100
@@ -23,12 +23,12 @@
  * @author artem
  */
 public class Changeset {
+	// TODO immutable
 	private /*final*/ Nodeid nodeid;
 	private String user;
 	private String comment;
 	private ArrayList<String> files;
 	private String timezone; // FIXME
-	public byte[] rawData; // FIXME
 	
 	public void dump() {
 		System.out.println("User:" + user);
@@ -42,50 +42,50 @@
 		}
 	}
 
-	public void read(byte[] buf, int offset, int length) {
-		rawData = new byte[length];
-		System.arraycopy(buf, offset, rawData, 0, length);
+	public static Changeset parse(byte[] data, int offset, int length) {
+		Changeset rv = new Changeset();
 		final int bufferEndIndex = offset + length;
 		final byte lineBreak = (byte) '\n';
-		int breakIndex1 = indexOf(buf, lineBreak, offset, bufferEndIndex);
+		int breakIndex1 = indexOf(data, lineBreak, offset, bufferEndIndex);
 		if (breakIndex1 == -1) {
 			throw new IllegalArgumentException("Bad Changeset data");
 		}
-		nodeid = Nodeid.fromAscii(buf, 0, breakIndex1);
-		int breakIndex2 = indexOf(buf, lineBreak, breakIndex1+1, bufferEndIndex);
+		rv.nodeid = Nodeid.fromAscii(data, 0, breakIndex1);
+		int breakIndex2 = indexOf(data, lineBreak, breakIndex1+1, bufferEndIndex);
 		if (breakIndex2 == -1) {
 			throw new IllegalArgumentException("Bad Changeset data");
 		}
-		user = new String(buf, breakIndex1+1, breakIndex2 - breakIndex1 - 1);
-		int breakIndex3 = indexOf(buf, lineBreak, breakIndex2+1, bufferEndIndex);
+		rv.user = new String(data, breakIndex1+1, breakIndex2 - breakIndex1 - 1);
+		int breakIndex3 = indexOf(data, lineBreak, breakIndex2+1, bufferEndIndex);
 		if (breakIndex3 == -1) {
 			throw new IllegalArgumentException("Bad Changeset data");
 		}
-		timezone = new String(buf, breakIndex2+1, breakIndex3 - breakIndex2 - 1);
+		rv.timezone = new String(data, breakIndex2+1, breakIndex3 - breakIndex2 - 1);
 		
 		//
 		int lastStart = breakIndex3 + 1;
-		int breakIndex4 = indexOf(buf, lineBreak, lastStart, bufferEndIndex);
-		files = new ArrayList<String>(5);
+		int breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
+		rv.files = new ArrayList<String>(5);
 		while (breakIndex4 != -1 && breakIndex4 + 1 < bufferEndIndex) {
-			files.add(new String(buf, lastStart, breakIndex4 - lastStart));
+			rv.files.add(new String(data, lastStart, breakIndex4 - lastStart));
 			lastStart = breakIndex4 + 1;
-			if (buf[breakIndex4 + 1] == lineBreak) {
+			if (data[breakIndex4 + 1] == lineBreak) {
 				// found \n\n
 				break;
 			} else {
-				breakIndex4 = indexOf(buf, lineBreak, lastStart, bufferEndIndex);
+				breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
 			}
 		}
 		if (breakIndex4 == -1 || breakIndex4 >= bufferEndIndex) {
 			throw new IllegalArgumentException("Bad Changeset data");
 		}
 		try {
-			comment = new String(buf, breakIndex4+2, bufferEndIndex - breakIndex4 - 2, "UTF-8");
+			rv.comment = new String(data, breakIndex4+2, bufferEndIndex - breakIndex4 - 2, "UTF-8");
 		} catch (UnsupportedEncodingException ex) {
-			comment = "";
+			rv.comment = "";
 			throw new IllegalStateException("Could hardly happen");
 		}
+		return rv;
 	}
 
 	private static int indexOf(byte[] src, byte what, int startOffset, int endIndex) {
@@ -97,7 +97,7 @@
 		return -1;
 	}
 
-	public interface Callback {
+	public interface Inspector {
 		// first(), last(), single().
 		// <T>
 		void next(Changeset cset);
--- a/src/com/tmate/hgkit/ll/HgDataFile.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/ll/HgDataFile.java	Mon Dec 20 02:50:36 2010 +0100
@@ -10,10 +10,21 @@
  */
 public class HgDataFile extends Revlog {
 
+	private final RevlogStream content; // XXX move up to Revlog?
+
+	// absolute from repo root?
+	// slashes, unix-style?
+	// repo location agnostic, just to give info to user, not to access real storage
 	private final String path;
 	
-	/*package-local*/HgDataFile(HgRepository hgRepo) {
+	/*package-local*/HgDataFile(HgRepository hgRepo, String path, RevlogStream content) {
 		super(hgRepo);
+		this.path = path;
+		this.content = content;
+	}
+	
+	public boolean exists() {
+		return content != null; // XXX need better impl
 	}
 
 	public String getPath() {
@@ -29,4 +40,24 @@
 	public byte[] content(int revision) {
 		throw HgRepository.notImplemented();
 	}
+
+	public void history(Changeset.Inspector inspector) {
+		if (!exists()) {
+			throw new IllegalStateException("Can't get history of invalid repository file node"); 
+		}
+		final int[] commitRevisions = new int[content.revisionCount()];
+		Revlog.Inspector insp = new Revlog.Inspector() {
+			int count = 0;
+			
+			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) {
+				commitRevisions[count++] = linkRevision;
+			}
+		};
+		content.iterate(0, -1, false, insp);
+		getRepo().getChangelog().range(inspector, commitRevisions);
+	}
+
+	public void history(int start, int end, Changeset.Inspector i) {
+		throw HgRepository.notImplemented();
+	}
 }
--- a/src/com/tmate/hgkit/ll/HgRepository.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/ll/HgRepository.java	Mon Dec 20 02:50:36 2010 +0100
@@ -37,8 +37,8 @@
 	public final Changelog getChangelog() {
 		if (this.changelog == null) {
 			// might want delegate to protected createChangelog() some day
-			this.changelog = new Changelog(this);
-			// TODO init
+			RevlogStream content = resolve("store/00changelog.i"); // XXX perhaps, knowledge about filenames should be in LocalHgRepo?
+			this.changelog = new Changelog(this, content);
 		}
 		return this.changelog;
 	}
@@ -62,5 +62,5 @@
 	/**
 	 * Perhaps, should be separate interface, like ContentLookup
 	 */
-	public abstract RevlogStream resolve(String string);
+	protected abstract RevlogStream resolve(String string);
 }
--- a/src/com/tmate/hgkit/ll/LocalHgRepo.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/ll/LocalHgRepo.java	Mon Dec 20 02:50:36 2010 +0100
@@ -5,6 +5,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
 
 /**
  * @author artem
@@ -30,4 +32,44 @@
 	public String getLocation() {
 		return repoLocation;
 	}
+
+	private final HashMap<String, SoftReference<RevlogStream>> streamsCache = new HashMap<String, SoftReference<RevlogStream>>();
+
+	/**
+	 * path - repository storage path (i.e. one usually with .i or .d)
+	 */
+	@Override
+	protected RevlogStream resolve(String path) {
+		final SoftReference<RevlogStream> ref = streamsCache.get(path);
+		RevlogStream cached = ref == null ? null : ref.get();
+		if (cached != null) {
+			return cached;
+		}
+		File f = new File(repoDir, path);
+		if (f.exists()) {
+			RevlogStream s = new RevlogStream(f);
+			streamsCache.put(path, new SoftReference<RevlogStream>(s));
+			return s;
+		}
+		return null;
+	}
+
+	@Override
+	public HgDataFile getFileNode(String path) {
+		String nPath = normalize(path);
+		String storagePath = toStoragePath(nPath);
+		RevlogStream content = resolve(storagePath);
+		// XXX no content when no file? or HgDataFile.exists() to detect that? How about files that were removed in previous releases?
+		return new HgDataFile(this, nPath, content);
+	}
+	
+	// FIXME much more to be done, see store.py:_hybridencode
+	private static String toStoragePath(String path) {
+		// XXX works for lowercase names only
+		return "store/data/" + path.replace('\\', '/') + ".i";
+	}
+
+	private static String normalize(String path) {
+		return path.replace('\\', '/');
+	}
 }
--- a/src/com/tmate/hgkit/ll/Revlog.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/ll/Revlog.java	Mon Dec 20 02:50:36 2010 +0100
@@ -23,6 +23,7 @@
 	}
 
 	public interface Inspector {
-		void next(int compressedLen, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*32*/] nodeid, byte[] data);
+		// XXX boolean retVal to indicate whether to continue?
+		void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*32*/] nodeid, byte[] data);
 	}
 }
--- a/src/com/tmate/hgkit/ll/RevlogStream.java	Sun Dec 19 05:41:31 2010 +0100
+++ b/src/com/tmate/hgkit/ll/RevlogStream.java	Mon Dec 20 02:50:36 2010 +0100
@@ -3,12 +3,19 @@
  */
 package com.tmate.hgkit.ll;
 
+import java.io.BufferedInputStream;
 import java.io.DataInput;
+import java.io.DataInputStream;
 import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
 /**
@@ -22,29 +29,54 @@
 
 	private List<IndexEntry> index; // indexed access highly needed
 	private boolean inline = false;
+	private final File indexFile;
+
+	RevlogStream(File indexFile) {
+		this.indexFile = indexFile;
+	}
 
 	private void detectVersion() {
 		
 	}
 
 	/*package*/ DataInput getIndexStream() {
-		// TODO Auto-generated method stub
-		return null;
+		DataInputStream dis = null;
+		try {
+			dis = new DataInputStream(new BufferedInputStream(new FileInputStream(indexFile)));
+		} catch (FileNotFoundException ex) {
+			ex.printStackTrace();
+			// should not happen, we checked for existence
+		}
+		return dis;
 	}
 
 	/*package*/ DataInput getDataStream() {
-		// TODO Auto-generated method stub
-		return null;
+		final String indexName = indexFile.getName();
+		File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d");
+		try {
+			return new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
+		} catch (FileNotFoundException ex) {
+			ex.printStackTrace();
+			return null;
+		}
 	}
+
 	public int revisionCount() {
 		initOutline();
 		return index.size();
 	}
 
-	// should be possible to use TIP, ALL
+	// should be possible to use TIP, ALL, or -1, -2, -n notation of Hg
+	// ? boolean needsNodeid
 	public void iterate(int start, int end, boolean needData, Revlog.Inspector inspector) {
 		initOutline();
 		final int indexSize = index.size();
+		if (indexSize == 0) {
+			return;
+		}
+		if (end == -1 /*FIXME TIP*/) {
+			end = indexSize - 1;
+		}
 		if (start < 0 || start >= indexSize) {
 			throw new IllegalArgumentException("Bad left range boundary " + start);
 		}
@@ -55,12 +87,13 @@
 		
 		DataInput diIndex = null, diData = null;
 		diIndex = getIndexStream();
-		if (needData) {
+		if (needData && !inline) {
 			diData = getDataStream();
 		}
 		try {
 			diIndex.skipBytes(inline ? (int) index.get(start).offset : start * 64);
-			for (int i = start; i <= end && i < indexSize; i++ ) {
+			byte[] lastData = null;
+			for (int i = start; i <= end; i++ ) {
 				IndexEntry ie = index.get(i);
 				long l = diIndex.readLong();
 				long offset = l >>> 16;
@@ -81,34 +114,71 @@
 					if (inline) {
 						diIndex.readFully(dataBuf);
 					} else {
-						diData.skipBytes((int) ie.offset); // FIXME not skip but seek!!!
+						diData.skipBytes((int) ie.offset); // FIXME not skip but seek!!! (skip would work only for the first time)
 						diData.readFully(dataBuf);
 					}
 					if (dataBuf[0] == 0x78 /* 'x' */) {
-						Inflater zlib = new Inflater();
-						zlib.setInput(dataBuf, 0, compressedLen);
-						byte[] result = new byte[actualLen*2]; // FIXME need to use zlib.finished() instead 
-						int resultLen = zlib.inflate(result);
-						zlib.end();
-						data = new byte[resultLen];
-						System.arraycopy(result, 0, data, 0, resultLen);
+						try {
+							Inflater zlib = new Inflater();
+							zlib.setInput(dataBuf, 0, compressedLen);
+							byte[] result = new byte[actualLen*2]; // FIXME need to use zlib.finished() instead 
+							int resultLen = zlib.inflate(result);
+							zlib.end();
+							data = new byte[resultLen];
+							System.arraycopy(result, 0, data, 0, resultLen);
+						} catch (DataFormatException ex) {
+							ex.printStackTrace();
+							data = new byte[0]; // FIXME need better failure strategy
+						}
 					} else if (dataBuf[0] == 0x75 /* 'u' */) {
 						data = new byte[dataBuf.length - 1];
 						System.arraycopy(dataBuf, 1, data, 0, data.length);
 					} else {
 						// XXX Python impl in fact throws exception when there's not 'x', 'u' or '0'
-						// but I don't see reason not to just return data as is 
+						// but I don't see reason not to return data as is 
 						data = dataBuf;
 					}
-					// FIXME if patch data (based on baseRevision), then apply patches to get true content
+					// XXX 
+					if (baseRevision != i) { // XXX not sure if this is the right way to detect a patch
+						// this is a patch
+						LinkedList<PatchRecord> patches = new LinkedList<PatchRecord>();
+						int patchElementIndex = 0;
+						do {
+							final int x = patchElementIndex; // shorthand
+							int p1 = (data[x] << 24) | (data[x+1] << 16) | (data[x+2] << 8) | data[x+3];
+							int p2 = (data[x+4] << 24) | (data[x+5] << 16) | (data[x+6] << 8) | data[x+7];
+							int len = (data[x+8] << 24) | (data[x+9] << 16) | (data[x+10] << 8) | data[x+11];
+							patchElementIndex += 12 + len;
+							patches.add(new PatchRecord(p1, p2, len, data, x+12));
+						} while (patchElementIndex < data.length);
+						//
+						byte[] baseRevContent;
+						if (baseRevision == i - 1) {
+							baseRevContent = lastData;
+						} else {
+							// FIXME implement delta collection from few revisions
+							// read baseRevision plus all deltas between this revision and base. Need to do this effectively.
+							throw HgRepository.notImplemented();
+						}
+						
+						// FIXME need to collect all patches between baseRevision and current version 
+						data = apply(baseRevContent, patches);
+					}
+				} else {
+					if (inline) {
+						diIndex.skipBytes(compressedLen);
+					}
 				}
-				inspector.next(compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, buf, data);
+				inspector.next(i, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, buf, data);
+				lastData = data;
 			}
 		} catch (EOFException ex) {
 			// should not happen as long as we read inside known boundaries
 			throw new IllegalStateException(ex);
 		} catch (IOException ex) {
-			FIXME 
+			throw new IllegalStateException(ex); // FIXME need better handling
+		} finally {
+			hackCloseFileStreams(diIndex, diData); // FIXME HACK!!!
 		}
 	}
 	
@@ -120,11 +190,11 @@
 		DataInput di = getIndexStream();
 		try {
 			int versionField = di.readInt();
-			// di.undreadInt();
+			di.readInt(); // just to skip next 2 bytes of offset + flags
 			final int INLINEDATA = 1 << 16;
 			inline = (versionField & INLINEDATA) != 0;
 			long offset = 0; // first offset is always 0, thus Hg uses it for other purposes
-			while(true) { // EOFExcepiton should get us outta here. FIXME Out inputstream should has explicit no-more-data indicator
+			while(true) { // EOFExcepiton should get us outta here. FIXME Our inputstream should has explicit no-more-data indicator
 				int compressedLen = di.readInt();
 				// 8+4 = 12 bytes total read
 //				int actualLen = di.readInt();
@@ -135,9 +205,9 @@
 //				byte[] nodeid = new byte[32];
 				res.add(new IndexEntry(offset, compressedLen));
 				if (inline) {
-					di.skipBytes(6*4 + 32 + compressedLen); // Check: 56 (skip) + 12 (read) = 64 (total RevlogNG record size)
+					di.skipBytes(5*4 + 32 + compressedLen); // Check: 52 (skip) + 12 (read) = 64 (total RevlogNG record size)
 				} else {
-					di.skipBytes(6*4 + 32);
+					di.skipBytes(5*4 + 32);
 				}
 				long l = di.readLong();
 				offset = l >>> 16;
@@ -150,6 +220,22 @@
 			// too bad, no outline then
 			index = Collections.emptyList();
 		}
+		hackCloseFileStreams(di, null); // FIXME HACK!!!
+	}
+	
+	// FIXME HACK to deal with File/FileStream nature of out data source. Won't need this once implement
+	// own DataInput based on bytearray chunks or RandomAccessFile
+	private void hackCloseFileStreams(DataInput index, DataInput data) {
+		try {
+			if (index != null) {
+				((DataInputStream) index).close();
+			}
+			if (data != null) {
+				((DataInputStream) data).close();
+			}
+		} catch (IOException ex) {
+			ex.printStackTrace();
+		}
 	}
 
 
@@ -163,4 +249,37 @@
 			length = l;
 		}
 	}
+
+	// mpatch.c : apply()
+	// FIXME need to implement patch merge (fold, combine, gather and discard from aforementioned mpatch.[c|py]), also see Revlog and Mercurial PDF
+	private static byte[] apply(byte[] baseRevisionContent, List<PatchRecord> patch) {
+		byte[] tempBuf = new byte[512]; // XXX
+		int last = 0, destIndex = 0;
+		for (PatchRecord pr : patch) {
+			System.arraycopy(baseRevisionContent, last, tempBuf, destIndex, pr.start-last);
+			destIndex += pr.start - last;
+			System.arraycopy(pr.data, 0, tempBuf, destIndex, pr.data.length);
+			destIndex += pr.data.length;
+			last = pr.end;
+		}
+		System.arraycopy(baseRevisionContent, last, tempBuf, destIndex, baseRevisionContent.length - last);
+		destIndex += baseRevisionContent.length - last; // total length
+		byte[] rv = new byte[destIndex];
+		System.arraycopy(tempBuf, 0, rv, 0, destIndex);
+		return rv;
+	}
+
+	static class PatchRecord { // copy of struct frag from mpatch.c
+		int start, end, len;
+		byte[] data;
+
+		public PatchRecord(int p1, int p2, int len, byte[] src, int srcOffset) {
+		start = p1;
+				end = p2;
+				this.len = len;
+				data = new byte[len];
+				System.arraycopy(src, srcOffset, data, 0, len);
+		}
+	}
+
 }