# HG changeset patch # User Artem Tikhomirov # Date 1292609159 -3600 # Node ID dbd663faec1f0175619cf7668bddc6350548b8d6 Basic changelog parsing diff -r 000000000000 -r dbd663faec1f .classpath --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.classpath Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,6 @@ + + + + + + diff -r 000000000000 -r dbd663faec1f .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,3 @@ +syntax:glob +bin +src/Extras.java diff -r 000000000000 -r dbd663faec1f .project --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.project Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,17 @@ + + + com.tmate.hgkit + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff -r 000000000000 -r dbd663faec1f .settings/org.eclipse.jdt.core.prefs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.settings/org.eclipse.jdt.core.prefs Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,12 @@ +#Wed Dec 15 01:43:42 CET 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff -r 000000000000 -r dbd663faec1f src/com/tmate/hgkit/console/Main.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/console/Main.java Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,158 @@ +package com.tmate.hgkit.console; + +import java.io.BufferedInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +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; + +/** + * + * @author artem + */ +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 changelog = new LinkedList(); + // + DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("/temp/hg/hello/" + ".hg/store/00changelog.i")))); + DataInput di = dis; + dis.mark(10); + int versionField = di.readInt(); + dis.reset(); + final int INLINEDATA = 1 << 16; + + boolean inlineData = (versionField & INLINEDATA) != 0; + System.out.printf("%#8x, inline: %b\n", versionField, inlineData); + System.out.println("\tOffset\tFlags\tPacked\t Actual\tBase Rev Link Rev\tParent1\tParent2\tnodeid"); + int entryCount = 0; + while (dis.available() > 0) { + long l = di.readLong(); + long offset = l >>> 16; + int flags = (int) (l & 0X0FFFF); + int compressedLen = di.readInt(); + int actualLen = di.readInt(); + int baseRevision = di.readInt(); + int linkRevision = di.readInt(); + int parent1Revision = di.readInt(); + int parent2Revision = di.readInt(); + byte[] buf = new byte[32]; + di.readFully(buf, 12, 20); + 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) { + byte[] data = new byte[compressedLen]; + di.readFully(data); + if (data[0] == 0x78 /* 'x' */) { + Inflater zlib = new Inflater(); + zlib.setInput(data, 0, compressedLen); + 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 bins = new LinkedList(); + 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 + } + } + dis.close(); + // + System.out.println("\n\n"); + System.out.println("====================>"); + for (Changeset cset : changelog) { + System.out.println(">"); + cset.dump(); + System.out.println("<"); + } + } + + + // mpatch.c : apply() + private static byte[] apply(byte[] baseRevisionContent, List 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); + } + } +} diff -r 000000000000 -r dbd663faec1f src/com/tmate/hgkit/ll/Changelog.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/Changelog.java Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +/** + * Representation of the Mercurial changelog file (list of ChangeSets) + * @author artem + */ +public class Changelog { + +} diff -r 000000000000 -r dbd663faec1f src/com/tmate/hgkit/ll/Changeset.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/Changeset.java Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; + +/** + * @see mercurial/changelog.py:read() + *
+        format used:
+        nodeid\n        : manifest node in ascii
+        user\n          : user, no \n or \r allowed
+        time tz extra\n : date (time is int or float, timezone is int)
+                        : extra is metadatas, encoded and separated by '\0'
+                        : older versions ignore it
+        files\n\n       : files modified by the cset, no \n or \r allowed
+        (.*)            : comment (free text, ideally utf-8)
+
+        changelog v0 doesn't use extra
+ * 
+ * @author artem + */ +public class Changeset { + private /*final*/ Nodeid nodeid; + private String user; + private String comment; + private ArrayList files; + private String timezone; // FIXME + public byte[] rawData; // FIXME + + public void dump() { + System.out.println("User:" + user); + System.out.println("Comment:" + comment); + System.out.println("Nodeid:" + nodeid); + System.out.println("Date:" + timezone); + System.out.println("Files: " + files.size()); + for (String s : files) { + System.out.print('\t'); + System.out.println(s); + } + } + + public void read(byte[] buf, int offset, int length) { + rawData = new byte[length]; + System.arraycopy(buf, offset, rawData, 0, length); + final int bufferEndIndex = offset + length; + final byte lineBreak = (byte) '\n'; + int breakIndex1 = indexOf(buf, 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); + 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); + if (breakIndex3 == -1) { + throw new IllegalArgumentException("Bad Changeset data"); + } + timezone = new String(buf, breakIndex2+1, breakIndex3 - breakIndex2 - 1); + + // + int lastStart = breakIndex3 + 1; + int breakIndex4 = indexOf(buf, lineBreak, lastStart, bufferEndIndex); + files = new ArrayList(5); + while (breakIndex4 != -1 && breakIndex4 + 1 < bufferEndIndex) { + files.add(new String(buf, lastStart, breakIndex4 - lastStart)); + lastStart = breakIndex4 + 1; + if (buf[breakIndex4 + 1] == lineBreak) { + // found \n\n + break; + } else { + breakIndex4 = indexOf(buf, 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"); + } catch (UnsupportedEncodingException ex) { + comment = ""; + throw new IllegalStateException("Could hardly happen"); + } + } + + private static int indexOf(byte[] src, byte what, int startOffset, int endIndex) { + for (int i = startOffset; i < endIndex; i++) { + if (src[i] == what) { + return i; + } + } + return -1; + } +} diff -r 000000000000 -r dbd663faec1f src/com/tmate/hgkit/ll/HgRepository.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/HgRepository.java Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +/** + * @author artem + * + */ +public class HgRepository { + + + private Changelog changelog; + + public void log() { + Changelog clog = getChangelog(); + assert clog != null; + // TODO get data to the client + } + + /** + * @return + */ + private Changelog getChangelog() { + if (this.changelog == null) { + this.changelog = new Changelog(); + // TODO init + } + return this.changelog; + } +} diff -r 000000000000 -r dbd663faec1f src/com/tmate/hgkit/ll/Nodeid.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/Nodeid.java Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +import java.math.BigInteger; +import java.util.Formatter; + +/** + * @see mercurial/node.py + * @author artem + * + */ +public class Nodeid { + + public static int NULLREV = -1; + private final byte[] binaryData; + + public Nodeid(byte[] binaryRepresentation) { + // 5 int fields => 32 bytes + // byte[20] => 48 bytes + this.binaryData = binaryRepresentation; + } + + @Override + public String toString() { + // FIXME temp impl. + // BEWARE, if binaryData[0] > 0x80, BigInteger treats it as negative + return new BigInteger(binaryData).toString(); + } + + // binascii.unhexlify() + public static Nodeid fromAscii(byte[] asciiRepresentation, int offset, int length) { + assert length % 2 == 0; // Python's binascii.hexlify convert each byte into 2 digits + byte[] data = new byte[length / 2]; // XXX use known size instead? nodeid is always 20 bytes + for (int i = 0, j = offset; i < data.length; i++) { + int hiNibble = Character.digit(asciiRepresentation[j++], 16); + int lowNibble = Character.digit(asciiRepresentation[j++], 16); + data[i] = (byte) (((hiNibble << 4) | lowNibble) & 0xFF); + } + return new Nodeid(data); + } + + +} diff -r 000000000000 -r dbd663faec1f src/com/tmate/hgkit/ll/RevlogIndexStreamAccess.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/RevlogIndexStreamAccess.java Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +import java.io.DataInput; +import java.io.IOException; + +/** + * @author artem + * + */ +public class RevlogIndexStreamAccess { + + private final RevlogStream stream; + + // takes RevlogStream. RevlogStream delegates calls for data to this accessor, which in turn refers back to RevlogStream to get + // correct [Input|Data]Stream according to revlog version (Revlogv0 or RevlogNG) + + public RevlogIndexStreamAccess(RevlogStream stream) { + this.stream = stream; + // TODO Auto-generated constructor stub + } + + + void readRevlogV0Record() throws IOException { + DataInput di = stream.getIndexStream(); + int offset = di.readInt(); + int compressedLen = di.readInt(); + int baseRevision = di.readInt(); + int linkRevision = di.readInt(); +// int r = (((buf[0] & 0xff) << 24) | ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff)); + byte[] buf = new byte[20]; + di.readFully(buf, 0, 20); + Object nodeidOwn = buf.clone(); + // XXX nodeid as an Object with hash/equals? + di.readFully(buf, 0, 20); + Object nodeidParent1 = buf.clone(); + di.readFully(buf, 0, 20); + Object nodeidParent2 = buf.clone(); + } + + // another subclass? + void readRevlogNGRecord() throws IOException { + DataInput di = stream.getIndexStream(); + long l = di.readLong(); + long offset = l >>> 16; + int flags = (int) (l & 0X0FFFF); + int compressedLen = di.readInt(); + int actualLen = di.readInt(); + int baseRevision = di.readInt(); + int linkRevision = di.readInt(); + int parent1Revision = di.readInt(); + int parent2Revision = di.readInt(); + byte[] buf = new byte[32]; + di.readFully(buf, 0, 20+12); + Object nodeid = buf/*[0..20]*/; + + } +} diff -r 000000000000 -r dbd663faec1f src/com/tmate/hgkit/ll/RevlogIterator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/RevlogIterator.java Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +/** + * To walk against revlog + * XXX consider external iterator approach + * @author artem + */ +public class RevlogIterator { + + public RevlogIterator(RevlogStream stream) { + + } +} diff -r 000000000000 -r dbd663faec1f src/com/tmate/hgkit/ll/RevlogStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/RevlogStream.java Fri Dec 17 19:05:59 2010 +0100 @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +import java.io.DataInput; + +/** + * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), + * or numerous RevlogStream with separate representation of the underlaying data (cached, lazy ChunkStream)? + * @author artem + * @see http://mercurial.selenic.com/wiki/Revlog + * @see http://mercurial.selenic.com/wiki/RevlogNG + */ +public class RevlogStream { + + private void detectVersion() { + + } + + /*package*/ DataInput getIndexStream() { + // TODO Auto-generated method stub + return null; + } + + /*package*/ DataInput getDataStream() { + // TODO Auto-generated method stub + return null; + } + +}