changeset 2:08db726a0fb7

Shaping out low-level Hg structures
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sun, 19 Dec 2010 05:41:31 +0100
parents a3576694a4d1
children 24bb4f365164
files design.txt 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/fs/RepositoryFinder.java src/com/tmate/hgkit/fs/RepositoryLookup.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/HgManifest.java src/com/tmate/hgkit/ll/HgRepository.java src/com/tmate/hgkit/ll/Revlog.java src/com/tmate/hgkit/ll/RevlogStream.java
diffstat 13 files changed, 360 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/design.txt	Sat Dec 18 05:47:35 2010 +0100
+++ b/design.txt	Sun Dec 19 05:41:31 2010 +0100
@@ -5,6 +5,15 @@
 
 Log --rev
 Log <file>
+HgDataFile.history() or Changelog.history(file)?
+
+
+Changelog.all() to return list with placeholder, not-parsed elements (i.e. read only compressedLen field and skip to next record), so that
+total number of elements in the list is correct
 
 hg cat
-Implementation: logic to find file by name in the repository is the same with Log and other commands 
\ No newline at end of file
+Implementation: logic to find file by name in the repository is the same with Log and other commands
+
+
+Revlog
+What happens when big entry is added to a file - when it detects it can't longer fit into .i and needs .d? Inline flag and .i format changes?
\ No newline at end of file
--- a/src/com/tmate/hgkit/console/Cat.java	Sat Dec 18 05:47:35 2010 +0100
+++ b/src/com/tmate/hgkit/console/Cat.java	Sun Dec 19 05:41:31 2010 +0100
@@ -3,7 +3,7 @@
  */
 package com.tmate.hgkit.console;
 
-import com.tmate.hgkit.fs.RepositoryFinder;
+import com.tmate.hgkit.fs.RepositoryLookup;
 import com.tmate.hgkit.ll.HgRepository;
 
 /**
@@ -13,7 +13,7 @@
 public class Cat {
 
 	public static void main(String[] args) throws Exception {
-		RepositoryFinder repoLookup = new RepositoryFinder();
+		RepositoryLookup repoLookup = new RepositoryLookup();
 		HgRepository hgRepo = repoLookup.detect(args);
 		if (hgRepo.isInvalid()) {
 			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
--- a/src/com/tmate/hgkit/console/Log.java	Sat Dec 18 05:47:35 2010 +0100
+++ b/src/com/tmate/hgkit/console/Log.java	Sun Dec 19 05:41:31 2010 +0100
@@ -3,7 +3,9 @@
  */
 package com.tmate.hgkit.console;
 
-import com.tmate.hgkit.fs.RepositoryFinder;
+import com.tmate.hgkit.fs.RepositoryLookup;
+import com.tmate.hgkit.ll.Changeset;
+import com.tmate.hgkit.ll.HgDataFile;
 import com.tmate.hgkit.ll.HgRepository;
 
 /**
@@ -12,15 +14,28 @@
 public class Log {
 
 	public static void main(String[] args) throws Exception {
-		RepositoryFinder repoLookup = new RepositoryFinder();
+		RepositoryLookup repoLookup = new RepositoryLookup();
 		HgRepository hgRepo = repoLookup.detect(args);
 		if (hgRepo.isInvalid()) {
 			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
 			return;
 		}
 		System.out.println(hgRepo.getLocation());
+		final Changeset.Callback callback = new Changeset.Callback() {
+			
+			public void next(Changeset cset) {
+				System.out.println();
+			}
+		};
+		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("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	Sat Dec 18 05:47:35 2010 +0100
+++ b/src/com/tmate/hgkit/console/Main.java	Sun Dec 19 05:41:31 2010 +0100
@@ -23,29 +23,6 @@
 public class Main {
 
 	public static void main(String[] args) throws Exception {
-		Deflater zip1 = new Deflater(6, true);
-		final byte[] input = "Abstractions are valueless".getBytes();
-		zip1.setInput(input);
-		zip1.finish();
-		byte[] result1 = new byte[100];
-		int resLen1 = zip1.deflate(result1);
-		System.out.printf("%3d:", resLen1);
-		for (int i = 0; i < resLen1; i++) {
-			System.out.printf("%02X", result1[i]);
-		}
-		System.out.println();
-		//
-		Deflater zip2 = new Deflater(6, false);
-		zip2.setInput(input);
-		zip2.finish();
-		byte[] result2 = new byte[100];
-		int resLen2 = zip2.deflate(result2);
-		System.out.printf("%3d:", resLen2);
-		for (int i = 0; i < resLen2; i++) {
-			System.out.printf("%02X", result2[i]);
-		}
-		System.out.println();
-		//
 		LinkedList<Changeset> changelog = new LinkedList<Changeset>();
 		//
 		DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("/temp/hg/hello/" + ".hg/store/00changelog.i"))));
--- a/src/com/tmate/hgkit/fs/RepositoryFinder.java	Sat Dec 18 05:47:35 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/**
- * Copyright (c) 2010 Artem Tikhomirov 
- */
-package com.tmate.hgkit.fs;
-
-import java.io.File;
-
-import com.tmate.hgkit.ll.HgRepository;
-import com.tmate.hgkit.ll.LocalHgRepo;
-
-/**
- * @author artem
- */
-public class RepositoryFinder {
-	
-	public HgRepository detect(String[] commandLineArgs) throws Exception {
-		if (commandLineArgs.length == 0) {
-			return detectFromWorkingDir();
-		}
-		return detect(commandLineArgs[0]);
-	}
-
-	public HgRepository detectFromWorkingDir() throws Exception {
-		return detect(System.getProperty("user.dir"));
-	}
-
-	public HgRepository detect(String location) throws Exception /*FIXME Exception type, RepoInitException? */ {
-		File dir = new File(location);
-		File repository;
-		do {
-			repository = new File(dir, ".hg");
-			if (repository.exists() && repository.isDirectory()) {
-				break;
-			}
-			repository = null;
-			dir = dir.getParentFile();
-			
-		} while(dir != null);
-		if (repository == null) {
-			return new LocalHgRepo(location);
-		}
-		return new LocalHgRepo(repository);
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/tmate/hgkit/fs/RepositoryLookup.java	Sun Dec 19 05:41:31 2010 +0100
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010 Artem Tikhomirov 
+ */
+package com.tmate.hgkit.fs;
+
+import java.io.File;
+
+import com.tmate.hgkit.ll.HgRepository;
+import com.tmate.hgkit.ll.LocalHgRepo;
+
+/**
+ * @author artem
+ */
+public class RepositoryLookup {
+	
+	public HgRepository detect(String[] commandLineArgs) throws Exception {
+		if (commandLineArgs.length == 0) {
+			return detectFromWorkingDir();
+		}
+		return detect(commandLineArgs[0]);
+	}
+
+	public HgRepository detectFromWorkingDir() throws Exception {
+		return detect(System.getProperty("user.dir"));
+	}
+
+	public HgRepository detect(String location) throws Exception /*FIXME Exception type, RepoInitException? */ {
+		File dir = new File(location);
+		File repository;
+		do {
+			repository = new File(dir, ".hg");
+			if (repository.exists() && repository.isDirectory()) {
+				break;
+			}
+			repository = null;
+			dir = dir.getParentFile();
+			
+		} while(dir != null);
+		if (repository == null) {
+			return new LocalHgRepo(location);
+		}
+		return new LocalHgRepo(repository);
+	}
+}
--- a/src/com/tmate/hgkit/ll/Changelog.java	Sat Dec 18 05:47:35 2010 +0100
+++ b/src/com/tmate/hgkit/ll/Changelog.java	Sun Dec 19 05:41:31 2010 +0100
@@ -3,10 +3,48 @@
  */
 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.List;
+
 /**
  * Representation of the Mercurial changelog file (list of ChangeSets)
  * @author artem
  */
-public class Changelog {
+public class Changelog extends Revlog {
 
+	private RevlogStream content;
+
+	/*package-local*/ Changelog(HgRepository hgRepo) {
+		super(hgRepo);
+		content = hgRepo.resolve(".hg/store/00changelog.i");
+	}
+
+	public List<Changeset> all() {
+		throw HgRepository.notImplemented();
+	}
+	
+	public void all(Changeset.Callback callback) {
+		throw HgRepository.notImplemented();
+	}
+
+	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();
+			}
+		};
+		content.iterate(start, end, true, i);
+		return rv; 
+	}
 }
--- a/src/com/tmate/hgkit/ll/Changeset.java	Sat Dec 18 05:47:35 2010 +0100
+++ b/src/com/tmate/hgkit/ll/Changeset.java	Sun Dec 19 05:41:31 2010 +0100
@@ -96,4 +96,10 @@
 		}
 		return -1;
 	}
+
+	public interface Callback {
+		// first(), last(), single().
+		// <T>
+		void next(Changeset cset);
+	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/tmate/hgkit/ll/HgDataFile.java	Sun Dec 19 05:41:31 2010 +0100
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010 Artem Tikhomirov 
+ */
+package com.tmate.hgkit.ll;
+
+/**
+ * Extends Revlog/uses RevlogStream?
+ * ? name:HgFileNode?
+ * @author artem
+ */
+public class HgDataFile extends Revlog {
+
+	private final String path;
+	
+	/*package-local*/HgDataFile(HgRepository hgRepo) {
+		super(hgRepo);
+	}
+
+	public String getPath() {
+		return path; // hgRepo.backresolve(this) -> name?
+	}
+
+	private static final int TIP = -2;
+
+	public byte[] content() {
+		return content(TIP);
+	}
+	
+	public byte[] content(int revision) {
+		throw HgRepository.notImplemented();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/tmate/hgkit/ll/HgManifest.java	Sun Dec 19 05:41:31 2010 +0100
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2010 Artem Tikhomirov 
+ */
+package com.tmate.hgkit.ll;
+
+/**
+ *
+ * @author artem
+ */
+public class HgManifest extends Revlog {
+
+	/*package-local*/ HgManifest(HgRepository hgRepo) {
+		super(hgRepo);
+	}
+}
--- a/src/com/tmate/hgkit/ll/HgRepository.java	Sat Dec 18 05:47:35 2010 +0100
+++ b/src/com/tmate/hgkit/ll/HgRepository.java	Sun Dec 19 05:41:31 2010 +0100
@@ -6,12 +6,18 @@
 
 /**
  * @author artem
- *
  */
 public abstract class HgRepository {
 
+	// temp aux marker method
+	public static IllegalStateException notImplemented() {
+		return new IllegalStateException("Not implemented");
+	}
+
 	
 	private Changelog changelog;
+	private HgManifest manifest;
+
 	private boolean isInvalid = true;
 	
 	public boolean isInvalid() {
@@ -23,21 +29,38 @@
 	}
 
 	public void log() {
-		Changelog clog = getChangelog();
+		Revlog clog = getChangelog();
 		assert clog != null;
 		// TODO get data to the client
 	}
 
-	/**
-	 * @return
-	 */
-	private Changelog getChangelog() {
+	public final Changelog getChangelog() {
 		if (this.changelog == null) {
-			this.changelog = new Changelog();
+			// might want delegate to protected createChangelog() some day
+			this.changelog = new Changelog(this);
 			// TODO init
 		}
 		return this.changelog;
 	}
+	
+	public final HgManifest getManifest() {
+		if (this.manifest == null) {
+			this.manifest = new HgManifest(this);
+		}
+		return this.manifest;
+	}
+	
+	public final Object/*HgDirstate*/ getDirstate() {
+		throw notImplemented();
+	}
+
+	public abstract HgDataFile getFileNode(String path);
 
 	public abstract String getLocation();
+
+
+	/**
+	 * Perhaps, should be separate interface, like ContentLookup
+	 */
+	public abstract RevlogStream resolve(String string);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/tmate/hgkit/ll/Revlog.java	Sun Dec 19 05:41:31 2010 +0100
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010 Artem Tikhomirov 
+ */
+package com.tmate.hgkit.ll;
+
+/**
+ *
+ * @author artem
+ */
+public abstract class Revlog {
+
+	private final HgRepository hgRepo;
+
+	protected Revlog(HgRepository hgRepo) {
+		if (hgRepo == null) {
+			throw new NullPointerException();
+		}
+		this.hgRepo = hgRepo;
+	}
+
+	public final HgRepository getRepo() {
+		return hgRepo;
+	}
+
+	public interface Inspector {
+		void next(int compressedLen, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*32*/] nodeid, byte[] data);
+	}
+}
--- a/src/com/tmate/hgkit/ll/RevlogStream.java	Sat Dec 18 05:47:35 2010 +0100
+++ b/src/com/tmate/hgkit/ll/RevlogStream.java	Sun Dec 19 05:41:31 2010 +0100
@@ -4,6 +4,12 @@
 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.List;
+import java.util.zip.Inflater;
 
 /**
  * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), 
@@ -13,7 +19,10 @@
  * @see http://mercurial.selenic.com/wiki/RevlogNG
  */
 public class RevlogStream {
-	
+
+	private List<IndexEntry> index; // indexed access highly needed
+	private boolean inline = false;
+
 	private void detectVersion() {
 		
 	}
@@ -27,5 +36,131 @@
 		// TODO Auto-generated method stub
 		return null;
 	}
+	public int revisionCount() {
+		initOutline();
+		return index.size();
+	}
+
+	// should be possible to use TIP, ALL
+	public void iterate(int start, int end, boolean needData, Revlog.Inspector inspector) {
+		initOutline();
+		final int indexSize = index.size();
+		if (start < 0 || start >= indexSize) {
+			throw new IllegalArgumentException("Bad left range boundary " + start);
+		}
+		if (end < start || end >= indexSize) {
+			throw new IllegalArgumentException("Bad right range boundary " + end);
+		}
+		// XXX may cache [start .. end] from index with a single read (pre-read)
+		
+		DataInput diIndex = null, diData = null;
+		diIndex = getIndexStream();
+		if (needData) {
+			diData = getDataStream();
+		}
+		try {
+			diIndex.skipBytes(inline ? (int) index.get(start).offset : start * 64);
+			for (int i = start; i <= end && i < indexSize; i++ ) {
+				IndexEntry ie = index.get(i);
+				long l = diIndex.readLong();
+				long offset = l >>> 16;
+				int flags = (int) (l & 0X0FFFF);
+				int compressedLen = diIndex.readInt();
+				int actualLen = diIndex.readInt();
+				int baseRevision = diIndex.readInt();
+				int linkRevision = diIndex.readInt();
+				int parent1Revision = diIndex.readInt();
+				int parent2Revision = diIndex.readInt();
+				byte[] buf = new byte[32];
+				// XXX Hg keeps 12 last bytes empty, we move them into front here
+				diIndex.readFully(buf, 12, 20);
+				diIndex.skipBytes(12);
+				byte[] data = null;
+				if (needData) {
+					byte[] dataBuf = new byte[compressedLen];
+					if (inline) {
+						diIndex.readFully(dataBuf);
+					} else {
+						diData.skipBytes((int) ie.offset); // FIXME not skip but seek!!!
+						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);
+					} 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 
+						data = dataBuf;
+					}
+					// FIXME if patch data (based on baseRevision), then apply patches to get true content
+				}
+				inspector.next(compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, buf, data);
+			}
+		} catch (EOFException ex) {
+			// should not happen as long as we read inside known boundaries
+			throw new IllegalStateException(ex);
+		} catch (IOException ex) {
+			FIXME 
+		}
+	}
 	
+	private void initOutline() {
+		if (index != null && !index.isEmpty()) {
+			return;
+		}
+		ArrayList<IndexEntry> res = new ArrayList<IndexEntry>();
+		DataInput di = getIndexStream();
+		try {
+			int versionField = di.readInt();
+			// di.undreadInt();
+			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
+				int compressedLen = di.readInt();
+				// 8+4 = 12 bytes total read
+//				int actualLen = di.readInt();
+//				int baseRevision = di.readInt();
+//				int linkRevision = di.readInt();
+//				int parent1Revision = di.readInt();
+//				int parent2Revision = di.readInt();
+//				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)
+				} else {
+					di.skipBytes(6*4 + 32);
+				}
+				long l = di.readLong();
+				offset = l >>> 16;
+			}
+		} catch (EOFException ex) {
+			// fine, done then
+			index = res;
+		} catch (IOException ex) {
+			ex.printStackTrace();
+			// too bad, no outline then
+			index = Collections.emptyList();
+		}
+	}
+
+
+	// perhaps, package-local or protected, if anyone else from low-level needs them
+	private static class IndexEntry {
+		public final long offset;
+		public final int length; // data past fixed record (need to decide whether including header size or not), and whether length is of compressed data or not
+
+		public IndexEntry(long o, int l) {
+			offset = o;
+			length = l;
+		}
+	}
 }